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

Add '--man-version', '--man-date' parameters #40

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
21 changes: 9 additions & 12 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
language: python
python:
- '2.7'
- pypy
- '3.5'
- '3.6'
- '3.7'
- '3.8'

- '2.7'
- '3.5'
- '3.6'
- '3.7'
- '3.8'
- pypy
install:
- python setup.py build sdist
- pip install -r requirements-dev.txt

script: nosetests --verbosity 2

- pip install tox-travis
script:
- tox
deploy:
provider: pypi
user: tuxtimo
Expand Down
63 changes: 4 additions & 59 deletions click_man/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,13 @@
click-man - Generate man pages for click application
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module provides a click CLI command to
generate man pages from a click application.
This module provides the ability to run the click man command via
``python -m click_main``.

:copyright: (c) 2016 by Timo Furrer.
:license: MIT, see LICENSE for more details.
"""

import os
import click
from pkg_resources import iter_entry_points, get_distribution
from click_man.shell import cli

from click_man.core import write_man_pages


@click.command(context_settings={'help_option_names': ['-h', '--help']})
@click.option('--target', '-t', default=os.path.join(os.getcwd(), 'man'),
type=click.Path(file_okay=False, dir_okay=True, resolve_path=True),
help='Target location for the man pages')
@click.version_option(get_distribution('click-man').version, '-V', '--version')
@click.argument('name')
def cli(target, name):
"""
Generate man pages for the scripts defined in the ``console_scripts`` entry point.

The cli application is gathered from entry points of installed packages.

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:
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:
os.makedirs(target)
except OSError:
pass

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

# 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:
raise click.ClickException('Could not find module name for "{0}".'.format(name))
ep_module = import_module(entry_point.module_name)
ep_members = getmembers(ep_module, lambda x: isinstance(x, click.Command))

if len(ep_members) < 1:
raise click.ClickException('Could not find click.Command object for "{0}".'.format(name))
(ep_name, cli) = ep_members[0]
click.echo('Found alternate entry point {0} in {1}'.format(ep_name, name))

click.echo('Generate man pages for {0} in {1}'.format(name, target))
write_man_pages(name, cli, version=entry_point.dist.version, target_dir=target)


if __name__ == '__main__':
cli()
cli()
28 changes: 22 additions & 6 deletions click_man/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,32 @@ def get_short_help_str(command, limit=45):
return command.short_help or command.help and click.utils.make_default_short_help(command.help, limit) or ''


def generate_man_page(ctx, version=None):
def generate_man_page(ctx, version=None, date=None):
"""
Generate documentation for the given command.

:param click.Context ctx: the click context for the
cli application.
cli application.
:param str version: The version information to include in the man page.
:param str date: The date information to include in the man page.

:rtype: str
:returns: the generate man page from the given click Context.
:rtype: str
"""
# Create man page with the details from the given context
man_page = ManPage(ctx.command_path)
man_page.version = version
man_page.short_help = get_short_help_str(ctx.command)
man_page.description = ctx.command.help
man_page.synopsis = ' '.join(ctx.command.collect_usage_pieces(ctx))
man_page.options = [x.get_help_record(ctx) for x in ctx.command.params if isinstance(x, click.Option)]
man_page.options = [
x.get_help_record(ctx) for x in ctx.command.params
if isinstance(x, click.Option)
]

if date:
man_page.date = date

