diff --git a/.gitattributes b/.gitattributes index c6f20dbf..2a233757 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ tests/resource/objects_mkdoc_zlib0.inv binary tests/resource/objects_attrs.txt binary +*.inv diff=objects_inv diff --git a/.github/workflows/release_enusre_no_ver_markers.yml b/.github/workflows/release_enusre_no_ver_markers.yml new file mode 100644 index 00000000..446bd552 --- /dev/null +++ b/.github/workflows/release_enusre_no_ver_markers.yml @@ -0,0 +1,28 @@ +name: 'RELEASE: Check docs source files' + +on: + pull_request: + types: + - opened + - reopened + - synchronize + - ready_for_review + branches: + - stable + +jobs: + ver_placeholder_search: + name: 'have no ##VER## markers' + runs-on: 'ubuntu-latest' + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + if: ${{ !github.event.pull_request.draft }} + + steps: + - name: Check out repo + uses: actions/checkout@v6 + + - name: Error if any markers found + run: | + grep -ri '#VER#' doc/source && exit 1 || exit 0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d39789b..4119a775 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,21 @@ changes. ### *Unreleased* +#### Added + + * Add `sphobjinv-textconv` CLI entrypoint ([#331]). + * Takes a single required argument, the path to a local inventory file, and + emits the plaintext inventory to `stdout`. + * The target use-case is as a Git textconv, primarily intended for + compressed `objects.inv` files; but, it will work with any valid type of + input file. + #### Tests + * Add tests exercising the new `sphobjinv-textconv` CLI entrypoint ([#331]). + * Required generalizing the `run_cmdline_test` fixture so that tests can + choose between the core and textconv entrypoints. + * Update `tox` env test matrix for `py310` to `py314` ([#325]). * Update test path calculations to always be relative to `__file__` ([#325]). @@ -36,6 +49,9 @@ changes. #### Internal + * Add Actions workflow to error on a non-draft release branch if any `#VER#` + markers remain in docs source ([#331]). + * Augment `black` and `flake8` `tox` envs to run `--version` first ([#327]). * Remove `-r requirements-flake.txt` from `requirements-dev.txt` ([#327]). @@ -57,6 +73,30 @@ changes. * This will provide `main` branch CI results for this workflow, for the GitHub badge to report. +#### Documentation + + * Dynamically retrieve the current values of `PrsConst.SUGGEST_CONFIRM_LENGTH` + and `PrsConst.DEF_THRESH` to define their replaces in `conf.py` ([#331]). + + * Add `cli/textconv.rst` to document the new `sphobjinv-textconv` CLI + entrypoint ([#331]). + + * Cull some superfluous replaces in `conf.py` ([#331]). + + * Relocate the 'help' and 'version' CLI usage documentation content to a new + 'orphan' page ([#331]). + * This keeps the content in the `objects.inv`, for completeness, but keeps + it off of the docs nav. + + * Revise 'CLI Usage' documentation to incorporate the `sphobjinv-textconv` + entrypoint ([#331]). + + * Remove the 'CLI Implementation' "API reference" docs ([#331]). + * They're not part of the public API contract, and they don't actually help + understand how the CLI is implemented; so, why bother maintaining them? + * Also cull the various `replace` directives defined in `conf.py` for these + docs. + #### Administrative * Add formal support for Python 3.14 ([#325]). @@ -739,3 +779,4 @@ changes. [#320]: https://github.com/bskinn/sphobjinv/pull/320 [#325]: https://github.com/bskinn/sphobjinv/pull/325 [#327]: https://github.com/bskinn/sphobjinv/pull/327 +[#331]: https://github.com/bskinn/sphobjinv/pull/331 diff --git a/doc/source/api/index.rst b/doc/source/api/index.rst index 04c908fc..480baf12 100644 --- a/doc/source/api/index.rst +++ b/doc/source/api/index.rst @@ -1,7 +1,7 @@ .. API page -API -=== +API Reference +============= Most (all?) of the objects documented in the below submodules are also exposed at the |soi| package root. For example, diff --git a/doc/source/cli/convert.rst b/doc/source/cli/convert.rst index b979fc78..f3871938 100644 --- a/doc/source/cli/convert.rst +++ b/doc/source/cli/convert.rst @@ -1,7 +1,7 @@ .. Description of convert commandline usage -Command-Line Usage: "convert" Subcommand -======================================== +Command-Line Usage: ``sphobjinv convert`` +========================================= .. program:: sphobjinv convert diff --git a/doc/source/cli/implementation/convert.rst b/doc/source/cli/implementation/convert.rst deleted file mode 100644 index cd68000b..00000000 --- a/doc/source/cli/implementation/convert.rst +++ /dev/null @@ -1,7 +0,0 @@ -.. Module API page for cli/convert.py - -sphobjinv.cli.convert -===================== - -.. automodule:: sphobjinv.cli.convert - :members: diff --git a/doc/source/cli/implementation/core.rst b/doc/source/cli/implementation/core.rst deleted file mode 100644 index c6fd0a53..00000000 --- a/doc/source/cli/implementation/core.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. Module API page for cli/core.py - -sphobjinv.cli.core -================== - -.. automodule:: sphobjinv.cli.core - :members: - diff --git a/doc/source/cli/implementation/index.rst b/doc/source/cli/implementation/index.rst deleted file mode 100644 index a174e6f3..00000000 --- a/doc/source/cli/implementation/index.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. Module API page for CLI submodule code - -sphobjinv.cli (non-API) -======================= - -.. toctree:: - :maxdepth: 1 - - convert - core - load - parser - paths - suggest - ui - write - - -.. .. |argparse| replace:: :mod:`argparse` diff --git a/doc/source/cli/implementation/load.rst b/doc/source/cli/implementation/load.rst deleted file mode 100644 index 2cdec7ec..00000000 --- a/doc/source/cli/implementation/load.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. Module API page for cli/load.py - -sphobjinv.cli.load -================== - -.. automodule:: sphobjinv.cli.load - :members: - diff --git a/doc/source/cli/implementation/parser.rst b/doc/source/cli/implementation/parser.rst deleted file mode 100644 index a7a18aa1..00000000 --- a/doc/source/cli/implementation/parser.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. Module API page for cli/parser.py - -sphobjinv.cli.parser -==================== - -.. automodule:: sphobjinv.cli.parser - :members: - diff --git a/doc/source/cli/implementation/paths.rst b/doc/source/cli/implementation/paths.rst deleted file mode 100644 index dad6f38b..00000000 --- a/doc/source/cli/implementation/paths.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. Module API page for cli/paths.py - -sphobjinv.cli.paths -=================== - -.. automodule:: sphobjinv.cli.paths - :members: - diff --git a/doc/source/cli/implementation/suggest.rst b/doc/source/cli/implementation/suggest.rst deleted file mode 100644 index 3424d1b5..00000000 --- a/doc/source/cli/implementation/suggest.rst +++ /dev/null @@ -1,7 +0,0 @@ -.. Module API page for cli/suggest.py - -sphobjinv.cli.suggest -===================== - -.. automodule:: sphobjinv.cli.suggest - :members: diff --git a/doc/source/cli/implementation/ui.rst b/doc/source/cli/implementation/ui.rst deleted file mode 100644 index bd59146d..00000000 --- a/doc/source/cli/implementation/ui.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. Module API page for cli/ui.py - -sphobjinv.cli.ui -================ - -.. automodule:: sphobjinv.cli.ui - :members: - diff --git a/doc/source/cli/implementation/write.rst b/doc/source/cli/implementation/write.rst deleted file mode 100644 index f1eb55a8..00000000 --- a/doc/source/cli/implementation/write.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. Module API page for cli/write.py - -sphobjinv.cli.write -=================== - -.. automodule:: sphobjinv.cli.write - :members: - diff --git a/doc/source/cli/index.rst b/doc/source/cli/index.rst index 1974dbdc..2d26cc07 100644 --- a/doc/source/cli/index.rst +++ b/doc/source/cli/index.rst @@ -3,23 +3,39 @@ Command-Line Usage ================== -The CLI for |soi| is implemented using two subcommands: +The primary CLI for |soi| is implemented using two subcommands of the +``sphobjinv`` entrypoint: - - A :doc:`convert ` subcommand, which handles conversion of - inventories between supported formats (currently zlib-compressed, + - ``sphobjinv convert`` (:doc:`docs page `), which handles conversion + of inventories between supported formats (currently zlib-compressed, plaintext, and JSON). - - A :doc:`suggest ` subcommand, which provides suggestions for + - ``sphobjinv suggest`` (:doc:`docs page `), which provides suggestions for objects in an inventory matching a desired search term. -More information about the underlying implementation of these subcommands can -be found :doc:`here ` and in the documentation for the -:class:`~sphobjinv.inventory.Inventory` object, in particular the -:meth:`~sphobjinv.inventory.Inventory.data_file` and -:meth:`~sphobjinv.inventory.Inventory.suggest` methods. +As of v##VER##, |soi| also provides an auxiliary entrypoint, +``sphobjinv-textconv`` (:doc:`docs page `), which takes one required +argument: a path to a file on disk. This entrypoint attempts to instantiate an +|Inventory| with this file and emit its plaintext contents to |stdout| with no +cosmetic whitespace. The following two invocations are thus nearly synonymous:: -Some notes on these CLI docs: + $ sphobjinv convert plain path/to/objects.inv - - * CLI docs examples are executed in a sandboxed directory pre-loaded with + $ sphobjinv-textconv path/to/objects.inv + +(Be sure to note the final hyphen in the first command.) The +``sphobjinv-textconv`` spelling is less awkward when configuring a Git +|textconv| to allow rendering diffs of |objects.inv| files in plaintext. See the +``sphobjinv-textconv`` :doc:`entrypoint documentation ` for more +information. + +---- + +Shell examples in the CLI docs execute from within |cour|\ /tests/resource\ +|/cour| unless indicated otherwise. + +For Python examples: + + * Examples are executed in a sandboxed directory pre-loaded with |cour|\ objects_attrs.inv\ |/cour| (from, e.g., `here `__). @@ -35,28 +51,10 @@ Some notes on these CLI docs: * |cour|\ file_head\ |/cour| is a helper function that retrieves the head of a specified file. - -.. program:: sphobjinv - -The options for the parent |soi| command are: - -.. option:: -h, --help - - Show help message and exit - -.. program-output:: sphobjinv --help - - -.. option:: -v, --version - - Print package version & other info - -.. program-output:: sphobjinv --version - - .. toctree:: :maxdepth: 1 :hidden: - "convert" Mode - "suggest" Mode + sphobjinv convert + sphobjinv suggest + sphobjinv-textconv diff --git a/doc/source/cli/soi_root.rst b/doc/source/cli/soi_root.rst new file mode 100644 index 00000000..83ca6337 --- /dev/null +++ b/doc/source/cli/soi_root.rst @@ -0,0 +1,26 @@ +:orphan: + +.. Description of root soi entrypoint commandline usage + +Root "sphobjinv" Entrypoint Usage +================================= + +.. program:: sphobjinv + +The following options of the 'root' of the ``sphobjinv`` CLI entrypoint are +documented in this 'orphan' page so that they will appear in the docset +`objects.inv` and be available for linking, if needed, but otherwise be hidden +from the navigation of the documentation. + +.. option:: -h, --help + + Show help message and exit + +.. program-output:: sphobjinv --help + + +.. option:: -v, --version + + Print package version & other info + +.. program-output:: sphobjinv --version diff --git a/doc/source/cli/suggest.rst b/doc/source/cli/suggest.rst index 08627aa8..4bf3b47e 100644 --- a/doc/source/cli/suggest.rst +++ b/doc/source/cli/suggest.rst @@ -1,7 +1,7 @@ .. Description of suggest commandline usage -Command-Line Usage: "suggest" Subcommand -======================================== +Command-Line Usage: ``sphobjinv suggest`` +========================================= .. program:: sphobjinv suggest @@ -82,9 +82,9 @@ If download of JSON files by URL is desirable, please .. option:: -a, --all - Display all search results without prompting, regardless of the number of hits. - Otherwise, prompt if number of results exceeds - :attr:`~sphobjinv.cli.parser.PrsConst.SUGGEST_CONFIRM_LENGTH`. + Display all search results without prompting, regardless of the number of + hits. Otherwise, prompt for confirmation before displaying the entire result + set if count exceeds |cli:SUGGEST_CONFIRM_LENGTH|. .. option:: -i, --index @@ -99,8 +99,7 @@ If download of JSON files by URL is desirable, please .. option:: -t, --thresh <#> Change the |fuzzywuzzy|_ match quality threshold (0-100; higher values - yield fewer results). Default is specified in - :attr:`~sphobjinv.cli.parser.PrsConst.DEF_THRESH`. + yield fewer results). Current default threshold is |cli:DEF_THRESH|. .. option:: -u, --url diff --git a/doc/source/cli/textconv.rst b/doc/source/cli/textconv.rst new file mode 100644 index 00000000..fd13a371 --- /dev/null +++ b/doc/source/cli/textconv.rst @@ -0,0 +1,85 @@ +.. Description of sphobjinv-textconv commandline usage + +Command-Line Usage: ``sphobjinv-textconv`` +========================================== + +.. program:: sphobjinv-textconv + +``sphobjinv-textconv`` is intentionally implemented with very narrow +functionality, focused on simplifying use of |soi| as a `Git "textconv" +`__, +which is a mechanism for rendering binary files in a diff-able text format. +There are many `examples`__ of `clever application`__ of `textconv`__ in the +wild. + +.. __: https://github.com/pixelb/crudini/issues/90 +.. __: https://github.com/syntevosmartgit/textconv +.. __: https://stackoverflow.com/questions/55601430/how-to-pass-a-filename-argument-gitconfig-diff-textconv + + +Ultimately, a textconv requires three things: + +1. A utility that takes in a file path as a single positional argument and emits + a plaintext representation to |stdout|, such as |sphobjinv-textconv|. + +2. An entry somewhere in Git config (system, user-global, per-repo, etc.) + declaring a "diff driver" set up to use that utility as its |textconv|. + Example:: + + [diff "objects_inv"] + textconv = sphobjinv-textconv + + Note that the utility must be on path in all contexts where you wish to use + it as a textconv. + +3. An entry somewhere in |.gitattributes| (system, user-global, per-repo, etc.) + that associates a particular file or glob pattern with the diff driver. Example:: + + *.inv diff=objects_inv + +With |sphobjinv-textconv| configured in this fashion as a textconv for Sphinx +inventory files, the following should all yield _nearly_ the same output. + +Using ``sphobjinv convert``: + +.. command-output:: sphobjinv convert plain objects_pdfminer.inv - + :cwd: /../../tests/resource + +Using ``sphobjinv-textconv`` (note the absence of blank lines between the shell +invocation and the inventory contents): + +.. command-output:: sphobjinv-textconv objects_pdfminer.inv + :cwd: /../../tests/resource + +Using ``git show --textconv`` (with the |textconv| set on-the-fly so that it +will render correctly in ReadTheDocs builds): + +.. command-output:: git -c diff.objects_inv.textconv=sphobjinv-textconv show --textconv HEAD:tests/resource/objects_pdfminer.inv + :cwd: /../../tests/resource + +---- + + +**Usage** + +.. command-output:: sphobjinv-textconv --help + :ellipsis: 4 + + +**Positional Arguments** + +.. option:: infile + + Path to file to be emitted to |stdout| in plaintext. + +**Flags** + +.. option:: -h, --help + + Display help message and exit. + +.. option:: -v, --version + + Display brief package version information and exit. + +.. versionadded:: ##VER## diff --git a/doc/source/conf.py b/doc/source/conf.py index f3afc081..5abed10c 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -14,6 +14,9 @@ # import sys # sys.path.insert(0, os.path.abspath('.')) +# -- Imports ----------------------------------------------------------------- +import sphobjinv as soi +from sphobjinv.cli.parser import PrsConst # -- Project information ----------------------------------------------------- @@ -22,7 +25,8 @@ author = "Brian Skinn" # The full version for `release`, including alpha/beta/rc tags -from sphobjinv import __version__ as release +release = soi.__version__ + # Just major.minor for `version` version = ".".join(release.split(".")[:2]) @@ -70,7 +74,7 @@ # -- Common epilogue definition ------------------------------------------------ -rst_epilog = r""" +rst_epilog = rf""" .. |extlink| image:: /_static/extlink.svg @@ -166,41 +170,19 @@ sphobjinv -.. |stdin| replace:: |cour|\ stdin\ |/cour| - -.. |stdout| replace:: |cour|\ stdout\ |/cour| - -.. |cli:ALL| replace:: :attr:`~sphobjinv.cli.parser.PrsConst.ALL` - -.. |cli:DEF_BASENAME| replace:: :attr:`~sphobjinv.cli.parser.PrsConst.DEF_BASENAME` - -.. |cli:DEF_OUT_EXT| replace:: :attr:`~sphobjinv.cli.parser.PrsConst.DEF_OUT_EXT` - -.. |cli:FOUND_URL| replace:: :attr:`~sphobjinv.cli.parser.PrsConst.FOUND_URL` +.. |cli:DEF_THRESH| replace:: {PrsConst.DEF_THRESH} -.. |cli:INDEX| replace:: :attr:`~sphobjinv.cli.parser.PrsConst.INDEX` +.. |cli:SUGGEST_CONFIRM_LENGTH| replace:: {PrsConst.SUGGEST_CONFIRM_LENGTH} -.. |cli:INFILE| replace:: :attr:`~sphobjinv.cli.parser.PrsConst.INFILE` - -.. |cli:MODE| replace:: :attr:`~sphobjinv.cli.parser.PrsConst.MODE` - -.. |cli:OUTFILE| replace:: :attr:`~sphobjinv.cli.parser.PrsConst.OUTFILE` - -.. |cli:OVERWRITE| replace:: :attr:`~sphobjinv.cli.parser.PrsConst.OVERWRITE` - -.. |cli:QUIET| replace:: :attr:`~sphobjinv.cli.parser.PrsConst.QUIET` - -.. |cli:SCORE| replace:: :attr:`~sphobjinv.cli.parser.PrsConst.SCORE` - -.. |cli:SUBPARSER_NAME| replace:: :attr:`~sphobjinv.cli.parser.PrsConst.SUBPARSER_NAME` +.. |stdin| replace:: |cour|\ stdin\ |/cour| -.. |cli:SUGGEST_CONFIRM_LENGTH| replace:: :attr:`~sphobjinv.cli.parser.PrsConst.SUGGEST_CONFIRM_LENGTH` +.. |stdout| replace:: |cour|\ stdout\ |/cour| -.. |cli:URL| replace:: :attr:`~sphobjinv.cli.parser.PrsConst.URL` +.. |textconv| replace:: |cour|\ textconv\ |/cour| -.. |cli:VERSION| replace:: :attr:`~sphobjinv.cli.parser.PrsConst.VERSION` +.. |.gitattributes| replace:: |cour|\ .gitattributes\ |/cour| -.. |resolve_inpath| replace:: :func:`~sphobjinv.cli.paths.resolve_inpath` +.. |sphobjinv-textconv| replace:: |cour|\ sphobjinv-textconv\ |/cour| """ diff --git a/doc/source/index.rst b/doc/source/index.rst index ad8f24a2..d07d5be4 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -100,8 +100,6 @@ The project source repository is on GitHub: `bskinn/sphobjinv levenshtein syntax api/index - CLI Implementation (non-API) - Indices and Tables diff --git a/pyproject.toml b/pyproject.toml index 4678a8cb..0ad2588e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,14 +43,16 @@ dependencies = [ dynamic = ["version", "readme"] [project.urls] -Homepage = "https://github.com/bskinn/sphobjinv" Changelog = "https://github.com/bskinn/sphobjinv/blob/main/CHANGELOG.md" Docs = "https://sphobjinv.readthedocs.io/en/stable/" -Thank = "https://fosstodon.org/@btskinn" Donate = "https://github.com/sponsors/bskinn" +Homepage = "https://github.com/bskinn/sphobjinv" +Thank = "https://fosstodon.org/@btskinn" [project.scripts] sphobjinv = "sphobjinv.cli.core:main" +sphobjinv-textconv = "sphobjinv.cli.core:main_textconv" + [tool.setuptools] package-dir = {"" = "src"} diff --git a/src/sphobjinv/cli/convert.py b/src/sphobjinv/cli/convert.py index d07353f3..1a342d63 100644 --- a/src/sphobjinv/cli/convert.py +++ b/src/sphobjinv/cli/convert.py @@ -36,13 +36,13 @@ def do_convert(inv, in_path, params): r"""Carry out the conversion operation, including writing output. - If |cli:OVERWRITE| is passed and the output file - (the default location, or as passed to |cli:OUTFILE|) + If OVERWRITE is passed and the output file + (the default location, or as passed to OUTFILE) exists, it will be overwritten without a prompt. Otherwise, the user will be queried if it is desired to overwrite the existing file. - If |cli:QUIET| is passed, nothing will be + If QUIET is passed, nothing will be printed to |cour|\ stdout\ |/cour| (potentially useful for scripting), and any existing output file will be overwritten @@ -53,7 +53,7 @@ def do_convert(inv, in_path, params): inv |Inventory| -- Inventory object to be output in the format - indicated by |cli:MODE|. + indicated by MODE. in_path diff --git a/src/sphobjinv/cli/core.py b/src/sphobjinv/cli/core.py index 05e6797b..9130f992 100644 --- a/src/sphobjinv/cli/core.py +++ b/src/sphobjinv/cli/core.py @@ -33,7 +33,7 @@ from sphobjinv.cli.convert import do_convert from sphobjinv.cli.load import inv_local, inv_stdin, inv_url -from sphobjinv.cli.parser import PrsConst, getparser +from sphobjinv.cli.parser import PrsConst, getparser, getparser_textconv from sphobjinv.cli.suggest import do_suggest from sphobjinv.cli.ui import print_stderr @@ -43,14 +43,14 @@ def main(): Parses command line arguments, handling the no-arguments and - |cli:VERSION| cases. + VERSION cases. Creates the |Inventory| from the indicated source and method. Invokes :func:`~sphobjinv.cli.convert.do_convert` or :func:`~sphobjinv.cli.suggest.do_suggest` - per the subparser name stored in |cli:SUBPARSER_NAME|. + per the subparser name stored in SUBPARSER_NAME. """ # If no args passed, stick in '-h' @@ -101,3 +101,26 @@ def main(): # Clean exit sys.exit(0) + + +def main_textconv(): + """Entrypoint for textconv operation.""" + # If no args passed, stick in '-h' + if len(sys.argv) == 1: + sys.argv.append("-h") + + prs = getparser_textconv() + params = vars(prs.parse_args()) + + # No version arg handling, using 'version' action in this parser + + inv, in_path = inv_local(params) + + params[PrsConst.CONTRACT] = False + params[PrsConst.EXPAND] = False + params[PrsConst.MODE] = PrsConst.PLAIN + params[PrsConst.OUTFILE] = "-" + + do_convert(inv, in_path, params) + + sys.exit(0) diff --git a/src/sphobjinv/cli/load.py b/src/sphobjinv/cli/load.py index 469d811f..32405758 100644 --- a/src/sphobjinv/cli/load.py +++ b/src/sphobjinv/cli/load.py @@ -83,8 +83,8 @@ def import_infile(in_path): def inv_local(params): """Create |Inventory| from local source. - Uses |resolve_inpath| to sanity-check and/or convert - |cli:INFILE|. + Uses ``resolve_inpath`` to sanity-check and/or convert + INFILE. Calls :func:`sys.exit` internally in error-exit situations. @@ -99,12 +99,12 @@ def inv_local(params): inv |Inventory| -- Object representation of the inventory - at |cli:INFILE| + at INFILE in_path |str| -- Input file path as resolved/checked by - |resolve_inpath| + ``resolve_inpath`` """ # Resolve input file path @@ -127,7 +127,7 @@ def inv_local(params): def inv_url(params): """Create |Inventory| from file downloaded from URL. - Initially, treats |cli:INFILE| as a download URL to be passed to + Initially, treats INFILE as a download URL to be passed to the `url` initialization argument of :class:`~sphobjinv.inventory.Inventory`. @@ -135,7 +135,7 @@ def inv_url(params): searches the directory tree of the URL for |objects.inv|. Injects the URL at which an inventory was found into `params` - under the |cli:FOUND_URL| key. + under the FOUND_URL key. Calls :func:`sys.exit` internally in error-exit situations. @@ -150,11 +150,11 @@ def inv_url(params): inv |Inventory| -- Object representation of the inventory - at |cli:INFILE| + at INFILE ret_path - |str| -- URL from |cli:INFILE| used to construct `inv`. + |str| -- URL from INFILE used to construct `inv`. If URL is longer than 45 characters, the central portion is elided. """ diff --git a/src/sphobjinv/cli/parser.py b/src/sphobjinv/cli/parser.py index 60d2fb4e..19727e18 100644 --- a/src/sphobjinv/cli/parser.py +++ b/src/sphobjinv/cli/parser.py @@ -52,6 +52,9 @@ class PrsConst: " https://sphobjinv.readthedocs.io\n" ) + #: Short version text for textconv entrypoint + VER_TXT_SHORT = f"sphobjinv v{__version__}" + # ### Subparser selectors and argparse param for storing subparser name #: Subparser name for inventory file conversions; stored in #: :data:`SUBPARSER_NAME` when selected @@ -383,3 +386,35 @@ def getparser(): ) return prs + + +def getparser_textconv(): + """Generate argument parser for textconv entrypoint. + + Returns + ------- + prs + + :class:`~argparse.ArgumentParser` -- Parser for textconv commandline + usage of |soi| + + """ + prs = ap.ArgumentParser( + description=( + "Emit the plaintext of the local Sphinx inventory at 'infile' to stdout." + ) + ) + prs.add_argument( + "-" + PrsConst.VERSION[0], + "--" + PrsConst.VERSION, + help="Print package version & other info", + action="version", + version=PrsConst.VER_TXT_SHORT, + ) + + prs.add_argument( + PrsConst.INFILE, + help=("Path to file to be converted"), + ) + + return prs diff --git a/src/sphobjinv/cli/paths.py b/src/sphobjinv/cli/paths.py index 06ea5f57..de9bfc23 100644 --- a/src/sphobjinv/cli/paths.py +++ b/src/sphobjinv/cli/paths.py @@ -72,11 +72,11 @@ def resolve_outpath(out_path, in_path, params): If the output path or basename are not specified, they are taken as the same as the input file. If the extension is unspecified, it is taken as the appropriate mode-specific value - from |cli:DEF_OUT_EXT|. + from DEF_OUT_EXT. - If |cli:URL| is passed, the input directory + If URL is passed, the input directory is taken to be :func:`os.getcwd` and the input basename - is taken as |cli:DEF_BASENAME|. + is taken as DEF_BASENAME. Parameters ---------- diff --git a/src/sphobjinv/cli/suggest.py b/src/sphobjinv/cli/suggest.py index 31db9585..1fb7dd56 100644 --- a/src/sphobjinv/cli/suggest.py +++ b/src/sphobjinv/cli/suggest.py @@ -43,18 +43,18 @@ def do_suggest(inv, params): Results are printed one per line. - If neither |cli:INDEX| nor |cli:SCORE| is specified, + If neither INDEX nor SCORE is specified, the results are output without a header. If either or both are specified, the results are output in a lightweight tabular format. If the number of results exceeds - |cli:SUGGEST_CONFIRM_LENGTH|, + SUGGEST_CONFIRM_LENGTH, the user will be queried whether to display all of the returned results - unless |cli:ALL| is specified. + unless ALL is specified. - No |cli:QUIET| option is available here, since + No QUIET option is available here, since a silent mode for suggestion output is nonsensical. Parameters @@ -62,7 +62,7 @@ def do_suggest(inv, params): inv |Inventory| -- Inventory object to be output in the format - indicated by |cli:MODE|. + indicated by MODE. params diff --git a/src/sphobjinv/cli/ui.py b/src/sphobjinv/cli/ui.py index 7248ea08..2ab2290b 100644 --- a/src/sphobjinv/cli/ui.py +++ b/src/sphobjinv/cli/ui.py @@ -37,7 +37,7 @@ def print_stderr(thing, params, *, end="\n"): r"""Print `thing` to stderr if not in quiet mode. - Quiet mode is indicated by the value at the |cli:QUIET| key + Quiet mode is indicated by the value at the QUIET key within `params`. Quiet mode is not implemented for the ":doc:`suggest `" @@ -58,7 +58,11 @@ def print_stderr(thing, params, *, end="\n"): |str| -- String to append to printed content (default: ``\n``\ ) """ - if params[PrsConst.SUBPARSER_NAME][:2] == "su" or not params[PrsConst.QUIET]: + if ( + PrsConst.SUBPARSER_NAME not in params # textconv + or params[PrsConst.SUBPARSER_NAME][:2] == "su" # suggest is never quiet + or not params[PrsConst.QUIET] # non-quiet convert + ): print(thing, file=sys.stderr, end=end) diff --git a/tests/conftest.py b/tests/conftest.py index b4c1f087..091df5cd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -45,6 +45,8 @@ from sphinx.util.inventory import InventoryFile as IFile import sphobjinv as soi +from sphobjinv.cli.core import main, main_textconv +from tests.enum import CLICommand from tests.fixtures_http import resource_http_base_url, resource_url # noqa: F401 @@ -240,20 +242,26 @@ def sphinx_version(): @pytest.fixture() # Must be function scope since uses monkeypatch def run_cmdline_test(monkeypatch): """Return function to perform command line exit code test.""" - from sphobjinv.cli.core import main - def func(arglist, *, expect=0): # , suffix=None): + def func(arglist, *, command=CLICommand.Core, expect=0): # , suffix=None): """Perform the CLI exit-code test.""" # Assemble execution arguments - runargs = ["sphobjinv"] + runargs = [command.value] runargs.extend(str(a) for a in arglist) + # Select the command function to use + match command: + case CLICommand.Core: + cmd_func = main + case CLICommand.Textconv: + cmd_func = main_textconv + # Mock sys.argv, run main, and restore sys.argv with monkeypatch.context() as m: m.setattr(sys, "argv", runargs) try: - main() + cmd_func() except SystemExit as e: retcode = e.args[0] ok = True diff --git a/tests/enum.py b/tests/enum.py new file mode 100644 index 00000000..b3469584 --- /dev/null +++ b/tests/enum.py @@ -0,0 +1,39 @@ +r"""*Test enums for* ``sphobjinv``. + +``sphobjinv`` is a toolkit for manipulation and inspection of +Sphinx |objects.inv| files. + +**Author** + Brian Skinn (brian.skinn@gmail.com) + +**File Created** + 22 Dec 2025 + +**Copyright** + \(c) Brian Skinn 2016-2025 + +**Source Repository** + http://www.github.com/bskinn/sphobjinv + +**Documentation** + https://sphobjinv.readthedocs.io/en/stable + +**License** + Code: `MIT License`_ + + Docs & Docstrings: |CC BY 4.0|_ + + See |license_txt|_ for full license terms. + +**Members** + +""" + +from enum import Enum + + +class CLICommand(str, Enum): + """Enumeration of CLI commands.""" + + Core = "sphobjinv" + Textconv = "sphobjinv-textconv" diff --git a/tests/resource/objects_textconv.inv b/tests/resource/objects_textconv.inv new file mode 100644 index 00000000..19a43231 Binary files /dev/null and b/tests/resource/objects_textconv.inv differ diff --git a/tests/test_cli_textconv.py b/tests/test_cli_textconv.py new file mode 100644 index 00000000..45156851 --- /dev/null +++ b/tests/test_cli_textconv.py @@ -0,0 +1,160 @@ +r"""*Textconv CLI tests for* ``sphobjinv``. + +``sphobjinv`` is a toolkit for manipulation and inspection of +Sphinx |objects.inv| files. + +**Author** + Brian Skinn (brian.skinn@gmail.com) + +**File Created** + 22 Dec 2025 + +**Copyright** + \(c) Brian Skinn 2016-2025 + +**Source Repository** + http://www.github.com/bskinn/sphobjinv + +**Documentation** + https://sphobjinv.readthedocs.io/en/stable + +**License** + Code: `MIT License`_ + + Docs & Docstrings: |CC BY 4.0|_ + + See |license_txt|_ for full license terms. + +**Members** + +""" + +import re +import shlex +import subprocess as sp # noqa: S404 +from pathlib import Path + +import pytest +from stdio_mgr import stdio_mgr + +from sphobjinv import Inventory +from tests.enum import CLICommand + +CLI_TEST_TIMEOUT = 2 +CLI_CMDS = ["sphobjinv-textconv"] + +pytestmark = [pytest.mark.cli, pytest.mark.textconv, pytest.mark.local] + + +class TestMisc: + """Tests for miscellaneous textconv entrypoint behavior.""" + + @pytest.mark.timeout(CLI_TEST_TIMEOUT) + @pytest.mark.parametrize("cmd", CLI_CMDS) + def test_cli_invocations(self, cmd): + """Confirm that actual shell invocations do not error.""" + runargs = shlex.split(cmd) + runargs.append("--help") + + out = sp.check_output(" ".join(runargs), shell=True).decode() # noqa: S602 + + assert "sphobjinv" in out + assert "infile" in out + + @pytest.mark.timeout(CLI_TEST_TIMEOUT) + def test_cli_version_exits_ok(self, run_cmdline_test): + """Confirm --version exits cleanly.""" + run_cmdline_test(["-v"], command=CLICommand.Textconv) + + @pytest.mark.timeout(CLI_TEST_TIMEOUT) + def test_cli_noargs_shows_help(self, run_cmdline_test): + """Confirm help shown when invoked with no arguments.""" + with stdio_mgr() as (in_, out_, err_): + run_cmdline_test([], command=CLICommand.Textconv) + + assert re.search("usage.+sphobjinv", out_.getvalue(), re.I) + + +class TestGood: + """Tests for expected-good textconv entrypoint functionality.""" + + def test_textconv_matches_main_conv(self, res_cmp, run_cmdline_test): + """Ensure that textconv conversion matches main CLI conversion.""" + with stdio_mgr() as (_, out_, _): + run_cmdline_test( + ["convert", "plain", res_cmp, "-"], command=CLICommand.Core + ) + core_output = out_.getvalue() + + with stdio_mgr() as (_, out_, _): + run_cmdline_test([res_cmp], command=CLICommand.Textconv) + textconv_output = out_.getvalue() + + assert core_output == textconv_output + + def test_textconv_matches_original(self, res_cmp, run_cmdline_test): + """Confirm textconv produces a consistent Inventory.""" + with stdio_mgr() as (_, out_, _): + run_cmdline_test([str(res_cmp.resolve())], command=CLICommand.Textconv) + + result = out_.getvalue() + + inv1 = Inventory(res_cmp) + inv2 = Inventory(result.encode("utf-8")) + + assert inv1 == inv2 + + +class TestFail: + """Tests for expected-fail textconv entrypoint behaviors.""" + + @pytest.mark.timeout(CLI_TEST_TIMEOUT) + def test_clifail_convert_wrongfiletype( + self, scratch_path, run_cmdline_test, monkeypatch + ): + """Confirm exit code 1 with invalid file format.""" + monkeypatch.chdir(scratch_path) + fname = "testfile" + Path(fname).write_bytes(b"this is not objects.inv\n") + + with stdio_mgr() as (in_, out_, err_): + run_cmdline_test([fname], command=CLICommand.Textconv, expect=1) + assert "Unrecognized" in err_.getvalue() + + @pytest.mark.timeout(CLI_TEST_TIMEOUT) + def test_clifail_convert_missingfile(self, run_cmdline_test): + """Confirm exit code 1 with nonexistent file specified.""" + run_cmdline_test( + ["thisfileshouldbeabsent.txt"], command=CLICommand.Textconv, expect=1 + ) + + @pytest.mark.timeout(CLI_TEST_TIMEOUT) + def test_clifail_convert_outputdir_provided( + self, res_cmp, scratch_path, run_cmdline_test + ): + """Confirm exit code 2 when too many inputs are provided.""" + run_cmdline_test( + [ + res_cmp, + str(scratch_path / "objects.txt"), + ], + command=CLICommand.Textconv, + expect=2, + ) + + @pytest.mark.timeout(CLI_TEST_TIMEOUT) + def test_clifail_convert_pathonlysrc(self, scratch_path, run_cmdline_test): + """Confirm cmdline plaintext convert with input directory arg fails.""" + run_cmdline_test( + [str(scratch_path)], + command=CLICommand.Textconv, + expect=1, + ) + + def test_clifail_no_url_arg(self, run_cmdline_test): + """Confirm textconv parser errors on non-existent -u flag.""" + with stdio_mgr() as (in_, out_, err_): + run_cmdline_test( + ["-u", "nofile.inv"], command=CLICommand.Textconv, expect=2 + ) + assert "unrecognized argument" in err_.getvalue() diff --git a/tox.ini b/tox.ini index 70068adb..c9207995 100644 --- a/tox.ini +++ b/tox.ini @@ -94,6 +94,7 @@ basepython= py310: python3.10 [testenv:black] +description=Autoformat code and tests with black skip_install=True deps=black commands= @@ -101,6 +102,7 @@ commands= black {posargs} . [testenv:flake8] +description=Lint code and tests with flake8 skip_install=True deps=-rrequirements-flake8.txt commands= @@ -108,6 +110,7 @@ commands= flake8 {posargs} src tests [testenv:flake8_noqa] +description=Lint noqa directives with flake8-noqa skip_install=True deps=-rrequirements-flake8.txt commands= @@ -115,6 +118,7 @@ commands= flake8 --color=never --exit-zero {posargs} tests src [testenv:interrogate] +description=Lint docstrings with interrogate skip_install=True deps=interrogate commands= @@ -129,6 +133,8 @@ commands= isort {posargs} src tests [testenv:linkcheck] +description=Run Sphinx linkcheck on docs (Linux only) +platform=linux skip_install=True deps=-rrequirements-dev.txt allowlist_externals= @@ -138,6 +144,7 @@ commands= make linkcheck [testenv:sdist_install] +description=Confirm that sdist installs and imports commands= python -Werror -c "import sphobjinv" deps= @@ -148,6 +155,7 @@ markers = nonloc: Tests requiring Internet access cli: Command-line interface tests api: Direct API tests + textconv: Textconv CLI tests intersphinx: Tests on intersphinx-related functionality fixture: Trivial tests for test suite fixtures testall: Tests that use *all* objects_xyz.inv files in tests/resource, if --testall is specified @@ -199,6 +207,3 @@ per_file_ignores = src/sphobjinv/cli/__init__.py: F401, RST305,RST306 # PIE786: CLI uses 'except Exception:' as a catchall... to be changed, eventually src/sphobjinv/cli/*: PIE786, RST305,RST306 - -#flake8-import-order -import-order-style = smarkets