click-contrib / click-man Goto Github PK
View Code? Open in Web Editor NEWAutomate generation of man pages for python click applications :star:
License: MIT License
Automate generation of man pages for python click applications :star:
License: MIT License
Add example for rpm packages and document as recipe in README.
Hi,
We use such setup:
from setuptools import setup, find_packages
setup(
name='ap',
version='1.0',
packages=find_packages(),
include_package_data=True,
install_requires=[
'Click'
],
entry_points='''
[console_scripts]
ap=main:cli
'''
)
with the dynamic sub-commands located in the [root]/scripts/commands and dynamic loading in the main.py
:
COMMANDS_FOLDER = os.path.join(os.path.dirname(__file__), 'scripts', 'commands')
class ApCli(click.MultiCommand):
def list_commands(self, ctx):
"""Provides names of all commands by searching the commands folder"""
rv = []
for filename in os.listdir(COMMANDS_FOLDER):
# find py files, ignore the ones that start with __ (f.e. __init__.py)
if filename.endswith('.py') and not filename.startswith('__'):
rv.append(filename[:-3])
rv.sort()
return rv
def get_command(self, ctx, name):
"""Returns compiled command for given name, expects that command has a function same as its name"""
ns = {}
fn = os.path.join(COMMANDS_FOLDER, name + '.py')
if os.path.isfile(fn):
with open(fn) as f:
code = compile(f.read(), fn, 'exec')
eval(code, ns, ns)
else:
return
if ns.get(name) is not None:
return ns[name]
@click.command(cls=ApCli)
@click.option('-v', '--verbose', is_flag=True, default=False, help='Enables verbose mode.')
@click.version_option()
@ap.pass_context
def cli(context, verbose):
"""Main entry point for the CLI"""
context.logger.isVerbose = verbose
However manual get generated only for our main entry point and for nothing else.. Any idea how can we fix this?
The presence of a date in the generated manpage tripped me up when I was trying to package this for Debian: I ended up having to use a bit of a hacky workaround to resolve it: https://salsa.debian.org/rpavlik-guest/click-man/-/blob/master/debian/rules#L21
Much better would be a way to disable inclusion of a date entirely, so that packages could set that option during their build.
By requiring a disutils Command to run click-man, you are also requiring that the user has a setup.py
file and an uninstalled package available locally. However, many (most?) packages these days document installation with pip install packagename
. And more and more packages are being distributed as wheels, which don't even contain a setup.py script. Naturally, a disutls Command does not work in these settings.
Therefore, click-man should provide its own command line tool (presumably implemented with click, which would need to be installed anyway). Then the tool could be pointed at either an already installed package (perhaps using whatever would be passed to python -m
) or a distribution package (whl/tar.gz) to generate man pages.
Then, using an installed package, the user would do something like this:
pip install foo
click-man --target path/to/man/pages foo
Of course, the foo
lib would need to support python -m foo
to run foo's commands for click-man foo
to work.
Or, if the user has a downloaded distribution file, then this would work:
click-man --target path/to/man/pages path/to/foo-1.0.0-py2.py3-none-any.whl
Presumably, click-man would look for foo/__main__.py
within the whl file.
[Click 7.0] allows us to define commands as 'hidden' by defining something along the lines of:
@cli.command(short_help="Foo some bar", hidden=True)
This is useful to use when deprecating a command.
Unfortunately, man pages are still generated for these 'hidden' commands.
The URL for the TravisCI status shield in the README.md file is pointing to the wrong repository, to https://travis-ci.org/timofurrer/click-man instead of https://travis-ci.org/click-contrib/click-man.
cmd.params.append(option)
cmd.callback = run_robot_job
self.add_command(cmd, Path(cmd.name).stem)
return super(ReservedCommand, self).parse_args(ctx, args)
When a docstring does not exist for a click group a few different errors are raised related to invalid processing of NoneType.
Example:
AttributeError: 'NoneType' object has no attribute 'split'
This isn't too hard to debug but would be nice to have an explicit exception when docstring is None.
As is documented in click here, the docstrings for click commands are expected to contain \b
characters before a paragraph which should not be made to wrap.
This allows better formatting of docstrings in the help output, without this it is impossible to make any kind of formatting of the help such as point form lists which describe the meaning of each click.Choice() option.
This works well for click's help output, but is also broken for the sphinx-click generated html docs, and for sphinx-man generated man pages.
Here are two examples to show the brokenness:
NAME
bst-show - Show elements in the pipeline
SYNOPSIS
bst show [OPTIONS] TARGET
DESCRIPTION
Show elements in the pipeline
By default this will only show the specified element, use the --deps option to show an entire pipeline.
FORMAT ~~~~~~ The --format option controls what should be printed for each element, the following symbols can be used in the format string:
%{name} The element name
%{key} The abbreviated cache key (if all sources are consistent)
%{full-key} The full cache key (if all sources are consistent)
%{state} cached, buildable, waiting or inconsistent
%{config} The element configuration
%{vars} Variable configuration
%{env} Environment settings
%{public} Public domain data
The value of the %{symbol} without the leading '%' character is understood as a pythonic formatting string, so python formatting features apply, examle:
build-stream show target.bst --format 'Name: %{name: ^20} Key: %{key: ^8} State: %{state}'
If you want to use a newline in a format string in bash, use the '$' modifier:
build-stream show target.bst --format $'---------- %{name} ----------1{vars}'
OPTIONS
-d, --deps [all|build|run]
Optionally specify a dependency scope to show
--order [stage|alpha]
Staging or alphabetic ordering of dependencies
-f, --format FORMAT
Format string for each element
-a, --arch TEXT
The target architecture (default: x86_64)
--variant TEXT
A variant of the specified target
09-May-2017 BST SHOW(1)
bst show --help
Usage: bst show [OPTIONS] TARGET
Show elements in the pipeline
By default this will only show the specified element, use the --deps
option to show an entire pipeline.
FORMAT
~~~~~~
The --format option controls what should be printed for each element,
the following symbols can be used in the format string:
%{name} The element name
%{key} The abbreviated cache key (if all sources are consistent)
%{full-key} The full cache key (if all sources are consistent)
%{state} cached, buildable, waiting or inconsistent
%{config} The element configuration
%{vars} Variable configuration
%{env} Environment settings
%{public} Public domain data
The value of the %{symbol} without the leading '%' character is understood
as a pythonic formatting string, so python formatting features apply,
examle:
build-stream show target.bst --format \
'Name: %{name: ^20} Key: %{key: ^8} State: %{state}'
If you want to use a newline in a format string in bash, use the '$'
modifier:
build-stream show target.bst --format \
$'---------- %{name} ----------\n%{vars}'
Options:
-d, --deps [all|build|run] Optionally specify a dependency scope to show
--order [stage|alpha] Staging or alphabetic ordering of dependencies
-f, --format FORMAT Format string for each element
-a, --arch TEXT The target architecture (default: x86_64)
--variant TEXT A variant of the specified target
--help Show this message and exit.
This is a relatively minor issue but the formatting on https://pypi.org/project/click-man seems broken - the markdown is rendered literally for some reason.
When I build the example click-man/examples/debian_pkg using debuild -us -uc
, I get the following log:
....
make[1]: Entering directory '/home/users/brunnel6/workspace/click-man/examples/debian_pkg'
python3 setup.py --command-packages=click_man.commands man_pages --target debian/tmp/manpages
running man_pages
Load entry point repo
Generate man pages for repo
dh_installman -O--buildsystem=pybuild
make[1]: Leaving directory '/home/users/brunnel6/workspace/click-man/examples/debian_pkg'
dh_python3 -O--buildsystem=pybuild
dh_perl -O--buildsystem=pybuild
dh_link -O--buildsystem=pybuild
dh_strip_nondeterminism -O--buildsystem=pybuild
dh_compress -O--buildsystem=pybuild
dh_fixperms -O--buildsystem=pybuild
dh_installdeb -O--buildsystem=pybuild
dh_gencontrol -O--buildsystem=pybuild
dh_md5sums -O--buildsystem=pybuild
dh_builddeb -O--buildsystem=pybuild
dpkg-deb: building package 'python3-repo' in '../python3-repo_0.1.0_all.deb'.
dpkg-genchanges >../repo_0.1.0_amd64.changes
dpkg-genchanges: including full source code in upload
dpkg-source --after-build debian_pkg
dpkg-buildpackage: full upload; Debian-native package (full source is included)
Now running lintian...
E: repo changes: bad-distribution-in-changes-file jessie
W: repo source: no-debian-copyright
W: repo source: out-of-date-standards-version 3.9.6 (current is 3.9.7)
E: python3-repo: no-copyright-file
W: python3-repo: manpage-has-bad-whatis-entry usr/share/man/man1/repo-clone.1.gz
W: python3-repo: manpage-has-bad-whatis-entry usr/share/man/man1/repo-commit.1.gz
W: python3-repo: manpage-has-bad-whatis-entry usr/share/man/man1/repo-copy.1.gz
W: python3-repo: manpage-has-bad-whatis-entry usr/share/man/man1/repo-delete.1.gz
W: python3-repo: manpage-has-bad-whatis-entry usr/share/man/man1/repo-setuser.1.gz
Finished running lintian.
The other warnings are because of example-specifc "mistakes", like "no-copyright-file", but in my project I also get the manpage-has-bad-whatis-entry, so I suppose it is a bug by click-man?
Many thanks for pushing out the 0.4.0 release to PyPi, but unfortunately the source archive file appears to have picked up a few oddities that make it difficult to package.
The file name, for some reason, is click-man-0.4.0.linux-x86_64.tar.gz
, not click-man-0.4.0.tar.gz
which would follow the pattern of click-man-0.3.0.tar.gz
and previous releases.
The contents of the file are... bizarre:
$ wget https://files.pythonhosted.org/packages/78/bf/72a69b18811db0d4df13f750c91e5b62c94c5ef4abeb948953f35762663a/click-man-0.4.0.linux-x86_64.tar.gz
--2020-03-10 22:21:01-- https://files.pythonhosted.org/packages/78/bf/72a69b18811db0d4df13f750c91e5b62c94c5ef4abeb948953f35762663a/click-man-0.4.0.linux-x86_64.tar.gz
Resolving files.pythonhosted.org (files.pythonhosted.org)... 151.101.1.63, 151.101.65.63, 151.101.129.63, ...
Connecting to files.pythonhosted.org (files.pythonhosted.org)|151.101.1.63|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10560 (10K) [application/x-tar]
Saving to: ‘click-man-0.4.0.linux-x86_64.tar.gz’
click-man-0.4.0.lin 100%[===================>] 10.31K --.-KB/s in 0s
2020-03-10 22:21:01 (54.6 MB/s) - ‘click-man-0.4.0.linux-x86_64.tar.gz’ saved [10560/10560]
$ tar tf click-man-0.4.0.linux-x86_64.tar.gz
./
./home/
./home/users/
./home/users/furrert/
./home/users/furrert/work/
./home/users/furrert/work/oss/
./home/users/furrert/work/oss/click-man/
./home/users/furrert/work/oss/click-man/env/
./home/users/furrert/work/oss/click-man/env/bin/
./home/users/furrert/work/oss/click-man/env/bin/click-man
./home/users/furrert/work/oss/click-man/env/lib/
./home/users/furrert/work/oss/click-man/env/lib/python3.7/
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man/
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man/__init__.py
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man/__main__.py
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man/__pycache__/
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man/__pycache__/__init__.cpython-37.pyc
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man/__pycache__/__main__.cpython-37.pyc
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man/__pycache__/core.cpython-37.pyc
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man/__pycache__/man.cpython-37.pyc
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man/commands/
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man/commands/__init__.py
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man/commands/__pycache__/
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man/commands/__pycache__/__init__.cpython-37.pyc
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man/commands/__pycache__/man_pages.cpython-37.pyc
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man/commands/man_pages.py
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man/core.py
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man/man.py
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man-0.4.0-py3.7.egg-info/
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man-0.4.0-py3.7.egg-info/PKG-INFO
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man-0.4.0-py3.7.egg-info/SOURCES.txt
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man-0.4.0-py3.7.egg-info/dependency_links.txt
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man-0.4.0-py3.7.egg-info/entry_points.txt
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man-0.4.0-py3.7.egg-info/requires.txt
./home/users/furrert/work/oss/click-man/env/lib/python3.7/site-packages/click_man-0.4.0-py3.7.egg-info/top_level.txt
...I'm a little reluctant to submit a PR for a new Fedora package build, working from the archive in its current state. Any way you could re-release the source tarball, cleaned up? Sorry for the hassle, and thanks!
Typer is a CLI python package built on click, so it stands to reason that this project should work just fine with CLIs built with typer.
Here's the output of the command to generate man pages :
poetry run click-man --target ./man my-cli
Load entry point my-cli
Error: Could not find click.Command object for "my-cli".
The entry point points to a typer.Typer
instance. The click object can be fetched using typer.main.get_command(app: typer.Typer)
, which should make it trivial to make the app work.
I've tried to make it work myself :
typer.main.get_command(app)
where app is my typer.Typer()
instance to a variablepoetry run click-man --target ./man my-cli
on itI may try to import click-man in my poetry scripts and run it using poetry run gen-man
or something, in order to make it work, but having typer support out of the box would be great.
Hi,
I realize from reading your README that you may not consider this a bug, this is fine I'm not arguing this, but I need some help to figure out how to do what the README recommends not doing, I've spent an entire day swimming in setuptools/distutils code and still have not resolved this.
Currently, I have a horrible work around which is to generate the man pages in tree and commit the result to my source tree, which means I need to manually re-run and re-commit man pages whenever my frontend CLI commands and/or documentation changes.
After this, I am able to specify with setup() 'data_files' that the man pages go into ${prefix}/share/man/man1, this part works very well because:
My related setup.py is here, and I tried to follow this blog post for the purpose of ensuring the man pages are automatically built in the build stage, but I can't seem to wrap my head around this or get it to work.
From what I understand, I should be able to:
build_py
with my own command by specifying cmdclass
in setup()build_py
"command" is runbuild_py
function, when click_man is finally guaranteed to existI am sadly not able to achieve the above, or have not understood the correct way to do this.
Any help here would be really greatly appreciated, thanks.
Seems that when running:
python3 setup.py --command-packages=click_man.commands man_pages
I dont see how I can tell it to generate man pages for each of the entry points defined in setup.py
Fix: #62
man.py, line 91
for option, description in self.options:
throws TypeError: cannot unpack non-iterable NoneType object
if CLI has a hidden option.
I just tried to build my application for debian 12 (bookworm) and discovered that the format of the generated man pages is broken. The reason for that is that in the recent click versions the docstring indentation isn't cleaned anymore by click.
Example click code:
def mycommand(ctx):
"""This is my command
ABCD
.SH EXAMPLE
Some example text ...
"""
Output in debian 11 with python3-click-man (0.4.1-2) and python3-click (7.1.2-1):
.SH DESCRIPTION
This is my command
.PP
ABCD
.PP
.SH EXAMPLE
Some example text ...
Output in debian 12 with python3-click-man (0.4.1-2) and python3-click (8.1.3-2):
.SH DESCRIPTION
This is my command
.PP
ABCD
.PP
.SH EXAMPLE
Some example text ...
As you can see the indentation is now also part of the man page in debian 12 with the result that indented formatting codes (like the .SH) aren't recognized.
All the examples assume –and some quick hacking suggests the code also assumes– that all projects use setuptools and are laid out with it in mind. My project uses GNU Autotools (because it does much more than just Python), but one of the pieces is a Python CLI app that uses Click. It should be possible to parse this Click app as well as any other, but nothing in the documentation suggests how to wire it so that it knows where to start.
Hi,
I've seen that click-man appears to generate man pages with blank lines,
which I'm given to understand are non-portable, by which I simply mean that
some versions of troff will not always do the "right" thing in the presence of
blank lines.
On submitting a patch to the project concerned I was told that the man
pages are automatically generated by click-man.
Do you agree that my patch to the above project is correct?
If so would you accept patches to make the output of click-man
match the modified man pages in the above patch?
Thanks,
Richard
Hello!
I just took the maintenance of this package in Fedora (let me know if you need anything..;) and it looks like we still don't have 0.4.2
because we rely on sources from PyPI. Could someone (probably @timofurrer) release it there?
(In our projects, we use this GitHub action to do it automatically -- I can send a PR if you want.)
Thanks in advance!
František
I would like to have the sections "copyright" "authors" & "exit status", like in
man man
:
EXIT-STATUS
0 erfolgreiche Programmausführung
1 Fehler beim Aufruf, in der Syntax oder in der Konfigurationsdatei
2 betriebsbedingter Fehler
3 Ein Kind-Prozess gab einen von Null verschiedenen EXIT-Status zurück.
16 Mindestens eine Seite/Datei oder ein Schlüsselwort gibt es nicht oder es wurden keine Treffer dafür gefunden.
man chmod
:
AUTHOR
Written by David MacKenzie and Jim Meyering.
COPYRIGHT
Copyright © 2016 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.
AttributeError: 'function' object has no attribute 'allow_extra_args'
Python version: 3.6
Command: python3 setup.py --command-packages=click_man.commands man_pages
Click allows you define options like so [1]:
@click.option('--public/--private', default=False)
In this case, two mutually exclusive options will appear for the command, --public
and --private
. In the above case, the default is False
which effectively means --private
is the default choice. However, looking at a rendered man page, I see something like so:
--public
/--private
I suspect this is because we need to special case these options but are not doing so.
Let's assume you have the command my-cmd
with the subcommand run
.
When you type man my-cmd
it shows run
as a subcommand, how it is supposed to be, but no information that you're able to type man my-cmd run
to get further information about the subcommand. This can be confusing for some users.
Docker solves this issue by writing this line under every subcommand:
See docker-push(1) for full documentation on the push command.
Maybe something similar would be a nice feature?
An example Click command has the following help text:
def convert(input_file_name, output_file_name):
"""
Converts the format of the \033[1minput file\033[0m and stores it in the \033[1moutput file\033[0m
"""
When invoking the --help option for the command the words "input file" and "output file" are displayed with a bold font.
However, the man page generated with python3 setup.py --command-packages=click_man.commands man_pages
displays it as [1minput file[0m
and [1moutput file[0m
.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.