Browse Source

[patatools] Replace subcommand manipulation by module `argdispatch` (#214)

* [patatools] Replace subcommand manipulation by module `argdispatch`

* [patatools] Behave well when no subcommand is given

* [pylint] Remove useless import, and fix import ordering
pull/216/head
Louis 9 years ago
parent
commit
482bfb217d
  1. 77
      patacrep/tools/__main__.py
  2. 5
      patacrep/tools/cache/__main__.py
  3. 3
      patacrep/tools/convert/__main__.py
  4. 2
      setup.py

77
patacrep/tools/__main__.py

@ -2,85 +2,26 @@
"""Command line client to :mod:`tools`""" """Command line client to :mod:`tools`"""
import argparse
import logging import logging
import operator
import os
import pkgutil
import re
import sys import sys
import argdispatch
import patacrep import patacrep
# Logging configuration # Logging configuration
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
LOGGER = logging.getLogger("patatools") LOGGER = logging.getLogger("patatools")
def _execlp(program, args):
"""Call :func:`os.execlp`, adding `program` as the first argument to itself."""
return os.execlp(program, program, *args)
def _iter_subcommands():
"""Iterate over subcommands.
The objects returned are tuples of:
- the name of the command;
- its description;
- the function to call to execute the subcommand.
"""
subcommands = []
# Get python subcommands
path = [os.path.join(item, "patacrep", "tools") for item in sys.path]
prefix = "patacrep.tools."
module_re = re.compile(r'{}(?P<subcommand>[^\.]*)\.__main__'.format(prefix))
for module_loader, name, _ in pkgutil.walk_packages(path, prefix):
match = module_re.match(name)
if match:
module = module_loader.find_module(match.string).load_module()
if hasattr(module, "SUBCOMMAND_DESCRIPTION"):
subcommands.append(match.groupdict()['subcommand'])
yield (
match.groupdict()['subcommand'],
getattr(module, "SUBCOMMAND_DESCRIPTION"),
module.main,
)
class ArgumentParser(argparse.ArgumentParser):
"""Proxy class to circumvent an :mod:`argparse` bug.
Contrarily to what documented, the `argparse.REMAINDER
<https://docs.python.org/3/library/argparse.html#nargs>`_ `nargs` setting
does not include the remainder arguments if the first one begins with `-`.
This bug is reperted as `17050 <https://bugs.python.org/issue17050>`_. This
class can be deleted once this bug has been fixed.
"""
def parse_args(self, args=None, namespace=None):
if args is None:
args = sys.argv[1:]
subcommands = [command[0] for command in set(_iter_subcommands())]
if len(args) > 0:
if args[0] in subcommands:
args = [args[0], "--"] + args[1:]
value = super().parse_args(args, namespace)
if hasattr(value, 'remainder'):
value.remainder = value.remainder[1:]
return value
def commandline_parser(): def commandline_parser():
"""Return a command line parser.""" """Return a command line parser."""
parser = ArgumentParser( parser = argdispatch.ArgumentParser(
prog="patatools", prog="patatools",
description=( description=(
"Miscellaneous tools for patacrep." "Miscellaneous tools for patacrep."
), ),
formatter_class=argparse.RawTextHelpFormatter, formatter_class=argdispatch.RawTextHelpFormatter,
) )
parser.add_argument( parser.add_argument(
@ -96,11 +37,7 @@ def commandline_parser():
) )
subparsers.required = True subparsers.required = True
subparsers.dest = "subcommand" subparsers.dest = "subcommand"
subparsers.add_submodules("patacrep.tools")
for command, message, function in sorted(_iter_subcommands(), key=operator.itemgetter(0)):
sub1 = subparsers.add_parser(command, help=message, add_help=False)
sub1.add_argument('remainder', nargs=argparse.REMAINDER)
sub1.set_defaults(function=function)
return parser return parser
@ -108,9 +45,7 @@ def main(args=None):
"""Main function""" """Main function"""
if args is None: if args is None:
args = sys.argv args = sys.argv
parser = commandline_parser() commandline_parser().parse_args(args[1:])
args = parser.parse_args(args[1:])
args.function(["patatools-{}".format(args.subcommand)] + args.remainder)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

5
patacrep/tools/cache/__main__.py

@ -1,4 +1,4 @@
"""`patatools cache` command: cache manipulation.""" """Perform operations on cache."""
import argparse import argparse
import logging import logging
@ -11,7 +11,6 @@ from patacrep import errors
from patacrep.songbook import open_songbook from patacrep.songbook import open_songbook
LOGGER = logging.getLogger("patatools.cache") LOGGER = logging.getLogger("patatools.cache")
SUBCOMMAND_DESCRIPTION = "Perform operations on cache."
def filename(name): def filename(name):
"""Check that argument is an existing, readable file name. """Check that argument is an existing, readable file name.
@ -27,7 +26,7 @@ def commandline_parser():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
prog="patatools cache", prog="patatools cache",
description=SUBCOMMAND_DESCRIPTION, description="Convert between song formats.",
formatter_class=argparse.RawTextHelpFormatter, formatter_class=argparse.RawTextHelpFormatter,
) )

3
patacrep/tools/convert/__main__.py

@ -1,4 +1,4 @@
"""`patatools.convert` command: convert between song formats""" """Convert between song formats."""
import os import os
import logging import logging
@ -10,7 +10,6 @@ from patacrep.utils import yesno
from patacrep.build import config_model from patacrep.build import config_model
LOGGER = logging.getLogger("patatools.convert") LOGGER = logging.getLogger("patatools.convert")
SUBCOMMAND_DESCRIPTION = "Convert between song formats"
def _usage(): def _usage():
return "patatools convert INPUTFORMAT OUTPUTFORMAT FILES" return "patatools convert INPUTFORMAT OUTPUTFORMAT FILES"

2
setup.py

@ -34,7 +34,7 @@ setup(
packages=find_packages(exclude=["test*"]), packages=find_packages(exclude=["test*"]),
license="GPLv2 or any later version", license="GPLv2 or any later version",
install_requires=[ install_requires=[
"unidecode", "jinja2", "ply", "pyyaml", "argdispatch", "unidecode", "jinja2", "ply", "pyyaml",
], ],
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [

Loading…
Cancel
Save