docs: Simplify module creation docs using script

Change-Id: I894d7bc2509d184aea47041ee822b646e66bc9fb
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/200231
Presubmit-Verified: CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Alexei Frolov <frolv@google.com>
Pigweed-Auto-Submit: Taylor Cramer <cramertj@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed-service-accounts.iam.gserviceaccount.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 973e71b..ef60866 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -76,8 +76,8 @@
 add_subdirectory(pw_assert_zephyr EXCLUDE_FROM_ALL)
 add_subdirectory(pw_async EXCLUDE_FROM_ALL)
 add_subdirectory(pw_async_basic EXCLUDE_FROM_ALL)
-add_subdirectorY(pw_async2 EXCLUDE_FROM_ALL)
-add_subdirectorY(pw_async2_basic EXCLUDE_FROM_ALL)
+add_subdirectory(pw_async2 EXCLUDE_FROM_ALL)
+add_subdirectory(pw_async2_basic EXCLUDE_FROM_ALL)
 add_subdirectory(pw_base64 EXCLUDE_FROM_ALL)
 add_subdirectory(pw_blob_store EXCLUDE_FROM_ALL)
 add_subdirectory(pw_bluetooth EXCLUDE_FROM_ALL)
diff --git a/docs/module_structure.rst b/docs/module_structure.rst
index 3ca2dad..80cf5aa 100644
--- a/docs/module_structure.rst
+++ b/docs/module_structure.rst
@@ -549,7 +549,8 @@
 
 Creating a new Pigweed module
 -----------------------------
-To create a new Pigweed module, follow the below steps.
+New Pigweed modules can be easily created using
+``pw module create MODULE_NAME`` (refer to the `Module name`_ guidelines).
 
 .. tip::
 
@@ -560,45 +561,28 @@
   accidentally duplicating work, or avoiding writing code that won't get
   accepted.
 
-1. Create module folder following `Module name`_ guidelines.
-2. Add `C++ public headers`_ files in
+This command will create a folder with the provided module name as well as a
+number of basic build files. After this command has run, new code should be
+added in the following locations:
+
+1. Add `C++ public headers`_ files in
    ``{pw_module_dir}/public/{pw_module_name}/``
-3. Add `C++ implementation files`_ files in ``{pw_module_dir}/``
-4. Add module documentation
+2. Add `C++ implementation files`_ files in ``{pw_module_dir}/``
+3. Add module documentation to ``{pw_module_dir}/docs.rst``. See
+   :ref:`seed-0102` for additional documentation options.
+4. Add GN build targets to ``{pw_module_dir}/BUILD.gn``.
 
-   - Add ``{pw_module_dir}/README.md`` that has a module summary
-   - Add ``{pw_module_dir}/docs.rst`` that contains the main module
-     documentation
-   - Add optional documentation as described in :ref:`seed-0102`
+   - Add new ``pw_test`` targets to the list in ``pw_test_group("tests")``.
+   - Add any additional ``.rst`` documentation files to
+     ``pw_docs_group("docs")``.
 
-5. Add GN build support in ``{pw_module_dir}/BUILD.gn``
+5. Add Bazel build targets to ``{pw_module_dir}/BUILD.bazel``.
 
-   - Declare tests in ``pw_test_group("tests")``
-   - Declare docs in ``pw_docs_group("docs")``
+6. Add CMake build targets to ``{pw_module_dir}/CMakeLists.txt``.
 
-   Both ``tests`` and ``docs`` are required, even if the module is empty!
-
-6. Add Bazel build support in ``{pw_module_dir}/BUILD.bazel``
-
-7. Add CMake build support in ``{pw_module_dir}/CMakeLists.txt``
-
-8. Add the new module to the ``/PIGWEED_MODULES`` list
-
-   Modules must be listed one per line with no extra spaces or comments. This
-   automatically adds the new module, its tests, and its docs, to the GN build.
-
-9. Update the generated Pigweed modules lists file
-
-   .. code-block:: bash
-
-      ninja -C out update_modules
-
-10. Add the new module to CMake build
-
-    - In ``/CMakeLists.txt`` add ``add_subdirectory(pw_new)``
-
-11. Run :ref:`module-pw_module-module-check`
+7. Run :ref:`module-pw_module-module-check` to ensure that the new module has
+   been properly configured.
 
     - ``$ pw module check {pw_module_dir}``
 
-12. Contribute your module to upstream Pigweed (optional but encouraged!)
+8. Contribute your module to upstream Pigweed (optional but encouraged!)
diff --git a/pw_module/py/pw_module/create.py b/pw_module/py/pw_module/create.py
index df626b4..ce24ed0 100644
--- a/pw_module/py/pw_module/create.py
+++ b/pw_module/py/pw_module/create.py
@@ -808,6 +808,8 @@
 ) -> _ModuleContext:
     """Creates the basic layout of a Pigweed module."""
     module_dir.mkdir()
