diff --git a/CHANGES.html b/CHANGES.html
index b87fd426a..a5a56dd9a 100644
--- a/CHANGES.html
+++ b/CHANGES.html
@@ -81,6 +81,7 @@ transmitted.
The internal TCP API for TcpCongestionOps has been extended to support the CongControl method to allow for delivery rate estimation feedback to the congestion control mechanism.
Functions LteEnbPhy::ReceiveUlHarqFeedback and LteUePhy::ReceiveLteDlHarqFeedback are renamed to LteEnbPhy::ReportUlHarqFeedback and LteUePhy::EnqueueDlHarqFeedback, respectively to avoid confusion about their functionality. LteHelper is updated accordingly.
Now on, instead of uint8_t, uint16_t would be used to store a bandwidth value in LTE.
+The preferred way to declare instances of CommandLine is now through a macro: COMMANDLINE (cmd). This enables us to add the CommandLine::Usage() message to the Doxygen for the program.
Changes to build system:
diff --git a/RELEASE_NOTES b/RELEASE_NOTES
index 2c7a60626..d83fafbf8 100644
--- a/RELEASE_NOTES
+++ b/RELEASE_NOTES
@@ -28,6 +28,7 @@ New user-visible features
- (propagation) 3GPP TR 38.901 pathloss and channel condition models added
- (spectrum) Addition three-gpp-channel-model (part of Integration of the 3GPP TR 38.901 fast fading model)
- (antenna) Addition of three-gpp-antenna-array-model (part of Integration of the 3GPP TR 38.901 fast fading model)
+- (core) CommandLine can now add the Usage message to the Doxygen for the program; see CommandLine for details.
Bugs fixed
----------
diff --git a/doc/.gitignore b/doc/.gitignore
index ae11ebb97..59912cb47 100644
--- a/doc/.gitignore
+++ b/doc/.gitignore
@@ -5,6 +5,7 @@
latex/
ns3-object.txt
introspected-doxygen.h
+introspected-command-line.h
doxygen.docset.conf
doxygen.log
doxygen.warnings.log
diff --git a/src/core/model/command-line.cc b/src/core/model/command-line.cc
index c8fd5c52b..496030146 100644
--- a/src/core/model/command-line.cc
+++ b/src/core/model/command-line.cc
@@ -18,12 +18,6 @@
* Authors: Mathieu Lacage
*/
-#include // for transform
-#include // for tolower
-#include // for exit
-#include // for setw, boolalpha
-#include
-#include
#include "command-line.h"
#include "des-metrics.h"
@@ -34,6 +28,14 @@
#include "type-id.h"
#include "string.h"
+#include // transform
+#include // tolower
+#include // exit, getenv
+#include // strlen
+#include // setw, boolalpha
+#include
+#include
+
/**
* \file
@@ -47,10 +49,22 @@ NS_LOG_COMPONENT_DEFINE ("CommandLine");
CommandLine::CommandLine ()
: m_NNonOptions (0),
- m_nonOptionCount (0)
+ m_nonOptionCount (0),
+ m_usage (),
+ m_shortName ()
{
NS_LOG_FUNCTION (this);
}
+CommandLine::CommandLine (const std::string filename)
+ : m_NNonOptions (0),
+ m_nonOptionCount (0),
+ m_usage ()
+{
+ NS_LOG_FUNCTION (this << filename);
+ std::string basename = SystemPath::Split (filename).back ();
+ m_shortName = basename.substr (0, basename.rfind (".cc"));
+}
+
CommandLine::CommandLine (const CommandLine &cmd)
{
Copy (cmd);
@@ -76,8 +90,9 @@ CommandLine::Copy (const CommandLine &cmd)
std::copy (cmd.m_nonOptions.begin (), cmd.m_nonOptions.end (), m_nonOptions.end ());
m_NNonOptions = cmd.m_NNonOptions;
- m_usage = cmd.m_usage;
- m_name = cmd.m_name;
+ m_nonOptionCount = 0;
+ m_usage = cmd.m_usage;
+ m_shortName = cmd.m_shortName;
}
void
CommandLine::Clear (void)
@@ -95,8 +110,8 @@ CommandLine::Clear (void)
m_options.clear ();
m_nonOptions.clear ();
m_NNonOptions = 0;
- m_usage = "";
- m_name = "";
+ m_usage = "";
+ m_shortName = "";
}
void
@@ -108,7 +123,7 @@ CommandLine::Usage (const std::string usage)
std::string
CommandLine::GetName () const
{
- return m_name;
+ return m_shortName;
}
CommandLine::Item::~Item ()
@@ -121,12 +136,12 @@ CommandLine::Parse (std::vector args)
{
NS_LOG_FUNCTION (this << args.size () << args);
+ PrintDoxygenUsage ();
+
m_nonOptionCount = 0;
- m_name = "";
if (args.size () > 0)
{
- m_name = SystemPath::Split (args[0]).back ();
args.erase (args.begin ()); // discard the program name
for (auto param : args)
@@ -218,6 +233,7 @@ CommandLine::HandleNonOption (const std::string &value)
std::cerr << "Invalid non-option argument value "
<< value << " for " << i->m_name
<< std::endl;
+ PrintHelp (std::cerr);
std::exit (1);
}
++m_nonOptionCount;
@@ -242,7 +258,7 @@ CommandLine::PrintHelp (std::ostream &os) const
// Hack to show just the declared non-options
Items nonOptions (m_nonOptions.begin (),
m_nonOptions.begin () + m_NNonOptions);
- os << m_name
+ os << m_shortName
<< (m_options.size () ? " [Program Options]" : "")
<< (nonOptions.size () ? " [Program Arguments]" : "")
<< " [General Arguments]"
@@ -316,6 +332,98 @@ CommandLine::PrintHelp (std::ostream &os) const
<< std::endl;
}
+#include // getcwd
+
+void
+CommandLine::PrintDoxygenUsage (void) const
+{
+ NS_LOG_FUNCTION (this);
+
+ {
+ char buf[1024];
+ std::string cwd= getcwd (buf, 1024);
+ }
+
+ const char * envVar = std::getenv ("NS_COMMANDLINE_INTROSPECTION");
+ if (envVar == 0 || std::strlen (envVar) == 0)
+ {
+ return;
+ }
+
+ if (m_shortName.size () == 0)
+ {
+ NS_FATAL_ERROR ("No file name on example-to-run; forgot to use COMMANDLINE (var)?");
+ return;
+ }
+
+ // Hack to show just the declared non-options
+ Items nonOptions (m_nonOptions.begin (),
+ m_nonOptions.begin () + m_NNonOptions);
+
+ std::string outf = SystemPath::Append (std::string (envVar), m_shortName + ".command-line");
+
+ NS_LOG_INFO ("Writing CommandLine doxy to " << outf);
+
+ std::fstream os (outf, std::fstream::out);
+
+
+ os << "/**\n \\file " << m_shortName << ".cc\n"
+ << "Usage
\n"
+ << "$ ./waf --run \"" << m_shortName
+ << (m_options.size () ? " [Program Options]" : "")
+ << (nonOptions.size () ? " [Program Arguments]" : "")
+ << "\"\n";
+
+ if (m_usage.length ())
+ {
+ os << m_usage << std::endl;
+ }
+
+ if (!m_options.empty ())
+ {
+ os << std::endl;
+ os << "Program Options
\n"
+ << "\n";
+ for (auto i : m_options)
+ {
+ os << " - \\c --" << i->m_name << "
\n"
+ << " - " << i->m_help;
+
+ if ( i->HasDefault ())
+ {
+ os << " [" << i->GetDefault () << "]";
+ }
+ os << "
\n";
+ }
+ os << "
\n";
+ }
+
+ if (!nonOptions.empty ())
+ {
+ os << std::endl;
+ os << "Program Arguments
\n"
+ << "\n";
+ for (auto i : nonOptions)
+ {
+ os << " - \\c " << i->m_name << "
\n"
+ << " - " << i->m_help;
+
+ if ( i->HasDefault ())
+ {
+ os << " [" << i->GetDefault () << "]";
+ }
+ os << "
\n";
+ }
+ os << "
\n";
+ }
+
+ os << "*/" << std::endl;
+
+ // All done, don't need to actually run the example
+ os.close ();
+ std::exit (0);
+}
+
void
CommandLine::PrintGlobals (std::ostream &os) const
{
@@ -514,6 +622,7 @@ CommandLine::HandleArgument (const std::string &name, const std::string &value)
{
std::cerr << "Invalid argument value: "
<< name << "=" << value << std::endl;
+ PrintHelp (std::cerr);
std::exit (1);
}
else
diff --git a/src/core/model/command-line.h b/src/core/model/command-line.h
index 887b64fd8..3c1e33c7e 100644
--- a/src/core/model/command-line.h
+++ b/src/core/model/command-line.h
@@ -45,6 +45,17 @@ namespace ns3 {
*
* The main entry point is CommandLine
*/
+
+/**
+ * \ingroup commandline
+ * \brief Declare a CommandLine instance.
+ *
+ * This form is preferred since it supports creating Doxygen
+ * documentation for programs from the CommandLine configuration.
+ */
+#define COMMANDLINE(var) \
+ CommandLine var ( __FILE__ )
+
/**
* \ingroup commandline
* \brief Parse command-line arguments
@@ -105,7 +116,6 @@ namespace ns3 {
* Suggested best practice is for scripts to report the values of all items
* settable through CommandLine, as done by the example below.
*
- *
* CommandLine can set the initial value of every attribute in the system
* with the \c --TypeIdName::AttributeName=value syntax, for example
* \verbatim
@@ -133,7 +143,7 @@ namespace ns3 {
* bool boolArg = false;
* std::string strArg = "strArg default";
*
- * CommandLine cmd;
+ * COMMANDLINE (cmd);
* cmd.Usage ("CommandLine example program.\n"
* "\n"
* "This little program demonstrates how to use CommandLine.");
@@ -194,7 +204,7 @@ namespace ns3 {
* int value1;
* int value2;
*
- * CommandLine cmd;
+ * COMMANDLINE (cmd);
* cmd.Usage ("...");
* cmd.AddValue ("value1", "first value", value1);
* cmd.AddValue ("value2", "second value", value1);
@@ -208,12 +218,33 @@ namespace ns3 {
* exit (-1);
* }
* \endcode
+ *
+ * Finally, note that for examples which will be run by \c test.py
+ * the preferred declaration of a CommandLine instance is
+ * to use the \c COMMANDLINE macro to declare the instance name:
+ *
+ * \code
+ * COMMANDLINE (cmd);
+ * \endcode
+ * This will ensure that the program usage and arguments can be added to
+ * the Doxygen documentation automatically.
*/
class CommandLine
{
public:
/** Constructor */
- CommandLine ();
+ CommandLine (void);
+ /**
+ * Construct and register the source file name.
+ * This would typically be called by using the \c COMMANDLINE macro:
+ * COMMANDLINE (cmd);
+ *
+ * This is just syntactic sugar for
+ * COMMANDLINE cmd (__FILE__);
+ * This form is required to generate Doxygen documentation of the
+ * arguments and options.
+ */
+ CommandLine (const std::string filename);
/**
* Copy constructor
*
@@ -359,7 +390,7 @@ public:
*
* Alternatively, an overloaded operator << can be used:
* \code
- * CommandLine cmd;
+ * COMMANDLINE (cmd);
* cmd.Parse (argc, argv);
* ...
*
@@ -520,6 +551,11 @@ private:
void Copy (const CommandLine &cmd);
/** Remove all arguments, Usage(), name */
void Clear (void);
+ /**
+ * Append usage message in Doxygen format to the file indicated
+ * by the NS_COMMANDLINE_INTROSPECTION environment variable.
+ */
+ void PrintDoxygenUsage (void) const;
typedef std::vector- Items; /**< Argument list container */
Items m_options; /**< The list of option arguments */
@@ -527,7 +563,7 @@ private:
std::size_t m_NNonOptions; /**< The expected number of non-option arguments */
std::size_t m_nonOptionCount; /**< The number of actual non-option arguments seen so far. */
std::string m_usage; /**< The Usage string */
- std::string m_name; /**< The program name */
+ std::string m_shortName; /**< The source file name (without `.cc`), as would be given to `waf --run` */
}; // class CommandLine
@@ -669,7 +705,7 @@ CommandLineHelper::UserItemParse (const std::string value, T & val)
*
* Example usage:
* \code
- * CommandLine cmd;
+ * COMMANDLINE (cmd);
* cmd.Parse (argc, argv);
* ...
*
diff --git a/wscript b/wscript
index 76afa884e..f5e7f4e36 100644
--- a/wscript
+++ b/wscript
@@ -11,6 +11,8 @@ import re
import shlex
import subprocess
import textwrap
+import fileinput
+import glob
from utils import read_config_file
@@ -1259,6 +1261,38 @@ def _print_introspected_doxygen(bld):
raise SystemExit(1)
text_out.close()
+ # Gather the CommandLine doxy
+ # test.py appears not to create or keep the output directory
+ # if no real tests are run, so we just stuff all the
+ # .command-line output files into testpy-output/
+ # NS_COMMANDLINE_INTROSPECTION=".." test.py --nowaf --constrain=example
+ Logs.info("Running CommandLine introspection")
+ proc_env['NS_COMMANDLINE_INTROSPECTION'] = '..'
+ subprocess.run(["test.py", "--nowaf", "--constrain=example"],
+ env=proc_env, stdout=subprocess.DEVNULL)
+
+ doxygen_out = os.path.join('doc', 'introspected-command-line.h')
+ try:
+ os.remove(doxygen_out)
+ except OSError as e:
+ pass
+
+ with open(doxygen_out, 'w') as out_file:
+ lines="""
+/* This file is automatically generated by
+CommandLine::PrintDoxygenUsage() from the CommandLine configuration
+in various example programs. Do not edit this file! Edit the
+CommandLine configuration in those files instead.
+*/\n
+"""
+ out_file.write(lines)
+ out_file.close()
+
+ with open(doxygen_out,'a') as outfile:
+ for in_file in glob.glob('testpy-output/*.command-line'):
+ with open(in_file,'r') as infile:
+ outfile.write(infile.read())
+
def _doxygen(bld, skip_pid=False):
env = wutils.bld.env
proc_env = wutils.get_proc_env()