commands = getattr(ctx.command, 'commands', None)
if commands:
man_page.commands = [
Expand All @@ -52,7 +61,9 @@ def generate_man_page(ctx, version=None):
return str(man_page)


def write_man_pages(name, cli, parent_ctx=None, version=None, target_dir=None):
def write_man_pages(
name, cli, parent_ctx=None, version=None, target_dir=None, date=None,
):
"""
Generate man page files recursively
for the given click cli function.
Expand All @@ -62,6 +73,7 @@ def write_man_pages(name, cli, parent_ctx=None, version=None, target_dir=None):
:param click.Context parent_ctx: the parent click context
:param str target_dir: the directory where the generated
man pages are stored.
:param date: the date to include in the header
"""
ctx = click.Context(cli, info_name=name, parent=parent_ctx)

Expand All @@ -80,4 +92,8 @@ def write_man_pages(name, cli, parent_ctx=None, version=None, target_dir=None):
if command.hidden:
# Do not write a man page for a hidden command
continue
write_man_pages(name, command, parent_ctx=ctx, version=version, target_dir=target_dir)

write_man_pages(
name, command, parent_ctx=ctx, version=version,
target_dir=target_dir, date=date,
)
100 changes: 100 additions & 0 deletions click_man/shell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""
click-man - Generate man pages for click application
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This module provides a click CLI command to
generate man pages from a click application.

:copyright: (c) 2016 by Timo Furrer.
:license: MIT, see LICENSE for more details.
"""

from datetime import datetime
import os
from pkg_resources import iter_entry_points, get_distribution

import click

from click_man.core import write_man_pages


@click.command(context_settings={'help_option_names': ['-h', '--help']})
@click.option(
'--target', '-t', default=os.path.join(os.getcwd(), 'man'),
type=click.Path(file_okay=False, dir_okay=True, resolve_path=True),
help='Target location for the man pages'
)
@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.argument('name')
def cli(target, name, man_version, man_date):
"""
Generate man pages for the scripts defined in the ``console_scripts`` entry
point.

The cli application is gathered from entry points of installed packages.

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:
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:
os.makedirs(target)
except OSError:
pass

if not man_version:
man_version = entry_point.dist.version

if man_date:
try:
datetime.strptime(man_date, '%Y-%m-%d')
except ValueError:
raise click.ClickException(
'"{0}" is not a valid date.'.format(man_date)
)

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

# 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:
raise click.ClickException(
'Could not find module name for "{0}".'.format(name)
)

ep_module = import_module(entry_point.module_name)
ep_members = getmembers(
ep_module, lambda x: isinstance(x, click.Command),
)

if len(ep_members) < 1:
raise click.ClickException(
'Could not find click.Command object for "{0}".'.format(name)
)

ep_name, cli = ep_members[0]
click.echo(
'Found alternate entry point {0} in {1}'.format(ep_name, name)
)

click.echo('Generate man pages for {0} in {1}'.format(name, target))
write_man_pages(
name, cli, version=man_version, target_dir=target, date=man_date,
)
86 changes: 86 additions & 0 deletions tests/test_shell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""
Module to test CLI functionality of click-man package.
"""

import os

import click
from click.testing import CliRunner as CLIRunner
import mock

from click_man import shell


@mock.patch.object(shell, 'iter_entry_points')
def test_missing_entry_point(mock_entry_points):
mock_entry_points.return_value = iter([])

runner = CLIRunner()
result = runner.invoke(shell.cli, 'foo')

assert result.exit_code == 1, result
assert 'not an installed console script' in result.output.strip()

mock_entry_points.assert_called_once_with('console_scripts', name='foo')


@mock.patch('os.makedirs', new=mock.Mock())
@mock.patch.object(shell, 'write_man_pages')
@mock.patch.object(click, 'echo')
@mock.patch.object(shell, 'iter_entry_points')
def test_is_click_command(mock_entry_points, mock_echo, mock_write):
fake_target = os.path.join(os.getcwd(), 'man')
fake_command = click.Command(name='foo')
fake_version = '1.2.3'
entry_point = mock.Mock()
entry_point.resolve.return_value = fake_command
entry_point.dist.version = fake_version

mock_entry_points.return_value = iter([entry_point])

runner = CLIRunner()
result = runner.invoke(shell.cli, ['foo'])

assert result.exit_code == 0, result.output

mock_entry_points.assert_called_once_with('console_scripts', name='foo')
mock_echo.assert_has_calls([
mock.call('Load entry point foo'),
mock.call('Generate man pages for foo in %s' % fake_target),
])
mock_write.assert_called_once_with(
'foo', fake_command, version=fake_version, target_dir=fake_target,
date=None,
)


@mock.patch('os.makedirs', new=mock.Mock())
@mock.patch.object(shell, 'write_man_pages')
@mock.patch.object(click, 'echo')
@mock.patch.object(shell, 'iter_entry_points')
def test_man_date_version(mock_entry_points, mock_echo, mock_write):
fake_target = os.path.join(os.getcwd(), 'man')
fake_command = click.Command(name='foo')
entry_point = mock.Mock()
entry_point.resolve.return_value = fake_command

mock_entry_points.return_value = iter([entry_point])

runner = CLIRunner()
result = runner.invoke(
shell.cli,
['foo', '--man-version', '3.2.1', '--man-date', '2020-01-01'],
)

assert result.exit_code == 0, result.output

mock_entry_points.assert_called_once_with('console_scripts', name='foo')
entry_point.dist.version.assert_not_called()
mock_echo.assert_has_calls([
mock.call('Load entry point foo'),
mock.call('Generate man pages for foo in %s' % fake_target),
])
mock_write.assert_called_once_with(
'foo', fake_command, version='3.2.1', target_dir=fake_target,
date='2020-01-01',
)