+    public_dir = module_dir / 'public' / module_name.full
+    public_dir.mkdir(parents=True)
 
     ctx = _ModuleContext(
         name=module_name,
@@ -827,6 +829,72 @@
     return ctx
 
 
+def _add_to_pigweed_modules_file(
+    project_root: Path,
+    module_name: _ModuleName,
+) -> None:
+    modules_file = project_root / 'PIGWEED_MODULES'
+    if not modules_file.exists():
+        _LOG.error(
+            'Could not locate PIGWEED_MODULES file; '
+            'your repository may be in a bad state.'
+        )
+        return
+
+    modules_gni_file = (
+        project_root / 'pw_build' / 'generated_pigweed_modules_lists.gni'
+    )
+
+    # Cut off the extra newline at the end of the file.
+    modules_list = modules_file.read_text().split('\n')[:-1]
+    modules_list.append(module_name.full)
+    modules_list.sort()
+    modules_list.append('')
+    modules_file.write_text('\n'.join(modules_list))
+    print('  modify  ' + str(modules_file.relative_to(Path.cwd())))
+
+    generate_modules_lists.main(
+        root=project_root,
+        modules_list=modules_file,
+        modules_gni_file=modules_gni_file,
+        mode=generate_modules_lists.Mode.UPDATE,
+    )
+    print('  modify  ' + str(modules_gni_file.relative_to(Path.cwd())))
+
+
+def _add_to_root_cmakelists(
+    project_root: Path,
+    module_name: _ModuleName,
+) -> None:
+    new_line = f'add_subdirectory({module_name.full} EXCLUDE_FROM_ALL)\n'
+
+    path = project_root / 'CMakeLists.txt'
+    if not path.exists():
+        _LOG.error('Could not locate root CMakeLists.txt file.')
+        return
+
+    lines = []
+    with path.open() as f:
+        lines = f.readlines()
+
+    add_subdir_start = 0
+    while add_subdir_start < len(lines):
+        if lines[add_subdir_start].startswith('add_subdirectory'):
+            break
+        add_subdir_start += 1
+
+    insert_point = add_subdir_start
+    while (
+        lines[insert_point].startswith('add_subdirectory')
+        and lines[insert_point] < new_line
+    ):
+        insert_point += 1
+
+    lines.insert(insert_point, new_line)
+    path.write_text(''.join(lines))
+    print('  modify  ' + str(path.relative_to(Path.cwd())))
+
+
 def _create_module(
     module: str, languages: Iterable[str], build_systems: Iterable[str]
 ) -> None:
@@ -876,33 +944,9 @@
         build_file.write()
 
     if is_upstream:
-        modules_file = project_root / 'PIGWEED_MODULES'
-        if not modules_file.exists():
-            _LOG.error(
-                'Could not locate PIGWEED_MODULES file; '
-                'your repository may be in a bad state.'
-            )
-            return
-
-        modules_gni_file = (
-            project_root / 'pw_build' / 'generated_pigweed_modules_lists.gni'
-        )
-
-        # Cut off the extra newline at the end of the file.
-        modules_list = modules_file.read_text().split('\n')[:-1]
-        modules_list.append(module_name.full)
-        modules_list.sort()
-        modules_list.append('')
-        modules_file.write_text('\n'.join(modules_list))
-        print('  modify  ' + str(modules_file.relative_to(Path.cwd())))
-
-        generate_modules_lists.main(
-            root=project_root,
-            modules_list=modules_file,
-            modules_gni_file=modules_gni_file,
-            mode=generate_modules_lists.Mode.UPDATE,
-        )
-        print('  modify  ' + str(modules_gni_file.relative_to(Path.cwd())))
+        _add_to_pigweed_modules_file(project_root, module_name)
+        if 'cmake' in build_systems:
+            _add_to_root_cmakelists(project_root, module_name)
 
     print()
     _LOG.info(
@@ -921,9 +965,9 @@
             'Comma-separated list of build systems the module supports. '
             f'Options: {", ".join(_BUILD_FILES.keys())}'
         ),
-        type=csv,
+        choices=_BUILD_FILES.keys(),
         default=_BUILD_FILES.keys(),
-        metavar='BUILD[,BUILD,...]',
+        type=csv,
     )
     parser.add_argument(
         '--languages',
@@ -931,9 +975,9 @@
             'Comma-separated list of languages the module will use. '
             f'Options: {", ".join(_LANGUAGE_GENERATORS.keys())}'
         ),
-        type=csv,
+        choices=_LANGUAGE_GENERATORS.keys(),
         default=[],
-        metavar='LANG[,LANG,...]',
+        type=csv,
     )
     parser.add_argument(
         'module', help='Name of the module to create.', metavar='MODULE_NAME'