7from pathlib
import Path
9CMAKELISTS_TEMPLATE =
"""\
10check_include_file_cxx(stdint.h HAVE_STDINT_H)
12 add_definitions(-DHAVE_STDINT_H)
15set(examples_as_tests_sources)
16if(${{ENABLE_EXAMPLES}})
17 set(examples_as_tests_sources
18 #test/{MODULE}-examples-test-suite.cc
24 SOURCE_FILES model/{MODULE}.cc
25 helper/{MODULE}-helper.cc
26 HEADER_FILES model/{MODULE}.h
27 helper/{MODULE}-helper.h
28 LIBRARIES_TO_LINK ${{libcore}}
29 TEST_SOURCES test/{MODULE}-test-suite.cc
30 ${{examples_as_tests_sources}}
35MODEL_CC_TEMPLATE =
"""\
47MODEL_H_TEMPLATE =
"""\
48#ifndef {INCLUDE_GUARD}
49#define {INCLUDE_GUARD}
51// Add a doxygen group for this module.
52// If you have more than one file, this should be in only one of them.
54 * @defgroup {MODULE} Description of the {MODULE}
60// Each class should be documented using Doxygen,
61// and have an @ingroup {MODULE} directive
67#endif // {INCLUDE_GUARD}
71HELPER_CC_TEMPLATE =
"""\
72#include "{MODULE}-helper.h"
83HELPER_H_TEMPLATE =
"""\
84#ifndef {INCLUDE_GUARD}
85#define {INCLUDE_GUARD}
87#include "ns3/{MODULE}.h"
92// Each class should be documented using Doxygen,
93// and have an @ingroup {MODULE} directive
99#endif // {INCLUDE_GUARD}
103EXAMPLES_CMAKELISTS_TEMPLATE =
"""\
105 NAME {MODULE}-example
106 SOURCE_FILES {MODULE}-example.cc
107 LIBRARIES_TO_LINK ${{lib{MODULE}}}
111EXAMPLE_CC_TEMPLATE =
"""\
112#include "ns3/core-module.h"
113#include "ns3/{MODULE}-helper.h"
118 * Explain here what the example does.
124main(int argc, char* argv[])
128 CommandLine cmd(__FILE__);
129 cmd.AddValue("verbose", "Tell application to log if true", verbose);
131 cmd.Parse(argc, argv);
136 Simulator::Destroy();
142TEST_CC_TEMPLATE =
"""\
143// Include a header file from your module to test.
144#include "ns3/{MODULE}.h"
146// An essential include is test.h
149// Do not put your test classes in namespace ns3. You may find it useful
150// to use the using directive to access the ns3 namespace directly
153// Add a doxygen group for tests.
154// If you have more than one test, this should be in only one of them.
156 * @defgroup {MODULE}-tests Tests for {MODULE}
161// This is an example TestCase.
163 * @ingroup {MODULE}-tests
164 * Test case for feature 1
166class {CAPITALIZED}TestCase1 : public TestCase
169 {CAPITALIZED}TestCase1();
170 ~{CAPITALIZED}TestCase1() override;
173 void DoRun() override;
176// Add some help text to this case to describe what it is intended to test
177{CAPITALIZED}TestCase1::{CAPITALIZED}TestCase1()
178 : TestCase("{CAPITALIZED} test case (does nothing)")
182// This destructor does nothing but we include it as a reminder that
183// the test case should clean up after itself
184{CAPITALIZED}TestCase1::~{CAPITALIZED}TestCase1()
189// This method is the pure virtual method from class TestCase that every
190// TestCase must implement
193{CAPITALIZED}TestCase1::DoRun()
195 // A wide variety of test macros are available in src/core/test.h
196 NS_TEST_ASSERT_MSG_EQ(true, true, "true doesn't equal true for some reason");
197 // Use this one for floating point comparisons
198 NS_TEST_ASSERT_MSG_EQ_TOL(0.01, 0.01, 0.001, "Numbers are not equal within tolerance");
201// The TestSuite class names the TestSuite, identifies what type of TestSuite,
202// and enables the TestCases to be run. Typically, only the constructor for
203// this class must be defined
206 * @ingroup {MODULE}-tests
207 * TestSuite for module {MODULE}
209class {CAPITALIZED}TestSuite : public TestSuite
212 {CAPITALIZED}TestSuite();
215// Type for TestSuite can be UNIT, SYSTEM, EXAMPLE, or PERFORMANCE
216{CAPITALIZED}TestSuite::{CAPITALIZED}TestSuite()
217 : TestSuite("{MODULE}", Type::UNIT)
219 // Duration for TestCase can be QUICK, EXTENSIVE or TAKES_FOREVER
220 AddTestCase(new {CAPITALIZED}TestCase1, TestCase::Duration::QUICK);
223// Do not forget to allocate an instance of this TestSuite
225 * @ingroup {MODULE}-tests
226 * Static variable for test initialization
228static {CAPITALIZED}TestSuite s{COMPOUND}TestSuite;
232DOC_RST_TEMPLATE =
"""Example Module Documentation
233============================
235.. include:: replace.txt
239 ============= Module Name
240 ------------- Section (#.#)
241 ~~~~~~~~~~~~~ Subsection (#.#.#)
243This is the outline for adding new module documentation to |ns3|.
244See ``src/lr-wpan/doc/lr-wpan.rst`` for an example.
246This first part is for describing what the model is trying to
247accomplish. General descriptions and design, overview of the project
248goes in this part without the need of any subsections. A visual summary
251For consistency (italicized formatting), please use |ns3| to refer to
252ns-3 in the documentation (and likewise, |ns2| for ns-2). These macros
253are defined in the file ``replace.txt``.
255Detailed |ns3| Sphinx documentation guidelines can be found `here <https://www.nsnam.org/docs/contributing/html/models.html>`_
258The source code for the new module lives in the directory ``{MODULE_DIR}``.
264This is should be your first section. Please use it to list the scope and
265limitations. What can the model do? What can it not do? Be brief and concise.
270Free form. Your second section of your documentation and its subsections goes here.
271The documentation can contain any number of sections but be careful to not use
272more than two levels in subsections.
277Free form. The last section of your documentation with content goes here.
282A brief description of the module usage goes here. This section must be present.
287A subsection with a description of the helpers used by the model goes here. Snippets of code are
288preferable when describing the helpers usage. This subsection must be present.
289If the model CANNOT provide helpers write "Not applicable".
294A subsection with a list of attributes used by the module, each attribute should include a small
295description. This subsection must be present.
296If the model CANNOT provide attributes write "Not applicable".
301A subsection with a list of the source traces used by the module, each trace should include a small
302description. This subsection must be present.
303If the model CANNOT provide traces write "Not applicable".
308A brief description of each example and test present in the model must be here.
309Include both the name of the example file and a brief description of the example.
310This section must be present.
315Describe how the model has been tested/validated. What tests run in the
316test suite? How much API and code is covered by the tests?
318This section must be present. Write ``No formal validation has been made`` if your
319model do not contain validations.
324The reference material used in the construction of the model.
325This section must be the last section and must be present.
326Use numbers for the index not names when listing the references. When possible, include the link of the referenced material.
330[`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.
336 artifact_path = Path(path)
339 with artifact_path.open(
"wt", encoding=
"utf-8")
as f:
340 f.write(template.format(**kwargs))
344 path = Path(moduledir,
"CMakeLists.txt")
346 create_file(path, CMAKELISTS_TEMPLATE, MODULE=modname)
352 modelpath = Path(moduledir,
"model")
353 modelpath.mkdir(parents=
True)
355 srcfile_path = modelpath.joinpath(modname).with_suffix(
".cc")
356 create_file(srcfile_path, MODEL_CC_TEMPLATE, MODULE=modname)
358 hfile_path = modelpath.joinpath(modname).with_suffix(
".h")
359 guard =
"{}_H".format(modname.replace(
"-",
"_").upper())
360 create_file(hfile_path, MODEL_H_TEMPLATE, MODULE=modname, INCLUDE_GUARD=guard)
366 testpath = Path(moduledir,
"test")
367 testpath.mkdir(parents=
True)
369 file_path = testpath.joinpath(modname +
"-test-suite").with_suffix(
".cc")
370 name_parts = modname.split(
"-")
375 CAPITALIZED=
"".join([word.capitalize()
for word
in name_parts]),
377 [word.capitalize()
if index > 0
else word
for index, word
in enumerate(name_parts)]
385 helperpath = Path(moduledir,
"helper")
386 helperpath.mkdir(parents=
True)
388 srcfile_path = helperpath.joinpath(modname +
"-helper").with_suffix(
".cc")
389 create_file(srcfile_path, HELPER_CC_TEMPLATE, MODULE=modname)
391 h_file_path = helperpath.joinpath(modname +
"-helper").with_suffix(
".h")
392 guard =
"{}_HELPER_H".format(modname.replace(
"-",
"_").upper())
393 create_file(h_file_path, HELPER_H_TEMPLATE, MODULE=modname, INCLUDE_GUARD=guard)
399 examplespath = Path(moduledir,
"examples")
400 examplespath.mkdir(parents=
True)
402 cmakelistspath = Path(examplespath,
"CMakeLists.txt")
403 create_file(cmakelistspath, EXAMPLES_CMAKELISTS_TEMPLATE, MODULE=modname)
405 examplesfile_path = examplespath.joinpath(modname +
"-example").with_suffix(
".cc")
406 create_file(examplesfile_path, EXAMPLE_CC_TEMPLATE, MODULE=modname)
412 docpath = Path(moduledir,
"doc")
413 docpath.mkdir(parents=
True)
417 mod_relpath = os.path.relpath(str(moduledir))
419 file_name =
"{}.rst".format(modname)
420 file_path = Path(docpath, file_name)
421 create_file(file_path, DOC_RST_TEMPLATE, MODULE=modname, MODULE_DIR=mod_relpath)
427 modulepath = Path(modpath, modname)
429 if modulepath.exists():
430 print(
"Module {!r} already exists".format(modname), file=sys.stderr)
433 print(
"Creating module {}".format(modulepath))
435 functions = (make_cmakelists, make_model, make_test, make_helper, make_examples, make_doc)
438 modulepath.mkdir(parents=
True)
440 success = all(func(modulepath, modname)
for func
in functions)
443 raise ValueError(
"Generating module artifacts failed")
445 except Exception
as e:
446 if modulepath.exists():
447 shutil.rmtree(modulepath)
449 print(
"Creating module {!r} failed: {}".format(modname, str(e)), file=sys.stderr)
457 description =
"""Generate scaffolding for ns-3 modules
459Generates the directory structure and skeleton files required for an ns-3
460module. All of the generated files are valid C/C++ and will compile successfully
461out of the box. ns3 configure must be run after creating new modules in order
462to integrate them into the ns-3 build system.
464The following directory structure is generated under the contrib directory:
470 |-- <modname>-example.cc
473 |-- <modname>-helper.cc
474 |-- <modname>-helper.h
479 |-- <modname>-test-suite.cc
482<modname> is the name of the module and is restricted to the following
483character groups: letters, numbers, -, _
484The script validates the module name and skips modules that have characters
485outside of the above groups. One exception to the naming rule is that src/
486or contrib/ may be added to the front of the module name to indicate where the
487module scaffold should be created. If the module name starts with src/, then
488the module is placed in the src directory. If the module name starts with
489contrib/, then the module is placed in the contrib directory. If the module
490name does not start with src/ or contrib/, then it defaults to contrib/.
491See the examples section for use cases.
494In some situations it can be useful to group multiple related modules under one
495directory. Use the --project option to specify a common parent directory where
496the modules should be generated. The value passed to --project is treated
497as a relative path. The path components have the same naming requirements as
498the module name: letters, numbers, -, _
499The project directory is placed under the contrib directory and any parts of the
500path that do not exist will be created. Creating projects in the src directory
501is not supported. Module names that start with src/ are not allowed when
502--project is used. Module names that start with contrib/ are treated the same
503as module names that don't start with contrib/ and are generated under the
507 epilog =
"""Examples:
509 %(prog)s contrib/module1
511 Creates a new module named module1 under the contrib directory
515 Creates a new module named module1 under the src directory
517 %(prog)s src/module1 contrib/module2, module3
519 Creates three modules, one under the src directory and two under the
522 %(prog)s --project myproject module1 module2
524 Creates two modules under contrib/myproject
526 %(prog)s --project myproject/sub_project module1 module2
528 Creates two modules under contrib/myproject/sub_project
532 formatter = argparse.RawDescriptionHelpFormatter
534 parser = argparse.ArgumentParser(
535 description=description, epilog=epilog, formatter_class=formatter
542 "Specify a relative path under the contrib directory "
543 "where the new modules will be generated. The path "
544 "will be created if it does not exist."
552 "One or more modules to generate. Module names "
553 "are limited to the following: letters, numbers, -, "
554 "_. Modules are generated under the contrib directory "
555 "except when the module name starts with src/. Modules "
556 "that start with src/ are generated under the src "
567 args = parser.parse_args(argv[1:])
569 project = args.project
570 modnames = args.modnames
572 base_path = Path.cwd()
574 src_path = base_path.joinpath(
"src")
575 contrib_path = base_path.joinpath(
"contrib")
577 for p
in (src_path, contrib_path):
580 "Cannot find the directory '{}'.\nPlease run this "
581 "script from the top level of the ns3 directory".format(p)
589 allowedRE = re.compile(
r"^(\w|-)+$")
596 project_path = Path(project)
598 if project_path.is_absolute():
600 project_path = project_path.relative_to(os.sep)
602 if not all(allowedRE.match(part)
for part
in project_path.parts):
603 parser.error(
"Project path may only contain the characters [a-zA-Z0-9_-].")
608 for name
in modnames:
611 name = name.strip(os.sep)
617 name_path = Path(name)
619 if len(name_path.parts) > 2:
620 print(
"Skipping {}: module name can not be a path".format(name))
624 modpath = contrib_path
626 if name_path.parts[0] ==
"src":
629 "{}: Cannot specify src/ in a module name when --project option is used".format(
637 name_path = name_path.relative_to(
"src")
639 elif name_path.parts[0] ==
"contrib":
640 modpath = contrib_path
643 name_path = name_path.relative_to(
"contrib")
648 modpath = contrib_path.joinpath(project_path)
650 modname = name_path.parts[0]
652 if not allowedRE.match(modname):
654 "Skipping {}: module name may only contain the characters [a-zA-Z0-9_-]".format(
660 modules.append((modpath, modname))
662 if all(
make_module(*module)
for module
in modules):
664 print(
"Successfully created new modules")
665 print(
"Run './ns3 configure' to include them in the build")
670if __name__ ==
"__main__":
673 return_value = main(sys.argv)
674 except Exception
as e:
675 print(
"Exception: '{}'".format(e), file=sys.stderr)
678 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)