pax_global_header00006660000000000000000000000064145033736210014516gustar00rootroot0000000000000052 comment=42943e6ac7f311f8ab56268536880d11ee344512 argparse-manpage-4.5/000077500000000000000000000000001450337362100146005ustar00rootroot00000000000000argparse-manpage-4.5/.github/000077500000000000000000000000001450337362100161405ustar00rootroot00000000000000argparse-manpage-4.5/.github/setup/000077500000000000000000000000001450337362100173005ustar00rootroot00000000000000argparse-manpage-4.5/.github/setup/copr-build-setup000077500000000000000000000015321450337362100224250ustar00rootroot00000000000000#! /bin/bash # Helper script (used by praiskup atm) to update CI scripting on Copr side. script=$(readlink -f "$(dirname "$0")")/copr-custom-script script_resultdir=argparse-manpage/rpm pkgname=argparse-manpage PROJECT_PUSH=praiskup/argparse-manpage-ci PROJECT_PR=praiskup/argparse-manpage-pull-requests build_deps=( git make python3-setuptools python-unversioned-command ) for PROJECT in $PROJECT_PR $PROJECT_PUSH; do copr_cmd=( copr edit-package-custom "$PROJECT" \ --webhook-rebuild on \ --script "$script" \ --script-chroot "fedora-latest-x86_64" \ --script-builddeps "${build_deps[*]}" \ --name "$pkgname" \ --script-resultdir "$script_resultdir" ) test "$PROJECT" = "$PROJECT_PR" && copr_cmd+=( --max-builds 20 ) "${copr_cmd[@]}" done argparse-manpage-4.5/.github/setup/copr-custom-script000066400000000000000000000011621450337362100230000ustar00rootroot00000000000000#! /bin/bash -x set -e clone_url_parent=https://github.com/praiskup/argparse-manpage workdir=$(basename "$clone_url_parent") workdir=${workdir%%.git} hook_payload=$(readlink -f "${HOOK_PAYLOAD-hook_payload}") # clone the helper scripts when needed, and add to PATH test -d copr-ci-tooling \ || git clone --depth 1 https://github.com/praiskup/copr-ci-tooling.git export PATH="$PWD/copr-ci-tooling:$PATH" # clone the tested project git clone \ --recursive \ --no-single-branch \ "$clone_url_parent" # checkout requested revision cd "$workdir" webhook-checkout "$hook_payload" cd rpm || exit 1 make srpm argparse-manpage-4.5/.github/workflows/000077500000000000000000000000001450337362100201755ustar00rootroot00000000000000argparse-manpage-4.5/.github/workflows/push-copr-build.yml000066400000000000000000000014621450337362100237400ustar00rootroot00000000000000--- name: RPM build in Fedora Copr on: push: branches: [main] pull_request: branches: [main] schedule: # This always runs against the default branch. Two runs per month. - cron: '0 0 1,16 * *' jobs: build: name: Submit a Copr build runs-on: ubuntu-latest steps: - name: Check out proper version of sources uses: actions/checkout@v1 - name: Submit the build env: COPR_PR_WEBHOOK: https://copr.fedorainfracloud.org/webhooks/custom/33631/780e2120-1749-4623-8222-b8705b3414c7/argparse-manpage/ COPR_PUSH_WEBHOOK: ${{ secrets.COPR_PUSH_WEBHOOK }} run: | curl https://raw.githubusercontent.com/praiskup/copr-ci-tooling/main/copr-gh-actions-submit > submit bash submit ${{ github.event.pull_request.number }} argparse-manpage-4.5/.github/workflows/python-diff-lint.yml000066400000000000000000000016141450337362100241150ustar00rootroot00000000000000--- name: Lint Python issues on: push: pull_request: branches: [main] permissions: contents: read jobs: python-lint-job: runs-on: ubuntu-latest permissions: # required for all workflows security-events: write steps: - name: Repository checkout uses: actions/checkout@v3 - id: VCS_Diff_Lint name: VCS Diff Lint uses: fedora-copr/vcs-diff-lint-action@v1 - name: Upload artifact with detected defects in SARIF format uses: actions/upload-artifact@v3 with: name: VCS Diff Lint SARIF path: ${{ steps.VCS_Diff_Lint.outputs.sarif }} if: ${{ always() }} - name: Upload SARIF to GitHub using github/codeql-action/upload-sarif uses: github/codeql-action/upload-sarif@v2 with: sarif_file: ${{ steps.VCS_Diff_Lint.outputs.sarif }} if: ${{ always() }} argparse-manpage-4.5/.github/workflows/tox.yml000066400000000000000000000010071450337362100215300ustar00rootroot00000000000000--- on: push: branches: - main pull_request: branches: - main name: Run Tox tests jobs: tox_test: name: Tox test steps: - uses: actions/checkout@v2 - name: Run Tox tests id: test uses: fedora-python/tox-github-action@main with: tox_env: ${{ matrix.tox_env }} strategy: matrix: # sync with tox.ini! tox_env: [py36, py37, py38, py39, py310, py311] # Use GitHub's Linux Docker host runs-on: ubuntu-latest argparse-manpage-4.5/.gitignore000066400000000000000000000001341450337362100165660ustar00rootroot00000000000000*.pyc *.1 *_install_dir/ build *.egg-info .cache MANIFEST dist/ man/ .mypy_cache/ tags TAGS argparse-manpage-4.5/.mypy.ini000066400000000000000000000000451450337362100163540ustar00rootroot00000000000000[mypy] ignore_missing_imports = True argparse-manpage-4.5/.travis.yml000066400000000000000000000003201450337362100167040ustar00rootroot00000000000000language: python arch: - amd64 - ppc64le python: - 2.7 - 3.5 - 3.6 - 3.7 - 3.8 - 3.9-dev install: - "pip install -r requirements.txt" script: "PYTHON=python$TRAVIS_PYTHON_VERSION ./check" argparse-manpage-4.5/AUTHORS000066400000000000000000000002101450337362100156410ustar00rootroot00000000000000Andi Albrecht Pavel Raiskup René 'Necoro' Neumann (seealso option) argparse-manpage-4.5/LICENSE000066400000000000000000000261351450337362100156140ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. argparse-manpage-4.5/MANIFEST.in000066400000000000000000000012111450337362100163310ustar00rootroot00000000000000include argparse-manpage include AUTHORS include LICENSE include NEWS recursive-include examples *.py *.cfg *.md expected-output.1 expected/**.1 requirements.txt recursive-include tests *.py recursive-include unittests *.py include examples/argument_groups/bin/test include examples/resalloc/expected/man/resalloc-maint.1 include examples/resalloc/expected/man/resalloc.1 include examples/copr/fake-deps/HACK include examples/copr/fake-deps/copr/README include examples/pre-written-man-page/psutils.1 include examples/raw-description/bin/dg include examples/resalloc/bin/resalloc include examples/resalloc/bin/resalloc-maint include tests/extra.man argparse-manpage-4.5/Makefile000066400000000000000000000002001450337362100162300ustar00rootroot00000000000000.PHONY: check lint unittests check: $(MAKE) unittests $(MAKE) lint unittests: PYTHON=python3 ./check lint: vcs-diff-lint argparse-manpage-4.5/NEWS000066400000000000000000000126371450337362100153100ustar00rootroot00000000000000~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ WARNING: The 'build_manpage' setup.py command will be removed v5 WARNING: We'll drop the Python 2.7 support in v5 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ News in v4.5 * We newly provide `build_manpages.build_py` and `build_manpages.install` command classes that are re-usable from `pyproject.toml`. No need to provide `setup.py` because of `argparse-manpage`. Solved issue#85. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ News in v4.4 * The `prog=` specifier (in setup.py/setup.cfg/pyproject.toml) is now better handled so it doesn't conflict with ArgumentParser(prog=..). Fixes https://github.com/praiskup/argparse-manpage/issues/79 News in v4.3 * The pyproject.toml parsing feature now depends on the python3-tomli library instead of python-toml for "python_environment >3, <=3.10". ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ News in v4.2 * Support for pyproject.toml specification of manpages added. * Support for pre-written man pages (the --manfile option) Bugfixes in version 4.2 * Incorrect dict access for --include support fixed. * Provide useful AUTHORS section with e-mail from Distribution.get_author_email() even if Distribution.get_author() returns None. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ News in v4.1 * A new `--include` feature, inspired by `help2man --include`. * Allow overriding build date with SOURCE_DATE_EPOCH environment variable in order to make builds reproducible. See this link for more info: https://reproducible-builds.org/specs/source-date-epoch/ * The AUTHORS section was changed to more standard AUTHOR. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ News in v4 * The manual page generator logic is now separated from the 'build_manpages' module (which provides setup.py integration helpers). Therefore the argparse-manpage doesn't necessarily have to depend on setuptools. Projects that want to integrate using 'setup.py' should though place a new "extra" named 'argparse-manpage[setuptools]' into their set of build requirements in project.toml file. * The old 'build_manpage' command (!= 'build_manpages') is now more isolated from the rest of the code, likely never loaded. * the Manpage class API was changed in v3 so it required an additional constructor 'data' argument. This change was reverted, and the only argument is again the ArgumentParser object. * The 'version' and 'description' options were fixed. * New options 'manual_section' and 'manual_title' were added. * The manual page now automatically generates a current date in headers. * Several groff escaping issues were fixed. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ New in version 3 * New man page format: single-commands-section * Add CLI and conf options for setting the output format * Add CLI and conf options to explicitly specify %prog value * Skip showing commands with help == SUPPRESS * Avoid rendering docs for command aliases * Print program name in upper case in header and footer * The --author CLI option has changed, and takes arbitrary string (typically 'Author Name '), and newly can be specified multiple times. Therefore, it now replaces the '--author-email' option. The --author-email option is kept, but is just an alias to the --author option. * All CLI options can be specified also in setup.cfg * Don't render AUTHORS and DISTRIBUTION if they would contain undefined values * Remove '... was written by' from AUTHORS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Bugfixes in version 2.2 * Fixed the testsuite against the setuptools v60+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ New in version 2.1 * Output manual pages should be built reproducibly, no matter the current size of the terminal. * Methods build_manpages, get_build_py_cmd, get_install_cmd are now provided in top-level module. * More portable opening and parsing given by python file name. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ New in version 2 * Split out options by generic argument groups, not only predefined "positional arguments", "options", and Subparsers. * RPM spec file updated to comply with the recent (Fedora 35+) guidelines. * Drop python3-six requirement. * Properly highligh all option argument METAVARs. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ New in version 1.5 * The "epilog" from argparse object is dumped to "COMMENTS" sections. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ New in version 1.4 * fixed testsuite for Python 3.9 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ New in version 1.3 * drop additional .br tag from paragraphs so the multiline text is nicer * provide argparse-manpage via entry_point ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ New in version 1.2 * Fixed shebang in /bin/argparse-manpage script so it works on Debian/Ubuntu as well as on Fedora. * LICENSE file included in release tarball. * Command-line executable now takes an optional '--output' argument. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ argparse-manpage-4.5/README.md000066400000000000000000000212561450337362100160650ustar00rootroot00000000000000# ArgumentParser instance → manual page Avoid documenting your Python script arguments on two places! This is typically done in an [argparse.ArgumentParser][ap-docs] help configuration (`help=`, `description=`, etc.), and also in a manually crafted manual page. The good thing about an `ArgumentParser` objects is that it actually provides a traversable "tree-like" structure, with all the necessary info needed to **automatically generate** documentation, for example in a *groff* typesetting system (manual pages). And this is where this project can help. There are two supported ways to generate the manual, either script it using the installed command `argparse-manpage`, or via `setup.py build` automation (with a slight bonus of automatic manual page installation with `setup.py install`). ## What is need? Most of the (meta)data is stored in the `ArgumentParser` object, therefore `argparse-manpage` needs to know its location—it can be either the object itself, or a method to call to get the object [^1]. On top of this, several manual page fields (like *author* or *project* name) need to be specified, either on command-line or via `setup.py` metadata. ## Command-line usage See the following example: ``` $ argparse-manpage --pyfile ./pythonfile.py --function get_parser \ --author "John --author-email doe@example.com" \ --project-name myproject --url https://pagure.io/myproject \ > cool-manpage.1 ``` This (a) processes the `./pythonfile.py`, (b) calls the `get_parser` inside to obtain the `ArgumentParser` instance, (c) transforms it into a manual page and (d) stores it into the `cool-manpage.1` file. Alternatively those options above can be combined with - option `--module mymodule.main`, to load a Python module `mymodule.main` from `PYTHONPATH`, or - `--object parser_object_name` if the `parser_object_name` is a global variable. ## Use with pyproject.toml First, you need to declare in `pyproject.toml` that argparse-manpage is needed at build-time and use the setuptools.builds_meta` backend: ```toml [build-system] requires = ["argparse-manpage[setuptools]"] build-backend = "setuptools.build_meta" ``` Alternatively, you can place the `build_manpages` (sub)directory from this project somewhere onto `PYTHONPATH` so you can use it at build time. For example: ```bash git submodule add --name build_manpages https://github.com/praiskup/build_manpages git submodule update --init ``` Then in `pyproject.toml` (re)define `cmdclass` commands: ```toml [tool.setuptools.cmdclass] build_py = "build_manpages.build_py" install = "build_manpages.install" build_manpages = "build_manpages.build_manpages" ``` And specify the list of built manual pages: ```toml [tool.build_manpages] manpages = [ "man/foo.1:object=parser:pyfile=bin/foo.py", "man/bar.1:function=get_parser:pyfile=bin/bar", "man/baz.1:function=get_parser:pyfile=bin/bar:prog=baz", ] ``` ## Use with setup.py In your `setup.py` use pattern like: ```python [...] from build_manpages import build_manpages, get_build_py_cmd, get_install_cmd setup( [...] cmdclass={ 'build_manpages': build_manpages, # Re-define build_py and install commands so the manual pages # are automatically re-generated and installed 'build_py': get_build_py_cmd(), 'install': get_install_cmd(), } ) ``` And in `setup.cfg` configure the manual pages you want to automatically generate and install: ``` [build_manpages] manpages = man/foo.1:object=parser:pyfile=bin/foo.py man/bar.1:function=get_parser:pyfile=bin/bar man/baz.1:function=get_parser:pyfile=bin/bar:prog=baz ``` ## List of manual pages The format of those lines is a colon separated list of arguments/options. The first argument determines the filename of the generated manual page. Then follows a list of options of format `option=value`. Supported values are: - pyfile - what python file the argparse object resides in - object - the name of arparse object in "pyfile" to import - function - the name of function in pyfile to call to get the argparse object - format - format of the generated man page: `pretty` (default), `single-commands-section` - author - author of the program; can be specified multiple times - description - description of the program, used in the NAME section, after the leading 'name - ' part, see man (7) man-pages for more info - project_name - name of the project the program is part of - version - version of the project, visible in manual page footer - prog - value that substitutes %prog in ArgumentParser's usage - url - link to project download page - manual_section - section of the manual, by default 1, see man (7) man-pages for more info about existing sections - manual_title - the title of the manual, by default "Generated Python Manual", see man (7) man-pages for more instructions - include - a file of extra material to include; see below for the format - manfile - a file containing a complete man page that just needs to be installed (such files must also be listed in `MANIFEST.in`) The values from setup.cfg override values from setup.py's setup(). Note that when `manfile` is set for a particular page, no other option is allowed. Then run `setup.py build_manpages` to build a manpages for your project. Also, if you used `get_build_py` helper, `setup.py build` then transitively builds the manual pages. ## Include file format The include file format is based on GNU `help2man`'s `--include` format. The format is simple: ``` [section] text /pattern/ text ``` Blocks of verbatim *roff text are inserted into the output either at the start of the given `section` (case insensitive), or after a paragraph matching `pattern`, a Python regular expression. Lines before the first section are silently ignored and may be used for comments and the like. Other sections are prepended to the automatically produced output for the standard sections given above, or included near the bottom of the man page, before the `AUTHOR` section, in the order they occur in the include file. Placement of the text within the section may be explicitly requested by using the syntax `[section]` to place the additional text before, in place of, or after the default output respectively. ## Installation This package is distributed [in PyPI][pypi-page], can be installed by: $ pip install argparse-manpage It can simply downloaded, or distributed as a git submodule (see above). ## Packaging status The Git snapshot RPMs–pre-release version automatically built from the `main` branch–are available in Fedora Copr build system [![build status](https://copr.fedorainfracloud.org/coprs/praiskup/argparse-manpage-ci/package/argparse-manpage/status_image/last_build.png)](https://copr.fedorainfracloud.org/coprs/praiskup/argparse-manpage-ci/) The `argparse-manpage` project is provided natively on many distributions: ![build status](https://repology.org/badge/vertical-allrepos/python:argparse-manpage.svg?exclude_unsupported=1&header=argparse-manpage) Try your package manager directly (e.g. on Fedora `dnf install -y argparse-manpage`). ## History The initial code was developed for CrunchyFrog, a database query tool for Gnome. The [frog] is now retired and [RunSQLRun] is it's successor. Then, the `build_manpage` command was developed in [andialbrecht] and edited slightly in [gabrielegiammatteo]. There's even an [old blog post] about this command. Since some useful work has been done in [python pull request], the code from the PR has been used here too. Later more options and flexibility has been implemented in this fork, with the help of many contributors. Thank you! Historically, `build_manpage` setup.py command was provided (mostly for `OptionParser`). Later we migrated to more versatile `build_manpages` command. But the old variant is still [supported](examples/old\_format/README.md). ## License This work is released under the terms of the Apache License v2.0. See LICENSE for details. [^1]: `argparse-manpage` needs to process the location (file/module) via Python interpreter, and thus please avoid side-effects (typically, the `main.py` files need to use the `if __name__ == "__main__"` condition, and similar). [gabrielegiammatteo]: https://github.com/andialbrecht/build\_manpage [andialbrecht]: https://github.com/andialbrecht/build\_manpage [frog]: http://crunchyfrog.googlecode.com/svn/ [RunSQLRun]: https://github.com/andialbrecht/runsqlrun [old blog post]: https://andialbrecht.wordpress.com/2009/03/17/creating-a-man-page-with-distutils-and-optparse/ [python pull request]: https://github.com/python/cpython/pull/1169 [ap-docs]: https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser [pypi-page]: https://pypi.org/project/argparse-manpage/ argparse-manpage-4.5/argparse-manpage000077500000000000000000000005451450337362100177440ustar00rootroot00000000000000#! /bin/sh # Copyright (C) 2022 Red Hat, Inc. srcdir=$(readlink -f "$(dirname "$0")") run_python=${PYTHON-python3} echo >&2 echo >&2 " !! running argparse-manpage from git, this is not supported PYTHON=$run_python!!" echo >&2 export PYTHONPATH=$srcdir/${PYTHONPATH+:$PYTHONPATH} exec $run_python -c 'from argparse_manpage.cli import main; main()' "$@" argparse-manpage-4.5/argparse_manpage/000077500000000000000000000000001450337362100200745ustar00rootroot00000000000000argparse-manpage-4.5/argparse_manpage/__init__.py000066400000000000000000000000661450337362100222070ustar00rootroot00000000000000""" argparse_manpage project """ __version__ = '4.5' argparse-manpage-4.5/argparse_manpage/cli.py000066400000000000000000000070051450337362100212170ustar00rootroot00000000000000# Copyright (C) 2017 Red Hat, Inc. # Python 2.7 hack. Without this, 'from build_manpages.build_manpage' attempts # to import 'build_manpages.build_manpages.build_manpage' because of our # unfortunate file naming. from __future__ import absolute_import import argparse from argparse_manpage.tooling import get_parser, write_to_filename from argparse_manpage.manpage import MANPAGE_DATA_ATTRS, Manpage description = """ Build manual page from Python's argparse.ArgumentParser object. """.strip() ap = argparse.ArgumentParser( prog='argparse-manpage', description=description, ) src_group = ap.add_mutually_exclusive_group(required=True) src_group.add_argument( "--module", help="search the OBJECT/FUNCTION in MODULE" ) src_group.add_argument( "--pyfile", help="search the OBJECT/FUNCTION in FILE" ) obj_group = ap.add_mutually_exclusive_group(required=True) obj_group.add_argument( "--function", help="call FUNCTION from MODULE/FILE to obtain ArgumentParser object", ) obj_group.add_argument( "--object", help="obtain ArgumentParser OBJECT from FUNCTION (to get argparse object) from MODULE or FILE", ) ap.add_argument("--project-name", help="Name of the project the documented program is part of.") ap.add_argument("--prog", help="Substitutes %%prog in ArgumentParser's usage.") ap.add_argument("--version", help=( "Version of the program, will be visible in the " "manual page footer.")) ap.add_argument("--description", metavar="TEXT", help=( "description of the program, used in the NAME section, after the " "leading 'name - ' part, see man (7) man-pages for more info")) ap.add_argument("--long-description", metavar="TEXT", help=argparse.SUPPRESS) ap.add_argument("--author", action="append", dest="authors", metavar="[AUTHOR]", help="Author of the program. Can be specified multiple times.") ap.add_argument("--author-email", action="append", dest="authors", help=argparse.SUPPRESS) ap.add_argument("--url", help="Link to project's homepage") ap.add_argument("--format", default="pretty", choices=("pretty", "single-commands-section"), help="Format of the generated man page. Defaults to 'pretty'.") ap.add_argument("--output", dest='outfile', default='-', help="Output file. Defaults to stdout.") ap.add_argument("--manual-section", help=( "Section of the manual, by default 1. See man (7) man-pages for more " "info about existing sections.")) ap.add_argument("--manual-title", help=( "The title of the manual, by default \"Generated Python Manual\". " "See man (7) man-pages for more instructions.")) ap.add_argument("--include", metavar="FILE", help=( "File that contains extra material for the man page.")) ap.add_argument("--manfile", metavar="FILE", help=( "File containing a complete man page.")) def args_to_manpage_data(args): data = {} for attr in MANPAGE_DATA_ATTRS: value = getattr(args, attr) data[attr] = value return data def main(): args = ap.parse_args() import_type = 'pyfile' import_from = args.pyfile if args.module: import_type = 'module' import_from = args.module obj_type = 'object' obj_name = args.object if args.function: obj_type = 'function' obj_name = args.function parser = get_parser(import_type, import_from, obj_name, obj_type, prog=args.prog) data = args_to_manpage_data(args) manpage = Manpage(parser, format=args.format, _data=data) write_to_filename(str(manpage), args.outfile) argparse-manpage-4.5/argparse_manpage/compat.py000066400000000000000000000032051450337362100217310ustar00rootroot00000000000000""" Compatibility hacks for the argparse-manpage project. """ import sys # Drop once Python 2.7 is dropped # pylint: disable=unused-import try: from configparser import ConfigParser, NoSectionError except ImportError: from ConfigParser import SafeConfigParser as ConfigParser, NoSectionError # type: ignore if sys.version_info < (3, 0): import imp # pylint: disable=deprecated-module def load_py_file(filename): """ Small wrapper having the same call arg list as runpy.run_path() """ return imp.load_source("argparse_manpage_loaded_file", filename) else: from runpy import run_path as load_py_file def get_module_object(module_or_dict, objname, objtype): """ Get OBJNAME from a given MODULE (or dict, if loaded using runpy.run_path(), but call the object first if OBJTYPE is 'function'. """ obj = None if isinstance(module_or_dict, dict): obj = module_or_dict[objname] else: obj = getattr(module_or_dict, objname) if objtype != 'object': obj = obj() return obj def load_file_as_module(filename): """ Load a given python filename as a dict (runpy on Python 3) or as a module (imp module, Python 2). Note that 'runpy.run_path()' doesn't work correctly with Python 2.7 (the imported object doesn't see it's own globals/imported modules), and 'imp' module is deprecated for modern Python 3. """ # We used to call 'runpy.run_path()' here, but that did not work correctly # with Python 2.7 where the imported object did not see it's own # globals/imported modules (including the 'argparse' module). return load_py_file(filename) argparse-manpage-4.5/argparse_manpage/manpage.py000066400000000000000000000520671450337362100220700ustar00rootroot00000000000000from argparse import SUPPRESS, HelpFormatter, _SubParsersAction, _HelpAction from collections import OrderedDict import datetime import os import time import re DEFAULT_GROUP_NAMES = { # We replace ArgumentGroup title (value) with alias (key). None: [ 'positional arguments', ], 'OPTIONS': [ 'optional arguments', 'options', ] } DEFAULT_GROUP_NAMES_SUBCOMMANDS = { # We replace ArgumentGroup title (value) with alias (key). "arguments:": [ 'positional arguments', ], 'options:': [ 'optional arguments', 'options', ] } # all manpage attributes that can be set via CLI or setup.cfg MANPAGE_DATA_ATTRS = ( "authors", # just "author" in setup.cfg, can be specified multiple times "description", "long_description", # not in the manpage.py, we use parser.description "project_name", # maps to distribution.get_name() "prog", "url", "version", "format", "manual_section", "manual_title", "include", "manfile", ) # manpage sections that are handled specially, so need special treatment # when --include'ing extra material; see Manpage.add_section. SPECIAL_MANPAGE_SECTIONS = ( "author", "comments", "description", "distribution", "synopsis", ) def _markup(text): """ Escape the text for the markdown format. """ if isinstance(text, str): return text.replace('\\', '\\\\').replace('-', r'\-') return text def get_manpage_data_from_distribution(distribution, data): """ Update `data` with values from `distribution`. """ # authors if not "authors" in data: author = None if distribution.get_author(): author = distribution.get_author() if distribution.get_author_email(): author += " <{}>".format(distribution.get_author_email()) elif distribution.get_author_email(): author = distribution.get_author_email() if author: data["authors"] = [author] attrs = list(MANPAGE_DATA_ATTRS) attrs.remove("authors") attrs.remove("prog") # not available, copied from 'project_name' later attrs.remove("format") # not available, must be set in setup.cfg # we want the utility description, not the project description attrs.remove("description") for attr in attrs: if data.get(attr, None): continue # map data["project_name"] to distribution.get_name() get_attr = "name" if attr == "project_name" else attr getter = getattr(distribution, "get_" + get_attr, None) if not getter: continue value = getter() data[attr] = value if "prog" not in data: data["prog"] = data["project_name"] def _get_footer_lines(data): ret = [] project_name = data.get("project_name", "") authors = data.get("authors") url = data.get("url") needs_separator = False if authors: ret.append('.SH AUTHOR') for author in authors: ret.append(".nf") ret.append(author) ret.append(".fi") needs_separator = True if url: if needs_separator: ret.append("") ret.append(".SH DISTRIBUTION") ret.append("The latest version of {0} may " "be downloaded from".format(_markup(project_name))) ret.append(".UR {0}".format(_markup(url))) ret.append(".UE") return ret def get_footer(data): """ Return a manual page footer based on the data returned from get_manpage_data_from_distribution(). Used only by the old build_manpage module. """ return "\n".join(_get_footer_lines(data)) + "\n" # This is already considered an API, and seems like a valid scenario: # https://github.com/pypa/pipx/blob/fd6650bcaeca3088/scripts/generate_man.py class Manpage(object): # pylint: disable=too-many-instance-attributes def __init__(self, parser, _data=None, format='pretty'): """ Manual page abstraction. Generates, with the help of formater, a manual page by __str__() method. Please avoid using the private _data argument (see https://github.com/praiskup/argparse-manpage/issues/7), instead override the `self.` when needed. """ self.prog = parser.prog self.parser = parser self.format = format self._data = _data or {} self._match_texts = [] if not getattr(parser, '_manpage', None): self.parser._manpage = [] self.manfile = self._data.get("manfile") if self.manfile: if len(self._data) > 1: raise ValueError("manfile set, so no other key is allowed") return self.formatter = self.parser._get_formatter() self.mf = _ManpageFormatter(self.prog, self.formatter, format=self.format) self.synopsis = self.parser.format_usage().split(':', 1)[-1].split() self.date = self._data.get("date") if not self.date: builddate = datetime.datetime.utcfromtimestamp( int(os.environ.get('SOURCE_DATE_EPOCH', time.time())) ) self.date = builddate.strftime('%Y-%m-%d') self.source = self._data.get("project_name") if not self.source: self.source = self.prog version = self._data.get("version") if version: self.source += " " + str(version) self.manual = self._data.get("manual_title") if not self.manual: self.manual = "Generated Python Manual" self.section = self._data.get("manual_section") if not self.section: self.section = 1 self.description = self._data.get("description") include = self._data.get("include") if include is not None: self.parse_include(include) def format_text(self, text): # Wrap by parser formatter and convert to manpage format return self.mf.format_text(self.formatter._format_text(text)).strip('\n') def __str__(self): if self.manfile: with open(self.manfile) as fd: return fd.read() lines = [] # Header # per man (7) man-pages: .TH title section date source manual header = '.TH {title} "{section}" "{date}" "{source}" "{manual}"' lines.append(header.format( title=_markup(self.prog.upper()), section=self.section, date=_markup(self.date), source=_markup(self.source), manual=_markup(self.manual), )) # Name lines.append('.SH NAME') line = self.prog description = None if getattr(self.parser, 'man_short_description', None): # Let's keep this undocumented. There's a way to specify this in # setup.cfg: 'description' description = self.parser.man_short_description if self.description: description = self.description if description: line += " - " + description lines.append(_markup(line)) # Synopsis synopsis_section = self.get_extra_section("synopsis") if self.synopsis or synopsis_section: lines.append('.SH SYNOPSIS') if synopsis_section: lines.append(synopsis_section["content"]) else: lines.append('.B {}'.format(_markup(self.synopsis[0]))) lines.append(' '.join(self.synopsis[1:])) extra_description = None description_section = self.get_extra_section("description") if description_section: extra_description = description_section["content"] lines.extend(self.mf.format_parser(self.parser, extra_description=extra_description)) comments_section = self.get_extra_section("comments") if self.parser.epilog or comments_section: lines.append("") lines.append('.SH COMMENTS') if comments_section: lines.append(comments_section["content"]) else: lines.append(self.format_text(self.parser.epilog)) # Additional sections for section in self.parser._manpage: # pylint: disable=protected-access if section["heading"] not in SPECIAL_MANPAGE_SECTIONS: lines.append('.SH {}'.format(section['heading'].upper())) lines.append(section['content']) lines.append("") lines.extend(self.mf.format_footer(self._data)) # Finally add --include sections that match text in the page final_lines = [] for line in lines: final_lines.append(line) for match in self._match_texts: if re.search(match['match_text'], line): final_lines.append(match['content']) return "\n".join(final_lines).strip("\n") + "\n" def get_extra_section(self, heading): """ Return supplementary section for the `Manpage` (created with `--include`), or `None` """ for section in self.parser._manpage: # pylint: disable=protected-access if section["heading"] == heading: return section return None def add_section(self, heading, position, content): """ Add a supplementary section to a `Manpage` """ # Sections that need special treatment heading = heading.lower() if heading in ("author", "distribution"): if heading == "author": self._data['authors'] = [content] elif heading == "distribution": self._data['url'] = content section = self.get_extra_section(heading) if section is None: section = {"heading": heading, "content": ""} self.parser._manpage.append(section) # pylint: disable=protected-access if position == '<': section["content"] = content + section["content"] elif position == '=': section["content"] = content elif position == '>': section["content"] += content else: raise ValueError("invalid position " + position) def parse_include(self, file): """ Parse include file and add its contents to the man page """ def get_section(lines, n): for i, line in enumerate(lines[n:]): if re.match(r'[\[/]', line): return n + i, lines[n:n + i] return len(lines), lines[n:] with open(file) as f: lines = f.readlines() i = 0 while i < len(lines): # Parse a header line m = re.match(r"/([^/]+)/$", lines[i]) if m: match_text = m.group(1) i, section_lines = get_section(lines, i + 1) self._match_texts.append({"match_text": match_text, "content": "".join(section_lines).strip()}) else: m = re.match(r"\[([<=>])?([^\]]+)\]$", lines[i]) if m: position = m.group(1) or '<' heading = m.group(2).upper() if heading == "NAME": raise ValueError("Invalid include section " + heading) i, section_lines = get_section(lines, i + 1) self.add_section(heading, position, "".join(section_lines).strip()) else: raise ValueError("Invalid or missing section header in include file %s:\n%s" % (file, lines[i])) def underline(text): """ Wrap text with \fI for underlined text """ return r'\fI\,{0}\/\fR'.format(_markup(text)) def bold(text): """ Wrap text by "bold" groff tags """ return r"\fB{0}\fR".format(_markup(text)) def quoted(text): """ Wrap by single-quotes """ return "'{0}'".format(text) class _ManpageFormatter(HelpFormatter): def __init__(self, prog, old_formatter, format): super(HelpFormatter, self).__init__() self._prog = prog self.of = old_formatter assert format in ("pretty", "single-commands-section") self.format = format @staticmethod def _get_aliases_str(aliases): if not aliases: return "" return " (" + ", ".join(aliases) + ")" def _format_action_invocation(self, action): if not action.option_strings: metavar, = self._metavar_formatter(action, action.dest)(1) return bold(metavar) parts = [] # if the Optional doesn't take a value, format is: # -s, --long if action.nargs == 0: parts.extend(map(bold, action.option_strings)) # if the Optional takes a value, format is: # -s ARGS, --long ARGS else: default = action.dest.upper() args_string = self._format_args(action, default) for option_string in action.option_strings: parts.append('{} {}'.format(bold(option_string), underline(args_string))) return ', '.join(parts) def _format_parser(self, parser, subcommand=None, aliases=None, help=None, extra_description=None): # The parser "tree" looks like # ---------------------------- # Parser -> [ActionGroup, ActionGroup, ..] # Group -> [Action, Action, ..] # Action -> Option # Action -> Subparsers # Subparser -> [Parser, Parser, ..] So called "choices". lines = [] if subcommand: if self.format == "pretty": lines.append("") # start a new section for each command first_line = ".SH COMMAND" first_line += " " + underline(quoted(subcommand)) elif self.format == "single-commands-section": # do not start a new section, start subsection of COMMANDS instead first_line = ".SS" first_line += " " + bold(subcommand + self._get_aliases_str(aliases)) lines.append(first_line) if help: if self.format == "pretty": # help is printed on top in the list of commands already pass elif self.format == "single-commands-section": # print help lines.append(help) lines.append("") lines.append(self.format_text(parser.format_usage())) if parser.description or extra_description: if subcommand: lines.append("") else: lines.append(".SH DESCRIPTION") if extra_description: lines.append(extra_description) if parser.description: lines.append(self.format_text(parser.description)) is_subsequent_ag = True for group in parser._action_groups: ag_lines = self._format_action_group(group, subcommand) if not ag_lines: continue if is_subsequent_ag: lines.append("") lines.extend(ag_lines) is_subsequent_ag = True return lines def format_parser(self, parser, extra_description=None): """ Return lines Groff formatted text for given parser """ return self._format_parser(parser, extra_description=extra_description) def _format_action(self, action): parts = [] parts.append('.TP') action_header = self._format_action_invocation(action) parts.append(action_header) # if there was help for the action, add lines of help text if action.help: help_text = self.of._format_text(self._expand_help(action)).strip('\n') parts.append(self.format_text(help_text)) return parts def _format_ag_subcommands(self, actions, prog): lines = [] for action in actions: if getattr(action, 'help', None) == SUPPRESS: continue lines.append('.TP') lines.append(bold(prog) + ' ' + underline(action.dest)) if hasattr(action, 'help'): lines.append(self.format_text(action.help)) return '\n'.join(lines) def _format_subparsers(self, action_group, action, subcommand=None): lines = [] if subcommand: if self.format == "pretty": # start a new section for each command lines.append('.SH') title = action_group.title.upper() title += " " + underline(quoted(subcommand)) lines.append(title) elif self.format == "single-commands-section": # do not start a new section, append subsections to the COMMANDS section pass else: # start a new section on top-level lines.append('.SH') title = action_group.title.upper() lines.append(title) if self.format == "pretty": # print list of subcommands lines.append(self._format_ag_subcommands(action._choices_actions, subcommand or self._prog)) elif self.format == "single-commands-section": # skip printing list of subcommands pass # gather (sub-)command aliases command_aliases = {} command_aliases_names = set() for name, command in action._name_parser_map.items(): if command not in command_aliases: command_aliases[command] = [] else: command_aliases[command].append(name) command_aliases_names.add(name) command_help = {} for i in action._choices_actions: command_help[i.dest] = i.help for name, choice in action.choices.items(): if name in command_aliases_names: # don't print aliased commands multiple times continue new_subcommand = "{} {}".format(subcommand or self._prog, name) aliases = command_aliases[choice] help = command_help.get(name, None) if help == SUPPRESS: # don't print hidden commands continue lines.extend(self._format_parser(choice, new_subcommand, aliases, help)) return lines def _format_action_group(self, action_group, subcommand=None): # Parser consists of these action_groups: # - positional arguments (no group_actions) # - ungrouped options # - group 1 options # - group 2 options # - ... # - subparsers content = [] some_action = False for action in action_group._group_actions: if action.help == SUPPRESS: continue if isinstance(action, _SubParsersAction): return self._format_subparsers(action_group, action, subcommand) if '--help' in action.option_strings: # TODO: put out some man page comment .. continue if some_action: # Separate actions content.append("") some_action = True content.extend(self._format_action(action)) # We don't print empty argument groups. if not some_action: return [] title = action_group.title group_names = DEFAULT_GROUP_NAMES if subcommand: if self.format == "pretty": pass elif self.format == "single-commands-section": group_names = DEFAULT_GROUP_NAMES_SUBCOMMANDS for replace_with, defaults in group_names.items(): if title in defaults: title = replace_with if subcommand: if self.format == "pretty": title = title.upper() if title else "" if title: title += " " + underline(quoted(subcommand)) title = [] if not title else [".SH " + title] elif self.format == "single-commands-section": title = [] if not title else [title] else: title = title.upper() if title else "" title = [] if not title else [".SH " + title] description = [] if action_group.description: description.append(self.format_text(action_group.description)) description.append("") if subcommand: if self.format == "pretty": # don't indent the whole content of a subcommand pass elif self.format == "single-commands-section": # indent the whole content of a subcommand content = [".RS 7"] + content + [".RE"] + [""] return title + description + content @staticmethod def format_text(text): """ Format a block of text as it was a single line in set of other lines (e.g. no trailing newline). """ return _markup(text.strip('\n')) @staticmethod def format_footer(data): """ Get lines for footer. """ return _get_footer_lines(data) argparse-manpage-4.5/argparse_manpage/tooling.py000066400000000000000000000044261450337362100221270ustar00rootroot00000000000000""" A tooling helpers for the argparse-manpage project. """ import importlib import os import sys from .compat import load_file_as_module, get_module_object def _environ_hack(): os.environ['BUILD_MANPAGES_RUNNING'] = 'TRUE' def get_parser_from_module(module, objname, objtype='object', prog=None): """ Read the given module and return the requested object from there. """ _environ_hack() # We need to set argv[0] to properly so argparse returns appropriate "usage" # strings. Like "usage: argparse-manpage [-h] ...", instead of # "usage: setup.py ...". backup_argv = sys.argv if prog: sys.argv = [prog] mod = importlib.import_module(module) obj = get_module_object(mod, objname, objtype) # Restore callee's argv sys.argv = backup_argv return obj def get_parser_from_file(filename, objname, objtype='object', prog=None): """ Load the given filename as a module and return the requested object from there. """ _environ_hack() # We need to set argv[0] to properly so argparse returns appropriate "usage" # strings. Like "usage: argparse-manpage [-h] ...", instead of # "usage: setup.py ...". backup_argv = sys.argv if prog: sys.argv = [prog] else: sys.argv = [os.path.basename(filename)] # Get the ArgumentParser object module_loaded = load_file_as_module(filename) obj = get_module_object(module_loaded, objname, objtype) # Restore callee's argv sys.argv = backup_argv return obj def get_parser(import_type, import_from, objname, objtype, prog=None): """ Load a function or object from a given file or module. """ if import_type == 'pyfile': return get_parser_from_file(import_from, objname, objtype, prog=prog) return get_parser_from_module(import_from, objname, objtype, prog=prog) def write_to_filename(text, filename): """ Write given text into a filename at once. Pre-create the parent directory if it doesn't exist yet. Print to stdout if filename == '-'. """ filename = filename if filename != '-' else '/dev/stdout' dirname = os.path.dirname(filename) if dirname and not os.path.exists(dirname): os.makedirs(dirname) with open(filename, 'w') as stream: stream.write(text) argparse-manpage-4.5/build_manpages/000077500000000000000000000000001450337362100175525ustar00rootroot00000000000000argparse-manpage-4.5/build_manpages/__init__.py000066400000000000000000000003441450337362100216640ustar00rootroot00000000000000""" Export some useful methods in top-level. """ from argparse_manpage import __version__ from .build_manpages import build_manpages, get_build_py_cmd, get_install_cmd install = get_install_cmd() build_py = get_build_py_cmd() argparse-manpage-4.5/build_manpages/build_manpage.py000066400000000000000000000163041450337362100227170ustar00rootroot00000000000000# -*- coding: utf-8 -*- """build_manpage command -- Generate man page from setup()""" import datetime import optparse import argparse import os import time import warnings from distutils.core import Command from distutils.errors import DistutilsOptionError from argparse_manpage.manpage import ( get_manpage_data_from_distribution, get_footer, ) from argparse_manpage.tooling import ( get_parser_from_file, get_parser_from_module, write_to_filename, ) warnings.warn( "The 'build_manpage' module will be removed in the next argparse-manpage " "version v5. Please migrate to 'build_manpages'.", DeprecationWarning, ) class ManPageWriter(object): _parser = None _command = None _type = None def __init__(self, parser, values): self._parser = parser self.values = values self._today = datetime.datetime.utcfromtimestamp( int(os.environ.get('SOURCE_DATE_EPOCH', time.time())) ) if isinstance(parser, argparse.ArgumentParser): self._type = 'argparse' if parser.formatter_class == argparse.HelpFormatter: # Hack for issue #36, to have reproducible manual page content # regardless the terminal window size. Long term we should avoid # using the built-in usage formatter, and generate our own. parser.formatter_class = \ lambda prog: argparse.HelpFormatter(prog, width=78) else: self._parser.formatter = ManPageFormatter() self._parser.formatter.set_parser(self._parser) def _markup(self, txt): return txt.replace('-', '\\-') def _write_header(self): version = self.values["version"] prog = self.values["prog"] ret = [] ret.append('.TH %s 1 %s "%s v.%s"\n' % (self._markup(prog), self._today.strftime('%Y\\-%m\\-%d'), prog, version)) description = self.values.get("description") if description: name = self._markup('%s - %s' % (prog, description.splitlines()[0])) else: name = self._markup(prog) ret.append('.SH NAME\n%s\n' % name) if getattr(self._parser, 'format_usage', None): synopsis = self._parser.format_usage() else: synopsis = self._parser.get_usage() if synopsis: synopsis = synopsis.replace('%s ' % prog, '') ret.append('.SH SYNOPSIS\n.B %s\n%s\n' % (self._markup(prog), synopsis)) long_desc = self.values.get("long_description", "") if long_desc: ret.append('.SH DESCRIPTION\n%s\n' % self._markup(long_desc)) return ''.join(ret) def _write_options(self, action_name=None, parser=None): if not parser: parser = self._parser if not action_name: ret = ['.SH OPTIONS\n'] else: ret = ['.SH OPTIONS ' + action_name.upper() + '\n'] ret.append(parser.format_option_help()) if self._type != 'argparse': return ''.join(ret) subparsers_actions = [ action for action in parser._actions if isinstance(action, argparse._SubParsersAction)] for subparser_action in subparsers_actions: for name, obj in subparser_action.choices.items(): if action_name: an = action_name + " " + name else: an = name ret.append(self._write_options(an, obj)) return ''.join(ret) def _write_seealso(self, text): ret = [] ret.append('.SH "SEE ALSO"\n') for i in text: name, sect = i.split(":") if len(ret) > 1: ret.append(',\n') ret.append('.BR %s (%s)' % (name, sect)) return ''.join(ret) def write(self, filename, seealso=None): manpage = [] manpage.append(self._write_header()) manpage.append(self._write_options()) manpage.append(get_footer(self.values)) if seealso: manpage.append(self._write_seealso(seealso)) write_to_filename(''.join(manpage), filename) class build_manpage(Command): description = 'Generate man page from setup().' user_options = [ ('output=', 'O', 'output file'), ('parser=', None, 'module path to optparser (e.g. mymod:func'), ('parser-file=', None, 'file to the parser module'), ('file-and-object=', None, 'import parser object from file, e.g. "bin/blah.py:fooparser"'), ('seealso=', None, 'list of manpages to put into the SEE ALSO section (e.g. bash:1)') ] def initialize_options(self): self.output = None self.parser = None self.seealso = None self.parser_file = None self.file_and_object = None def _get_parser_from_module(self): mod_name, func_name = self.parser.split(':') if self.parser_file: # The 'modname' is entirely ignored in this case. This is a design # issue from 516ca12512979ab8e1a45f24e502a9cd1331f284. But we keep # the 'build_manpage' for compatibility reasons. return get_parser_from_file(self.parser_file, func_name, 'function') return get_parser_from_module(mod_name, func_name, 'function') def finalize_options(self): if self.output is None: raise DistutilsOptionError('\'output\' option is required') if self.parser is None and self.file_and_object is None: raise DistutilsOptionError('\'parser\' or \'file-and-object\' option is required') self.ensure_string_list('seealso') if self.file_and_object: filename, objname = self.file_and_object.split(':') self._parser = get_parser_from_file(filename, objname) else: self._parser = self._get_parser_from_module() def run(self): self.announce('Writing man page %s' % self.output) data = {} get_manpage_data_from_distribution(self.distribution, data) # the format is actually unused mpw = ManPageWriter(self._parser, data) mpw.write(self.output, seealso=self.seealso) class ManPageFormatter(optparse.HelpFormatter): def __init__(self, indent_increment=2, max_help_position=24, width=None, short_first=1): optparse.HelpFormatter.__init__(self, indent_increment, max_help_position, width, short_first) def _markup(self, txt): return txt.replace('-', '\\-') def format_usage(self, usage): return self._markup(usage) def format_heading(self, heading): if self.level == 0: return '' return '.TP\n%s\n' % self._markup(heading.upper()) def format_option(self, option): result = [] opts = self.option_strings[option] result.append('.TP\n.B %s\n' % self._markup(opts)) if option.help: help_text = '%s\n' % self._markup(self.expand_default(option)) result.append(help_text) return ''.join(result) argparse-manpage-4.5/build_manpages/build_manpages.py000066400000000000000000000145101450337362100230770ustar00rootroot00000000000000""" build_manpages command -- generate set of manual pages by the setup() command. """ import os import shutil try: import tomllib from tomllib import TOMLDecodeError except ImportError: try: import tomli as tomllib from tomli import TOMLDecodeError except ImportError: import toml as tomllib from toml import TomlDecodeError as TOMLDecodeError from argparse_manpage.compat import ConfigParser, NoSectionError from argparse_manpage.tooling import get_parser, write_to_filename from argparse_manpage.manpage import ( Manpage, MANPAGE_DATA_ATTRS, get_manpage_data_from_distribution, ) from .compat import ( build_py, Command, DistutilsOptionError, install, ) DEFAULT_CMD_NAME = 'build_manpages' def parse_manpages_spec(string): manpages_data = {} for spec in string.strip().split('\n'): manpagedata = {} output = True basename = None for option in spec.split(':'): if output: outputfile = option output = False continue oname, ovalue = option.split('=') if oname == 'function' or oname == 'object': assert(not 'objtype' in manpagedata) manpagedata['objtype'] = oname manpagedata['objname'] = ovalue elif oname == 'pyfile' or oname == 'module': assert(not 'import_type' in manpagedata) manpagedata['import_type'] = oname manpagedata['import_from'] = ovalue if oname == 'pyfile': basename = os.path.basename(ovalue) elif oname == 'format': assert(not 'format' in manpagedata) manpagedata[oname] = ovalue elif oname == 'author': manpagedata.setdefault("authors", []).append(ovalue) elif oname in MANPAGE_DATA_ATTRS and oname != "authors": assert(not oname in manpagedata) manpagedata[oname] = ovalue else: raise ValueError("Unknown manpage configuration option: {}".format(oname)) if "prog" not in manpagedata and basename: manpagedata["prog"] = basename manpages_data[outputfile] = manpagedata return manpages_data def get_pyproject_settings(): """Parse and handle errors of a toml configuration file.""" try: with open("pyproject.toml", mode="r") as fp: content = tomllib.loads(fp.read()) except TOMLDecodeError: return None try: value = content["tool"][DEFAULT_CMD_NAME]["manpages"] if isinstance(value, list): value = "\n".join(value) return str(value) except KeyError: return None class build_manpages(Command): description = 'Generate set of man pages from setup().' user_options = [ ('manpages=', 'O', 'list man pages specifications'), ] def initialize_options(self): self.manpages = None def finalize_options(self): manpages = self.manpages or get_pyproject_settings() if not manpages: raise DistutilsOptionError('\'manpages\' option is required') self.manpages_data = parse_manpages_spec(manpages) # if a value wasn't set in setup.cfg, use the value from setup.py for page, data in self.manpages_data.items(): get_manpage_data_from_distribution(self.distribution, data) def run(self): for page, data in self.manpages_data.items(): if data.get('manfile'): print ("using pre-written " + page) return print ("generating " + page) parser = get_parser(data['import_type'], data['import_from'], data['objname'], data['objtype'], data.get('prog', None)) format = data.get('format', 'pretty') if format in ('pretty', 'single-commands-section'): manpage = Manpage(parser, format=format, _data=data) write_to_filename(str(manpage), page) elif format == 'old': # TODO: drop the "old" format support, and stop depending on ManPageWriter # pylint: disable=import-outside-toplevel from .build_manpage import ManPageWriter mw = ManPageWriter(parser, data) mw.write(page) else: raise ValueError("Unknown format: {}".format(format)) def get_build_py_cmd(command=build_py): """ Override the default 'setup.py build_py' command with one that automatically generates manual pages. By default we use an overridden 'setuptools.command.build_py' (class). If your project already uses an overridden class, specify the optional 'command=YourCommandClass`. """ class _build_manpages_build_py(command): def run(self): self.run_command(DEFAULT_CMD_NAME) command.run(self) return _build_manpages_build_py def get_install_cmd(command=install): """ Override the default 'setup.py install' command with one that automatically installs the manual pages generated by `build_manpages`, see 'build_manpages.get_build_py_cmd()'. By default we use the 'setuptools.command.install' class as the base. If you already use an such an overridden class, set the optional 'command=YourCommandClass'. """ class _build_manpages_install(command): def install_manual_pages(self): """ Additional logic for installing the generated manual pages """ config = ConfigParser() config.read('setup.cfg') try: spec = config.get(DEFAULT_CMD_NAME, 'manpages') except NoSectionError: spec = get_pyproject_settings() if spec is None: raise ValueError("'manpage' configuration not found in setup.cfg or pyproject.toml") data = parse_manpages_spec(spec) mandir = os.path.join(self.install_data, 'share/man/man1') if not os.path.exists(mandir): os.makedirs(mandir) for key, _ in data.items(): print ('installing {0}'.format(key)) shutil.copy(key, mandir) def run(self): command.run(self) self.install_manual_pages() return _build_manpages_install argparse-manpage-4.5/build_manpages/compat.py000066400000000000000000000017241450337362100214130ustar00rootroot00000000000000""" Compat hacks for argparse-manpage's build_manpages module """ # pylint: disable=unused-import,deprecated-module,raise-missing-from try: from setuptools import Command from setuptools.command.build_py import build_py from setuptools.command.install import install except ImportError: try: from distutils.core import Command from distutils.command.build_py import build_py from distutils.command.install import install except ImportError as orig_err: raise ImportError( "To use the 'build_manpages' tool on Python 3.12+, " "you need to install 'setuptools'." ) # A separate try-except block from the one above. This is more likely to fail # than the above setuptools part and we would start using distutils just # beacause of the exception. try: from setuptools.errors import OptionError as DistutilsOptionError except ImportError: from distutils.errors import DistutilsOptionError argparse-manpage-4.5/build_manpages/manpage.py000066400000000000000000000002351450337362100215340ustar00rootroot00000000000000""" Compat only module. Users should use argparse_manpage.manpage:Manpage """ # pylint: disable=unused-import from argparse_manpage.manpage import Manpage argparse-manpage-4.5/check000077500000000000000000000001751450337362100156060ustar00rootroot00000000000000#! /bin/bash -x for python in ${PYTHON-python3}; do PYTHONPATH=$(pwd) ${python/-dev/} -m pytest -vv "$@" || exit 1 done argparse-manpage-4.5/examples/000077500000000000000000000000001450337362100164165ustar00rootroot00000000000000argparse-manpage-4.5/examples/argument_groups/000077500000000000000000000000001450337362100216375ustar00rootroot00000000000000argparse-manpage-4.5/examples/argument_groups/bin/000077500000000000000000000000001450337362100224075ustar00rootroot00000000000000argparse-manpage-4.5/examples/argument_groups/bin/test000077500000000000000000000043121450337362100233140ustar00rootroot00000000000000#!/bin/python from argparse import ArgumentParser parser = ArgumentParser() parser.man_short_description = "templating system/generator for distributions" group1 = parser.add_argument_group("group1 title", "description for group 1") group2 = parser.add_argument_group() group1.add_argument( '--group-1-option', metavar='PROJECTDIR', type=str, help='Directory with project (defaults to CWD)', default="." ) group2.add_argument( '--group-2-option', metavar='DIST', type=str, help='Use distribution metadata specified by DIST yaml file', default="fedora-21-x86_64.yaml", ) group2.add_argument( "g2arg", help=( "Some longer multiline description should go here, " "and here and here. If you want, even here." ), ) parser.add_argument( '--top-option', metavar='MULTISPEC', type=str, help='Use MULTISPEC yaml file to fill the TEMPLATE file', ) subparsers = parser.add_subparsers( title="subparsers", description="subparsers description", help="subparsers help") parser_a = subparsers.add_parser('subparserA', help='a help') group = parser_a.add_argument_group( "subgroup", description=( "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Mauris " "tincidunt sem sed arcu. Etiam dictum tincidunt diam. Duis sapien " "nunc, commodo et, interdum suscipit, sollicitudin et, dolor. " )) group.add_argument("--subgroup-option") parser_a.add_argument("--sub-parser-option") subsubparsers = parser_a.add_subparsers( title="sub-subparsers title", help="Some help text for sub-subparser", ) subsubparser_a = subsubparsers.add_parser('sub-subparserA', help='a help') subsubparser_a.add_argument("--doh") tpl_or_combinations = parser.add_mutually_exclusive_group(required=True) tpl_or_combinations.add_argument( '--template', metavar='TEMPLATE', type=str, help='Use TEMPLATE file, e.g. docker.tpl or a template string, ' 'e.g. "{{ config.docker.from }}"' ) tpl_or_combinations.add_argument( '--multispec-combinations', action='store_true', help='Print available multispec combinations', ) def _main(): parser.parse_args() if __name__ == "__main__": _main() argparse-manpage-4.5/examples/argument_groups/expected/000077500000000000000000000000001450337362100234405ustar00rootroot00000000000000argparse-manpage-4.5/examples/argument_groups/expected/test.1000066400000000000000000000041151450337362100245020ustar00rootroot00000000000000.TH TEST "2" "2023\-04\-09" "example 0.1.0" "Test Manual" .SH NAME test \- templating system/generator for distributions .SH SYNOPSIS .B test [-h] [--group-1-option PROJECTDIR] [--group-2-option DIST] [--top-option MULTISPEC] (--template TEMPLATE | --multispec-combinations) g2arg {subparserA} ... .SH OPTIONS .TP \fB\-\-top\-option\fR \fI\,MULTISPEC\/\fR Use MULTISPEC yaml file to fill the TEMPLATE file .TP \fB\-\-template\fR \fI\,TEMPLATE\/\fR Use TEMPLATE file, e.g. docker.tpl or a template string, e.g. "{{ config.docker.from }}" .TP \fB\-\-multispec\-combinations\fR Print available multispec combinations .SH GROUP1 TITLE description for group 1 .TP \fB\-\-group\-1\-option\fR \fI\,PROJECTDIR\/\fR Directory with project (defaults to CWD) .TP \fB\-\-group\-2\-option\fR \fI\,DIST\/\fR Use distribution metadata specified by DIST yaml file .TP \fBg2arg\fR Some longer multiline description should go here, and here and here. If you want, even here. .SH SUBPARSERS .TP \fBtest\fR \fI\,subparserA\/\fR a help .SH COMMAND \fI\,'test subparserA'\/\fR usage: test g2arg subparserA [\-h] [\-\-subgroup\-option SUBGROUP_OPTION] [\-\-sub\-parser\-option SUB_PARSER_OPTION] {sub\-subparserA} ... .SH OPTIONS \fI\,'test subparserA'\/\fR .TP \fB\-\-sub\-parser\-option\fR \fI\,SUB_PARSER_OPTION\/\fR .SH SUBGROUP \fI\,'test subparserA'\/\fR Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Mauris tincidunt sem sed arcu. Etiam dictum tincidunt diam. Duis sapien nunc, commodo et, interdum suscipit, sollicitudin et, dolor. .TP \fB\-\-subgroup\-option\fR \fI\,SUBGROUP_OPTION\/\fR .SH SUB-SUBPARSERS TITLE \fI\,'test subparserA'\/\fR .TP \fBtest subparserA\fR \fI\,sub\-subparserA\/\fR a help .SH COMMAND \fI\,'test subparserA sub\-subparserA'\/\fR usage: test g2arg subparserA sub\-subparserA [\-h] [\-\-doh DOH] .SH OPTIONS \fI\,'test subparserA sub\-subparserA'\/\fR .TP \fB\-\-doh\fR \fI\,DOH\/\fR .SH AUTHOR .nf John Doe .fi .SH DISTRIBUTION The latest version of example may be downloaded from .UR http://example.com .UE argparse-manpage-4.5/examples/argument_groups/setup.cfg000066400000000000000000000001631450337362100234600ustar00rootroot00000000000000[build_manpages] manpages = man/test.1:object=parser:pyfile=bin/test:manual_section=2:manual_title=Test Manual argparse-manpage-4.5/examples/argument_groups/setup.py000066400000000000000000000017301450337362100233520ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Example of argparse taken from: # https://pagure.io/copr/copr/blob/a4feb01bc35b8554f503d41795e7a184ff929dd4/f/cli/copr_cli import os import sys from setuptools import setup, find_packages # Just to make sure that build_manpage can be found. sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../..')) from build_manpages.build_manpages \ import build_manpages, get_build_py_cmd, get_install_cmd from setuptools.command.install import install from distutils.command.build import build setup( name='example', description='This project does nothing.', long_description=('Long description of the project.'), author='John Doe', author_email='jd@example.com', version='0.1.0', url='http://example.com', packages=find_packages(), scripts=['bin/test'], cmdclass={ 'build_manpages': build_manpages, 'build': get_build_py_cmd(build), 'install': get_install_cmd(install), }, ) argparse-manpage-4.5/examples/copr/000077500000000000000000000000001450337362100173615ustar00rootroot00000000000000argparse-manpage-4.5/examples/copr/.gitignore000066400000000000000000000000061450337362100213450ustar00rootroot00000000000000build argparse-manpage-4.5/examples/copr/copr_cli/000077500000000000000000000000001450337362100211535ustar00rootroot00000000000000argparse-manpage-4.5/examples/copr/copr_cli/__init__.py000066400000000000000000000000001450337362100232520ustar00rootroot00000000000000argparse-manpage-4.5/examples/copr/copr_cli/build_config.py000066400000000000000000000020531450337362100241510ustar00rootroot00000000000000# coding: utf-8 from jinja2 import Environment template_string = """\ # This is development/testing only mock profile, not exactly the same as # is used on copr builders; but it is basically similar. If you need an # exact mock configuration (because you e.g. try to reproduce failed # build), such configuration is put alongside the built RPMs. include('/etc/mock/{{chroot}}.cfg') config_opts['root'] = '{{project_id}}_{{chroot}}' config_opts['chroot_additional_packages'] = ' {%- for pkg in additional_packages -%} {%- if loop.last -%} {{ pkg }} {%- else -%} {{ pkg }} {% endif -%} {%- endfor -%}' {% if repos %} config_opts['yum.conf'] += \"\"\" {% for repo in repos %} [{{ repo.id }}] name="{{ repo.name }}" baseurl={{ repo.url }} gpgcheck=0 enabled=1 skip_if_unavailable=1 metadata_expire=0 cost=1 best=1 {% endfor %} \"\"\" {% endif %} """ class MockProfile(object): def __init__(self, data): self.data = data def __str__(self): template = Environment().from_string(template_string) return template.render(self.data) argparse-manpage-4.5/examples/copr/copr_cli/main.py000066400000000000000000001401221450337362100224510ustar00rootroot00000000000000#!/usr/bin/python3 # -*- coding: UTF-8 -*- import os import re import subprocess import argparse import sys import datetime import time import simplejson from collections import defaultdict import logging try: from urllib.parse import urlparse except ImportError: from urlparse import urlparse if sys.version_info < (2, 7): class NullHandler(logging.Handler): def emit(self, record): pass else: from logging import NullHandler log = logging.getLogger(__name__) log.addHandler(NullHandler()) from copr import CoprClient from copr.client.responses import CoprResponse import copr.exceptions as copr_exceptions from .util import ProgressBar from .build_config import MockProfile import pkg_resources ON_OFF_MAP = { 'on': True, 'off': False, None: None, } no_config_warning = """ ================= WARNING: ======================= File '{0}' is missing or incorrect. See documentation: man copr-cli. Any operation requiring credentials will fail! ================================================== """ class Commands(object): def __init__(self, config): self.config = config try: self.client = CoprClient.create_from_file_config(config) except (copr_exceptions.CoprNoConfException, copr_exceptions.CoprConfigException): sys.stderr.write(no_config_warning.format(config or "~/.config/copr")) self.client = CoprClient( copr_url=u"http://copr.fedoraproject.org", no_config=True ) def requires_api_auth(func): """ Decorator that checks config presence """ def wrapper(self, args): if self.client.no_config: sys.stderr.write("Error: Operation requires api authentication\n") sys.exit(6) return func(self, args) wrapper.__doc__ = func.__doc__ wrapper.__name__ = func.__name__ return wrapper def check_username_presence(func): """ Decorator that checks if username was provided """ def wrapper(self, args): if self.client.no_config and args.username is None: sys.stderr.write( "Error: Operation requires username\n" "Pass username to command or create `~/.config/copr`\n") sys.exit(6) if args.username is None and self.client.username is None: sys.stderr.write( "Error: Operation requires username\n" "Pass username to command or add it to `~/.config/copr`\n") sys.exit(6) return func(self, args) wrapper.__doc__ = func.__doc__ wrapper.__name__ = func.__name__ return wrapper def _watch_builds(self, build_ids): """ :param build_ids: list of build IDs """ print("Watching build(s): (this may be safely interrupted)") prevstatus = defaultdict(lambda: None) failed_ids = [] watched = set(build_ids) done = set() try: while watched != done: for build_id in watched: if build_id in done: continue build_details = self.client.get_build_details(build_id) if build_details.output != "ok": errmsg = " Build {1}: Unable to get build status: {0}". \ format(build_details.error, build_id) raise copr_exceptions.CoprRequestException(errmsg) now = datetime.datetime.now() if prevstatus[build_id] != build_details.status: prevstatus[build_id] = build_details.status print(" {0} Build {2}: {1}".format( now.strftime("%H:%M:%S"), build_details.status, build_id)) if build_details.status in ["failed"]: failed_ids.append(build_id) if build_details.status in ["succeeded", "skipped", "failed", "canceled"]: done.add(build_id) if build_details.status == "unknown": raise copr_exceptions.CoprBuildException( "Unknown status.") if watched == done: break time.sleep(30) if failed_ids: raise copr_exceptions.CoprBuildException( "Build(s) {0} failed.".format( ", ".join(str(x) for x in failed_ids))) except KeyboardInterrupt: pass def action_whoami(self, args): """ Simply print out the current user as defined in copr config. """ print(self.client.username) @requires_api_auth def action_build(self, args): """ Method called when the 'build' action has been selected by the user. :param args: argparse arguments provided by the user """ self.client.authentication_check() bar = None progress_callback = None builds = [] for pkg in args.pkgs: if os.path.exists(pkg): bar = ProgressBar(max=os.path.getsize(pkg)) # pylint: disable=function-redefined def progress_callback(monitor): bar.next(n=8192) print('Uploading package {0}'.format(pkg)) data = { "pkgs": [pkg], "progress_callback": progress_callback, } builds.append(self.process_build(args, self.client.create_new_build, data, bar=bar)) return builds @requires_api_auth def action_build_pypi(self, args): """ Method called when the 'buildpypi' action has been selected by the user. :param args: argparse arguments provided by the user """ username, copr = parse_name(args.copr) data = { "pypi_package_name": args.packagename, "pypi_package_version": args.packageversion, "python_versions": args.pythonversions, } return self.process_build(args, self.client.create_new_build_pypi, data) @requires_api_auth def action_build_tito(self, args): """ Method called when the 'buildtito' action has been selected by the user. :param args: argparse arguments provided by the user """ data = { "git_url": args.git_url, "git_dir": args.git_dir, "git_branch": args.git_branch, "tito_test": args.tito_test, } return self.process_build(args, self.client.create_new_build_tito, data) @requires_api_auth def action_build_mock(self, args): """ Method called when the 'build-mock' action has been selected by the user. :param args: argparse arguments provided by the user """ data = { "scm_type": args.scm_type, "scm_url": args.scm_url, "scm_branch": args.scm_branch, "spec": args.spec, } return self.process_build(args, self.client.create_new_build_mock, data) @requires_api_auth def action_build_rubygems(self, args): """ Method called when the 'buildgem' action has been selected by the user. :param args: argparse arguments provided by the user """ data = {"gem_name": args.gem_name} return self.process_build(args, self.client.create_new_build_rubygems, data) @requires_api_auth def action_build_distgit(self, args): """ Method called when the 'buildfedpkg' action has been selected by the user. :param args: argparse arguments provided by the user """ data = {"clone_url": args.clone_url, "branch": args.branch} return self.process_build(args, self.client.create_new_build_distgit, data) def process_build(self, args, build_function, data, bar=None): username, copr = parse_name(args.copr) result = build_function(username=username, projectname=copr, chroots=args.chroots, memory=args.memory, timeout=args.timeout, background=args.background, **data) if bar: bar.finish() if result.output != "ok": sys.stderr.write(result.error + "\n") return print(result.message) build_ids = [bw.build_id for bw in result.builds_list] print("Created builds: {0}".format(" ".join(map(str, build_ids)))) if not args.nowait: self._watch_builds(build_ids) @requires_api_auth def action_create(self, args): """ Method called when the 'create' action has been selected by the user. :param args: argparse arguments provided by the user """ username, copr = parse_name(args.name) result = self.client.create_project( username=username, projectname=copr, description=args.description, instructions=args.instructions, chroots=args.chroots, repos=args.repos, initial_pkgs=args.initial_pkgs, disable_createrepo=args.disable_createrepo, unlisted_on_hp=ON_OFF_MAP[args.unlisted_on_hp], enable_net=ON_OFF_MAP[args.enable_net], persistent=args.persistent, auto_prune=ON_OFF_MAP[args.auto_prune] ) print(result.message) @requires_api_auth def action_modify_project(self, args): """ Method called when the 'modify' action has been selected by the user. :param args: argparse arguments provided by the user """ username, copr = parse_name(args.name) result = self.client.modify_project( username=username, projectname=copr, description=args.description, instructions=args.instructions, repos=args.repos, disable_createrepo=args.disable_createrepo, unlisted_on_hp=ON_OFF_MAP[args.unlisted_on_hp], enable_net=ON_OFF_MAP[args.enable_net], auto_prune=ON_OFF_MAP[args.auto_prune], chroots=args.chroots, ) @requires_api_auth def action_delete(self, args): """ Method called when the 'delete' action has been selected by the user. :param args: argparse arguments provided by the user """ username, copr = parse_name(args.copr) result = self.client.delete_project(username=username, projectname=copr) print(result.message) @requires_api_auth def action_fork(self, args): """ Method called when the 'fork' action has been selected by the user. :param args: argparse arguments provided by the user """ username, copr = parse_name(args.dst) result = self.client.fork_project(source=args.src, username=username, projectname=copr, confirm=args.confirm) print(result.message) def action_mock_config(self, args): """ Method called when the 'list' action has been selected by the user. :param args: argparse arguments provided by the user """ username = self.client.username project = args.project.split("/") if len(project) != 2: args.project = username + "/" + args.project result = self.client.get_build_config(args.project, args.chroot) if result.output != "ok": sys.stderr.write(result.error + "\n") sys.stderr.write("Un-expected data returned, please report this issue\n") print(MockProfile(result.build_config)) @check_username_presence def action_list(self, args): """ Method called when the 'list' action has been selected by the user. :param args: argparse arguments provided by the user """ username = args.username or self.client.username result = self.client.get_projects_list(username) # import ipdb; ipdb.set_trace() if result.output != "ok": sys.stderr.write(result.error + "\n") sys.stderr.write("Un-expected data returned, please report this issue\n") elif not result.projects_list: sys.stderr.write("No copr retrieved for user: '{0}'\n".format(username)) return for prj in result.projects_list: print(prj) def action_status(self, args): result = self.client.get_build_details(args.build_id) print(result.status) def action_download_build(self, args): result = self.client.get_build_details(args.build_id) base_len = len(os.path.split(result.results)) for chroot, url in result.results_by_chroot.items(): if args.chroots and chroot not in args.chroots: continue cmd = "wget -r -nH --no-parent --reject 'index.html*'".split(' ') cmd.extend(['-P', os.path.join(args.dest, chroot)]) cmd.extend(['--cut-dirs', str(base_len + 4)]) cmd.append(url) subprocess.call(cmd) @requires_api_auth def action_cancel(self, args): """ Method called when the 'cancel' action has been selected by the user. :param args: argparse arguments provided by the user """ result = self.client.cancel_build(args.build_id) print(result.status) def action_watch_build(self, args): self._watch_builds(args.build_id) def action_delete_build(self, args): result = self.client.delete_build(args.build_id) print(result.status) ######################################################### ### Chroot actions ### ######################################################### @requires_api_auth def action_edit_chroot(self, args): """ Method called when the 'edit-chroot' action has been selected by the user. :param args: argparse arguments provided by the user """ owner, copr, chroot = parse_chroot_path(args.coprchroot) result = self.client.edit_chroot( ownername=owner, projectname=copr, chrootname=chroot, upload_comps=args.upload_comps, delete_comps=args.delete_comps, packages=args.packages, repos=args.repos ) print(result.message) def action_get_chroot(self, args): """ Method called when the 'get-chroot' action has been selected by the user. :param args: argparse arguments provided by the user """ owner, copr, chroot = parse_chroot_path(args.coprchroot) result = self.client.get_chroot( ownername=owner, projectname=copr, chrootname=chroot ) print(simplejson.dumps(result.chroot, indent=4, sort_keys=True, for_json=True)) ######################################################### ### Package actions ### ######################################################### @requires_api_auth def action_add_or_edit_package_tito(self, args): ownername, projectname = parse_name(args.copr) data = { "package_name": args.name, "git_url": args.git_url, "git_dir": args.git_dir, "git_branch": args.git_branch, "tito_test": ON_OFF_MAP[args.tito_test], "webhook_rebuild": ON_OFF_MAP[args.webhook_rebuild], } if args.create: result = self.client.add_package_tito(ownername=ownername, projectname=projectname, **data) else: result = self.client.edit_package_tito(ownername=ownername, projectname=projectname, **data) print(result.message) @requires_api_auth def action_add_or_edit_package_pypi(self, args): ownername, projectname = parse_name(args.copr) data = { "package_name": args.name, "pypi_package_name": args.packagename, "pypi_package_version": args.packageversion, "python_versions": args.pythonversions, "webhook_rebuild": ON_OFF_MAP[args.webhook_rebuild], } if args.create: result = self.client.add_package_pypi(ownername=ownername, projectname=projectname, **data) else: result = self.client.edit_package_pypi(ownername=ownername, projectname=projectname, **data) print(result.message) @requires_api_auth def action_add_or_edit_package_mockscm(self, args): ownername, projectname = parse_name(args.copr) data = { "package_name": args.name, "scm_type": args.scm_type, "scm_url": args.scm_url, "scm_branch": args.scm_branch, "spec": args.spec, "webhook_rebuild": ON_OFF_MAP[args.webhook_rebuild], } if args.create: result = self.client.add_package_mockscm(ownername=ownername, projectname=projectname, **data) else: result = self.client.edit_package_mockscm(ownername=ownername, projectname=projectname, **data) print(result.message) @requires_api_auth def action_add_or_edit_package_rubygems(self, args): ownername, projectname = parse_name(args.copr) data = { "package_name": args.name, "gem_name": args.gem_name, "webhook_rebuild": ON_OFF_MAP[args.webhook_rebuild], } if args.create: result = self.client.add_package_rubygems(ownername=ownername, projectname=projectname, **data) else: result = self.client.edit_package_rubygems(ownername=ownername, projectname=projectname, **data) print(result.message) def action_list_packages(self, args): ownername, projectname = parse_name(args.copr) data = { "with_latest_build": args.with_latest_build, "with_latest_succeeded_build": args.with_latest_succeeded_build, "with_all_builds": args.with_all_builds, } result = self.client.get_packages_list(ownername=ownername, projectname=projectname, **data) print(simplejson.dumps(result.packages_list, indent=4, sort_keys=True, for_json=True)) def action_list_package_names(self, args): ownername, projectname = parse_name(args.copr) result = self.client.get_packages_list(ownername=ownername, projectname=projectname) for package in result.packages_list: print(package.name) def action_get_package(self, args): ownername, projectname = parse_name(args.copr) data = { "pkg_name": args.name, "with_latest_build": args.with_latest_build, "with_latest_succeeded_build": args.with_latest_succeeded_build, "with_all_builds": args.with_all_builds, } result = self.client.get_package(ownername=ownername, projectname=projectname, **data) print(simplejson.dumps(result.package, indent=4, sort_keys=True, for_json=True)) def action_delete_package(self, args): ownername, projectname = parse_name(args.copr) data = { "pkg_name": args.name } result = self.client.delete_package(ownername=ownername, projectname=projectname, **data) print(result.message) def action_reset_package(self, args): ownername, projectname = parse_name(args.copr) data = { "pkg_name": args.name } result = self.client.reset_package(ownername=ownername, projectname=projectname, **data) print(result.message) def action_build_package(self, args): ownername, projectname = parse_name(args.copr) data = { "pkg_name": args.name, "chroots": args.chroots, #"memory": args.memory, "timeout": args.timeout } result = self.client.build_package(ownername=ownername, projectname=projectname, **data) if result.output != "ok": sys.stderr.write(result.error + "\n") return print(result.message) build_ids = [bw.build_id for bw in result.builds_list] print("Created builds: {0}".format(" ".join(map(str, build_ids)))) if not args.nowait: self._watch_builds(build_ids) def action_build_module(self, args): """ Build module via Copr MBS """ ownername, projectname = parse_name(args.copr or "") modulemd = open(args.yaml, "rb") if args.yaml else args.url response = self.client.build_module(modulemd, ownername, projectname) print(response.message if response.output == "ok" else response.error) def version(): if 'BUILD_MANPAGES_RUNNING' in os.environ: # pkg_resources magic doesn't work during 'setup.py build', but it # doesn't matter as the version in the manual page is read directly from # setuptools. return 'fake' return pkg_resources.require('copr-cli')[0].version def setup_parser(): """ Set the main arguments. """ ######################################################### ### General options ### ######################################################### parser = argparse.ArgumentParser(prog="copr", epilog="dummy text") # General connection options parser.add_argument("--debug", dest="debug", action="store_true", help="Enable debug output") parser.add_argument("--config", dest="config", help="Path to an alternative configuration file") parser.add_argument("--version", action="version", version="%(prog)s version " + version()) subparsers = parser.add_subparsers(title="actions") ######################################################### ### Project options ### ######################################################### # TEST: add a subcommand that should not be rendered in the output parser_whoami = subparsers.add_parser( "hidden", help=argparse.SUPPRESS, ) parser_whoami = subparsers.add_parser( "whoami", help="Print username that the client authenticates with against copr-frontend" ) parser_whoami.set_defaults(func="action_whoami") # create the parser for the "list" command parser_list = subparsers.add_parser( "list", help="List all the copr of the " "provided " ) parser_list.add_argument( "username", metavar="username|@groupname", nargs="?", help="The username or @groupname that you would like to " "list the coprs of (defaults to current user)" ) parser_list.set_defaults(func="action_list") parser_mock_config = subparsers.add_parser( "mock-config", help="Get the mock profile (similar to koji mock-config)" ) parser_mock_config.add_argument( "project", help="Expected format is /, / (including '@') or (name of project you own)." ) parser_mock_config.add_argument( "chroot", help="chroot id, e.g. 'fedora-rawhide-x86_64'" ) parser_mock_config.set_defaults(func="action_mock_config") # create the parser for the "create" command parser_create = subparsers.add_parser("create", help="Create a new copr") parser_create.add_argument("name", help="The name of the copr to create") parser_create.add_argument("--chroot", dest="chroots", action="append", help="Chroot to use for this copr") parser_create.add_argument("--repo", dest="repos", action="append", help="Repository to add to this copr") parser_create.add_argument("--initial-pkgs", dest="initial_pkgs", action="append", help="List of packages URL to build in this " "new copr") parser_create.add_argument("--description", help="Description of the copr") parser_create.add_argument("--instructions", help="Instructions for the copr") parser_create.add_argument("--disable_createrepo", help="Disable metadata auto generation") parser_create.add_argument("--enable-net", choices=["on", "off"], default="off", help="If net should be enabled for builds in this project (default is off)") parser_create.add_argument("--unlisted-on-hp", choices=["on", "off"], help="The project will not be shown on COPR home page") parser_create.add_argument("--persistent", action="store_true", help="Project and its builds will be undeletable. This option can only be specified by a COPR admin.") parser_create.add_argument("--auto-prune", choices=["on", "off"], default="on", help="If auto-deletion of project's obsoleted builds should be enabled (default is on).\ This option can only be specified by a COPR admin.") parser_create.set_defaults(func="action_create") # create the parser for the "modify_project" command parser_modify = subparsers.add_parser("modify", help="Modify existing copr") parser_modify.add_argument("name", help="The name of the copr to modify") parser_modify.add_argument("--chroot", dest="chroots", action="append", help="Chroot to use for this copr") parser_modify.add_argument("--description", help="Description of the copr") parser_modify.add_argument("--instructions", help="Instructions for the copr") parser_modify.add_argument("--repo", dest="repos", action="append", help="Repository to add to this copr") parser_modify.add_argument("--disable_createrepo", help="Disable metadata auto generation") parser_modify.add_argument("--enable-net", choices=["on", "off"], help="If net should be enabled for builds in this project (default is \"don't change\")") parser_modify.add_argument("--unlisted-on-hp", choices=["on", "off"], help="The project will not be shown on COPR home page") parser_modify.add_argument("--auto-prune", choices=["on", "off"], help="If auto-deletion of project's obsoleted builds should be enabled.\ This option can only be specified by a COPR admin.") parser_modify.set_defaults(func="action_modify_project") # create the parser for the "delete" command parser_delete = subparsers.add_parser("delete", help="Deletes the entire project") parser_delete.add_argument("copr", help="Name of your project to be deleted.") parser_delete.set_defaults(func="action_delete") # create the parser for the "fork" command parser_delete = subparsers.add_parser("fork", help="Fork the project and builds in it") parser_delete.add_argument("src", help="Which project should be forked") parser_delete.add_argument("dst", help="Name of the new project") parser_delete.add_argument("--confirm", action="store_true", help="Confirm forking into existing project") parser_delete.set_defaults(func="action_fork") ######################################################### ### Source-type related options ### ######################################################### parser_tito_args_parent = argparse.ArgumentParser(add_help=False) parser_tito_args_parent.add_argument("--git-url", metavar="URL", dest="git_url", required=True, help="URL to a project managed by Tito") parser_tito_args_parent.add_argument("--git-dir", metavar="DIRECTORY", dest="git_dir", help="Relative path from Git root to directory containing .spec file") parser_tito_args_parent.add_argument("--git-branch", metavar="BRANCH", dest="git_branch", help="Git branch that you want to build from") parser_tito_args_parent.add_argument("--test", dest="tito_test", choices=["on", "off"], help="Build the last commit instead of the last release tag") parser_pypi_args_parent = argparse.ArgumentParser(add_help=False) parser_pypi_args_parent.add_argument("--pythonversions", nargs="*", type=int, metavar="VERSION", default=[3, 2], help="For what Python versions to build (by default: 3 2)") parser_pypi_args_parent.add_argument("--packageversion", metavar = "PYPIVERSION", help="Version of the PyPI package to be built (by default latest)") parser_pypi_args_parent.add_argument("--packagename", required=True, metavar="PYPINAME", help="Name of the PyPI package to be built, required.") parser_mockscm_args_parent = argparse.ArgumentParser(add_help=False) parser_mockscm_args_parent.add_argument("--scm-type", metavar="TYPE", dest="scm_type", choices=["git", "svn"], default="git", help="specify versioning tool, default is 'git'") parser_mockscm_args_parent.add_argument("--scm-url", metavar="URL", dest="scm_url", help="url to a project versioned by Git or SVN, required") parser_mockscm_args_parent.add_argument("--scm-branch", metavar="BRANCH", dest="scm_branch", help="") parser_mockscm_args_parent.add_argument("--spec", dest="spec", metavar="FILE", help="relative path from SCM root to .spec file, required") parser_rubygems_args_parent = argparse.ArgumentParser(add_help=False) parser_rubygems_args_parent.add_argument("--gem", metavar="GEM", dest="gem_name", help="Specify gem name") parser_distgit_args_parent = argparse.ArgumentParser(add_help=False) parser_distgit_args_parent.add_argument("--clone-url", metavar="URL", dest="clone_url", required=True, help="Specify clone url for the distgit repository") parser_distgit_args_parent.add_argument("--branch", metavar="BRANCH", dest="branch", help="Specify branch to be used") ######################################################### ### Build options ### ######################################################### # parent parser for the builds commands below parser_build_parent = argparse.ArgumentParser(add_help=False) parser_build_parent.add_argument("copr", help="The copr repo to build the package in. Can be just name of project or even in format username/project or @groupname/project.") parser_build_parent.add_argument("--memory", dest="memory", help="") parser_build_parent.add_argument("--timeout", dest="timeout", help="") parser_build_parent.add_argument("--nowait", action="store_true", default=False, help="Don't wait for build") parser_build_parent.add_argument("-r", "--chroot", dest="chroots", action="append", help="If you don't need this build for all the project's chroots. You can use it several times for each chroot you need.") parser_build_parent.add_argument("--background", dest="background", action="store_true", default=False, help="Mark the build as a background job. It will have lesser priority than regular builds.") # create the parser for the "build" (url/upload) command parser_build = subparsers.add_parser("build", parents=[parser_build_parent], help="Build packages to a specified copr") parser_build.add_argument("pkgs", nargs="+", help="filename of SRPM or URL of packages to build") parser_build.set_defaults(func="action_build") # create the parser for the "buildpypi" command parser_build_pypi = subparsers.add_parser("buildpypi", parents=[parser_pypi_args_parent, parser_build_parent], help="Build PyPI package to a specified copr") parser_build_pypi.set_defaults(func="action_build_pypi") # create the parser for the "buildgem" command parser_build_rubygems = subparsers.add_parser("buildgem", parents=[parser_rubygems_args_parent, parser_build_parent], help="Build gem from rubygems.org to a specified copr") parser_build_rubygems.set_defaults(func="action_build_rubygems") # create the parser for the "buildfedpkg" command parser_build_distgit = subparsers.add_parser("buildfedpkg", parents=[parser_distgit_args_parent, parser_build_parent], help="Build package from pkgs.fedoraproject.org") parser_build_distgit.set_defaults(func="action_build_distgit") # create the parser for the "buildtito" command parser_build_tito = subparsers.add_parser("buildtito", parents=[parser_tito_args_parent, parser_build_parent], help="submit a build from Git repository via Tito to a specified copr") parser_build_tito.set_defaults(func="action_build_tito") # create the parser for the "buildmock" command parser_build_mock = subparsers.add_parser("buildmock", parents=[parser_mockscm_args_parent, parser_build_parent], help="submit a build from SCM repository via Mock to a specified copr") parser_build_mock.set_defaults(func="action_build_mock") # create the parser for the "status" command parser_status = subparsers.add_parser("status", help="Get build status of build specified by its ID") parser_status.add_argument("build_id", help="Build ID", type=int) parser_status.set_defaults(func="action_status") # create the parser for the "download-build" command parser_download_build = subparsers.add_parser("download-build", help="Fetches built packages") parser_download_build.add_argument("build_id", help="Build ID") parser_download_build.add_argument("-r", "--chroot", dest="chroots", action="append", help="Select chroots to fetch") parser_download_build.add_argument("--dest", "-d", dest="dest", help="Base directory to store packages", default=".") parser_download_build.set_defaults(func="action_download_build") # create the parser for the "cancel" command parser_cancel = subparsers.add_parser("cancel", help="Cancel build specified by its ID") parser_cancel.add_argument("build_id", help="Build ID") parser_cancel.set_defaults(func="action_cancel") # create the parser for the "watch-build" command parser_watch = subparsers.add_parser("watch-build", help="Watch status and progress of build(s)" " specified by their ID") parser_watch.add_argument("build_id", nargs="+", help="Build ID", type=int) parser_watch.set_defaults(func="action_watch_build") # create the parser for the "delete-build" command parser_delete = subparsers.add_parser("delete-build", help="Delete build specified by its ID") parser_delete.add_argument("build_id", help="Build ID", type=int) parser_delete.set_defaults(func="action_delete_build") ######################################################### ### Chroot options ### ######################################################### parser_edit_chroot = subparsers.add_parser("edit-chroot", help="Edit chroot of a project") parser_edit_chroot.add_argument("coprchroot", help="Path to a project chroot as owner/project/chroot or project/chroot") parser_edit_chroot_comps_group = parser_edit_chroot.add_mutually_exclusive_group() parser_edit_chroot_comps_group.add_argument("--upload-comps", metavar="FILEPATH", help="filepath to the comps.xml file to be uploaded") parser_edit_chroot_comps_group.add_argument("--delete-comps", action="store_true", help="deletes already existing comps.xml for the chroot") parser_edit_chroot.add_argument("--packages", help="space separated string of package names to be added to buildroot") parser_edit_chroot.add_argument("--repos", help="space separated string of additional repo urls for chroot") parser_edit_chroot.set_defaults(func="action_edit_chroot") parser_get_chroot = subparsers.add_parser("get-chroot", help="Get chroot of a project") parser_get_chroot.add_argument("coprchroot", help="Path to a project chroot as owner/project/chroot or project/chroot") parser_get_chroot.set_defaults(func="action_get_chroot") ######################################################### ### Package options ### ######################################################### # package edit/create parent parser_add_or_edit_package_parent = argparse.ArgumentParser(add_help=False) parser_add_or_edit_package_parent.add_argument("--name", help="Name of the package to be edited or created", metavar="PKGNAME", required=True) parser_add_or_edit_package_parent.add_argument("copr", help="The copr repo for the package. Can be just name of project or even in format username/project or @groupname/project.") parser_add_or_edit_package_parent.add_argument("--webhook-rebuild", choices=["on", "off"], help="Enable auto-rebuilding.") # Tito edit/create parser_add_package_tito = subparsers.add_parser("add-package-tito", help="Creates a new Tito package", parents=[parser_tito_args_parent, parser_add_or_edit_package_parent]) parser_add_package_tito.set_defaults(func="action_add_or_edit_package_tito", create=True) parser_edit_package_tito = subparsers.add_parser("edit-package-tito", help="Edits an existing Tito package", parents=[parser_tito_args_parent, parser_add_or_edit_package_parent]) parser_edit_package_tito.set_defaults(func="action_add_or_edit_package_tito", create=False) # PyPI edit/create parser_add_package_pypi = subparsers.add_parser("add-package-pypi", help="Creates a new PyPI package", parents=[parser_pypi_args_parent, parser_add_or_edit_package_parent]) parser_add_package_pypi.set_defaults(func="action_add_or_edit_package_pypi", create=True) parser_edit_package_pypi = subparsers.add_parser("edit-package-pypi", help="Edits an existing PyPI package", parents=[parser_pypi_args_parent, parser_add_or_edit_package_parent]) parser_edit_package_pypi.set_defaults(func="action_add_or_edit_package_pypi", create=False) # MockSCM edit/create parser_add_package_mockscm = subparsers.add_parser("add-package-mockscm", help="Creates a new Mock-SCM package", parents=[parser_mockscm_args_parent, parser_add_or_edit_package_parent]) parser_add_package_mockscm.set_defaults(func="action_add_or_edit_package_mockscm", create=True) parser_edit_package_mockscm = subparsers.add_parser("edit-package-mockscm", help="Edits an existing Mock-SCM package", parents=[parser_mockscm_args_parent, parser_add_or_edit_package_parent]) parser_edit_package_mockscm.set_defaults(func="action_add_or_edit_package_mockscm", create=False) # Rubygems edit/create parser_add_package_rubygems = subparsers.add_parser("add-package-rubygems", help="Creates a new RubyGems package", parents=[parser_rubygems_args_parent, parser_add_or_edit_package_parent]) parser_add_package_rubygems.set_defaults(func="action_add_or_edit_package_rubygems", create=True) parser_edit_package_rubygems = subparsers.add_parser("edit-package-rubygems", help="Edits a new RubyGems package", parents=[parser_rubygems_args_parent, parser_add_or_edit_package_parent]) parser_edit_package_rubygems.set_defaults(func="action_add_or_edit_package_rubygems", create=False) # package listing parser_list_packages = subparsers.add_parser("list-packages", help="Returns list of packages in the given copr") parser_list_packages.add_argument("copr", help="The copr repo to list the packages of. Can be just name of project or even in format owner/project.") parser_list_packages.add_argument("--with-latest-build", action="store_true", help="Also display data related to the latest build for the package.") parser_list_packages.add_argument("--with-latest-succeeded-build", action="store_true", help="Also display data related to the latest succeeded build for the package.") parser_list_packages.add_argument("--with-all-builds", action="store_true", help="Also display data related to the builds for the package.") parser_list_packages.set_defaults(func="action_list_packages") # package names listing parser_list_package_names = subparsers.add_parser("list-package-names", help="Returns list of package names in the given copr") parser_list_package_names.add_argument("copr", help="The copr repo to list the packages of. Can be just name of project or even in format owner/project.") parser_list_package_names.set_defaults(func="action_list_package_names") # single package fetching parser_get_package = subparsers.add_parser("get-package", help="Returns package of the given name in the given copr") parser_get_package.add_argument("copr", help="The copr repo to list the packages of. Can be just name of project or even in format owner/project.") parser_get_package.add_argument("--name", help="Name of a single package to be displayed", metavar="PKGNAME", required=True) parser_get_package.add_argument("--with-latest-build", action="store_true", help="Also display data related to the latest build for each package.") parser_get_package.add_argument("--with-latest-succeeded-build", action="store_true", help="Also display data related to the latest succeeded build for each package.") parser_get_package.add_argument("--with-all-builds", action="store_true", help="Also display data related to the builds for each package.") parser_get_package.set_defaults(func="action_get_package") # package deletion parser_delete_package = subparsers.add_parser("delete-package", help="Deletes the specified package") parser_delete_package.add_argument("copr", help="The copr repo to list the packages of. Can be just name of project or even in format owner/project.") parser_delete_package.add_argument("--name", help="Name of a package to be deleted", metavar="PKGNAME", required=True) parser_delete_package.set_defaults(func="action_delete_package") # package resetting parser_reset_package = subparsers.add_parser("reset-package", help="Resets (clears) default source of the specified package") parser_reset_package.add_argument("copr", help="The copr repo to list the packages of. Can be just name of project or even in format owner/project.") parser_reset_package.add_argument("--name", help="Name of a package to be reset", metavar="PKGNAME", required=True) parser_reset_package.set_defaults(func="action_reset_package") # package building parser_build_package = subparsers.add_parser("build-package", parents=[parser_build_parent], help="Builds the package from its default source") parser_build_package.add_argument("--name", help="Name of a package to be built", metavar="PKGNAME", required=True) parser_build_package.set_defaults(func="action_build_package") # module building parser_build_module = subparsers.add_parser("build-module", help="Builds a given module in Copr") parser_build_module.add_argument("copr", help="The copr repo to list the packages of. Can be just name of project or even in format owner/project.", nargs="?") parser_build_module_mmd_source = parser_build_module.add_mutually_exclusive_group(required=True) parser_build_module_mmd_source.add_argument("--url", help="SCM with modulemd file in yaml format") parser_build_module_mmd_source.add_argument("--yaml", help="Path to modulemd file in yaml format") parser_build_module.set_defaults(func="action_build_module") return parser def parse_name(name): m = re.match(r"([^/]+)/(.*)", name) if m: owner = m.group(1) name = m.group(2) else: owner = None return owner, name def parse_chroot_path(path): m = re.match(r"(([^/]+)/)?([^/]+)/(.*)", path) if m: return m.group(2), m.group(3), m.group(4) return None def enable_debug(): logging.basicConfig( level=logging.DEBUG, format='[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s', datefmt='%H:%M:%S' ) log.debug("# Debug log enabled #") def main(argv=sys.argv[1:]): try: # Set up parser for global args parser = setup_parser() # Parse the commandline arg = parser.parse_args(argv) if arg.debug: enable_debug() if not "func" in arg: parser.print_help() return commands = Commands(arg.config) getattr(commands, arg.func)(arg) except KeyboardInterrupt: sys.stderr.write("\nInterrupted by user.") sys.exit(1) except copr_exceptions.CoprBuildException as e: sys.stderr.write("\nBuild error: {0}\n".format(e)) sys.exit(4) except copr_exceptions.CoprUnknownResponseException as e: sys.stderr.write("\nError: {0}\n".format(e)) sys.exit(5) except copr_exceptions.CoprRequestException as e: sys.stderr.write("\nSomething went wrong:") sys.stderr.write("\nError: {0}\n".format(e)) sys.exit(1) except argparse.ArgumentTypeError as e: sys.stderr.write("\nError: {0}".format(e)) sys.exit(2) except copr_exceptions.CoprException as e: sys.stderr.write("\nError: {0}\n".format(e)) sys.exit(3) # except Exception as e: # print "Error: {0}".format(e) # sys.exit(100) if __name__ == "__main__": main() argparse-manpage-4.5/examples/copr/copr_cli/util.py000066400000000000000000000020531450337362100225020ustar00rootroot00000000000000# coding: utf-8 try: from progress.bar import Bar except ImportError: progress = False else: progress = True def format_size(bytes_in): if bytes_in > 1000 * 1000: return '%.1fMB' % (bytes_in / 1000.0 / 1000) elif bytes_in > 10 * 1000: return '%ikB' % (bytes_in / 1000) elif bytes_in > 1000: return '%.1fkB' % (bytes_in / 1000.0) else: return '%ibytes' % bytes_in class ProgressMixin(object): @property def download_speed(self): if self.avg == 0.0: return "..." return format_size(1 / self.avg) + "/s" @property def downloaded(self): return format_size(self.index) class DummyBar(object): # pylint: disable=redefined-builtin def __init__(self, max=None): pass def next(self, n=None): pass def finish(self): pass if progress: class ProgressBar(Bar, ProgressMixin): message = "%(percent)d%%" suffix = "%(downloaded)s %(download_speed)s eta %(eta_td)s" else: ProgressBar = DummyBar argparse-manpage-4.5/examples/copr/expected-output.1000066400000000000000000000636431450337362100226160ustar00rootroot00000000000000.TH COPR "1" "2023\-04\-09" "example setup\-py\-overriden" "Generated Python Manual" .SH NAME copr .SH SYNOPSIS .B copr [-h] [--debug] [--config CONFIG] [--version] {hidden,whoami,list,mock-config,create,modify,delete,fork,build,buildpypi,buildgem,buildfedpkg,buildtito,buildmock,status,download-build,cancel,watch-build,delete-build,edit-chroot,get-chroot,add-package-tito,edit-package-tito,add-package-pypi,edit-package-pypi,add-package-mockscm,edit-package-mockscm,add-package-rubygems,edit-package-rubygems,list-packages,list-package-names,get-package,delete-package,reset-package,build-package,build-module} ... .SH OPTIONS .TP \fB\-\-debug\fR Enable debug output .TP \fB\-\-config\fR \fI\,CONFIG\/\fR Path to an alternative configuration file .TP \fB\-\-version\fR show program's version number and exit .SH ACTIONS .TP \fBcopr\fR \fI\,whoami\/\fR Print username that the client authenticates with against copr\-frontend .TP \fBcopr\fR \fI\,list\/\fR List all the copr of the provided .TP \fBcopr\fR \fI\,mock\-config\/\fR Get the mock profile (similar to koji mock\-config) .TP \fBcopr\fR \fI\,create\/\fR Create a new copr .TP \fBcopr\fR \fI\,modify\/\fR Modify existing copr .TP \fBcopr\fR \fI\,delete\/\fR Deletes the entire project .TP \fBcopr\fR \fI\,fork\/\fR Fork the project and builds in it .TP \fBcopr\fR \fI\,build\/\fR Build packages to a specified copr .TP \fBcopr\fR \fI\,buildpypi\/\fR Build PyPI package to a specified copr .TP \fBcopr\fR \fI\,buildgem\/\fR Build gem from rubygems.org to a specified copr .TP \fBcopr\fR \fI\,buildfedpkg\/\fR Build package from pkgs.fedoraproject.org .TP \fBcopr\fR \fI\,buildtito\/\fR submit a build from Git repository via Tito to a specified copr .TP \fBcopr\fR \fI\,buildmock\/\fR submit a build from SCM repository via Mock to a specified copr .TP \fBcopr\fR \fI\,status\/\fR Get build status of build specified by its ID .TP \fBcopr\fR \fI\,download\-build\/\fR Fetches built packages .TP \fBcopr\fR \fI\,cancel\/\fR Cancel build specified by its ID .TP \fBcopr\fR \fI\,watch\-build\/\fR Watch status and progress of build(s) specified by their ID .TP \fBcopr\fR \fI\,delete\-build\/\fR Delete build specified by its ID .TP \fBcopr\fR \fI\,edit\-chroot\/\fR Edit chroot of a project .TP \fBcopr\fR \fI\,get\-chroot\/\fR Get chroot of a project .TP \fBcopr\fR \fI\,add\-package\-tito\/\fR Creates a new Tito package .TP \fBcopr\fR \fI\,edit\-package\-tito\/\fR Edits an existing Tito package .TP \fBcopr\fR \fI\,add\-package\-pypi\/\fR Creates a new PyPI package .TP \fBcopr\fR \fI\,edit\-package\-pypi\/\fR Edits an existing PyPI package .TP \fBcopr\fR \fI\,add\-package\-mockscm\/\fR Creates a new Mock\-SCM package .TP \fBcopr\fR \fI\,edit\-package\-mockscm\/\fR Edits an existing Mock\-SCM package .TP \fBcopr\fR \fI\,add\-package\-rubygems\/\fR Creates a new RubyGems package .TP \fBcopr\fR \fI\,edit\-package\-rubygems\/\fR Edits a new RubyGems package .TP \fBcopr\fR \fI\,list\-packages\/\fR Returns list of packages in the given copr .TP \fBcopr\fR \fI\,list\-package\-names\/\fR Returns list of package names in the given copr .TP \fBcopr\fR \fI\,get\-package\/\fR Returns package of the given name in the given copr .TP \fBcopr\fR \fI\,delete\-package\/\fR Deletes the specified package .TP \fBcopr\fR \fI\,reset\-package\/\fR Resets (clears) default source of the specified package .TP \fBcopr\fR \fI\,build\-package\/\fR Builds the package from its default source .TP \fBcopr\fR \fI\,build\-module\/\fR Builds a given module in Copr .SH COMMAND \fI\,'copr whoami'\/\fR usage: copr whoami [\-h] .SH COMMAND \fI\,'copr list'\/\fR usage: copr list [\-h] [username|@groupname] .TP \fBusername|@groupname\fR The username or @groupname that you would like to list the coprs of (defaults to current user) .SH COMMAND \fI\,'copr mock\-config'\/\fR usage: copr mock\-config [\-h] project chroot .TP \fBproject\fR Expected format is /, / (including '@') or (name of project you own). .TP \fBchroot\fR chroot id, e.g. 'fedora\-rawhide\-x86_64' .SH COMMAND \fI\,'copr create'\/\fR usage: copr create [\-h] [\-\-chroot CHROOTS] [\-\-repo REPOS] [\-\-initial\-pkgs INITIAL_PKGS] [\-\-description DESCRIPTION] [\-\-instructions INSTRUCTIONS] [\-\-disable_createrepo DISABLE_CREATEREPO] [\-\-enable\-net {on,off}] [\-\-unlisted\-on\-hp {on,off}] [\-\-persistent] [\-\-auto\-prune {on,off}] name .TP \fBname\fR The name of the copr to create .SH OPTIONS \fI\,'copr create'\/\fR .TP \fB\-\-chroot\fR \fI\,CHROOTS\/\fR Chroot to use for this copr .TP \fB\-\-repo\fR \fI\,REPOS\/\fR Repository to add to this copr .TP \fB\-\-initial\-pkgs\fR \fI\,INITIAL_PKGS\/\fR List of packages URL to build in this new copr .TP \fB\-\-description\fR \fI\,DESCRIPTION\/\fR Description of the copr .TP \fB\-\-instructions\fR \fI\,INSTRUCTIONS\/\fR Instructions for the copr .TP \fB\-\-disable_createrepo\fR \fI\,DISABLE_CREATEREPO\/\fR Disable metadata auto generation .TP \fB\-\-enable\-net\fR \fI\,{on,off}\/\fR If net should be enabled for builds in this project (default is off) .TP \fB\-\-unlisted\-on\-hp\fR \fI\,{on,off}\/\fR The project will not be shown on COPR home page .TP \fB\-\-persistent\fR Project and its builds will be undeletable. This option can only be specified by a COPR admin. .TP \fB\-\-auto\-prune\fR \fI\,{on,off}\/\fR If auto\-deletion of project's obsoleted builds should be enabled (default is on). This option can only be specified by a COPR admin. .SH COMMAND \fI\,'copr modify'\/\fR usage: copr modify [\-h] [\-\-chroot CHROOTS] [\-\-description DESCRIPTION] [\-\-instructions INSTRUCTIONS] [\-\-repo REPOS] [\-\-disable_createrepo DISABLE_CREATEREPO] [\-\-enable\-net {on,off}] [\-\-unlisted\-on\-hp {on,off}] [\-\-auto\-prune {on,off}] name .TP \fBname\fR The name of the copr to modify .SH OPTIONS \fI\,'copr modify'\/\fR .TP \fB\-\-chroot\fR \fI\,CHROOTS\/\fR Chroot to use for this copr .TP \fB\-\-description\fR \fI\,DESCRIPTION\/\fR Description of the copr .TP \fB\-\-instructions\fR \fI\,INSTRUCTIONS\/\fR Instructions for the copr .TP \fB\-\-repo\fR \fI\,REPOS\/\fR Repository to add to this copr .TP \fB\-\-disable_createrepo\fR \fI\,DISABLE_CREATEREPO\/\fR Disable metadata auto generation .TP \fB\-\-enable\-net\fR \fI\,{on,off}\/\fR If net should be enabled for builds in this project (default is "don't change") .TP \fB\-\-unlisted\-on\-hp\fR \fI\,{on,off}\/\fR The project will not be shown on COPR home page .TP \fB\-\-auto\-prune\fR \fI\,{on,off}\/\fR If auto\-deletion of project's obsoleted builds should be enabled. This option can only be specified by a COPR admin. .SH COMMAND \fI\,'copr delete'\/\fR usage: copr delete [\-h] copr .TP \fBcopr\fR Name of your project to be deleted. .SH COMMAND \fI\,'copr fork'\/\fR usage: copr fork [\-h] [\-\-confirm] src dst .TP \fBsrc\fR Which project should be forked .TP \fBdst\fR Name of the new project .SH OPTIONS \fI\,'copr fork'\/\fR .TP \fB\-\-confirm\fR Confirm forking into existing project .SH COMMAND \fI\,'copr build'\/\fR usage: copr build [\-h] [\-\-memory MEMORY] [\-\-timeout TIMEOUT] [\-\-nowait] [\-r CHROOTS] [\-\-background] copr pkgs [pkgs ...] .TP \fBcopr\fR The copr repo to build the package in. Can be just name of project or even in format username/project or @groupname/project. .TP \fBpkgs\fR filename of SRPM or URL of packages to build .SH OPTIONS \fI\,'copr build'\/\fR .TP \fB\-\-memory\fR \fI\,MEMORY\/\fR .TP \fB\-\-timeout\fR \fI\,TIMEOUT\/\fR .TP \fB\-\-nowait\fR Don't wait for build .TP \fB\-r\fR \fI\,CHROOTS\/\fR, \fB\-\-chroot\fR \fI\,CHROOTS\/\fR If you don't need this build for all the project's chroots. You can use it several times for each chroot you need. .TP \fB\-\-background\fR Mark the build as a background job. It will have lesser priority than regular builds. .SH COMMAND \fI\,'copr buildpypi'\/\fR usage: copr buildpypi [\-h] [\-\-pythonversions [VERSION ...]] [\-\-packageversion PYPIVERSION] \-\-packagename PYPINAME [\-\-memory MEMORY] [\-\-timeout TIMEOUT] [\-\-nowait] [\-r CHROOTS] [\-\-background] copr .TP \fBcopr\fR The copr repo to build the package in. Can be just name of project or even in format username/project or @groupname/project. .SH OPTIONS \fI\,'copr buildpypi'\/\fR .TP \fB\-\-pythonversions\fR \fI\,[VERSION ...]\/\fR For what Python versions to build (by default: 3 2) .TP \fB\-\-packageversion\fR \fI\,PYPIVERSION\/\fR Version of the PyPI package to be built (by default latest) .TP \fB\-\-packagename\fR \fI\,PYPINAME\/\fR Name of the PyPI package to be built, required. .TP \fB\-\-memory\fR \fI\,MEMORY\/\fR .TP \fB\-\-timeout\fR \fI\,TIMEOUT\/\fR .TP \fB\-\-nowait\fR Don't wait for build .TP \fB\-r\fR \fI\,CHROOTS\/\fR, \fB\-\-chroot\fR \fI\,CHROOTS\/\fR If you don't need this build for all the project's chroots. You can use it several times for each chroot you need. .TP \fB\-\-background\fR Mark the build as a background job. It will have lesser priority than regular builds. .SH COMMAND \fI\,'copr buildgem'\/\fR usage: copr buildgem [\-h] [\-\-gem GEM] [\-\-memory MEMORY] [\-\-timeout TIMEOUT] [\-\-nowait] [\-r CHROOTS] [\-\-background] copr .TP \fBcopr\fR The copr repo to build the package in. Can be just name of project or even in format username/project or @groupname/project. .SH OPTIONS \fI\,'copr buildgem'\/\fR .TP \fB\-\-gem\fR \fI\,GEM\/\fR Specify gem name .TP \fB\-\-memory\fR \fI\,MEMORY\/\fR .TP \fB\-\-timeout\fR \fI\,TIMEOUT\/\fR .TP \fB\-\-nowait\fR Don't wait for build .TP \fB\-r\fR \fI\,CHROOTS\/\fR, \fB\-\-chroot\fR \fI\,CHROOTS\/\fR If you don't need this build for all the project's chroots. You can use it several times for each chroot you need. .TP \fB\-\-background\fR Mark the build as a background job. It will have lesser priority than regular builds. .SH COMMAND \fI\,'copr buildfedpkg'\/\fR usage: copr buildfedpkg [\-h] \-\-clone\-url URL [\-\-branch BRANCH] [\-\-memory MEMORY] [\-\-timeout TIMEOUT] [\-\-nowait] [\-r CHROOTS] [\-\-background] copr .TP \fBcopr\fR The copr repo to build the package in. Can be just name of project or even in format username/project or @groupname/project. .SH OPTIONS \fI\,'copr buildfedpkg'\/\fR .TP \fB\-\-clone\-url\fR \fI\,URL\/\fR Specify clone url for the distgit repository .TP \fB\-\-branch\fR \fI\,BRANCH\/\fR Specify branch to be used .TP \fB\-\-memory\fR \fI\,MEMORY\/\fR .TP \fB\-\-timeout\fR \fI\,TIMEOUT\/\fR .TP \fB\-\-nowait\fR Don't wait for build .TP \fB\-r\fR \fI\,CHROOTS\/\fR, \fB\-\-chroot\fR \fI\,CHROOTS\/\fR If you don't need this build for all the project's chroots. You can use it several times for each chroot you need. .TP \fB\-\-background\fR Mark the build as a background job. It will have lesser priority than regular builds. .SH COMMAND \fI\,'copr buildtito'\/\fR usage: copr buildtito [\-h] \-\-git\-url URL [\-\-git\-dir DIRECTORY] [\-\-git\-branch BRANCH] [\-\-test {on,off}] [\-\-memory MEMORY] [\-\-timeout TIMEOUT] [\-\-nowait] [\-r CHROOTS] [\-\-background] copr .TP \fBcopr\fR The copr repo to build the package in. Can be just name of project or even in format username/project or @groupname/project. .SH OPTIONS \fI\,'copr buildtito'\/\fR .TP \fB\-\-git\-url\fR \fI\,URL\/\fR URL to a project managed by Tito .TP \fB\-\-git\-dir\fR \fI\,DIRECTORY\/\fR Relative path from Git root to directory containing .spec file .TP \fB\-\-git\-branch\fR \fI\,BRANCH\/\fR Git branch that you want to build from .TP \fB\-\-test\fR \fI\,{on,off}\/\fR Build the last commit instead of the last release tag .TP \fB\-\-memory\fR \fI\,MEMORY\/\fR .TP \fB\-\-timeout\fR \fI\,TIMEOUT\/\fR .TP \fB\-\-nowait\fR Don't wait for build .TP \fB\-r\fR \fI\,CHROOTS\/\fR, \fB\-\-chroot\fR \fI\,CHROOTS\/\fR If you don't need this build for all the project's chroots. You can use it several times for each chroot you need. .TP \fB\-\-background\fR Mark the build as a background job. It will have lesser priority than regular builds. .SH COMMAND \fI\,'copr buildmock'\/\fR usage: copr buildmock [\-h] [\-\-scm\-type TYPE] [\-\-scm\-url URL] [\-\-scm\-branch BRANCH] [\-\-spec FILE] [\-\-memory MEMORY] [\-\-timeout TIMEOUT] [\-\-nowait] [\-r CHROOTS] [\-\-background] copr .TP \fBcopr\fR The copr repo to build the package in. Can be just name of project or even in format username/project or @groupname/project. .SH OPTIONS \fI\,'copr buildmock'\/\fR .TP \fB\-\-scm\-type\fR \fI\,TYPE\/\fR specify versioning tool, default is 'git' .TP \fB\-\-scm\-url\fR \fI\,URL\/\fR url to a project versioned by Git or SVN, required .TP \fB\-\-scm\-branch\fR \fI\,BRANCH\/\fR .TP \fB\-\-spec\fR \fI\,FILE\/\fR relative path from SCM root to .spec file, required .TP \fB\-\-memory\fR \fI\,MEMORY\/\fR .TP \fB\-\-timeout\fR \fI\,TIMEOUT\/\fR .TP \fB\-\-nowait\fR Don't wait for build .TP \fB\-r\fR \fI\,CHROOTS\/\fR, \fB\-\-chroot\fR \fI\,CHROOTS\/\fR If you don't need this build for all the project's chroots. You can use it several times for each chroot you need. .TP \fB\-\-background\fR Mark the build as a background job. It will have lesser priority than regular builds. .SH COMMAND \fI\,'copr status'\/\fR usage: copr status [\-h] build_id .TP \fBbuild_id\fR Build ID .SH COMMAND \fI\,'copr download\-build'\/\fR usage: copr download\-build [\-h] [\-r CHROOTS] [\-\-dest DEST] build_id .TP \fBbuild_id\fR Build ID .SH OPTIONS \fI\,'copr download\-build'\/\fR .TP \fB\-r\fR \fI\,CHROOTS\/\fR, \fB\-\-chroot\fR \fI\,CHROOTS\/\fR Select chroots to fetch .TP \fB\-\-dest\fR \fI\,DEST\/\fR, \fB\-d\fR \fI\,DEST\/\fR Base directory to store packages .SH COMMAND \fI\,'copr cancel'\/\fR usage: copr cancel [\-h] build_id .TP \fBbuild_id\fR Build ID .SH COMMAND \fI\,'copr watch\-build'\/\fR usage: copr watch\-build [\-h] build_id [build_id ...] .TP \fBbuild_id\fR Build ID .SH COMMAND \fI\,'copr delete\-build'\/\fR usage: copr delete\-build [\-h] build_id .TP \fBbuild_id\fR Build ID .SH COMMAND \fI\,'copr edit\-chroot'\/\fR usage: copr edit\-chroot [\-h] [\-\-upload\-comps FILEPATH | \-\-delete\-comps] [\-\-packages PACKAGES] [\-\-repos REPOS] coprchroot .TP \fBcoprchroot\fR Path to a project chroot as owner/project/chroot or project/chroot .SH OPTIONS \fI\,'copr edit\-chroot'\/\fR .TP \fB\-\-upload\-comps\fR \fI\,FILEPATH\/\fR filepath to the comps.xml file to be uploaded .TP \fB\-\-delete\-comps\fR deletes already existing comps.xml for the chroot .TP \fB\-\-packages\fR \fI\,PACKAGES\/\fR space separated string of package names to be added to buildroot .TP \fB\-\-repos\fR \fI\,REPOS\/\fR space separated string of additional repo urls for chroot .SH COMMAND \fI\,'copr get\-chroot'\/\fR usage: copr get\-chroot [\-h] coprchroot .TP \fBcoprchroot\fR Path to a project chroot as owner/project/chroot or project/chroot .SH COMMAND \fI\,'copr add\-package\-tito'\/\fR usage: copr add\-package\-tito [\-h] \-\-git\-url URL [\-\-git\-dir DIRECTORY] [\-\-git\-branch BRANCH] [\-\-test {on,off}] \-\-name PKGNAME [\-\-webhook\-rebuild {on,off}] copr .TP \fBcopr\fR The copr repo for the package. Can be just name of project or even in format username/project or @groupname/project. .SH OPTIONS \fI\,'copr add\-package\-tito'\/\fR .TP \fB\-\-git\-url\fR \fI\,URL\/\fR URL to a project managed by Tito .TP \fB\-\-git\-dir\fR \fI\,DIRECTORY\/\fR Relative path from Git root to directory containing .spec file .TP \fB\-\-git\-branch\fR \fI\,BRANCH\/\fR Git branch that you want to build from .TP \fB\-\-test\fR \fI\,{on,off}\/\fR Build the last commit instead of the last release tag .TP \fB\-\-name\fR \fI\,PKGNAME\/\fR Name of the package to be edited or created .TP \fB\-\-webhook\-rebuild\fR \fI\,{on,off}\/\fR Enable auto\-rebuilding. .SH COMMAND \fI\,'copr edit\-package\-tito'\/\fR usage: copr edit\-package\-tito [\-h] \-\-git\-url URL [\-\-git\-dir DIRECTORY] [\-\-git\-branch BRANCH] [\-\-test {on,off}] \-\-name PKGNAME [\-\-webhook\-rebuild {on,off}] copr .TP \fBcopr\fR The copr repo for the package. Can be just name of project or even in format username/project or @groupname/project. .SH OPTIONS \fI\,'copr edit\-package\-tito'\/\fR .TP \fB\-\-git\-url\fR \fI\,URL\/\fR URL to a project managed by Tito .TP \fB\-\-git\-dir\fR \fI\,DIRECTORY\/\fR Relative path from Git root to directory containing .spec file .TP \fB\-\-git\-branch\fR \fI\,BRANCH\/\fR Git branch that you want to build from .TP \fB\-\-test\fR \fI\,{on,off}\/\fR Build the last commit instead of the last release tag .TP \fB\-\-name\fR \fI\,PKGNAME\/\fR Name of the package to be edited or created .TP \fB\-\-webhook\-rebuild\fR \fI\,{on,off}\/\fR Enable auto\-rebuilding. .SH COMMAND \fI\,'copr add\-package\-pypi'\/\fR usage: copr add\-package\-pypi [\-h] [\-\-pythonversions [VERSION ...]] [\-\-packageversion PYPIVERSION] \-\-packagename PYPINAME \-\-name PKGNAME [\-\-webhook\-rebuild {on,off}] copr .TP \fBcopr\fR The copr repo for the package. Can be just name of project or even in format username/project or @groupname/project. .SH OPTIONS \fI\,'copr add\-package\-pypi'\/\fR .TP \fB\-\-pythonversions\fR \fI\,[VERSION ...]\/\fR For what Python versions to build (by default: 3 2) .TP \fB\-\-packageversion\fR \fI\,PYPIVERSION\/\fR Version of the PyPI package to be built (by default latest) .TP \fB\-\-packagename\fR \fI\,PYPINAME\/\fR Name of the PyPI package to be built, required. .TP \fB\-\-name\fR \fI\,PKGNAME\/\fR Name of the package to be edited or created .TP \fB\-\-webhook\-rebuild\fR \fI\,{on,off}\/\fR Enable auto\-rebuilding. .SH COMMAND \fI\,'copr edit\-package\-pypi'\/\fR usage: copr edit\-package\-pypi [\-h] [\-\-pythonversions [VERSION ...]] [\-\-packageversion PYPIVERSION] \-\-packagename PYPINAME \-\-name PKGNAME [\-\-webhook\-rebuild {on,off}] copr .TP \fBcopr\fR The copr repo for the package. Can be just name of project or even in format username/project or @groupname/project. .SH OPTIONS \fI\,'copr edit\-package\-pypi'\/\fR .TP \fB\-\-pythonversions\fR \fI\,[VERSION ...]\/\fR For what Python versions to build (by default: 3 2) .TP \fB\-\-packageversion\fR \fI\,PYPIVERSION\/\fR Version of the PyPI package to be built (by default latest) .TP \fB\-\-packagename\fR \fI\,PYPINAME\/\fR Name of the PyPI package to be built, required. .TP \fB\-\-name\fR \fI\,PKGNAME\/\fR Name of the package to be edited or created .TP \fB\-\-webhook\-rebuild\fR \fI\,{on,off}\/\fR Enable auto\-rebuilding. .SH COMMAND \fI\,'copr add\-package\-mockscm'\/\fR usage: copr add\-package\-mockscm [\-h] [\-\-scm\-type TYPE] [\-\-scm\-url URL] [\-\-scm\-branch BRANCH] [\-\-spec FILE] \-\-name PKGNAME [\-\-webhook\-rebuild {on,off}] copr .TP \fBcopr\fR The copr repo for the package. Can be just name of project or even in format username/project or @groupname/project. .SH OPTIONS \fI\,'copr add\-package\-mockscm'\/\fR .TP \fB\-\-scm\-type\fR \fI\,TYPE\/\fR specify versioning tool, default is 'git' .TP \fB\-\-scm\-url\fR \fI\,URL\/\fR url to a project versioned by Git or SVN, required .TP \fB\-\-scm\-branch\fR \fI\,BRANCH\/\fR .TP \fB\-\-spec\fR \fI\,FILE\/\fR relative path from SCM root to .spec file, required .TP \fB\-\-name\fR \fI\,PKGNAME\/\fR Name of the package to be edited or created .TP \fB\-\-webhook\-rebuild\fR \fI\,{on,off}\/\fR Enable auto\-rebuilding. .SH COMMAND \fI\,'copr edit\-package\-mockscm'\/\fR usage: copr edit\-package\-mockscm [\-h] [\-\-scm\-type TYPE] [\-\-scm\-url URL] [\-\-scm\-branch BRANCH] [\-\-spec FILE] \-\-name PKGNAME [\-\-webhook\-rebuild {on,off}] copr .TP \fBcopr\fR The copr repo for the package. Can be just name of project or even in format username/project or @groupname/project. .SH OPTIONS \fI\,'copr edit\-package\-mockscm'\/\fR .TP \fB\-\-scm\-type\fR \fI\,TYPE\/\fR specify versioning tool, default is 'git' .TP \fB\-\-scm\-url\fR \fI\,URL\/\fR url to a project versioned by Git or SVN, required .TP \fB\-\-scm\-branch\fR \fI\,BRANCH\/\fR .TP \fB\-\-spec\fR \fI\,FILE\/\fR relative path from SCM root to .spec file, required .TP \fB\-\-name\fR \fI\,PKGNAME\/\fR Name of the package to be edited or created .TP \fB\-\-webhook\-rebuild\fR \fI\,{on,off}\/\fR Enable auto\-rebuilding. .SH COMMAND \fI\,'copr add\-package\-rubygems'\/\fR usage: copr add\-package\-rubygems [\-h] [\-\-gem GEM] \-\-name PKGNAME [\-\-webhook\-rebuild {on,off}] copr .TP \fBcopr\fR The copr repo for the package. Can be just name of project or even in format username/project or @groupname/project. .SH OPTIONS \fI\,'copr add\-package\-rubygems'\/\fR .TP \fB\-\-gem\fR \fI\,GEM\/\fR Specify gem name .TP \fB\-\-name\fR \fI\,PKGNAME\/\fR Name of the package to be edited or created .TP \fB\-\-webhook\-rebuild\fR \fI\,{on,off}\/\fR Enable auto\-rebuilding. .SH COMMAND \fI\,'copr edit\-package\-rubygems'\/\fR usage: copr edit\-package\-rubygems [\-h] [\-\-gem GEM] \-\-name PKGNAME [\-\-webhook\-rebuild {on,off}] copr .TP \fBcopr\fR The copr repo for the package. Can be just name of project or even in format username/project or @groupname/project. .SH OPTIONS \fI\,'copr edit\-package\-rubygems'\/\fR .TP \fB\-\-gem\fR \fI\,GEM\/\fR Specify gem name .TP \fB\-\-name\fR \fI\,PKGNAME\/\fR Name of the package to be edited or created .TP \fB\-\-webhook\-rebuild\fR \fI\,{on,off}\/\fR Enable auto\-rebuilding. .SH COMMAND \fI\,'copr list\-packages'\/\fR usage: copr list\-packages [\-h] [\-\-with\-latest\-build] [\-\-with\-latest\-succeeded\-build] [\-\-with\-all\-builds] copr .TP \fBcopr\fR The copr repo to list the packages of. Can be just name of project or even in format owner/project. .SH OPTIONS \fI\,'copr list\-packages'\/\fR .TP \fB\-\-with\-latest\-build\fR Also display data related to the latest build for the package. .TP \fB\-\-with\-latest\-succeeded\-build\fR Also display data related to the latest succeeded build for the package. .TP \fB\-\-with\-all\-builds\fR Also display data related to the builds for the package. .SH COMMAND \fI\,'copr list\-package\-names'\/\fR usage: copr list\-package\-names [\-h] copr .TP \fBcopr\fR The copr repo to list the packages of. Can be just name of project or even in format owner/project. .SH COMMAND \fI\,'copr get\-package'\/\fR usage: copr get\-package [\-h] \-\-name PKGNAME [\-\-with\-latest\-build] [\-\-with\-latest\-succeeded\-build] [\-\-with\-all\-builds] copr .TP \fBcopr\fR The copr repo to list the packages of. Can be just name of project or even in format owner/project. .SH OPTIONS \fI\,'copr get\-package'\/\fR .TP \fB\-\-name\fR \fI\,PKGNAME\/\fR Name of a single package to be displayed .TP \fB\-\-with\-latest\-build\fR Also display data related to the latest build for each package. .TP \fB\-\-with\-latest\-succeeded\-build\fR Also display data related to the latest succeeded build for each package. .TP \fB\-\-with\-all\-builds\fR Also display data related to the builds for each package. .SH COMMAND \fI\,'copr delete\-package'\/\fR usage: copr delete\-package [\-h] \-\-name PKGNAME copr .TP \fBcopr\fR The copr repo to list the packages of. Can be just name of project or even in format owner/project. .SH OPTIONS \fI\,'copr delete\-package'\/\fR .TP \fB\-\-name\fR \fI\,PKGNAME\/\fR Name of a package to be deleted .SH COMMAND \fI\,'copr reset\-package'\/\fR usage: copr reset\-package [\-h] \-\-name PKGNAME copr .TP \fBcopr\fR The copr repo to list the packages of. Can be just name of project or even in format owner/project. .SH OPTIONS \fI\,'copr reset\-package'\/\fR .TP \fB\-\-name\fR \fI\,PKGNAME\/\fR Name of a package to be reset .SH COMMAND \fI\,'copr build\-package'\/\fR usage: copr build\-package [\-h] [\-\-memory MEMORY] [\-\-timeout TIMEOUT] [\-\-nowait] [\-r CHROOTS] [\-\-background] \-\-name PKGNAME copr .TP \fBcopr\fR The copr repo to build the package in. Can be just name of project or even in format username/project or @groupname/project. .SH OPTIONS \fI\,'copr build\-package'\/\fR .TP \fB\-\-memory\fR \fI\,MEMORY\/\fR .TP \fB\-\-timeout\fR \fI\,TIMEOUT\/\fR .TP \fB\-\-nowait\fR Don't wait for build .TP \fB\-r\fR \fI\,CHROOTS\/\fR, \fB\-\-chroot\fR \fI\,CHROOTS\/\fR If you don't need this build for all the project's chroots. You can use it several times for each chroot you need. .TP \fB\-\-background\fR Mark the build as a background job. It will have lesser priority than regular builds. .TP \fB\-\-name\fR \fI\,PKGNAME\/\fR Name of a package to be built .SH COMMAND \fI\,'copr build\-module'\/\fR usage: copr build\-module [\-h] (\-\-url URL | \-\-yaml YAML) [copr] .TP \fBcopr\fR The copr repo to list the packages of. Can be just name of project or even in format owner/project. .SH OPTIONS \fI\,'copr build\-module'\/\fR .TP \fB\-\-url\fR \fI\,URL\/\fR SCM with modulemd file in yaml format .TP \fB\-\-yaml\fR \fI\,YAML\/\fR Path to modulemd file in yaml format .SH COMMENTS dummy text .SH AUTHOR .nf John Doe .fi .SH DISTRIBUTION The latest version of example may be downloaded from .UR http://example.com .UE argparse-manpage-4.5/examples/copr/fake-deps/000077500000000000000000000000001450337362100212205ustar00rootroot00000000000000argparse-manpage-4.5/examples/copr/fake-deps/HACK000066400000000000000000000000001450337362100216370ustar00rootroot00000000000000argparse-manpage-4.5/examples/copr/fake-deps/copr/000077500000000000000000000000001450337362100221635ustar00rootroot00000000000000argparse-manpage-4.5/examples/copr/fake-deps/copr/README000066400000000000000000000000431450337362100230400ustar00rootroot00000000000000Hack to work-around missing stuff. argparse-manpage-4.5/examples/copr/fake-deps/copr/__init__.py000066400000000000000000000000531450337362100242720ustar00rootroot00000000000000# Hack class CoprClient(object): pass argparse-manpage-4.5/examples/copr/fake-deps/copr/client/000077500000000000000000000000001450337362100234415ustar00rootroot00000000000000argparse-manpage-4.5/examples/copr/fake-deps/copr/client/__init__.py000066400000000000000000000000001450337362100255400ustar00rootroot00000000000000argparse-manpage-4.5/examples/copr/fake-deps/copr/client/responses.py000066400000000000000000000000351450337362100260320ustar00rootroot00000000000000class CoprResponse: pass argparse-manpage-4.5/examples/copr/fake-deps/copr/exceptions.py000066400000000000000000000000001450337362100247040ustar00rootroot00000000000000argparse-manpage-4.5/examples/copr/fake-deps/jinja2.py000066400000000000000000000000341450337362100227440ustar00rootroot00000000000000class Environment: pass argparse-manpage-4.5/examples/copr/fake-deps/simplejson.py000077700000000000000000000000001450337362100244012HACKustar00rootroot00000000000000argparse-manpage-4.5/examples/copr/setup.cfg000066400000000000000000000001611450337362100212000ustar00rootroot00000000000000[build_manpages] manpages = copr-cli.1:module=copr_cli.main:function=setup_parser:version=setup-py-overriden argparse-manpage-4.5/examples/copr/setup.py000066400000000000000000000017051450337362100210760ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Example of argparse taken from: # https://pagure.io/copr/copr/blob/a4feb01bc35b8554f503d41795e7a184ff929dd4/f/cli/copr_cli import os import sys from setuptools import setup, find_packages # Just to make sure that build_manpage can be found. sys.path = [os.path.join(os.getcwd(), 'fake-deps')] + sys.path from build_manpages.build_manpages \ import build_manpages, get_build_py_cmd, get_install_cmd from setuptools.command.build_py import build_py from setuptools.command.install import install setup( name='example', description='This project does nothing.', long_description=('Long description of the project.'), author='John Doe', author_email='jd@example.com', version='0.1.0', url='http://example.com', cmdclass={ 'build_manpages': build_manpages, 'build_py': get_build_py_cmd(build_py), 'install': get_install_cmd(install), }, packages=find_packages(), ) argparse-manpage-4.5/examples/old_format/000077500000000000000000000000001450337362100205445ustar00rootroot00000000000000argparse-manpage-4.5/examples/old_format/README.md000066400000000000000000000016251450337362100220270ustar00rootroot00000000000000# Usage (DEPRECATED, USE BUILD\_MANPAGES INSTEAD OF BUILD\_MANPAGE) Download `build_manpage.py` and place it somewhere where Python can find it. In your `setup.py` add: ```python [...] from build_manpage import build_manpage setup( [...] cmdclass={'build_manpage': build_manpage} ) ``` In your `setup.cfg` add: ``` [build_manpage] output=data/mymanpage.1 parser=myapp.somemod:get_parser ``` It's also possible to specify filename to use: ``` [build_manpage] output=data/mymanpage.1 parser=UNUSED:get_parser parser-file=example.py ``` The `output` is the destination path for the generated manpage and `parser` is an import path pointing to a optparser instance or a function returning such an instance. Note that this doesn't work with `argparse` module. Please use `build_manpages` (not `build_manpage`) to have argparse support. Then run `setup.py build_manpage` to build a manpage for your project. argparse-manpage-4.5/examples/old_format/example.py000066400000000000000000000010071450337362100225470ustar00rootroot00000000000000from optparse import OptionParser # This function returns a OptionParser def get_parser(): parser = OptionParser( usage="The usage.", description="This program does nothing.") parser.add_option("-f", "--file", dest="filename", help="write report to FILE", metavar="FILE") parser.add_option("-q", "--quiet", action="store_false", dest="verbose", default=True, help="don't print status messages to stdout") return parser argparse-manpage-4.5/examples/old_format/expected-output.1000066400000000000000000000007671450337362100237770ustar00rootroot00000000000000.TH example 1 2017\-09\-24 "example v.0.1.0.dev0" .SH NAME example .SH SYNOPSIS .B example The usage. .SH DESCRIPTION Description and long description are both used by build_manpage. .SH OPTIONS .TP .B \-h, \-\-help show this help message and exit .TP .B \-f FILE, \-\-file=FILE write report to FILE .TP .B \-q, \-\-quiet don't print status messages to stdout .SH AUTHOR .nf John Doe .fi .SH DISTRIBUTION The latest version of example may be downloaded from .UR http://example.com .UE argparse-manpage-4.5/examples/old_format/setup.cfg000066400000000000000000000000731450337362100223650ustar00rootroot00000000000000[build_manpage] output=example.1 parser=example:get_parser argparse-manpage-4.5/examples/old_format/setup.py000066400000000000000000000014251450337362100222600ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Example setup.py script # # The path tweaking at the top is just to make sure, that build_manpage # can be imported. # # 1. Run "setup.py build_manpage" # 2. Run "man ./example.1" to see the result. # import os import sys from distutils.core import setup # Just to make sure that build_manpage can be found. sys.path.insert(0, os.getcwd()) from build_manpages.build_manpage import build_manpage setup( name='example', description='This script does nothing.', long_description=( 'Description and long description are both used by build_manpage.'), author='John Doe', author_email='jd@example.com', version='0.1.0-dev', url='http://example.com', py_modules=['example'], cmdclass={'build_manpage': build_manpage} ) argparse-manpage-4.5/examples/old_format_file_name/000077500000000000000000000000001450337362100225435ustar00rootroot00000000000000argparse-manpage-4.5/examples/old_format_file_name/README.md000066400000000000000000000011111450337362100240140ustar00rootroot00000000000000# Usage Download `build_manpage.py` and place it somewhere where Python can find it. In your `setup.py` add: ```python [...] from build_manpage import build_manpage setup( [...] cmdclass={'build_manpage': build_manpage} ) ``` In your `setup.cfg` add: ``` [build_manpage] output=data/mymanpage.1 parser=myapp.somemod:get_parser ``` where `output` is the destination path for the generated manpage and `parser` is an import path pointing to a optparser instance or a function returning such an instance. Then run `setup.py build_manpage` to build a manpage for your project. argparse-manpage-4.5/examples/old_format_file_name/example.py000066400000000000000000000010071450337362100245460ustar00rootroot00000000000000from optparse import OptionParser # This function returns a OptionParser def get_parser(): parser = OptionParser( usage="The usage.", description="This program does nothing.") parser.add_option("-f", "--file", dest="filename", help="write report to FILE", metavar="FILE") parser.add_option("-q", "--quiet", action="store_false", dest="verbose", default=True, help="don't print status messages to stdout") return parser argparse-manpage-4.5/examples/old_format_file_name/expected-output.1000066400000000000000000000007671450337362100257760ustar00rootroot00000000000000.TH example 1 2017\-09\-24 "example v.0.1.0.dev0" .SH NAME example .SH SYNOPSIS .B example The usage. .SH DESCRIPTION Description and long description are both used by build_manpage. .SH OPTIONS .TP .B \-h, \-\-help show this help message and exit .TP .B \-f FILE, \-\-file=FILE write report to FILE .TP .B \-q, \-\-quiet don't print status messages to stdout .SH AUTHOR .nf John Doe .fi .SH DISTRIBUTION The latest version of example may be downloaded from .UR http://example.com .UE argparse-manpage-4.5/examples/old_format_file_name/setup.cfg000066400000000000000000000001221450337362100243570ustar00rootroot00000000000000[build_manpage] output=example.1 parser=IGNORED:get_parser parser-file=example.py argparse-manpage-4.5/examples/old_format_file_name/setup.py000066400000000000000000000014251450337362100242570ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Example setup.py script # # The path tweaking at the top is just to make sure, that build_manpage # can be imported. # # 1. Run "setup.py build_manpage" # 2. Run "man ./example.1" to see the result. # import os import sys from distutils.core import setup # Just to make sure that build_manpage can be found. sys.path.insert(0, os.getcwd()) from build_manpages.build_manpage import build_manpage setup( name='example', description='This script does nothing.', long_description=( 'Description and long description are both used by build_manpage.'), author='John Doe', author_email='jd@example.com', version='0.1.0-dev', url='http://example.com', py_modules=['example'], cmdclass={'build_manpage': build_manpage} ) argparse-manpage-4.5/examples/osc/000077500000000000000000000000001450337362100172025ustar00rootroot00000000000000argparse-manpage-4.5/examples/osc/expected-output.1000066400000000000000000000043061450337362100224260ustar00rootroot00000000000000.TH OSC "1" "2023\-04\-09" "osc 1.0" "Generated Python Manual" .SH NAME osc \- openSUSE commander command\-line .SH SYNOPSIS .B osc [global opts] [--help] [opts] [args] osc --help .SH COMMANDS .SS \fBosc build\fR Build a package on your local machine usage: osc [global opts] build (will try to guess a build environment) osc [global opts] build REPOSITORY ARCH BUILD_DESCR osc [global opts] build REPOSITORY ARCH osc [global opts] build REPOSITORY (ARCH = hostarch, BUILD_DESCR is detected automatically) osc [global opts] build ARCH (REPOSITORY = build_repository (config option), BUILD_DESCR is detected automatically) osc [global opts] build BUILD_DESCR (REPOSITORY = build_repository (config option), ARCH = hostarch) osc [global opts] build (REPOSITORY = build_repository (config option), ARCH = hostarch, BUILD_DESCR is detected automatically) Build command description. options: .RS 7 .TP \fB\-\-clean\fR Delete old build root before initializing it .RE .SS \fBosc list\fR List sources or binaries on the server usage: osc [global opts] list [PROJECT [PACKAGE]] osc [global opts] list \-b [PROJECT [PACKAGE [REPO [ARCH]]]] List command description. options: .RS 7 .TP \fB\-M\fR, \fB\-\-meta\fR list meta data files .TP \fB\-v\fR, \fB\-\-verbose\fR print extra information .RE .SS \fBosc foo\fR Test command with subcommands usage: osc [global opts] foo [\-h] {bar,baz} ... Some description. .SS \fBosc foo bar\fR help bar usage: osc [global opts] foo bar [\-h] [\-\-opt\-bar1 OPT_BAR1] [\-\-opt\-bar2 OPT_BAR2] options: .RS 7 .TP \fB\-\-opt\-bar1\fR \fI\,OPT_BAR1\/\fR help opt\-bar1 .TP \fB\-\-opt\-bar2\fR \fI\,OPT_BAR2\/\fR help opt\-bar2 .RE .SS \fBosc foo baz\fR help baz usage: osc [global opts] foo baz [\-h] [\-\-opt\-baz1 OPT_BAZ1] [\-\-opt\-baz2 OPT_BAZ2] options: .RS 7 .TP \fB\-\-opt\-baz1\fR \fI\,OPT_BAZ1\/\fR help opt\-baz1 .TP \fB\-\-opt\-baz2\fR \fI\,OPT_BAZ2\/\fR help opt\-baz2 .RE .SH AUTHOR .nf Contributors to the osc project. See the project's GIT history for the complete list. .fi .SH DISTRIBUTION The latest version of osc may be downloaded from .UR http://en.opensuse.org/openSUSE:OSC .UE argparse-manpage-4.5/examples/osc/osc/000077500000000000000000000000001450337362100177665ustar00rootroot00000000000000argparse-manpage-4.5/examples/osc/osc/__init__.py000066400000000000000000000000001450337362100220650ustar00rootroot00000000000000argparse-manpage-4.5/examples/osc/osc/main.py000077500000000000000000000110341450337362100212660ustar00rootroot00000000000000#!/usr/bin/python3 import argparse class HelpFormatter(argparse.RawDescriptionHelpFormatter): def _format_action(self, action): if isinstance(action, argparse._SubParsersAction): parts = [] for i in action._get_subactions(): if i.help == argparse.SUPPRESS: # don't display commands with suppressed help continue if len(i.metavar) > 20: parts.append("%*s%-21s" % (self._current_indent, "", i.metavar)) parts.append("%*s %s" % (self._current_indent + 21, "", i.help)) else: parts.append("%*s%-21s %s" % (self._current_indent, "", i.metavar, i.help)) return "\n".join(parts) return super(HelpFormatter, self)._format_action(action) def _format_usage(self, usage, actions, groups, prefix): if usage: usage = usage.strip() usage = usage % dict(prog=self._prog) else: usage = super(HelpFormatter, self)._format_usage(usage, actions, groups, prefix) if usage.startswith("usage: "): usage = usage[7:] result = ["usage: "] for line in usage.strip().splitlines(): result.append(" " + line) result.append("") result.append("") return "\n".join(result) def get_parser(): parser = argparse.ArgumentParser( prog="osc", usage= "%(prog)s [global opts] [--help] [opts] [args]\n" "%(prog)s --help", formatter_class=HelpFormatter, ) commands = parser.add_subparsers( title="commands", dest="command", ) # IMPORTANT: # it is necessary to specify 'prog=' in all subcommands to get usage rendered correctly cmd_build = commands.add_parser( "build", help="Build a package on your local machine", description="Build command description.", prog="osc [global opts] build", usage= "%(prog)s (will try to guess a build environment)\n" "%(prog)s REPOSITORY ARCH BUILD_DESCR\n" "%(prog)s REPOSITORY ARCH\n" "%(prog)s REPOSITORY (ARCH = hostarch, BUILD_DESCR is detected automatically)\n" "%(prog)s ARCH (REPOSITORY = build_repository (config option), BUILD_DESCR is detected automatically)\n" "%(prog)s BUILD_DESCR (REPOSITORY = build_repository (config option), ARCH = hostarch)\n" "%(prog)s (REPOSITORY = build_repository (config option), ARCH = hostarch, BUILD_DESCR is detected automatically)", formatter_class=HelpFormatter, ) cmd_build.add_argument( "--clean", action="store_true", help="Delete old build root before initializing it", ) cmd_list = commands.add_parser( "list", help="List sources or binaries on the server", description="List command description.", prog="osc [global opts] list", usage= "%(prog)s [PROJECT [PACKAGE]]\n" "%(prog)s -b [PROJECT [PACKAGE [REPO [ARCH]]]]", formatter_class=HelpFormatter, ) cmd_list.add_argument( "-M", "--meta", action="store_true", help="list meta data files", ) cmd_list.add_argument( "-v", "--verbose", action="store_true", help="print extra information", ) cmd_foo = commands.add_parser( "foo", help="Test command with subcommands", description="Some description.", prog="osc [global opts] foo", formatter_class=HelpFormatter, ) cmd_foo_subcommands = cmd_foo.add_subparsers( title="commands", dest="commands", ) cmd_foo_bar = cmd_foo_subcommands.add_parser( "bar", help="help bar", prog="osc [global opts] foo bar", ) cmd_foo_bar.add_argument("--opt-bar1", help="help opt-bar1") cmd_foo_bar.add_argument("--opt-bar2", help="help opt-bar2") cmd_foo_baz = cmd_foo_subcommands.add_parser( "baz", help="help baz", prog="osc [global opts] foo baz", ) cmd_foo_baz.add_argument("--opt-baz1", help="help opt-baz1") cmd_foo_baz.add_argument("--opt-baz2", help="help opt-baz2") cmd_hidden = commands.add_parser( "hidden", help=argparse.SUPPRESS, description="A hidden command, not displayed in help", formatter_class=HelpFormatter, ) return parser def main(): parser = get_parser() parser.parse_args() if __name__ == "__main__": main() argparse-manpage-4.5/examples/osc/setup.cfg000066400000000000000000000003731450337362100210260ustar00rootroot00000000000000[build_manpages] manpages = osc.1:module=osc.main:function=get_parser:format=single-commands-section:description=openSUSE commander command-line:prog=osc:author=Contributors to the osc project. See the project's GIT history for the complete list. argparse-manpage-4.5/examples/osc/setup.py000066400000000000000000000014041450337362100207130ustar00rootroot00000000000000import os import sys from setuptools import setup, find_packages from setuptools.command.build_py import build_py from setuptools.command.install import install # insert path to build_manpage sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) from build_manpages.build_manpages \ import build_manpages, get_build_py_cmd, get_install_cmd setup( name='osc', version='1.0', description='openSUSE commander', author='openSUSE project', author_email='opensuse-buildservice@opensuse.org', url='http://en.opensuse.org/openSUSE:OSC', cmdclass={ 'build_manpages': build_manpages, 'build_py': get_build_py_cmd(build_py), 'install': get_install_cmd(install), }, packages=find_packages(), ) argparse-manpage-4.5/examples/pre-written-man-page/000077500000000000000000000000001450337362100223615ustar00rootroot00000000000000argparse-manpage-4.5/examples/pre-written-man-page/psutils.1000066400000000000000000000015471450337362100241550ustar00rootroot00000000000000.TH PSUTILS 1 "PSUtils" .SH NAME PSUtils \- PostScript utilities .SH DESCRIPTION PSUtils is a set of utilities for manipulating PostScript documents which follow the Adobe Document Structuring Conventions. .SH UNITS OF LENGTH PSUtils utilities accept lengths in various units: .IP \(bu .B pt (PostScript points, 72 points per inch) .IP \(bu .B mm (millimetres) .IP \(bu .B cm (centimetres) .IP \(bu .B in (inches, 1 inch is 25.4mm) .PP Write the length as a number directly followed by the unit, for example, .B 4.5mm or .BR 72pt . If no unit is given, PostScript points are assumed. .SH AUTHOR Written by Angus J. C. Duggan. .SH "SEE ALSO" .BR psbook (1), .BR psselect (1), .BR pstops (1), .BR epsffit (1), .BR psnup (1), .BR psresize (1), .BR psjoin (1), .BR extractres (1), .BR includeres (1) .SH TRADEMARKS .B PostScript is a trademark of Adobe Systems Incorporated. argparse-manpage-4.5/examples/pre-written-man-page/setup.cfg000066400000000000000000000000741450337362100242030ustar00rootroot00000000000000[build_manpages] manpages = psutils.1:manfile=psutils.1 argparse-manpage-4.5/examples/pre-written-man-page/setup.py000066400000000000000000000004201450337362100240670ustar00rootroot00000000000000from build_manpages.build_manpages import build_manpages, get_install_cmd, get_build_py_cmd from setuptools import setup setup( cmdclass={ 'build_manpages': build_manpages, 'build_py': get_build_py_cmd(), 'install': get_install_cmd(), } ) argparse-manpage-4.5/examples/raw-description/000077500000000000000000000000001450337362100215305ustar00rootroot00000000000000argparse-manpage-4.5/examples/raw-description/bin/000077500000000000000000000000001450337362100223005ustar00rootroot00000000000000argparse-manpage-4.5/examples/raw-description/bin/dg000077500000000000000000000105471450337362100226270ustar00rootroot00000000000000#!/bin/python from __future__ import print_function import sys import tempfile import shutil from argparse import ArgumentParser, RawDescriptionHelpFormatter def error(msg): print(msg, file=sys.stderr) def die(msg): error(msg) sys.exit(1) description = \ """ Generate script using predefined metadata about distribution and templates. As an example of 'dg' usage, to generate _Dockerfile_ for Fedora 21 64-bit system, you may use command(s): $ cd project/directory $ dg --spec docker-data.yaml \\ --template docker.tpl \\ --distro fedora-21-x86_64.yaml """ parser = ArgumentParser( description=description, formatter_class=RawDescriptionHelpFormatter, ) parser.man_short_description = "templating system/generator for distributions" parser.add_argument( '--projectdir', metavar='PROJECTDIR', type=str, help='Directory with project (defaults to CWD)', default="." ) parser.add_argument( '--distro', metavar='DIST', type=str, help='Use distribution metadata specified by DIST yaml file', default="fedora-21-x86_64.yaml", ) parser.add_argument( '--multispec', metavar='MULTISPEC', type=str, help='Use MULTISPEC yaml file to fill the TEMPLATE file', ) parser.add_argument( '--multispec-selector', metavar='MULTISPEC_SELECTOR', type=str, help='Selectors for the multispec file', action='append', default=[], ) parser.add_argument( '--spec', metavar='SPEC', type=str, help='Use SPEC yaml file to fill the TEMPLATE file', action='append', ) parser.add_argument( '--output', metavar='OUTPUT', type=str, help='Write result to OUTPUT file instead of stdout', ) parser.add_argument( '--macros-from', metavar='PROJECTDIR', type=str, action='append', help='Load variables from PROJECTDIR', ) parser.add_argument( '--container', metavar='CONTAINER_TYPE', type=str, help='Container type, e.g. \'docker\'', default=False, ) parser.add_argument( '--macro', metavar='MACRO', type=str, action='append', help='Define distgen\'s macro', ) parser.add_argument( '--max-passes', metavar='PASSES', type=int, default=1, help='Maximum number of rendering passes, defaults to 1 (== no re-rendering)', ) tpl_or_combinations = parser.add_mutually_exclusive_group(required=True) tpl_or_combinations.add_argument( '--template', metavar='TEMPLATE', type=str, help='Use TEMPLATE file, e.g. docker.tpl or a template string, ' 'e.g. "{{ config.docker.from }}"' ) tpl_or_combinations.add_argument( '--multispec-combinations', action='store_true', help='Print available multispec combinations', ) def print_multispec_combinations(args): ms = Multispec.from_path(args.projectdir, args.multispec) for c in ms.get_all_combinations(): to_print = ['--distro {0}'.format(c.pop('distro'))] [to_print.append('--multispec-selector {0}={1}'.format(k, v)) for k, v in c.items()] print(' '.join(to_print)) def render_template(args): temp_filename = False output = sys.stdout try: if args.output: _, temp_filename = tempfile.mkstemp(prefix="distgen-") output = open(temp_filename, 'w') except: die("can't create temporary file for '{0}'".format(args.output)) cmd_cfg = CommandsConfig() cmd_cfg.container = args.container explicit_macros = {} if args.macro: for i in args.macro: key, value = i.split(' ', 1) explicit_macros[key] = value if args.template == '-': args.template = "/proc/self/fd/0" generator = Generator() generator.load_project(args.projectdir) generator.render( args.spec, args.multispec, args.multispec_selector, args.template, args.distro, cmd_cfg, output, args.macros_from, explicit_macros, args.max_passes, ) if temp_filename: try: output.close() shutil.move(temp_filename, args.output) except: die("can't move '{0}' into '{1}'".format(temp_filename, args.output)) def main(): args = parser.parse_args() if args.multispec_combinations: print_multispec_combinations(args) else: render_template(args) if __name__ == "__main__": main() argparse-manpage-4.5/examples/raw-description/expected-output.1000066400000000000000000000037631450337362100247620ustar00rootroot00000000000000.TH DG "1" "2023\-04\-09" "example 0.1.0" "Generated Python Manual" .SH NAME dg \- templating system/generator for distributions .SH SYNOPSIS .B dg [-h] [--projectdir PROJECTDIR] [--distro DIST] [--multispec MULTISPEC] [--multispec-selector MULTISPEC_SELECTOR] [--spec SPEC] [--output OUTPUT] [--macros-from PROJECTDIR] [--container CONTAINER_TYPE] [--macro MACRO] [--max-passes PASSES] (--template TEMPLATE | --multispec-combinations) .SH DESCRIPTION Generate script using predefined metadata about distribution and templates. As an example of 'dg' usage, to generate _Dockerfile_ for Fedora 21 64\-bit system, you may use command(s): $ cd project/directory $ dg \-\-spec docker\-data.yaml \\ \-\-template docker.tpl \\ \-\-distro fedora\-21\-x86_64.yaml .SH OPTIONS .TP \fB\-\-projectdir\fR \fI\,PROJECTDIR\/\fR Directory with project (defaults to CWD) .TP \fB\-\-distro\fR \fI\,DIST\/\fR Use distribution metadata specified by DIST yaml file .TP \fB\-\-multispec\fR \fI\,MULTISPEC\/\fR Use MULTISPEC yaml file to fill the TEMPLATE file .TP \fB\-\-multispec\-selector\fR \fI\,MULTISPEC_SELECTOR\/\fR Selectors for the multispec file .TP \fB\-\-spec\fR \fI\,SPEC\/\fR Use SPEC yaml file to fill the TEMPLATE file .TP \fB\-\-output\fR \fI\,OUTPUT\/\fR Write result to OUTPUT file instead of stdout .TP \fB\-\-macros\-from\fR \fI\,PROJECTDIR\/\fR Load variables from PROJECTDIR .TP \fB\-\-container\fR \fI\,CONTAINER_TYPE\/\fR Container type, e.g. 'docker' .TP \fB\-\-macro\fR \fI\,MACRO\/\fR Define distgen's macro .TP \fB\-\-max\-passes\fR \fI\,PASSES\/\fR Maximum number of rendering passes, defaults to 1 (== no re\-rendering) .TP \fB\-\-template\fR \fI\,TEMPLATE\/\fR Use TEMPLATE file, e.g. docker.tpl or a template string, e.g. "{{ config.docker.from }}" .TP \fB\-\-multispec\-combinations\fR Print available multispec combinations .SH AUTHOR .nf John Doe .fi .SH DISTRIBUTION The latest version of example may be downloaded from .UR http://example.com .UE argparse-manpage-4.5/examples/raw-description/setup.cfg000066400000000000000000000001051450337362100233450ustar00rootroot00000000000000[build_manpages] manpages = man/dg.1:object=parser:pyfile=bin/dg argparse-manpage-4.5/examples/raw-description/setup.py000066400000000000000000000017261450337362100232500ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Example of argparse taken from: # https://pagure.io/copr/copr/blob/a4feb01bc35b8554f503d41795e7a184ff929dd4/f/cli/copr_cli import os import sys from setuptools import setup, find_packages # Just to make sure that build_manpage can be found. sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../..')) from build_manpages.build_manpages \ import build_manpages, get_build_py_cmd, get_install_cmd from setuptools.command.install import install from distutils.command.build import build setup( name='example', description='This project does nothing.', long_description=('Long description of the project.'), author='John Doe', author_email='jd@example.com', version='0.1.0', url='http://example.com', packages=find_packages(), scripts=['bin/dg'], cmdclass={ 'build_manpages': build_manpages, 'build': get_build_py_cmd(build), 'install': get_install_cmd(install), }, ) argparse-manpage-4.5/examples/resalloc/000077500000000000000000000000001450337362100202225ustar00rootroot00000000000000argparse-manpage-4.5/examples/resalloc/bin/000077500000000000000000000000001450337362100207725ustar00rootroot00000000000000argparse-manpage-4.5/examples/resalloc/bin/resalloc000077500000000000000000000060041450337362100225240ustar00rootroot00000000000000#!/usr/bin/env python # Resalloc client. # Copyright (C) 2017 Red Hat, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import sys import argparse from resalloc.client import Ticket, Connection from resalloc.version import resalloc_version parser = argparse.ArgumentParser() parser.add_argument( "--connection", dest="connection", default="http://localhost:49100") parser.add_argument( '--version', action='version', version='%(prog)s (client) {0}'.format(resalloc_version)) subparsers = parser.add_subparsers(title="actions", dest='subparser') subparsers.required = True parser_new_ticket = subparsers.add_parser( "ticket", help="Create ticket") parser_new_ticket.add_argument( "--tag", dest="tags", action="append", required=True, help="What tag the Resource should have") parser_get_ticket = subparsers.add_parser( "ticket-check", help="Obtain ticket") parser_get_ticket.add_argument( "ticket", help="Get the ticket") parser_wait_ticket = subparsers.add_parser( "ticket-wait", help="Wait till ticket is ready and write the output" ) parser_wait_ticket.add_argument( "ticket", help="ID of ticket to wait for") parser_close_ticket = subparsers.add_parser( "ticket-close", help="Close a ticket") parser_close_ticket.add_argument( "ticket", help="ID of ticket to be closed") def main(): try: arg = parser.parse_args() conn = Connection(arg.connection) if 'ticket' == arg.subparser: ticket = conn.newTicket(arg.tags) print(ticket.id) elif 'ticket-check' == arg.subparser: ticket = conn.getTicket(arg.ticket) if ticket.collect(): sys.stdout.write(str(ticket.output)) else: sys.stderr.write("ticket is still not processed\n") return 1 elif 'ticket-wait' == arg.subparser: ticket = conn.getTicket(arg.ticket) output = ticket.wait() print(str(output)) elif 'ticket-close' == arg.subparser: ticket = conn.getTicket(arg.ticket) ticket.close() else: assert(0) except KeyboardInterrupt: sys.stderr.write("\nInterrupted by user.") sys.exit(1) if __name__ == "__main__": sys.exit(main()) argparse-manpage-4.5/examples/resalloc/bin/resalloc-maint000077500000000000000000000040261450337362100236340ustar00rootroot00000000000000#!/usr/bin/env python # Resalloc administrating tool. # Copyright (C) 2017 Red Hat, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import sys import argparse from resallocserver.maint import Maintainer parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(title="actions", dest='subparser') subparsers.required = True parser.add_argument("--duh", help="huhh") p_list = subparsers.add_parser( "resource-list", help="List available resources") p_list.add_argument( "--up", dest='up', action='store_true', help="List only ready-to-take resources") parser_resource_delete = subparsers.add_parser( "resource-delete", help="Delete resource") parser_resource_delete.add_argument( "resource", help="The resource ID") p_t_list = subparsers.add_parser( "ticket-list", help="List not-yet-closed tickets") def main(): try: args = parser.parse_args() maint = Maintainer() if 'resource-list' in args.subparser: maint.resource_list(up=args.up) elif 'resource-delete' in args.subparser: maint.resource_delete(args.resource) elif 'ticket-list' in args.subparser: maint.ticket_list() except KeyboardInterrupt: sys.stderr.write("\nInterrupted by user.") sys.exit(1) if __name__ == "__main__": sys.exit(main()) argparse-manpage-4.5/examples/resalloc/expected/000077500000000000000000000000001450337362100220235ustar00rootroot00000000000000argparse-manpage-4.5/examples/resalloc/expected/man/000077500000000000000000000000001450337362100225765ustar00rootroot00000000000000argparse-manpage-4.5/examples/resalloc/expected/man/resalloc-maint.1000066400000000000000000000021431450337362100255720ustar00rootroot00000000000000.TH RESALLOC\-MAINT "1" "2023\-04\-09" "resalloc 0" "Generated Python Manual" .SH NAME resalloc\-maint .SH SYNOPSIS .B resalloc\-maint [-h] [--duh DUH] {resource-list,resource-delete,ticket-list} ... .SH OPTIONS .TP \fB\-\-duh\fR \fI\,DUH\/\fR huhh .SH ACTIONS .TP \fBresalloc\-maint\fR \fI\,resource\-list\/\fR List available resources .TP \fBresalloc\-maint\fR \fI\,resource\-delete\/\fR Delete resource .TP \fBresalloc\-maint\fR \fI\,ticket\-list\/\fR List not\-yet\-closed tickets .SH COMMAND \fI\,'resalloc\-maint resource\-list'\/\fR usage: resalloc\-maint resource\-list [\-h] [\-\-up] .SH OPTIONS \fI\,'resalloc\-maint resource\-list'\/\fR .TP \fB\-\-up\fR List only ready\-to\-take resources .SH COMMAND \fI\,'resalloc\-maint resource\-delete'\/\fR usage: resalloc\-maint resource\-delete [\-h] resource .TP \fBresource\fR The resource ID .SH COMMAND \fI\,'resalloc\-maint ticket\-list'\/\fR usage: resalloc\-maint ticket\-list [\-h] .SH AUTHOR .nf Pavel Raiskup .fi .SH DISTRIBUTION The latest version of resalloc may be downloaded from .UR https://github.com/praiskup/resalloc .UE argparse-manpage-4.5/examples/resalloc/expected/man/resalloc.1000066400000000000000000000025071450337362100244700ustar00rootroot00000000000000.TH RESALLOC "1" "2023\-04\-09" "resalloc 0" "Generated Python Manual" .SH NAME resalloc .SH SYNOPSIS .B resalloc [-h] [--connection CONNECTION] [--version] {ticket,ticket-check,ticket-wait,ticket-close} ... .SH OPTIONS .TP \fB\-\-connection\fR \fI\,CONNECTION\/\fR .TP \fB\-\-version\fR show program's version number and exit .SH ACTIONS .TP \fBresalloc\fR \fI\,ticket\/\fR Create ticket .TP \fBresalloc\fR \fI\,ticket\-check\/\fR Obtain ticket .TP \fBresalloc\fR \fI\,ticket\-wait\/\fR Wait till ticket is ready and write the output .TP \fBresalloc\fR \fI\,ticket\-close\/\fR Close a ticket .SH COMMAND \fI\,'resalloc ticket'\/\fR usage: resalloc ticket [\-h] \-\-tag TAGS .SH OPTIONS \fI\,'resalloc ticket'\/\fR .TP \fB\-\-tag\fR \fI\,TAGS\/\fR What tag the Resource should have .SH COMMAND \fI\,'resalloc ticket\-check'\/\fR usage: resalloc ticket\-check [\-h] ticket .TP \fBticket\fR Get the ticket .SH COMMAND \fI\,'resalloc ticket\-wait'\/\fR usage: resalloc ticket\-wait [\-h] ticket .TP \fBticket\fR ID of ticket to wait for .SH COMMAND \fI\,'resalloc ticket\-close'\/\fR usage: resalloc ticket\-close [\-h] ticket .TP \fBticket\fR ID of ticket to be closed .SH AUTHOR .nf Pavel Raiskup .fi .SH DISTRIBUTION The latest version of resalloc may be downloaded from .UR https://github.com/praiskup/resalloc .UE argparse-manpage-4.5/examples/resalloc/requirements.txt000066400000000000000000000000001450337362100234740ustar00rootroot00000000000000argparse-manpage-4.5/examples/resalloc/resalloc/000077500000000000000000000000001450337362100220265ustar00rootroot00000000000000argparse-manpage-4.5/examples/resalloc/resalloc/__init__.py000066400000000000000000000000001450337362100241250ustar00rootroot00000000000000argparse-manpage-4.5/examples/resalloc/resalloc/client.py000066400000000000000000000000631450337362100236550ustar00rootroot00000000000000class Ticket: pass class Connection: pass argparse-manpage-4.5/examples/resalloc/resalloc/version.py000066400000000000000000000000271450337362100240640ustar00rootroot00000000000000resalloc_version = '0' argparse-manpage-4.5/examples/resalloc/resallocserver/000077500000000000000000000000001450337362100232555ustar00rootroot00000000000000argparse-manpage-4.5/examples/resalloc/resallocserver/__init__.py000066400000000000000000000000001450337362100253540ustar00rootroot00000000000000argparse-manpage-4.5/examples/resalloc/resallocserver/maint.py000066400000000000000000000000331450337362100247330ustar00rootroot00000000000000class Maintainer: pass argparse-manpage-4.5/examples/resalloc/setup.cfg000066400000000000000000000002221450337362100220370ustar00rootroot00000000000000[build_manpages] manpages = man/resalloc.1:object=parser:pyfile=bin/resalloc man/resalloc-maint.1:object=parser:pyfile=bin/resalloc-maint argparse-manpage-4.5/examples/resalloc/setup.py000066400000000000000000000043011450337362100217320ustar00rootroot00000000000000# Resalloc setup script. # Copyright (C) 2017 Red Hat, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import os, sys from setuptools import setup, find_packages from resalloc.version import resalloc_version from os import listdir, path # For the manual pages generator. from setuptools.command.build_py import build_py from setuptools.command.install import install try: sys.path = [os.path.join(os.getcwd(), 'build_manpages')] + sys.path from build_manpages.build_manpages \ import build_manpages, get_build_py_cmd, get_install_cmd except: print("=======================================") print("Use 'git submodule update --init' first") print("=======================================") raise project = "resalloc" datadir = "share" pkgdatadir = datadir + "/" + project def get_requirements(): with open('requirements.txt') as f: return f.read().splitlines() long_description=""" Resource allocator """.strip() setup( name=project, version=resalloc_version, description='Client/server application for maintaining (expensive) resources', long_description=long_description, author='Pavel Raiskup', author_email='praiskup@redhat.com', license='GPLv2+', url='https://github.com/praiskup/resalloc', platforms=['any'], packages=find_packages(), scripts=['bin/resalloc', 'bin/resalloc-maint'], install_requires=get_requirements(), cmdclass={ 'build_manpages': build_manpages, 'build_py': get_build_py_cmd(build_py), 'install': get_install_cmd(install), }, ) argparse-manpage-4.5/pylintrc000066400000000000000000000067431450337362100164010ustar00rootroot00000000000000# Copr clients pylint configuration [MASTER] # Pickle collected data for later comparisons. persistent=no init-hook= import os import subprocess gitrootdir = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode("utf-8").strip() sys.path.insert(0, os.path.join(gitrootdir, '.pylintpath')) [MESSAGES CONTROL] # Reasoning for wide warning ignore # --------------------------------- # import-error # This is to fix our CI where we do not have all the build requirements for # all our sub-components. We can afford not listening to this error because # our packaging CI would discover the problems anyways. # useless-object-inheritance # We need to keep compatibility with Python 2 for EL7. # consider-using-f-string # We still support Python 2.7 (EL7) for clients. # unspecified-encoding # Python2.7: TypeError: 'encoding' is an invalid keyword argument for this function disable=import-error,useless-object-inheritance,super-with-arguments,consider-using-f-string,unspecified-encoding [VARIABLES] # A regular expression matching names used for dummy variables (i.e. not used). dummy-variables-rgx=_|dummy [BASIC] # Regular expression which should only match correct module names module-rgx=([a-zA-Z_][a-zA-Z0-9_]+)$ # Regular expression which should only match correct module level names const-rgx=(([a-zA-Z_][a-zA-Z0-9_]*)|(__.*__))$ # Regular expression which should only match correct class names class-rgx=[a-zA-Z_][a-zA-Z0-9_]+$ # Regular expression which should only match correct function names function-rgx=[a-z_][a-zA-Z0-9_]{,42}$ # Regular expression which should only match correct method names method-rgx=[a-z_][a-zA-Z0-9_]{,42}$ # Regular expression which should only match correct instance attribute names attr-rgx=[a-z_][a-zA-Z0-9_]{,30}$ # Regular expression which should only match correct argument names argument-rgx=[a-z_][a-zA-Z0-9_]{,30}$ # Regular expression which should only match correct variable names variable-rgx=[a-z_][a-zA-Z0-9_]{,30}$ # Regular expression which should only match correct list comprehension / # generator expression variable names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Regular expression which should only match correct class sttribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,42}|(__.*__))$ # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata [DESIGN] # Maximum number of arguments for function / method max-args=10 # Maximum number of locals for function / method body max-locals=20 # Maximum number of return / yield for function / method body max-returns=6 # Maximum number of branch for function / method body max-branches=20 # Maximum number of statements in function / method body max-statements=50 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of attributes for a class (see R0902). max-attributes=7 # Minimum number of public methods for a class (see R0903). min-public-methods=1 # Maximum number of public methods for a class (see R0904). max-public-methods=20 [FORMAT] # Maximum number of characters on a single line. max-line-length=120 # Maximum number of lines in a module max-module-lines=1000 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes= argparse-manpage-4.5/pyproject.toml000066400000000000000000000004011450337362100175070ustar00rootroot00000000000000[build-system] requires = [ "setuptools", "tomli; python_version >= '3' and python_version < '3.11'", "toml; python_version < '3'", "packaging", ] [tool.build_manpages] manpages = [ "some-file.1:pyfile=some-file:function=get_parser", ] argparse-manpage-4.5/pytest.ini000066400000000000000000000000351450337362100166270ustar00rootroot00000000000000[pytest] norecursedirs = rpm argparse-manpage-4.5/requirements.txt000066400000000000000000000000001450337362100200520ustar00rootroot00000000000000argparse-manpage-4.5/rpm/000077500000000000000000000000001450337362100153765ustar00rootroot00000000000000argparse-manpage-4.5/rpm/.gitignore000066400000000000000000000000651450337362100173670ustar00rootroot00000000000000*.spec *.tar.gz *.src.rpm noarch/ argparse-manpage*/ argparse-manpage-4.5/rpm/Makefile000066400000000000000000000017651450337362100170470ustar00rootroot00000000000000PYTHON := python3 BUILD_HELPER := ./build-helper VERSION = $(shell $(BUILD_HELPER) --version) RELEASE = $(shell $(BUILD_HELPER) --release) DATE = $(shell date +'%a %b %d %Y') TARBALL = argparse-manpage-$(VERSION).tar.gz all: argparse-manpage.spec argparse-manpage-$(VERSION).tar.gz .PHONY: $(TARBALL) $(TARBALL): rm -f $(TARBALL) cd .. && python3 setup.py sdist && cp "dist/argparse-manpage-$(VERSION).tar.gz" rpm/ argparse-manpage.spec: argparse-manpage.spec.tpl ../setup.py @echo " GEN $@" ; \ rm -f "$@.output" ; \ sed -e "s|@VERSION@|$(VERSION)|g" \ -e "s|@RELEASE@|$(RELEASE)|g" \ -e "s|@DATE@|$(DATE)|g" \ $< > $@.output ; \ chmod 0444 "$@.output" ; \ mv -f "$@.output" "$@" # move when successful srpm: all dir=`pwd` ; \ rm *.src.rpm ; \ rpmbuild --define "_sourcedir $$dir" \ --define "_rpmdir $$dir" \ --define "_builddir $$dir" \ --define "_specdir $$dir" \ --define "_srcrpmdir $$dir" \ -bs *.spec clean: rm -rf *.src.rpm *.tar.gz *.spec noarch argparse-manpage-4.5/rpm/argparse-manpage.spec.tpl000066400000000000000000000075371450337362100222760ustar00rootroot00000000000000%if 0%{?fedora} || 0%{?rhel} >= 9 %bcond_without pyproject %bcond_with python2 %bcond_with python3 %else %bcond_with pyproject %if 0%{?rhel} > 7 %bcond_with python2 %bcond_without python3 %else %bcond_without python2 %bcond_with python3 %endif %endif %bcond_without check %global sum() Build manual page from %* ArgumentParser object %global desc \ Generate manual page an automatic way from ArgumentParser object, so the \ manpage 1:1 corresponds to the automatically generated --help output. The \ manpage generator needs to known the location of the object, user can \ specify that by (a) the module name or corresponding python filename and \ (b) the object name or the function name which returns the object. \ There is a limited support for (deprecated) optparse objects, too. Name: argparse-manpage Version: @VERSION@ Release: @RELEASE@%{?dist} Summary: %{sum Python} BuildArch: noarch License: Apache-2.0 URL: https://github.com/praiskup/%{name} Source0: https://github.com/praiskup/%{name}/archive/v%{version}/%{name}-%{version}.tar.gz %if %{with python2} BuildRequires: python2-setuptools python2-devel BuildRequires: python2-packaging BuildRequires: python2-toml %if %{with check} %if 0%{?rhel} && 0%{?rhel} == 7 BuildRequires: pytest %else BuildRequires: python2-pytest %endif %endif %endif %if %{with python3} BuildRequires: python3-setuptools python3-devel BuildRequires: python3-packaging BuildRequires: python3-tomli %if %{with check} BuildRequires: python3-pytest %endif %endif %if %{with pyproject} BuildRequires: python3-devel # EL9 needs this explicitly BuildRequires: pyproject-rpm-macros %if %{with check} BuildRequires: python3-pytest %endif %endif %if %{with python3} || %{with pyproject} Requires: python3-%name = %version-%release %else Requires: python2-%name = %version-%release %endif %description %desc %package -n python2-%name Summary: %{sum Python 2} Requires: python2-setuptools Requires: python2-toml %description -n python2-%name %{desc} %package -n python3-%name Summary: %{sum Python 3} %if %{without pyproject} Requires: python3-setuptools %endif %description -n python3-%name %{desc} %if %{with pyproject} %pyproject_extras_subpkg -n python3-%{name} setuptools %endif %prep %setup -q %if %{with pyproject} %generate_buildrequires %pyproject_buildrequires %endif %build %if %{with python2} %py2_build %endif %if %{with python3} %py3_build %endif %if %{with pyproject} %pyproject_wheel %endif %install %if %{with python2} %py2_install %endif %if %{with python3} %py3_install %endif %if %{with pyproject} %pyproject_install %endif %if %{with check} %check %if %{with python2} PYTHONPATH=%buildroot%python2_sitearch %__python2 -m pytest -vv %endif %if %{with python3} PYTHONPATH=%buildroot%python3_sitearch %__python3 -m pytest -vv %endif %if %{with pyproject} %pytest %endif %endif %files %license LICENSE %{_bindir}/argparse-manpage %_mandir/man1/argparse-manpage.1.* %if %{with python3} || %{with pyproject} %python3_sitelib/argparse_manpage/cli.py %else %python2_sitelib/argparse_manpage/cli.py %endif %if %{with python2} %files -n python2-%name %license LICENSE %python2_sitelib/build_manpages %python2_sitelib/argparse_manpage %python2_sitelib/argparse_manpage-%{version}*.egg-info %exclude %python2_sitelib/argparse_manpages/cli.py %endif %if %{with python3} || %{with pyproject} %files -n python3-%name %license LICENSE %python3_sitelib/build_manpages %python3_sitelib/argparse_manpage %if %{with pyproject} %python3_sitelib/argparse_manpage-*dist-info %else %python3_sitelib/argparse_manpage-%{version}*.egg-info %endif %exclude %python3_sitelib/argparse_manpage/cli.py %endif %changelog * @DATE@ Pavel Raiskup - @VERSION@-@RELEASE@ - built from upstream, changelog ignored argparse-manpage-4.5/rpm/build-helper000077500000000000000000000002611450337362100176770ustar00rootroot00000000000000#! /bin/sh case $1 in --version) cd .. ; ${PYTHON-python3} setup.py --version ;; --release) echo "git.$(date +"%Y%m%d_%H%M%S")" ;; esac argparse-manpage-4.5/setup.cfg000066400000000000000000000001401450337362100164140ustar00rootroot00000000000000[build_manpages] manpages = man/argparse-manpage.1:object=ap:pyfile=argparse_manpage/cli.py argparse-manpage-4.5/setup.py000066400000000000000000000030201450337362100163050ustar00rootroot00000000000000import os import sys from setuptools import setup, find_packages # Simplify bootstrapping. We don't need to have an older argparse-manpage # installed to successfully build argparse-manpage. # pylint: disable=wrong-import-position ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) sys.path.append(ROOT_DIR) from build_manpages import __version__ from build_manpages import ( build_manpages, get_build_py_cmd, get_install_cmd, ) def get_readme(): with open(os.path.join(ROOT_DIR, 'README.md')) as fh: return ''.join(fh.readlines()) if sys.version_info >= (3,): install_requires = ['tomli;python_version<"3.11"'] else: install_requires = ['toml'] setup( name='argparse-manpage', version=__version__, url='https://github.com/praiskup/argparse-manpage', license='Apache-2.0', author='Gabriele Giammatteo', author_email='gabriele.giammatteo@eng.it', maintainer='Pavel Raiskup', maintainer_email='praiskup@redhat.com', packages=find_packages(), entry_points={ 'console_scripts': [ 'argparse-manpage=argparse_manpage.cli:main', ], }, description='Build manual page from python\'s ArgumentParser object.', long_description=get_readme(), long_description_content_type='text/markdown', cmdclass={ 'build_manpages': build_manpages, 'build_py': get_build_py_cmd(), 'install': get_install_cmd(), }, install_requires=install_requires, extras_require={ 'setuptools': ["setuptools"] }, ) argparse-manpage-4.5/tests/000077500000000000000000000000001450337362100157425ustar00rootroot00000000000000argparse-manpage-4.5/tests/argparse_testlib.py000066400000000000000000000015211450337362100216450ustar00rootroot00000000000000""" unit-tests helpers """ import os from platform import python_version from pkg_resources import parse_version from contextlib import contextmanager import pytest def skip_on_python_older_than(minimal_version, message, condition=None): """ Raise the "skip" exception if python doesn't match the minimal required version for the calling test case """ if condition is not None and not condition: return if parse_version(python_version()) < parse_version(minimal_version): generic_msg = "Python {0} required, have {1}".format( minimal_version, python_version(), ) raise pytest.skip("{0} ({1})".format(message, generic_msg)) @contextmanager def pushd(path): old_dir = os.getcwd() os.chdir(path) try: yield finally: os.chdir(old_dir) argparse-manpage-4.5/tests/extra.man000066400000000000000000000001201450337362100175530ustar00rootroot00000000000000[EXTRA SECTION] This is an extra section. /John Doe/ Developer extraordinaire. argparse-manpage-4.5/tests/test_basic.py000066400000000000000000000037321450337362100204410ustar00rootroot00000000000000import unittest import os.path import sys import argparse sys.path = [os.path.join(os.path.dirname(os.path.realpath(__file__)),'..')]+sys.path from argparse_testlib import skip_on_python_older_than from build_manpages.manpage import Manpage class Tests(unittest.TestCase): def test_backslash_escape(self): parser = argparse.ArgumentParser('duh') parser.add_argument("--jej", help="c:\\something") man = Manpage(parser) assert 'c:\\\\something' in str(man).split('\n') assert '.SH OPTIONS' in str(man).split('\n') def test_argument_groups(self): parser1 = argparse.ArgumentParser('duh') parser = parser1.add_argument_group('g1') parser.add_argument("--jej", help="c:\\something") parser2 = parser1.add_argument_group('g2') parser2.add_argument("--jej2", help="c:\\something") parser2.add_argument("--else", help="c:\\something") man = Manpage(parser1) self.assertIn('.SH G1', str(man).split('\n')) self.assertIn('.SH G2', str(man).split('\n')) self.assertNotIn('.SH OPTIONS', str(man).split('\n')) def test_aliases(self): skip_on_python_older_than("3", "Python 2 doesn't support aliases=") parser = argparse.ArgumentParser('aliases_test') subparsers = parser.add_subparsers(title="actions") parser_list = subparsers.add_parser( "list", # TEST: add an alias that should not be rendered in the output aliases=["ls"], help="List all the copr of the " "provided " ) manpage_lines = str(Manpage(parser)).split("\n") exp_line = '\\fBaliases_test\\fR \\fI\\,list\\/\\fR' not_exp_line = '\\fBaliases_test\\fR \\fI\\,ls\\/\\fR' assert exp_line in manpage_lines assert not_exp_line not in manpage_lines assert 1 == sum([1 if "COMMAND" in line else 0 for line in manpage_lines]) if __name__ == "__main__": unittest.main() argparse-manpage-4.5/tests/test_examples.py000066400000000000000000000162031450337362100211730ustar00rootroot00000000000000import errno import unittest import os import re import shutil import subprocess import sys import sysconfig from contextlib import contextmanager import pytest sys.path = [os.path.join(os.path.dirname(os.path.realpath(__file__)),'..')]+sys.path from argparse_testlib import skip_on_python_older_than, pushd def _mandir(prefix, num=1): data = sysconfig.get_path('data', vars={'base': prefix}) return os.path.join(data, 'share/man/man' + str(num)) @contextmanager def change_argv(argv): old_argv = sys.argv sys.argv = argv try: yield finally: sys.argv = old_argv def _rmtree(directory): try: shutil.rmtree(directory) except OSError as err: if err.errno != errno.ENOENT: raise def run_pip(args): environ = os.environ.copy() environ['PYTHONPATH'] = ':'.join(sys.path) from pip import __version__ pip_version = tuple([int(x) for x in __version__.split('.')[:2]]) if pip_version < (21, 3): subprocess.call( [sys.executable, '-m', 'pip'] + args + ["--use-feature=in-tree-build", "."], env=environ) else: subprocess.call([sys.executable, '-m', 'pip'] + args + ["."], env=environ) def run_setup_py(args): environ = os.environ.copy() environ['PYTHONPATH'] = ':'.join(sys.path) with change_argv(['setup.py'] + args): return subprocess.call([sys.executable, 'setup.py'] + args, env=environ) def run_one_installer(installer, args): """ Run 'pip .' or 'python setup.py ' """ skip_on_python_older_than( "3.10", "Too old Python version for testing PIP installation", installer == "pip", ) method = run_pip if installer == "pip" else run_setup_py method(args) def file_cmp(file1, file2, filter_string=None): with open(file1, 'r') as f1: with open(file2, 'r') as f2: a1 = f1.readlines() a2 = f2.readlines() if len(a1) != len(a2): # get the pretty diff assert a1 == a2 first = True for left, right in zip(a1, a2): if first: left = re.sub('[0-9]{4}\\\\-[0-9]{2}\\\\-[0-9]{2}', '!!DATE!!', left) left = left.replace("-dev", ".dev0") # issue #50, setuptools < v60 right = re.sub('[0-9]{4}\\\\-[0-9]{2}\\\\-[0-9]{2}', '!!DATE!!', right) first = False if filter_string is not None: left = filter_string(left) right = filter_string(right) assert left == right class TestAllExamples: def test_old_example(self): with pushd('examples/old_format'): try: os.remove('example.1') except OSError: pass run_setup_py(['build_manpage']) file_cmp('example.1', 'expected-output.1') def test_old_example_file_name(self): with pushd('examples/old_format_file_name'): try: os.remove('example.1') except OSError: pass run_setup_py(['build_manpage']) file_cmp('example.1', 'expected-output.1') @pytest.mark.parametrize("installer", ["pip", "setuppy"]) def test_copr(self, installer): with pushd('examples/copr'): name = 'copr-cli.1' prefix = '/usr' idir = os.path.join(os.getcwd(), installer + "_install_dir") mandir = os.path.join(idir, _mandir("usr/")) _rmtree(idir) run_one_installer(installer, ['install', '--root', idir, '--prefix', prefix]) def version_version_filter(string): return string.replace('[VERSION [VERSION ...]]', '[VERSION ...]') file_cmp(os.path.join(mandir, os.path.basename(name)), 'expected-output.1', filter_string=version_version_filter) file_cmp(name, 'expected-output.1', filter_string=version_version_filter) @pytest.mark.parametrize("installer", ["pip", "setuppy"]) def test_distgen(self, installer): with pushd('examples/raw-description'): name = 'man/dg.1' prefix = "/usr" idir = os.path.join(os.getcwd(), installer + "_install_dir") _rmtree(idir) mandir = os.path.join(idir, _mandir("usr/")) run_one_installer(installer, ['install', '--root', idir, '--prefix', prefix]) file_cmp(os.path.join(mandir, os.path.basename(name)), 'expected-output.1') file_cmp(name, 'expected-output.1') @pytest.mark.parametrize("installer", ["pip", "setuppy"]) def test_resalloc(self, installer): with pushd('examples/resalloc'): prefix = "/usr" idir = os.path.join(os.getcwd(), installer + "_install_dir") _rmtree(idir) mandir = os.path.join(idir, _mandir("usr/")) run_one_installer(installer, ['install', '--root', idir, '--prefix', prefix]) for name in ['man/resalloc.1', 'man/resalloc-maint.1']: file_cmp(os.path.join(mandir, os.path.basename(name)), 'expected/' + name) file_cmp(name, 'expected/' + name) @pytest.mark.parametrize("installer", ["pip", "setuppy"]) def test_argument_groups_example(self, installer): with pushd('examples/argument_groups'): prefix = "/usr" idir = os.path.join(os.getcwd(), installer + "_install_dir") _rmtree(idir) mandir = os.path.join(idir, _mandir("usr/")) run_one_installer(installer, ['install', '--root', idir, '--prefix', prefix]) compiled = os.path.join('man', 'test.1') base = os.path.basename(compiled) expected = os.path.join('expected', base) installed = os.path.join(mandir, base) file_cmp(installed, expected) file_cmp(compiled, expected) @pytest.mark.parametrize("installer", ["pip", "setuppy"]) def test_osc(self, installer): with pushd('examples/osc'): name = 'osc.1' prefix = '/usr' idir = os.path.join(os.getcwd(), installer + "_install_dir") mandir = os.path.join(idir, _mandir("usr/")) _rmtree(idir) run_one_installer(installer, ['install', '--root', idir, '--prefix', prefix]) file_cmp(os.path.join(mandir, os.path.basename(name)), 'expected-output.1') file_cmp(name, 'expected-output.1') @pytest.mark.parametrize("installer", ["pip", "setuppy"]) def test_pre_written_man_page(self, installer): with pushd('examples/pre-written-man-page'): name = 'psutils.1' prefix = '/usr' idir = os.path.join(os.getcwd(), installer + "_install_dir") mandir = os.path.join(idir, _mandir("usr/")) _rmtree(idir) run_one_installer(installer, ['install', '--root', idir, '--prefix', prefix]) file_cmp(os.path.join(mandir, name), name) if __name__ == "__main__": unittest.main() argparse-manpage-4.5/tests/test_script.py000066400000000000000000000137221450337362100206640ustar00rootroot00000000000000""" Tests for the 'argparse-manpage' script. """ import datetime import os import shutil import sys import subprocess import tempfile import time import warnings from packaging import version import setuptools from test_examples import run_setup_py from argparse_testlib import pushd SIMPLE_FILE_CONTENTS = """\ import argparse def get_parser(): parser = argparse.ArgumentParser({ap_arguments}) parser.add_argument("test") return parser def main(): print("here") get_parser().parse_args() if __name__ == "__main__": main() """ SIMPLE_OUTPUT = """\ .TH {NAME} "1" "{DATE}" "{name}{version}" "Generated Python Manual" .SH NAME {name} .SH SYNOPSIS .B {name} [-h] test .TP \\fBtest\\fR """ FULL_OUTPUT = """\ .TH {NAME} "3" "{DATE}" "Proj\\-On\\-Cmdline 1.alpha" "Some\\-long Manual Name" .SH NAME {name} \\- some description .SH SYNOPSIS .B {name} [-h] test .TP \\fBtest\\fR .SH EXTRA SECTION This is an extra section. .SH AUTHOR .nf John Doe Developer extraordinaire. .fi .nf Mr. Foo and friends .fi """ SETUP_PY_FILE_CONTENTS = """\ from build_manpages import build_manpages, get_install_cmd, get_build_py_cmd from setuptools import setup setup( cmdclass={ 'build_manpages': build_manpages, 'build_py': get_build_py_cmd(), 'install': get_install_cmd(), } ) """ PYPROJECT_TOML_FILE_CONTENTS = """\ [tool.build_manpages] manpages = [ "some-file.1:project_name=some-file:module=somefile:function=get_parser", ] """ DATE = datetime.datetime.utcfromtimestamp( int(os.environ.get('SOURCE_DATE_EPOCH', time.time())) ).strftime("%Y\\-%m\\-%d") class TestsArgparseManpageScript: def setup_method(self, _): self.workdir = tempfile.mkdtemp(prefix="argparse-manpage-tests-") os.environ["PYTHON"] = sys.executable os.environ["PYTHONPATH"] = os.getcwd() def teardown_method(self, _): shutil.rmtree(self.workdir) @staticmethod def _get_am_executable(): testdir = os.path.dirname(__file__) executable = os.path.join(testdir, '..', 'argparse-manpage') return executable def test_filepath_function(self): """ Test --pyfile && --function. """ name = "some-file" expname = r"some\-file" tested_executable = os.path.join(self.workdir, name) with open(tested_executable, "w+") as script_fd: script_fd.write(SIMPLE_FILE_CONTENTS.format(ap_arguments="")) cmd = [ self._get_am_executable(), "--pyfile", tested_executable, "--function", "get_parser", ] output = subprocess.check_output(cmd).decode("utf-8") assert output == SIMPLE_OUTPUT.format(name=expname, version="", NAME=expname.upper(), DATE=DATE) def test_filepath_prog(self): """ Test --pyfile --function with prog explicitly specified in ArgumentParser name. """ name = "some-file" expname = "progname" tested_executable = os.path.join(self.workdir, name) with open(tested_executable, "w+") as script_fd: script_fd.write(SIMPLE_FILE_CONTENTS.format(ap_arguments='"progname"')) cmd = [ self._get_am_executable(), "--pyfile", tested_executable, "--function", "get_parser", ] output = subprocess.check_output(cmd).decode("utf-8") name="progname" assert output == SIMPLE_OUTPUT.format(name=expname, version="", NAME=expname.upper(), DATE=DATE) def test_full_args(self): """ Submit as many commandline arguments as possible. """ name = "full_name" tested_executable = os.path.join(self.workdir, name) with open(tested_executable, "w+") as script_fd: script_fd.write(SIMPLE_FILE_CONTENTS.format(ap_arguments='"progname"')) cmd = [ self._get_am_executable(), "--pyfile", tested_executable, "--function", "get_parser", "--manual-section", "3", "--manual-title", "Some-long Manual Name", "--version", "1.alpha", "--author", "John Doe ", "--author", "Mr. Foo and friends", "--project-name", "Proj-On-Cmdline", "--description", "some description", "--long-description", "Some long description.", # unused "--include", "tests/extra.man", ] output = subprocess.check_output(cmd).decode("utf-8") name="progname" assert output == FULL_OUTPUT.format(name=name, NAME=name.upper(), DATE=DATE) def test_pyproject_toml(self): """ Test that we can read information from pyproject.toml. """ current_dir = os.getcwd() with pushd(self.workdir): with open("pyproject.toml", "w+") as script_fd: script_fd.write(PYPROJECT_TOML_FILE_CONTENTS.format(gitdir=current_dir)) with open("setup.py", "w+") as script_fd: script_fd.write(SETUP_PY_FILE_CONTENTS) modname = "somefile" name = r"some\-file" os.mkdir(modname) with open(os.path.join(modname, "__init__.py"), "w+") as script_fd: script_fd.write(SIMPLE_FILE_CONTENTS.format(ap_arguments="")) assert 0 == run_setup_py(["build"]) if version.parse(setuptools.__version__) >= version.parse("62.2.0"): with open("some-file.1") as script_fd: output = script_fd.read() assert output == SIMPLE_OUTPUT.format(name=name, version=" 0.0.0", NAME=name.upper(), DATE=DATE) else: warnings.warn("setuptools >= 62.2.0 required to generate man pages with pyprojects.toml") argparse-manpage-4.5/tox.ini000066400000000000000000000003241450337362100161120ustar00rootroot00000000000000[tox] # sync with .github/workflows/tox.yml! envlist = py{36,37,38,39,310,311} skipsdist = True [testenv] deps = pytest setuptools py{36,37,38,39,310}: tomli commands = python -m pytest -v {posargs}