diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index 091ed242..00000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Update Sphinx documentation - -on: [push, pull_request, workflow_dispatch] - -permissions: - contents: write - -jobs: - docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: ./.github/actions/setup - - run: make docs - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} - with: - publish_branch: gh-pages - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: _build - force_orphan: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0cd297a0..2537a754 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,29 @@ env: GIT_CLIFF_VERSION: 2.12.0 jobs: + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: gardenlinux/python-gardenlinux-lib/.github/actions/setup@main + - run: make docs + - if: ${{ github.ref_name != 'main' }} + name: Upload as artifact + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # pin@v6.0.0 + with: + name: docs + path: _build/ + - if: ${{ github.ref_name == 'main' }} + name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 + with: + publish_branch: gh-pages + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: _build + force_orphan: true + create-pre-release: + if: ${{ github.ref_name == 'main' }} runs-on: ubuntu-latest permissions: contents: write diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 00000000..91fdf275 --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,318 @@ +/* Garden Linux Design System - Sphinx Theme Customization */ + +:root { + /* Primary Green */ + --gardenlinux-primary-base: #027154; + --gardenlinux-primary-dark: #015a43; + --gardenlinux-primary-darker: #014332; + --gardenlinux-primary-darkest: #012c21; + --gardenlinux-primary-light: #038865; + --gardenlinux-primary-lighter: #04a076; + --gardenlinux-primary-lightest: #05b887; + + /* Secondary Green */ + --gardenlinux-secondary-base: #009f76; + --gardenlinux-secondary-dark: #008060; + --gardenlinux-secondary-darker: #006149; + --gardenlinux-secondary-darkest: #004232; + --gardenlinux-secondary-light: #00be8c; + --gardenlinux-secondary-lighter: #00dd9f; + --gardenlinux-secondary-lightest: #00fcb2; + + /* Accent Yellow/Gold */ + --gardenlinux-accent-base: #dfbe00; + --gardenlinux-accent-dark: #b29900; + --gardenlinux-accent-darker: #857400; + --gardenlinux-accent-darkest: #584f00; + --gardenlinux-accent-light: #ffd500; + --gardenlinux-accent-lighter: #ffec33; + --gardenlinux-accent-lightest: #fff366; + + /* Semantic Colors */ + --gardenlinux-success: #009f76; + --gardenlinux-success-light: #00be8c; + --gardenlinux-success-dark: #008060; + --gardenlinux-warning: #dfbe00; + --gardenlinux-warning-light: #ffd500; + --gardenlinux-warning-dark: #b29900; + --gardenlinux-error: #d32f2f; + --gardenlinux-error-light: #ef5350; + --gardenlinux-error-dark: #c62828; + --gardenlinux-info: #027154; + --gardenlinux-info-light: #038865; + --gardenlinux-info-dark: #015a43; +} + +/* Override Read the Docs theme colors with Garden Linux colors */ +.wy-side-nav-search { + background-color: var(--gardenlinux-primary-base); +} + +.wy-side-nav-search > a { + color: #ffffff !important; +} + +.wy-side-nav-search > div.version { + color: rgba(255, 255, 255, 0.9) !important; +} + +/* Ensure all text in the search header is white */ +.wy-side-nav-search * { + color: #ffffff !important; +} + +.wy-side-nav-search input[type="text"] { + background-color: rgba(255, 255, 255, 0.2); + border-color: rgba(255, 255, 255, 0.3); + color: #ffffff; +} + +.wy-side-nav-search input[type="text"]::placeholder { + color: rgba(255, 255, 255, 0.7); +} + +/* Menu colors - improved readability - white background */ +.wy-side-scroll { + background-color: #ffffff; +} + +.wy-menu-vertical { + background-color: #ffffff; +} + +.wy-menu-vertical a { + color: #1a1a1a !important; + font-weight: 500; +} + +/* Make unselected/collapsed menu items more readable */ +.wy-menu-vertical li:not(.current) > a { + color: #1a1a1a !important; +} + +.wy-menu-vertical li.toctree-l1:not(.current) > a { + color: #1a1a1a !important; + font-weight: 500; +} + +.wy-menu-vertical a:hover { + background-color: var(--gardenlinux-primary-lightest); + color: var(--gardenlinux-primary-darkest) !important; +} + +.wy-menu-vertical li.current { + background-color: var(--gardenlinux-primary-lightest); +} + +.wy-menu-vertical li.current > a { + color: var(--gardenlinux-primary-darkest) !important; + border-right: 3px solid var(--gardenlinux-primary-base); + font-weight: 600; + background-color: var(--gardenlinux-primary-lightest); +} + +.wy-menu-vertical li.toctree-l1.current > a { + border-bottom: 1px solid var(--gardenlinux-primary-light); +} + +/* Improve nested menu items readability */ +.wy-menu-vertical li.toctree-l2 a, +.wy-menu-vertical li.toctree-l3 a, +.wy-menu-vertical li.toctree-l4 a { + color: #1a1a1a !important; +} + +/* Make unselected nested items more readable */ +.wy-menu-vertical li.toctree-l2:not(.current) > a, +.wy-menu-vertical li.toctree-l3:not(.current) > a, +.wy-menu-vertical li.toctree-l4:not(.current) > a { + color: #1a1a1a !important; + font-weight: 400; +} + +.wy-menu-vertical li.toctree-l2.current > a, +.wy-menu-vertical li.toctree-l3.current > a, +.wy-menu-vertical li.toctree-l4.current > a { + background-color: var(--gardenlinux-primary-lightest) !important; + color: var(--gardenlinux-primary-darkest) !important; + font-weight: 600; +} + +.wy-menu-vertical header, +.wy-menu-vertical p.caption { + color: #1a1a1a; + font-weight: 600; + background-color: #ffffff; +} + +/* Ensure all sidebar elements have white background */ +.wy-nav-side { + background-color: #ffffff !important; +} + +.wy-nav-side .wy-menu-vertical { + background-color: #ffffff !important; +} + +.wy-side-nav-search input[type="text"] { + background-color: rgba(255, 255, 255, 0.2); + border-color: rgba(255, 255, 255, 0.3); + color: #ffffff; +} + +/* Override any black/dark backgrounds in sidebar */ +.wy-menu-vertical ul { + background-color: #ffffff !important; +} + +.wy-menu-vertical li { + background-color: transparent; +} + +/* Fix selected sub-sections - use light background with dark text */ +.wy-menu-vertical li.current a, +.wy-menu-vertical li.current a:visited { + color: var(--gardenlinux-primary-darkest) !important; + background-color: var(--gardenlinux-primary-lightest) !important; +} + +.wy-menu-vertical li.current a:hover { + color: var(--gardenlinux-primary-darkest) !important; + background-color: var(--gardenlinux-primary-lighter) !important; +} + +/* Fix expand/collapse icon squares - make them dark and readable */ +.wy-menu-vertical .toctree-expand, +.wy-menu-vertical span.toctree-expand, +.wy-menu-vertical a .toctree-expand { + color: #1a1a1a !important; + border-color: #1a1a1a !important; + opacity: 1 !important; + font-weight: bold; +} + +.wy-menu-vertical .toctree-expand:before, +.wy-menu-vertical span.toctree-expand:before, +.wy-menu-vertical a .toctree-expand:before { + color: #1a1a1a !important; + border-color: #1a1a1a !important; +} + +/* Fix icon boxes for expandable items at all levels */ +.wy-menu-vertical li.toctree-l1 > a .toctree-expand, +.wy-menu-vertical li.toctree-l2 > a .toctree-expand, +.wy-menu-vertical li.toctree-l3 > a .toctree-expand, +.wy-menu-vertical li.toctree-l4 > a .toctree-expand { + color: #1a1a1a !important; + border-color: #1a1a1a !important; + background-color: transparent !important; +} + +/* Target the icon element directly */ +.wy-menu-vertical li .toctree-expand { + color: #1a1a1a !important; + border: 1px solid #1a1a1a !important; +} + +/* Make sure plus/minus signs are dark */ +.wy-menu-vertical .toctree-expand:after { + color: #1a1a1a !important; +} + +/* Links */ +a { + color: var(--gardenlinux-primary-base); +} + +a:hover { + color: var(--gardenlinux-secondary-base); +} + +a:visited { + color: var(--gardenlinux-primary-dark); +} + +/* Code blocks */ +.highlight { + background-color: #f5f5f5; +} + +.highlight .hll { + background-color: var(--gardenlinux-primary-lightest); +} + +/* Buttons and important elements */ +.btn-primary, +.rst-content .btn-primary { + background-color: var(--gardenlinux-primary-base); + border-color: var(--gardenlinux-primary-base); + color: #ffffff; +} + +.btn-primary:hover, +.rst-content .btn-primary:hover { + background-color: var(--gardenlinux-primary-dark); + border-color: var(--gardenlinux-primary-dark); +} + +/* Admonitions */ +.rst-content .admonition { + border-left: 4px solid; +} + +.rst-content .admonition.note { + border-left-color: var(--gardenlinux-info); + background-color: var(--gardenlinux-info-lightest); +} + +.rst-content .admonition.warning { + border-left-color: var(--gardenlinux-warning); + background-color: var(--gardenlinux-accent-lightest); +} + +.rst-content .admonition.danger, +.rst-content .admonition.error { + border-left-color: var(--gardenlinux-error); + background-color: var(--gardenlinux-error-light); +} + +.rst-content .admonition.success, +.rst-content .admonition.tip { + border-left-color: var(--gardenlinux-success); + background-color: var(--gardenlinux-secondary-lightest); +} + +/* Headers */ +h1, +h2, +h3, +h4, +h5, +h6 { + color: var(--gardenlinux-primary-base); +} + +/* Table of contents */ +.rst-content .toctree-wrapper > p.caption { + color: var(--gardenlinux-primary-base); + font-weight: bold; +} + +/* Logo styling */ +.logo { + max-width: 250px; + height: auto; + margin: 10px 0; +} + +/* Footer */ +footer { + border-top: 1px solid var(--gardenlinux-primary-light); +} + +/* Responsive adjustments */ +@media screen and (max-width: 768px) { + .wy-side-nav-search { + padding: 0.809em; + } +} diff --git a/docs/_static/gardenlinux-logo.svg b/docs/_static/gardenlinux-logo.svg new file mode 100644 index 00000000..4b63366a --- /dev/null +++ b/docs/_static/gardenlinux-logo.svg @@ -0,0 +1 @@ +Garden Linux_logo diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 00000000..2d68330a --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,22 @@ +API Reference +============= + +This section provides detailed documentation for all Python modules and classes in python-gardenlinux-lib. + +.. automodule:: gardenlinux + :members: + +.. automodule:: gardenlinux.apt + :members: + +.. automodule:: gardenlinux.features + :members: + +.. automodule:: gardenlinux.flavors + :members: + +.. automodule:: gardenlinux.git + :members: + +.. automodule:: gardenlinux.oci + :members: diff --git a/docs/cli.rst b/docs/cli.rst new file mode 100644 index 00000000..80cc963c --- /dev/null +++ b/docs/cli.rst @@ -0,0 +1,63 @@ +Command-Line Interface +====================== + +This page documents all available command-line tools provided by python-gardenlinux-lib. + +Features Commands +----------------- + +gl-cname +~~~~~~~~ + +Generate a canonical name (cname) from feature sets. + +.. autoprogram:: gardenlinux.features.cname_main:get_parser() + +gl-features-parse +~~~~~~~~~~~~~~~~~ + +Parse and extract information from GardenLinux features. + +.. autoprogram:: gardenlinux.features.__main__:get_parser() + +Flavors Commands +---------------- + +gl-flavors-parse +~~~~~~~~~~~~~~~~ + +Parse flavors.yaml and generate combinations. + +.. autoprogram:: gardenlinux.flavors.__main__:get_parser() + +OCI Commands +------------ + +gl-oci +~~~~~~ + +Push OCI artifacts to a registry and manage manifests. + +.. click:: gardenlinux.oci.__main__:cli + :prog: gl-oci + :show-nested: + +S3 Commands +----------- + +gl-s3 +~~~~~ + +Upload and download artifacts from S3 buckets. + +.. autoprogram:: gardenlinux.s3.__main__:get_parser() + +GitHub Commands +--------------- + +gl-gh-release +~~~~~~~~~~~~~~ + +Create and manage GitHub releases. + +.. autoprogram:: gardenlinux.github.release.__main__:get_parser() diff --git a/docs/conf.py b/docs/conf.py index 0464e350..30c97c2f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,8 +1,4 @@ -import os -import sys - -sys.path.insert(0, os.path.abspath("../src")) - +from sphinx.application import Sphinx # Configuration file for the Sphinx documentation builder. # @@ -12,9 +8,9 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = "python-gardenlinux-lib" -copyright = "2024, gardenlinux maintainer" -author = "gardenlinux maintainer" +project = "Garden Linux Python Library" +copyright = "2024, Garden Linux Maintainers" +author = "Garden Linux Maintainers" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration @@ -24,6 +20,8 @@ "sphinx.ext.coverage", "sphinx_rtd_theme", "sphinx.ext.napoleon", + "sphinx_click", + "sphinxcontrib.autoprogram", ] templates_path = ["_templates"] @@ -36,3 +34,24 @@ html_theme = "sphinx_rtd_theme" html_static_path = ["_static"] + +# Logo configuration +html_logo = "_static/gardenlinux-logo.svg" + +html_theme_options = { + "logo_only": False, + "prev_next_buttons_location": "bottom", + "style_external_links": True, + "vcs_pageview_mode": "", + "style_nav_header_background": "#027154", # Primary green + "collapse_navigation": True, + "sticky_navigation": True, + "navigation_depth": 4, + "includehidden": True, + "titles_only": False, +} + + +# Add custom CSS +def setup(app: Sphinx) -> None: + app.add_css_file("custom.css") diff --git a/docs/index.rst b/docs/index.rst index c6fa838c..9de35821 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,28 +1,63 @@ -.. parse_features_lib documentation master file, created by - sphinx-quickstart on Thu Aug 15 22:42:36 2024. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -python-gardenlinux-lib documentation -==================================== -.. automodule:: gardenlinux - :members: -.. automodule:: gardenlinux.apt - :members: -.. automodule:: gardenlinux.features - :members: -.. automodule:: gardenlinux.flavors - :members: -.. automodule:: gardenlinux.git - :members: -.. automodule:: gardenlinux.oci - :members: +Garden Linux Python Library Documentation +========================================== + +Welcome to the Garden Linux Python Library documentation. This library provides +Python tools and utilities for working with Garden Linux features, flavors, +OCI artifacts, S3 buckets, and GitHub releases. + +.. image:: _static/gardenlinux-logo.svg + :alt: Garden Linux Logo + :class: logo + :align: center + +Overview +-------- + +The Garden Linux Python Library is a comprehensive toolkit for managing and +interacting with Garden Linux components. It includes: + +* **Feature Management**: Parse and work with Garden Linux features and generate canonical names +* **Flavor Processing**: Parse flavors.yaml and generate combinations +* **OCI Operations**: Push OCI artifacts to registries and manage manifests +* **S3 Integration**: Upload and download artifacts from S3 buckets +* **GitHub Integration**: Create and manage GitHub releases with release notes + +Quick Start +----------- + +Command-Line Interface +~~~~~~~~~~~~~~~~~~~~~~ + +The library provides several command-line tools for common operations. See the +:doc:`Command-Line Interface documentation ` for detailed information about +all available commands. + +Release Management +~~~~~~~~~~~~~~~~~~ + +For information about versioning and release procedures, see the +:doc:`Release documentation `. + +API Reference +~~~~~~~~~~~~~ + +For detailed Python API documentation, including all modules, classes, and +functions, see the :doc:`API Reference `. + +Documentation Sections +---------------------- + .. toctree:: :maxdepth: 3 - :caption: Contents: + :caption: Documentation: + + cli + release + api + +Additional Resources +-------------------- -Indices and tables -================== -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` +* :ref:`genindex` - Complete index of all functions, classes, and modules +* :ref:`modindex` - Index of all modules +* :ref:`search` - Search the documentation diff --git a/docs/release.rst b/docs/release.rst index da6a50a0..0b98175f 100644 --- a/docs/release.rst +++ b/docs/release.rst @@ -1,5 +1,5 @@ -python-gardenlinux-lib release documentation -============================================ +release documentation +===================== *python-gardenlinux-lib* strictly follow syntax and intention of `Semantic Versioning `. Each release reflects the intention and expected impact therefore. diff --git a/poetry.lock b/poetry.lock index f82f3135..18bb2b95 100644 --- a/poetry.lock +++ b/poetry.lock @@ -821,7 +821,7 @@ version = "8.3.1" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["main", "docs"] files = [ {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, @@ -841,7 +841,7 @@ files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\"", docs = "sys_platform == \"win32\""} +markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\"", docs = "platform_system == \"Windows\" or sys_platform == \"win32\""} [[package]] name = "coverage" @@ -2287,6 +2287,23 @@ sphinxcontrib-jsmath = ">=1.0.1" sphinxcontrib-qthelp = ">=1.0.6" sphinxcontrib-serializinghtml = ">=1.1.9" +[[package]] +name = "sphinx-click" +version = "4.4.0" +description = "Sphinx extension that automatically documents click applications" +optional = false +python-versions = ">=3.7" +groups = ["docs"] +files = [ + {file = "sphinx-click-4.4.0.tar.gz", hash = "sha256:cc67692bd28f482c7f01531c61b64e9d2f069bfcf3d24cbbb51d4a84a749fa48"}, + {file = "sphinx_click-4.4.0-py3-none-any.whl", hash = "sha256:2821c10a68fc9ee6ce7c92fad26540d8d8c8f45e6d7258f0e4fb7529ae8fab49"}, +] + +[package.dependencies] +click = ">=7.0" +docutils = "*" +sphinx = ">=2.0" + [[package]] name = "sphinx-rtd-theme" version = "3.1.0" @@ -2324,6 +2341,21 @@ lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] standalone = ["Sphinx (>=5)"] test = ["pytest"] +[[package]] +name = "sphinxcontrib-autoprogram" +version = "0.1.9" +description = "Documenting CLI programs" +optional = false +python-versions = ">=3.8" +groups = ["docs"] +files = [ + {file = "sphinxcontrib-autoprogram-0.1.9.tar.gz", hash = "sha256:219655507fadca29b3062b5d86c37d94db48f03bde4b58d61526872bf72f57cc"}, + {file = "sphinxcontrib_autoprogram-0.1.9-py2.py3-none-any.whl", hash = "sha256:79a5282d7640337e4bf11f624970a43709f1b704c5c59a59756d45e824db5301"}, +] + +[package.dependencies] +Sphinx = ">=1.2" + [[package]] name = "sphinxcontrib-devhelp" version = "2.0.0" @@ -2584,4 +2616,4 @@ test = ["pytest", "pytest-cov"] [metadata] lock-version = "2.1" python-versions = ">=3.13,!=3.14.1" -content-hash = "0e92dd06c0f4f5a06988e7218eaed5d650d9a5a24bb2c8e2c6b5cda303f53f07" +content-hash = "d3d6d40261960ebf3ab3a7a3a939c9f61381a502ec3a33ffc680eef810db2116" diff --git a/pyproject.toml b/pyproject.toml index 18db1b22..bb939d71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,8 @@ boto3-stubs = { extras = ["s3"], version = "^1.42.30" } [tool.poetry.group.docs.dependencies] sphinx-rtd-theme = "^3.0.2" +sphinx-click = "^4.4.0" +sphinxcontrib-autoprogram = "^0.1.8" [tool.poetry.scripts] gl-cname = "gardenlinux.features.cname_main:main" diff --git a/src/gardenlinux/features/__main__.py b/src/gardenlinux/features/__main__.py index 76224da2..d0b7f8ad 100644 --- a/src/gardenlinux/features/__main__.py +++ b/src/gardenlinux/features/__main__.py @@ -39,33 +39,98 @@ """ -def main() -> None: +def get_parser() -> argparse.ArgumentParser: """ - gl-features-parse main() + Get the argument parser for gl-features-parse. + Used for documentation generation. - :since: 0.7.0 + :return: ArgumentParser instance + :since: 0.10.9 """ - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser( + prog="gl-features-parse", + description="Parse and extract information from GardenLinux features.", + ) + + parser.add_argument( + "--arch", + dest="arch", + help="Target architecture (e.g., amd64, arm64). Overrides architecture from cname.", + ) + + parser.add_argument( + "--cname", + dest="cname", + required=True, + help="Canonical name (cname) to parse. Must be a valid GardenLinux canonical name.", + ) + + parser.add_argument( + "--commit", + dest="commit", + help="Git commit hash. If not specified, will be read from COMMIT file or release file.", + ) + + parser.add_argument( + "--feature-dir", + default="features", + help="Path to the features directory (default: 'features'). Either --feature-dir or --release-file must be provided.", + ) + + parser.add_argument( + "--release-file", + dest="release_file", + help="Path to a release file containing cname metadata. Either --feature-dir or --release-file must be provided.", + ) + + parser.add_argument( + "--default-arch", + dest="default_arch", + help="Default architecture to use if architecture cannot be determined from cname or other sources.", + ) + + parser.add_argument( + "--default-version", + dest="default_version", + help="Default version to use if version cannot be determined from files or other sources.", + ) - parser.add_argument("--arch", dest="arch") - parser.add_argument("--cname", dest="cname", required=True) - parser.add_argument("--commit", dest="commit") - parser.add_argument("--feature-dir", default="features") - parser.add_argument("--release-file", dest="release_file") - parser.add_argument("--default-arch", dest="default_arch") - parser.add_argument("--default-version", dest="default_version") - parser.add_argument("--version", dest="version") + parser.add_argument( + "--version", + dest="version", + help="Version string. If not specified, will be read from VERSION file or release file.", + ) parser.add_argument( "--ignore", dest="ignore", type=lambda arg: set([f for f in arg.split(",") if f]), default=set(), + help="Comma-separated list of features to ignore when processing (e.g., 'feature1,feature2').", ) - parser.add_argument("type", nargs="?", choices=_ARGS_TYPE_ALLOWED, default="cname") + parser.add_argument( + "type", + nargs="?", + choices=_ARGS_TYPE_ALLOWED, + default="cname", + help="Type of output to generate. Choices: {}. Default: 'cname'.".format( + ", ".join(_ARGS_TYPE_ALLOWED) + ), + ) + + return parser + + +def main() -> None: + """ + gl-features-parse main() + + :since: 0.7.0 + """ + parser = get_parser() args = parser.parse_args() assert bool(args.feature_dir) or bool(args.release_file), ( diff --git a/src/gardenlinux/features/cname_main.py b/src/gardenlinux/features/cname_main.py index 517d1cd0..5ce5998c 100644 --- a/src/gardenlinux/features/cname_main.py +++ b/src/gardenlinux/features/cname_main.py @@ -18,6 +18,52 @@ from .parser import Parser +def get_parser() -> argparse.ArgumentParser: + """ + Get the argument parser for gl-cname. + Used for documentation generation. + + :return: ArgumentParser instance + :since: 1.0.0 + """ + + parser = argparse.ArgumentParser( + prog="gl-cname", + description="Generate a canonical name (cname) from feature sets.", + ) + + parser.add_argument( + "--arch", + dest="arch", + help="Target architecture (e.g., amd64, arm64). If not specified, will be determined from the cname or feature set.", + ) + + parser.add_argument( + "--commit", + dest="commit", + help="Git commit hash. If not specified, will be read from COMMIT file in the GardenLinux root directory.", + ) + + parser.add_argument( + "--feature-dir", + default="features", + help="Path to the features directory (default: 'features').", + ) + + parser.add_argument( + "--version", + dest="version", + help="Version string. If not specified, will be read from VERSION file in the GardenLinux root directory.", + ) + + parser.add_argument( + "cname", + help="Canonical name (cname) to process. Must be a valid GardenLinux canonical name format.", + ) + + return parser + + def main() -> None: """ gl-cname main() @@ -25,14 +71,7 @@ def main() -> None: :since: 0.7.0 """ - parser = argparse.ArgumentParser() - - parser.add_argument("--arch", dest="arch") - parser.add_argument("--commit", dest="commit") - parser.add_argument("--feature-dir", default="features") - parser.add_argument("--version", dest="version") - parser.add_argument("cname") - + parser = get_parser() args = parser.parse_args() re_match = re.match( diff --git a/src/gardenlinux/flavors/__main__.py b/src/gardenlinux/flavors/__main__.py index ea2974d3..9933298f 100644 --- a/src/gardenlinux/flavors/__main__.py +++ b/src/gardenlinux/flavors/__main__.py @@ -6,7 +6,7 @@ """ import json -from argparse import ArgumentParser, Namespace +from argparse import ArgumentParser from pathlib import Path from tempfile import TemporaryDirectory from typing import Any, List, Tuple @@ -47,12 +47,13 @@ def generate_markdown_table(combinations: List[Tuple[Any, str]]) -> str: return table -def parse_args() -> Namespace: +def get_parser() -> ArgumentParser: """ - Parses arguments used for main() + Get the argument parser for gl-flavors-parse. + Used for documentation generation. - :return: (object) Parsed argparse.ArgumentParser namespace - :since: 0.7.0 + :return: ArgumentParser instance + :since: 1.0.0 """ parser = ArgumentParser(description="Parse flavors.yaml and generate combinations.") @@ -62,67 +63,78 @@ def parse_args() -> Namespace: default=None, help="Commit hash to fetch flavors.yaml from GitHub. An existing 'flavors.yaml' file will be preferred.", ) + parser.add_argument( "--no-arch", action="store_true", help="Exclude architecture from the flavor output.", ) + parser.add_argument( "--include-only", action="append", default=[], help="Restrict combinations to those matching wildcard patterns (can be specified multiple times).", ) + parser.add_argument( "--exclude", action="append", default=[], help="Exclude combinations based on wildcard patterns (can be specified multiple times).", ) + parser.add_argument( "--build", action="store_true", help="Filter combinations to include only those with build enabled.", ) + parser.add_argument( "--publish", action="store_true", help="Filter combinations to include only those with publish enabled.", ) + parser.add_argument( "--test", action="store_true", help="Filter combinations to include only those with test enabled.", ) + parser.add_argument( "--test-platform", action="store_true", help="Filter combinations to include only platforms with test-platform: true.", ) + parser.add_argument( "--category", action="append", default=[], help="Filter combinations to include only platforms belonging to the specified categories (can be specified multiple times).", ) + parser.add_argument( "--exclude-category", action="append", default=[], help="Exclude platforms belonging to the specified categories (can be specified multiple times).", ) + parser.add_argument( "--json-by-arch", action="store_true", help="Output a JSON dictionary where keys are architectures and values are lists of flavors.", ) + parser.add_argument( "--markdown-table-by-platform", action="store_true", help="Generate a markdown table by platform.", ) - return parser.parse_args() + return parser def main() -> None: @@ -132,7 +144,8 @@ def main() -> None: :since: 0.7.0 """ - args = parse_args() + parser = get_parser() + args = parser.parse_args() try: flavors_data = _get_flavors_file_data(Path(Repository().root, "flavors.yaml")) diff --git a/src/gardenlinux/github/release/__main__.py b/src/gardenlinux/github/release/__main__.py index e403893f..013108b9 100644 --- a/src/gardenlinux/github/release/__main__.py +++ b/src/gardenlinux/github/release/__main__.py @@ -14,35 +14,146 @@ LOGGER = LoggerSetup.get_logger("gardenlinux.github", logging.INFO) -def main() -> None: - parser = argparse.ArgumentParser(description="GitHub Release Script") - subparsers = parser.add_subparsers(dest="command") - - create_parser = subparsers.add_parser("create") - create_parser.add_argument("--owner", default="gardenlinux") - create_parser.add_argument("--repo", default="gardenlinux") - create_parser.add_argument("--tag", required=True) - create_parser.add_argument("--name") - create_parser.add_argument("--body", required=True) - create_parser.add_argument("--commit") - create_parser.add_argument("--pre-release", action="store_true", default=True) - create_parser.add_argument("--latest", action="store_true", default=False) - - create_parser_gl = subparsers.add_parser("create-with-gl-release-notes") - create_parser_gl.add_argument("--owner", default="gardenlinux") - create_parser_gl.add_argument("--repo", default="gardenlinux") - create_parser_gl.add_argument("--tag", required=True) - create_parser_gl.add_argument("--commit", required=True) - create_parser_gl.add_argument("--latest", action="store_true", default=False) - create_parser_gl.add_argument("--dry-run", action="store_true", default=False) - - upload_parser = subparsers.add_parser("upload") - upload_parser.add_argument("--owner", default="gardenlinux") - upload_parser.add_argument("--repo", default="gardenlinux") - upload_parser.add_argument("--release_id", required=True) - upload_parser.add_argument("--file_path", required=True) - upload_parser.add_argument("--dry-run", action="store_true", default=False) +def get_parser() -> argparse.ArgumentParser: + """ + Get the argument parser for gl-gh-release. + Used for documentation generation. + + :return: ArgumentParser instance + :since: 1.0.0 + """ + + parser = argparse.ArgumentParser( + prog="gl-gh-release", description="Create and manage GitHub releases." + ) + subparsers = parser.add_subparsers(dest="command", help="Available commands") + + create_parser = subparsers.add_parser("create", help="Create a new GitHub release.") + + create_parser.add_argument( + "--owner", + default="gardenlinux", + help="GitHub repository owner (default: 'gardenlinux').", + ) + + create_parser.add_argument( + "--repo", + default="gardenlinux", + help="GitHub repository name (default: 'gardenlinux').", + ) + + create_parser.add_argument( + "--tag", required=True, help="Git tag name for the release (required)." + ) + + create_parser.add_argument( + "--name", help="Release name/title. If not specified, the tag will be used." + ) + + create_parser.add_argument( + "--body", required=True, help="Release notes/description body (required)." + ) + + create_parser.add_argument( + "--commit", + help="Git commit hash. If not specified, the tag will be used to find the commit.", + ) + + create_parser.add_argument( + "--pre-release", + action="store_true", + default=True, + help="Mark the release as a pre-release (default: True).", + ) + + create_parser.add_argument( + "--latest", + action="store_true", + default=False, + help="Mark this release as the latest release (default: False).", + ) + + create_parser_gl = subparsers.add_parser( + "create-with-gl-release-notes", + help="Create a GitHub release with auto-generated GardenLinux release notes.", + ) + + create_parser_gl.add_argument( + "--owner", + default="gardenlinux", + help="GitHub repository owner (default: 'gardenlinux').", + ) + create_parser_gl.add_argument( + "--repo", + default="gardenlinux", + help="GitHub repository name (default: 'gardenlinux').", + ) + + create_parser_gl.add_argument( + "--tag", required=True, help="Git tag name for the release (required)." + ) + + create_parser_gl.add_argument( + "--commit", + required=True, + help="Git commit hash used to generate release notes (required).", + ) + + create_parser_gl.add_argument( + "--latest", + action="store_true", + default=False, + help="Mark this release as the latest release (default: False).", + ) + + create_parser_gl.add_argument( + "--dry-run", + action="store_true", + default=False, + help="Perform a dry run without actually creating the release.", + ) + + upload_parser = subparsers.add_parser( + "upload", help="Upload a file to an existing GitHub release." + ) + + upload_parser.add_argument( + "--owner", + default="gardenlinux", + help="GitHub repository owner (default: 'gardenlinux').", + ) + + upload_parser.add_argument( + "--repo", + default="gardenlinux", + help="GitHub repository name (default: 'gardenlinux').", + ) + + upload_parser.add_argument( + "--release_id", + required=True, + help="GitHub release ID to upload the file to (required).", + ) + + upload_parser.add_argument( + "--file_path", + required=True, + help="Path to the file to upload (required).", + ) + + upload_parser.add_argument( + "--dry-run", + action="store_true", + default=False, + help="Perform a dry run without actually uploading the file.", + ) + + return parser + + +def main() -> None: + parser = get_parser() args = parser.parse_args() if args.command == "create": diff --git a/src/gardenlinux/s3/__main__.py b/src/gardenlinux/s3/__main__.py index 1c2da70d..f794631c 100644 --- a/src/gardenlinux/s3/__main__.py +++ b/src/gardenlinux/s3/__main__.py @@ -10,27 +10,62 @@ from .s3_artifacts import S3Artifacts -def main() -> None: +def get_parser() -> argparse.ArgumentParser: """ - gl-s3 main() + Get the argument parser for gl-s3. + Used for documentation generation. - :since: 0.8.0 + :return: ArgumentParser instance + :since: 0.10.9 """ - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser("gl-s3") + + parser.add_argument( + "--bucket", dest="bucket", help="S3 bucket name to upload to or download from." + ) - parser.add_argument("--bucket", dest="bucket") - parser.add_argument("--path", required=False, dest="path") - parser.add_argument("--dry-run", action="store_true") + parser.add_argument( + "--path", + required=False, + dest="path", + help="Local directory path for upload (source) or download (destination).", + ) - subparsers = parser.add_subparsers(dest="action") + parser.add_argument( + "--dry-run", + action="store_true", + help="Perform a dry run without actually uploading or downloading files.", + ) + + subparsers = parser.add_subparsers(dest="action", help="Action to perform.") download_parser = subparsers.add_parser("download-artifacts-from-bucket") - download_parser.add_argument("--cname", required=False, dest="cname") + + download_parser.add_argument( + "--cname", + required=False, + dest="cname", + help="Canonical name (cname) used as the S3 key prefix for artifacts.", + ) upload_parser = subparsers.add_parser("upload-artifacts-to-bucket") - upload_parser.add_argument("--artifact-name", required=False, dest="artifact_name") + upload_parser.add_argument( + "--artifact-name", dest="artifact_name", help="S3 artifact base name." + ) + + return parser + + +def main() -> None: + """ + gl-s3 main() + + :since: 0.8.0 + """ + + parser = get_parser() args = parser.parse_args() if args.action == "download-artifacts-from-bucket": diff --git a/tests/flavors/test_main.py b/tests/flavors/test_main.py index b74a5e2b..8b0a0a89 100644 --- a/tests/flavors/test_main.py +++ b/tests/flavors/test_main.py @@ -44,7 +44,7 @@ def test_parse_args(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(sys, "argv", argv) # Act - args = fm.parse_args() + args = fm.get_parser().parse_args() # Assert assert args.no_arch is True