From cfe9dbf1176939ef84ca43368e002742d677090c Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Tue, 19 Nov 2024 10:35:13 +0100 Subject: [PATCH] Docs/frontmatter: init automatic reference for frontmatter --- docs/mkdocs.yml | 1 + docs/nix/flake-module.nix | 11 ++++- docs/nix/render_options/__init__.py | 63 +++++++++++++++++++++++++++-- docs/site/clanmodules/index.md | 61 +--------------------------- lib/constraints/interface.nix | 9 +++++ lib/frontmatter/default.nix | 23 +++++------ lib/frontmatter/interface.nix | 29 ++++++++++++- lib/inventory/schemas/default.nix | 3 ++ 8 files changed, 123 insertions(+), 77 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 6a715e924..ed86b6576 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -104,6 +104,7 @@ nav: - reference/clanModules/zerotier-static-peers.md - reference/clanModules/zerotier.md - reference/clanModules/zt-tcp-relay.md + - reference/clanModules/frontmatter/index.md - CLI: - reference/cli/index.md diff --git a/docs/nix/flake-module.nix b/docs/nix/flake-module.nix index 5a8ffa3c6..07e6cdf60 100644 --- a/docs/nix/flake-module.nix +++ b/docs/nix/flake-module.nix @@ -16,6 +16,13 @@ evalClanModules = self.lib.evalClanModules; }; + # Frontmatter for clanModules + clanModulesFrontmatter = + let + docs = pkgs.nixosOptionsDoc { options = self.lib.modules.frontmatterOptions; }; + in + docs.optionsJSON; + clanModulesFileInfo = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModules); # clanModulesReadmes = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModulesReadmes); # clanModulesMeta = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModulesMeta); @@ -56,8 +63,6 @@ buildInputs = [ pkgs.python3 self'.packages.clan-cli - # TODO: see postFixup clan-cli/default.nix:L188 - self'.packages.clan-cli.propagatedBuildInputs ]; } '' @@ -65,6 +70,8 @@ export CLAN_CORE_DOCS=${jsonDocs.clanCore}/share/doc/nixos/options.json # A file that contains the links to all clanModule docs export CLAN_MODULES=${clanModulesFileInfo} + # Frontmatter format for clanModules + export CLAN_MODULES_FRONTMATTER_DOCS=${clanModulesFrontmatter}/share/doc/nixos/options.json # buildClan options export BUILD_CLAN_PATH=${buildClanOptions}/share/doc/nixos/options.json diff --git a/docs/nix/render_options/__init__.py b/docs/nix/render_options/__init__.py index 8300a45b4..90bec6042 100644 --- a/docs/nix/render_options/__init__.py +++ b/docs/nix/render_options/__init__.py @@ -34,6 +34,7 @@ from clan_cli.errors import ClanError # Get environment variables CLAN_CORE_PATH = Path(os.environ["CLAN_CORE_PATH"]) CLAN_CORE_DOCS = Path(os.environ["CLAN_CORE_DOCS"]) +CLAN_MODULES_FRONTMATTER_DOCS = os.environ.get("CLAN_MODULES_FRONTMATTER_DOCS") CLAN_MODULES = os.environ.get("CLAN_MODULES") BUILD_CLAN_PATH = os.environ.get("BUILD_CLAN_PATH") @@ -155,6 +156,63 @@ Your can customize your machines behavior with the configuration [options](#modu options_head = "\n## Module Options\n" +def produce_clan_modules_frontmatter_docs() -> None: + if not CLAN_MODULES_FRONTMATTER_DOCS: + msg = f"Environment variables are not set correctly: $CLAN_CORE_DOCS={CLAN_CORE_DOCS}" + raise ClanError(msg) + + if not OUT: + msg = f"Environment variables are not set correctly: $out={OUT}" + raise ClanError(msg) + + with Path(CLAN_MODULES_FRONTMATTER_DOCS).open() as f: + options: dict[str, dict[str, Any]] = json.load(f) + + # header + output = """# Frontmatter + +Every clan module has a `frontmatter` section within its readme. It provides machine readable metadata about the module. + +!!! example + + The used format is `TOML` + + The content is separated by `---` and the frontmatter must be placed at the very top of the `README.md` file. + + ```toml + --- + description = "A description of the module" + categories = ["category1", "category2"] + + [constraints] + roles.client.max = 10 + roles.server.min = 1 + --- + # Readme content + ... + ``` + +""" + + output += """"## Overview + +This provides an overview of the available attributes of the `frontmatter` within the `README.md` of a clan module. + +""" + for option_name, info in options.items(): + if option_name == "_module.args": + continue + output += render_option(option_name, info) + + outfile = Path(OUT) / "clanModules/frontmatter/index.md" + outfile.parent.mkdir( + parents=True, + exist_ok=True, + ) + with outfile.open("w") as of: + of.write(output) + + def produce_clan_core_docs() -> None: if not CLAN_CORE_DOCS: msg = f"Environment variables are not set correctly: $CLAN_CORE_DOCS={CLAN_CORE_DOCS}" @@ -259,12 +317,10 @@ def produce_clan_modules_docs() -> None: for module_name, options_file in links.items(): readme_file = CLAN_CORE_PATH / "clanModules" / module_name / "README.md" - print(module_name, readme_file) with readme_file.open() as f: readme = f.read() frontmatter: Frontmatter frontmatter, readme_content = extract_frontmatter(readme, str(readme_file)) - print(frontmatter, readme_content) modules_index += build_option_card(module_name, frontmatter) @@ -386,7 +442,6 @@ Each attribute is documented below """ with Path(BUILD_CLAN_PATH).open() as f: options: dict[str, dict[str, Any]] = json.load(f) - # print(options) for option_name, info in options.items(): # Skip underscore options if option_name.startswith("_"): @@ -479,3 +534,5 @@ if __name__ == "__main__": # produce_clan_core_docs() produce_clan_modules_docs() + + produce_clan_modules_frontmatter_docs() diff --git a/docs/site/clanmodules/index.md b/docs/site/clanmodules/index.md index 670995314..8434cbce7 100644 --- a/docs/site/clanmodules/index.md +++ b/docs/site/clanmodules/index.md @@ -63,7 +63,7 @@ description = "Module A" This is the example module that does xyz. ``` -See the [frontmatter reference](#frontmatter-reference) for all supported attributes. +See the [Full Frontmatter reference](../reference/clanModules/frontmatter/index.md) further details and all supported attributes. ## Roles @@ -148,61 +148,4 @@ Assuming that there is a common code path or a common interface between `server` # ... imports = [ ../common.nix ]; } -``` - -## Frontmatter Reference - -`description` (**Required** `String`) -: Short description of the module - -`categories` (Optional `[ String ]`) -: default `[ "Uncategorized" ]` - - Categories are used for Grouping and searching. - - While initial oriented on [freedesktop](https://specifications.freedesktop.org/menu-spec/latest/category-registry.html) the following categories are allowed - - - AudioVideo - - Audio - - Video - - Development - - Education - - Game - - Graphics - - Social - - Network - - Office - - Science - - System - - Settings - - Utility - - Uncategorized - -`features` (Optional `[ String ]`) -: default `[]` - - Clans Features that the module implements support for. - - Available feature flags are: - - - `inventory` - - !!! warning "Important" - Every ClanModule, that specifies `features = [ "inventory" ]` MUST have at least one role. - Many modules use `roles/default.nix` which registers the role `default`. - - If you are a clan module author and your module has only one role where you cannot determine the name, then we would like you to follow the convention. - - -`constraints.roles..` (Optional `int`) (Experimental) -: Contraints for the module - - The following example requires exactly one `server` - and supports up to `7` clients - - ```md - --- - constraints.roles.server.eq = 1 - constraints.roles.client.max = 7 - --- - ``` +``` \ No newline at end of file diff --git a/lib/constraints/interface.nix b/lib/constraints/interface.nix index 38a26296b..84d0e6f65 100644 --- a/lib/constraints/interface.nix +++ b/lib/constraints/interface.nix @@ -18,6 +18,9 @@ in options.roles = lib.mapAttrs ( _name: _: mkOption { + description = '' + Sub-attributes of `${_name}` are constraints for the role. + ''; default = { }; type = types.submoduleWith { modules = [ @@ -26,10 +29,16 @@ in max = mkOption { type = types.nullOr types.int; default = null; + description = '' + Maximum number of instances of this role that can be assigned to a module of this type. + ''; }; min = mkOption { type = types.int; default = 0; + description = '' + Minimum number of instances of this role that must at least be assigned to a module of this type. + ''; }; }; } diff --git a/lib/frontmatter/default.nix b/lib/frontmatter/default.nix index 945bc813c..c91c94fb9 100644 --- a/lib/frontmatter/default.nix +++ b/lib/frontmatter/default.nix @@ -20,17 +20,16 @@ let ]; }; - frontmatterDocsOptions = - lib.optionAttrSetToDocList - (lib.evalModules { - specialArgs = { - moduleName = "{moduleName}"; - allRoles = [ "{roleName}" ]; - }; - modules = [ - ./interface.nix - ]; - }).options; + frontmatterOptions = + (lib.evalModules { + specialArgs = { + moduleName = "{moduleName}"; + allRoles = [ "{roleName}" ]; + }; + modules = [ + ./interface.nix + ]; + }).options; getRoles = serviceName: @@ -91,7 +90,7 @@ in { inherit evalFrontmatter - frontmatterDocsOptions + frontmatterOptions getFrontmatter getReadme diff --git a/lib/frontmatter/interface.nix b/lib/frontmatter/interface.nix index c8ecc7a81..45e33f01e 100644 --- a/lib/frontmatter/interface.nix +++ b/lib/frontmatter/interface.nix @@ -11,11 +11,16 @@ in description = mkOption { type = types.str; description = '' - + A Short description of the module. ''; }; categories = mkOption { default = [ "Uncategorized" ]; + description = '' + Categories are used for Grouping and searching. + + While initial oriented on [freedesktop](https://specifications.freedesktop.org/menu-spec/latest/category-registry.html) the following categories are allowed + ''; type = types.listOf ( types.enum [ "AudioVideo" @@ -38,6 +43,15 @@ in }; features = mkOption { default = [ ]; + description = '' + Clans Features that the module implements support for. + + !!! warning "Important" + Every ClanModule, that specifies `features = [ "inventory" ]` MUST have at least one role. + Many modules use `roles/default.nix` which registers the role `default`. + + If you are a clan module author and your module has only one role where you cannot determine the name, then we would like you to follow the convention. + ''; type = types.listOf ( types.enum [ "inventory" @@ -47,6 +61,19 @@ in constraints = mkOption { default = { }; + description = '' + Contraints for the module + + The following example requires exactly one `server` + and supports up to `7` clients + + ```md + --- + constraints.roles.server.eq = 1 + constraints.roles.client.max = 7 + --- + ``` + ''; type = types.submoduleWith { inherit specialArgs; modules = [ diff --git a/lib/inventory/schemas/default.nix b/lib/inventory/schemas/default.nix index 0206314a9..56d508918 100644 --- a/lib/inventory/schemas/default.nix +++ b/lib/inventory/schemas/default.nix @@ -22,6 +22,8 @@ let header = { }; }; + frontMatterSchema = jsonLib.parseOptions self.lib.modules.frontmatterOptions { }; + inventorySchema = jsonLib.parseModule (import ../build-inventory/interface.nix); renderSchema = pkgs.writers.writePython3Bin "render-schema" { @@ -48,6 +50,7 @@ let in { inherit + frontMatterSchema inventorySchema modulesSchema renderSchema