Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Python support matrix #64

Merged
merged 4 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python: ['3.7', '3.8', '3.9', '3.10']
python: ['3.9', '3.10', '3.11', '3.12', '3.13']
steps:
- name: Checkout source code
uses: actions/checkout@v3
Expand All @@ -37,7 +37,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
python-version: '3.13'
- name: Install dependencies
run: python -m pip install build
- name: Build a binary wheel and a source tarball
Expand Down
28 changes: 7 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
Create **man pages** for [click](https://github.com/pallets/click) application as easy as this:

```bash
python3 setup.py --command-packages=click_man.commands man_pages
click-man foo
```

where `foo` is the name of your script, as defined in [`console_scripts`](https://python-packaging.readthedocs.io/en/latest/command-line-scripts.html#the-console-scripts-entry-point).

→ Checkout the [debian packaging example](#debian-packages)

## What it does

*click-man* will generate one man page per command of your click CLI application specified in `console_scripts` in your `setup.py`.
*click-man* will generate one man page per command of your click CLI application specified in `console_scripts` in your `setup.py` / `setup.cfg` / `pyproject.toml`.

## Installation

Expand Down Expand Up @@ -47,29 +49,14 @@ To specify a target directory for the man pages, use the `--target` option:
click-man --target path/to/man/pages commandname
```

### Use with setuptools

**click-man** provides a sane setuptools command extension which can be used like the following:

```bash
python3 setup.py --command-packages=click_man.commands man_pages
```

or specify the man pages target directory:

```bash
python3 setup.py --command-packages=click_man.commands man_pages --target path/to/man/pages
```

### Automatic man page installation with setuptools and pip

This approach of installing man pages is problematic for various reasons:

#### (1) Man pages are a UNIX thing

Python in general and with that pip and setuptools are aimed to be platform independent.
Man pages are **not**: they are a UNIX thing which means setuptools does not provide a sane
solution to generate and install man pages.
Man pages are **not**: they are a UNIX thing which means setuptools does not provide a sane solution to generate and install man pages.
We should consider using automatic man page installation only with vendor specific packaging, e.g. for `*.deb` or `*.rpm` packages.

#### (2) Man pages are not compatible with Python virtualenvs
Expand All @@ -88,8 +75,7 @@ the version and behavior available in any given virtualenv.
#### (3) We want to generate man pages on the fly

First, we do not want to commit man pages to our source control.
We want to generate them on the fly. Either
during build or installation time.
We want to generate them on the fly, either during build or installation time.

With setuptools and pip we face two problems:

Expand All @@ -109,7 +95,7 @@ We override the rule provided by `dh_installman` to generate our man pages in ad

```Makefile
override_dh_installman:
python3 setup.py --command-packages=click_man.commands man_pages --target debian/tmp/manpages
click-man <executable> --target debian/tmp/manpages
dh_installman -O--buildsystem=pybuild
```

Expand Down
14 changes: 0 additions & 14 deletions click_man/__main__.py

This file was deleted.

5 changes: 0 additions & 5 deletions click_man/commands/__init__.py

This file was deleted.

67 changes: 0 additions & 67 deletions click_man/commands/man_pages.py

This file was deleted.

5 changes: 2 additions & 3 deletions click_man/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@
"""

import os
from distutils.version import LooseVersion

import click

from .man import ManPage

CLICK_VERSION = tuple(int(x) for x in click.__version__.split('.'))
CLICK_VERSION = tuple(int(x) for x in click.__version__.split('.')[:2])


def get_short_help_str(command, limit=45):
Expand Down Expand Up @@ -84,7 +83,7 @@ def write_man_pages(

commands = getattr(cli, 'commands', {})
for name, command in commands.items():
if LooseVersion(click.__version__) >= LooseVersion("7.0"):
if CLICK_VERSION >= (7, 0):
# Since Click 7.0, we have been able to mark commands as hidden
if command.hidden:
# Do not write a man page for a hidden command
Expand Down
43 changes: 31 additions & 12 deletions click_man/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,35 @@
"""

from datetime import datetime
import importlib.metadata
import os
from pkg_resources import iter_entry_points, get_distribution
import sys
from typing import Optional

import click

from click_man.core import write_man_pages


def _get_entry_point(name: str) -> Optional[importlib.metadata.EntryPoint]:
entry_points = importlib.metadata.entry_points()
if sys.version_info >= (3, 10):
console_scripts = entry_points.select(
group='console_scripts', name=name
)
else:
console_scripts = [
ep for ep in entry_points.get('console_scripts', [])
if ep.name == name
]

if len(console_scripts) < 1:
return None

# Only generate man pages for first console script
return tuple(console_scripts)[0]


@click.command(context_settings={'help_option_names': ['-h', '--help']})
@click.option(
'--target', '-t', default=os.path.join(os.getcwd(), 'man'),
Expand All @@ -26,7 +47,9 @@
)
@click.option('--man-version', help='Version to use in generated man page(s)')
@click.option('--man-date', help='Date to use in generated man page(s)')
@click.version_option(get_distribution('click-man').version, '-V', '--version')
@click.version_option(
importlib.metadata.version('click-man'), '-V', '--version'
)
@click.argument('name')
def cli(target, name, man_version, man_date):
"""
Expand All @@ -38,15 +61,11 @@ def cli(target, name, man_version, man_date):
The generated man pages are written to files in the directory given
by ``--target``.
"""
console_scripts = [
ep for ep in iter_entry_points('console_scripts', name=name)
]
if len(console_scripts) < 1:
entry_point = _get_entry_point(name)
if not entry_point:
raise click.ClickException(
'"{0}" is not an installed console script.'.format(name)
)
# Only generate man pages for first console script
entry_point = console_scripts[0]

# create target directory if it does not exist yet
try:
Expand All @@ -55,7 +74,7 @@ def cli(target, name, man_version, man_date):
pass

if not man_version:
man_version = entry_point.dist.version
man_version = entry_point.version

if man_date:
try:
Expand All @@ -66,20 +85,20 @@ def cli(target, name, man_version, man_date):
)

click.echo('Load entry point {0}'.format(name))
cli = entry_point.resolve()
cli = entry_point.load()

# If the entry point isn't a click.Command object, try to find it in the
# module
if not isinstance(cli, click.Command):
from importlib import import_module
from inspect import getmembers

if not entry_point.module_name:
if not entry_point.module:
raise click.ClickException(
'Could not find module name for "{0}".'.format(name)
)

ep_module = import_module(entry_point.module_name)
ep_module = import_module(entry_point.module)
ep_members = getmembers(
ep_module, lambda x: isinstance(x, click.Command),
)
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,21 @@ def read(fname):
author_email='[email protected]',
install_requires=[
'click',
'setuptools',
],
packages=find_packages(exclude=('tests', )),
entry_points={
'console_scripts': [
'click-man = click_man.__main__:cli',
]
},
python_requires='>=3.9',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3 :: Only',
'Topic :: Documentation',
],
)
Loading
Loading