7from pathlib
import Path
9CMAKELISTS_TEMPLATE =
"""\
10set(examples_as_tests_sources)
11if(${{ENABLE_EXAMPLES}})
12 set(examples_as_tests_sources
13 #test/{MODULE}-examples-test-suite.cc
19 SOURCE_FILES model/{MODULE}.cc
20 helper/{MODULE}-helper.cc
21 HEADER_FILES model/{MODULE}.h
22 helper/{MODULE}-helper.h
23 LIBRARIES_TO_LINK ${{libcore}}
24 TEST_SOURCES test/{MODULE}-test-suite.cc
25 ${{examples_as_tests_sources}}
30MODEL_CC_TEMPLATE =
"""\
42MODEL_H_TEMPLATE =
"""\
43#ifndef {INCLUDE_GUARD}
44#define {INCLUDE_GUARD}
46// Add a doxygen group for this module.
47// If you have more than one file, this should be in only one of them.
49 * @defgroup {MODULE} Description of the {MODULE}
55// Each class should be documented using Doxygen,
56// and have an @ingroup {MODULE} directive
62#endif // {INCLUDE_GUARD}
66HELPER_CC_TEMPLATE =
"""\
67#include "{MODULE}-helper.h"
78HELPER_H_TEMPLATE =
"""\
79#ifndef {INCLUDE_GUARD}
80#define {INCLUDE_GUARD}
82#include "ns3/{MODULE}.h"
87// Each class should be documented using Doxygen,
88// and have an @ingroup {MODULE} directive
94#endif // {INCLUDE_GUARD}
98EXAMPLES_CMAKELISTS_TEMPLATE =
"""\
100 NAME {MODULE}-example
101 SOURCE_FILES {MODULE}-example.cc
102 LIBRARIES_TO_LINK ${{lib{MODULE}}}
106EXAMPLE_CC_TEMPLATE =
"""\
107#include "ns3/core-module.h"
108#include "ns3/{MODULE}-helper.h"
113 * Explain here what the example does.
119main(int argc, char* argv[])
123 CommandLine cmd(__FILE__);
124 cmd.AddValue("verbose", "Tell application to log if true", verbose);
126 cmd.Parse(argc, argv);
131 Simulator::Destroy();
137TEST_CC_TEMPLATE =
"""\
138// Include a header file from your module to test.
139#include "ns3/{MODULE}.h"
141// An essential include is test.h
144// Do not put your test classes in namespace ns3. You may find it useful
145// to use the using directive to access the ns3 namespace directly
148// Add a doxygen group for tests.
149// If you have more than one test, this should be in only one of them.
151 * @defgroup {MODULE}-tests Tests for {MODULE}
156// This is an example TestCase.
158 * @ingroup {MODULE}-tests
159 * Test case for feature 1
161class {CAPITALIZED}TestCase1 : public TestCase
164 {CAPITALIZED}TestCase1();
165 ~{CAPITALIZED}TestCase1() override;
168 void DoRun() override;
171// Add some help text to this case to describe what it is intended to test
172{CAPITALIZED}TestCase1::{CAPITALIZED}TestCase1()
173 : TestCase("{CAPITALIZED} test case (does nothing)")
177// This destructor does nothing but we include it as a reminder that
178// the test case should clean up after itself
179{CAPITALIZED}TestCase1::~{CAPITALIZED}TestCase1()
184// This method is the pure virtual method from class TestCase that every
185// TestCase must implement
188{CAPITALIZED}TestCase1::DoRun()
190 // A wide variety of test macros are available in src/core/test.h
191 NS_TEST_ASSERT_MSG_EQ(true, true, "true doesn't equal true for some reason");
192 // Use this one for floating point comparisons
193 NS_TEST_ASSERT_MSG_EQ_TOL(0.01, 0.01, 0.001, "Numbers are not equal within tolerance");
196// The TestSuite class names the TestSuite, identifies what type of TestSuite,
197// and enables the TestCases to be run. Typically, only the constructor for
198// this class must be defined
201 * @ingroup {MODULE}-tests
202 * TestSuite for module {MODULE}
204class {CAPITALIZED}TestSuite : public TestSuite
207 {CAPITALIZED}TestSuite();
210// Type for TestSuite can be UNIT, SYSTEM, EXAMPLE, or PERFORMANCE
211{CAPITALIZED}TestSuite::{CAPITALIZED}TestSuite()
212 : TestSuite("{MODULE}", Type::UNIT)
214 // Duration for TestCase can be QUICK, EXTENSIVE or TAKES_FOREVER
215 AddTestCase(new {CAPITALIZED}TestCase1, TestCase::Duration::QUICK);
218// Do not forget to allocate an instance of this TestSuite
220 * @ingroup {MODULE}-tests
221 * Static variable for test initialization
223static {CAPITALIZED}TestSuite s{COMPOUND}TestSuite;
227DOC_RST_TEMPLATE =
"""Example Module Documentation
228============================
230.. include:: replace.txt
234 ============= Module Name
235 ------------- Section (#.#)
236 ~~~~~~~~~~~~~ Subsection (#.#.#)
238This is the outline for adding new module documentation to |ns3|.
239See ``src/lr-wpan/doc/lr-wpan.rst`` for an example.
241This first part is for describing what the model is trying to
242accomplish. General descriptions and design, overview of the project
243goes in this part without the need of any subsections. A visual summary
246For consistency (italicized formatting), please use |ns3| to refer to
247ns-3 in the documentation (and likewise, |ns2| for ns-2). These macros
248are defined in the file ``replace.txt``.
250Detailed |ns3| Sphinx documentation guidelines can be found `here <https://www.nsnam.org/docs/contributing/html/models.html>`_
253The source code for the new module lives in the directory ``{MODULE_DIR}``.
259This is should be your first section. Please use it to list the scope and
260limitations. What can the model do? What can it not do? Be brief and concise.
265Free form. Your second section of your documentation and its subsections goes here.
266The documentation can contain any number of sections but be careful to not use
267more than two levels in subsections.
272Free form. The last section of your documentation with content goes here.
277A brief description of the module usage goes here. This section must be present.
282A subsection with a description of the helpers used by the model goes here. Snippets of code are
283preferable when describing the helpers usage. This subsection must be present.
284If the model CANNOT provide helpers write "Not applicable".
289A subsection with a list of attributes used by the module, each attribute should include a small
290description. This subsection must be present.
291If the model CANNOT provide attributes write "Not applicable".
296A subsection with a list of the source traces used by the module, each trace should include a small
297description. This subsection must be present.
298If the model CANNOT provide traces write "Not applicable".
303A brief description of each example and test present in the model must be here.
304Include both the name of the example file and a brief description of the example.
305This section must be present.
310Describe how the model has been tested/validated. What tests run in the
311test suite? How much API and code is covered by the tests?
313This section must be present. Write ``No formal validation has been made`` if your
314model do not contain validations.
319The reference material used in the construction of the model.
320This section must be the last section and must be present.
321Use numbers for the index not names when listing the references. When possible, include the link of the referenced material.
325[`1 <https://ieeexplore.ieee.org/document/6012487>`_] IEEE Standard for Local and metropolitan area networks--Part 15.4: Low-Rate Wireless Personal Area Networks (LR-WPANs)," in IEEE Std 802.15.4-2011 (Revision of IEEE Std 802.15.4-2006) , vol., no., pp.1-314, 5 Sept. 2011, doi: 10.1109/IEEESTD.2011.6012487.
331 artifact_path = Path(path)
334 with artifact_path.open(
"wt", encoding=
"utf-8")
as f:
335 f.write(template.format(**kwargs))
339 path = Path(moduledir,
"CMakeLists.txt")
341 create_file(path, CMAKELISTS_TEMPLATE, MODULE=modname)
347 modelpath = Path(moduledir,
"model")
348 modelpath.mkdir(parents=
True)
350 srcfile_path = modelpath.joinpath(modname).with_suffix(
".cc")
351 create_file(srcfile_path, MODEL_CC_TEMPLATE, MODULE=modname)
353 hfile_path = modelpath.joinpath(modname).with_suffix(
".h")
354 guard =
"{}_H".format(modname.replace(
"-",
"_").upper())
355 create_file(hfile_path, MODEL_H_TEMPLATE, MODULE=modname, INCLUDE_GUARD=guard)
361 testpath = Path(moduledir,
"test")
362 testpath.mkdir(parents=
True)
364 file_path = testpath.joinpath(modname +
"-test-suite").with_suffix(
".cc")
365 name_parts = modname.split(
"-")
370 CAPITALIZED=
"".join([word.capitalize()
for word
in name_parts]),
372 [word.capitalize()
if index > 0
else word
for index, word
in enumerate(name_parts)]
380 helperpath = Path(moduledir,
"helper")
381 helperpath.mkdir(parents=
True)
383 srcfile_path = helperpath.joinpath(modname +
"-helper").with_suffix(
".cc")
384 create_file(srcfile_path, HELPER_CC_TEMPLATE, MODULE=modname)
386 h_file_path = helperpath.joinpath(modname +
"-helper").with_suffix(
".h")
387 guard =
"{}_HELPER_H".format(modname.replace(
"-",
"_").upper())
388 create_file(h_file_path, HELPER_H_TEMPLATE, MODULE=modname, INCLUDE_GUARD=guard)
394 examplespath = Path(moduledir,
"examples")
395 examplespath.mkdir(parents=
True)
397 cmakelistspath = Path(examplespath,
"CMakeLists.txt")
398 create_file(cmakelistspath, EXAMPLES_CMAKELISTS_TEMPLATE, MODULE=modname)
400 examplesfile_path = examplespath.joinpath(modname +
"-example").with_suffix(
".cc")
401 create_file(examplesfile_path, EXAMPLE_CC_TEMPLATE, MODULE=modname)
407 docpath = Path(moduledir,
"doc")
408 docpath.mkdir(parents=
True)
412 mod_relpath = os.path.relpath(str(moduledir))
414 file_name =
"{}.rst".format(modname)
415 file_path = Path(docpath, file_name)
416 create_file(file_path, DOC_RST_TEMPLATE, MODULE=modname, MODULE_DIR=mod_relpath)
422 modulepath = Path(modpath, modname)
424 if modulepath.exists():
425 print(
"Module {!r} already exists".format(modname), file=sys.stderr)
428 print(
"Creating module {}".format(modulepath))
430 functions = (make_cmakelists, make_model, make_test, make_helper, make_examples, make_doc)
433 modulepath.mkdir(parents=
True)
435 success = all(func(modulepath, modname)
for func
in functions)
438 raise ValueError(
"Generating module artifacts failed")
440 except Exception
as e:
441 if modulepath.exists():
442 shutil.rmtree(modulepath)
444 print(
"Creating module {!r} failed: {}".format(modname, str(e)), file=sys.stderr)
452 description =
"""Generate scaffolding for ns-3 modules
454Generates the directory structure and skeleton files required for an ns-3
455module. All of the generated files are valid C/C++ and will compile successfully
456out of the box. ns3 configure must be run after creating new modules in order
457to integrate them into the ns-3 build system.
459The following directory structure is generated under the contrib directory:
465 |-- <modname>-example.cc
468 |-- <modname>-helper.cc
469 |-- <modname>-helper.h
474 |-- <modname>-test-suite.cc
477<modname> is the name of the module and is restricted to the following
478character groups: letters, numbers, -, _
479The script validates the module name and skips modules that have characters
480outside of the above groups. One exception to the naming rule is that src/
481or contrib/ may be added to the front of the module name to indicate where the
482module scaffold should be created. If the module name starts with src/, then
483the module is placed in the src directory. If the module name starts with
484contrib/, then the module is placed in the contrib directory. If the module
485name does not start with src/ or contrib/, then it defaults to contrib/.
486See the examples section for use cases.
489In some situations it can be useful to group multiple related modules under one
490directory. Use the --project option to specify a common parent directory where
491the modules should be generated. The value passed to --project is treated
492as a relative path. The path components have the same naming requirements as
493the module name: letters, numbers, -, _
494The project directory is placed under the contrib directory and any parts of the
495path that do not exist will be created. Creating projects in the src directory
496is not supported. Module names that start with src/ are not allowed when
497--project is used. Module names that start with contrib/ are treated the same
498as module names that don't start with contrib/ and are generated under the
502 epilog =
"""Examples:
504 %(prog)s contrib/module1
506 Creates a new module named module1 under the contrib directory
510 Creates a new module named module1 under the src directory
512 %(prog)s src/module1 contrib/module2, module3
514 Creates three modules, one under the src directory and two under the
517 %(prog)s --project myproject module1 module2
519 Creates two modules under contrib/myproject
521 %(prog)s --project myproject/sub_project module1 module2
523 Creates two modules under contrib/myproject/sub_project
527 formatter = argparse.RawDescriptionHelpFormatter
529 parser = argparse.ArgumentParser(
530 description=description, epilog=epilog, formatter_class=formatter
537 "Specify a relative path under the contrib directory "
538 "where the new modules will be generated. The path "
539 "will be created if it does not exist."
547 "One or more modules to generate. Module names "
548 "are limited to the following: letters, numbers, -, "
549 "_. Modules are generated under the contrib directory "
550 "except when the module name starts with src/. Modules "
551 "that start with src/ are generated under the src "
562 args = parser.parse_args(argv[1:])
564 project = args.project
565 modnames = args.modnames
567 base_path = Path.cwd()
569 src_path = base_path.joinpath(
"src")
570 contrib_path = base_path.joinpath(
"contrib")
572 for p
in (src_path, contrib_path):
575 "Cannot find the directory '{}'.\nPlease run this "
576 "script from the top level of the ns3 directory".format(p)
584 allowedRE = re.compile(
r"^(\w|-)+$")
591 project_path = Path(project)
593 if project_path.is_absolute():
595 project_path = project_path.relative_to(os.sep)
597 if not all(allowedRE.match(part)
for part
in project_path.parts):
598 parser.error(
"Project path may only contain the characters [a-zA-Z0-9_-].")
603 for name
in modnames:
606 name = name.strip(os.sep)
612 name_path = Path(name)
614 if len(name_path.parts) > 2:
615 print(
"Skipping {}: module name can not be a path".format(name))
619 modpath = contrib_path
621 if name_path.parts[0] ==
"src":
624 "{}: Cannot specify src/ in a module name when --project option is used".format(
632 name_path = name_path.relative_to(
"src")
634 elif name_path.parts[0] ==
"contrib":
635 modpath = contrib_path
638 name_path = name_path.relative_to(
"contrib")
643 modpath = contrib_path.joinpath(project_path)
645 modname = name_path.parts[0]
647 if not allowedRE.match(modname):
649 "Skipping {}: module name may only contain the characters [a-zA-Z0-9_-]".format(
655 modules.append((modpath, modname))
657 if all(
make_module(*module)
for module
in modules):
659 print(
"Successfully created new modules")
660 print(
"Run './ns3 configure' to include them in the build")
665if __name__ ==
"__main__":
668 return_value = main(sys.argv)
669 except Exception
as e:
670 print(
"Exception: '{}'".format(e), file=sys.stderr)
673 sys.exit(return_value)
create_file(path, template, **kwargs)
make_doc(moduledir, modname)
make_test(moduledir, modname)
make_cmakelists(moduledir, modname)
make_examples(moduledir, modname)
make_module(modpath, modname)
make_model(moduledir, modname)
make_helper(moduledir, modname)