feat: remove legacy command system
typer is the recommended approach, but any could be used. we have nothing significant to offer so better to just not.
This commit is contained in:
parent
fd76766f56
commit
a76cbf29e9
|
@ -1,6 +0,0 @@
|
||||||
|
|
||||||
``wuttjamaican.cmd.base``
|
|
||||||
=========================
|
|
||||||
|
|
||||||
.. automodule:: wuttjamaican.cmd.base
|
|
||||||
:members:
|
|
|
@ -1,8 +0,0 @@
|
||||||
|
|
||||||
``wuttjamaican.cmd.date_organize``
|
|
||||||
==================================
|
|
||||||
|
|
||||||
.. automodule:: wuttjamaican.cmd.date_organize
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. program-output:: wutta date-organize -h
|
|
|
@ -1,8 +0,0 @@
|
||||||
|
|
||||||
``wuttjamaican.cmd.make_appdir``
|
|
||||||
================================
|
|
||||||
|
|
||||||
.. automodule:: wuttjamaican.cmd.make_appdir
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. program-output:: wutta make-appdir -h
|
|
|
@ -1,23 +0,0 @@
|
||||||
|
|
||||||
``wuttjamaican.cmd``
|
|
||||||
====================
|
|
||||||
|
|
||||||
.. automodule:: wuttjamaican.cmd
|
|
||||||
:members:
|
|
||||||
|
|
||||||
The core framework is contained in :mod:`wuttjamaican.cmd.base`.
|
|
||||||
|
|
||||||
Note that :class:`~wuttjamaican.cmd.base.Command` serves as the base
|
|
||||||
class for top-level :term:`commands<command>` but it also functions as
|
|
||||||
the top-level ``wutta`` command.
|
|
||||||
|
|
||||||
Some :term:`subcommands<subcommand>` are available as well; these are
|
|
||||||
registered under the ``wutta`` command.
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
cmd.base
|
|
||||||
cmd.date_organize
|
|
||||||
cmd.make_appdir
|
|
||||||
cmd.setup
|
|
|
@ -1,8 +0,0 @@
|
||||||
|
|
||||||
``wuttjamaican.cmd.setup``
|
|
||||||
==========================
|
|
||||||
|
|
||||||
.. automodule:: wuttjamaican.cmd.setup
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. program-output:: wutta setup -h
|
|
|
@ -8,7 +8,6 @@
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
app
|
app
|
||||||
cmd
|
|
||||||
conf
|
conf
|
||||||
db
|
db
|
||||||
db.conf
|
db.conf
|
||||||
|
|
|
@ -43,13 +43,16 @@ be used to avoid unwanted splits.
|
||||||
Specifying via Command Line
|
Specifying via Command Line
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
All :term:`commands<command>` accept the ``-c`` or ``--config`` params:
|
While no :term:`commands<command>` are shipped with WuttJamaican,
|
||||||
|
certain other packages may ship with commands (notably, Rattail). The
|
||||||
|
convention is to accept a ``--config`` (or ``-c``) param on the
|
||||||
|
command line, e.g.:
|
||||||
|
|
||||||
.. code-block:: sh
|
.. code-block:: sh
|
||||||
|
|
||||||
wutta --config=myapp.conf
|
rattail --config=myapp.conf
|
||||||
|
|
||||||
wutta -c first.conf -c second.conf
|
rattail -c first.conf -c second.conf
|
||||||
|
|
||||||
|
|
||||||
Specifying via Environment Variable
|
Specifying via Environment Variable
|
||||||
|
|
|
@ -43,7 +43,5 @@ For more info see:
|
||||||
* :class:`~wuttjamaican.conf.WuttaConfig` and especially
|
* :class:`~wuttjamaican.conf.WuttaConfig` and especially
|
||||||
:meth:`~wuttjamaican.conf.WuttaConfig.get()`
|
:meth:`~wuttjamaican.conf.WuttaConfig.get()`
|
||||||
|
|
||||||
You can also define your own command line interface; see:
|
You can also define your own command line interface; see
|
||||||
|
:doc:`/narr/cli/index`.
|
||||||
* :class:`~wuttjamaican.commands.base.Command`
|
|
||||||
* :class:`~wuttjamaican.commands.base.Subcommand`
|
|
||||||
|
|
|
@ -39,16 +39,6 @@ docs = ["Sphinx", "sphinxcontrib-programoutput", "furo"]
|
||||||
tests = ["pytest-cov", "tox"]
|
tests = ["pytest-cov", "tox"]
|
||||||
|
|
||||||
|
|
||||||
[project.scripts]
|
|
||||||
wutta = "wuttjamaican.cmd.base:main"
|
|
||||||
|
|
||||||
|
|
||||||
[project.entry-points."wutta.subcommands"]
|
|
||||||
date-organize = "wuttjamaican.cmd.date_organize:DateOrganize"
|
|
||||||
make-appdir = "wuttjamaican.cmd.make_appdir:MakeAppDir"
|
|
||||||
setup = "wuttjamaican.cmd.setup:Setup"
|
|
||||||
|
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Homepage = "https://rattailproject.org/"
|
Homepage = "https://rattailproject.org/"
|
||||||
Repository = "https://kallithea.rattailproject.org/rattail-project/wuttjamaican"
|
Repository = "https://kallithea.rattailproject.org/rattail-project/wuttjamaican"
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# WuttJamaican -- Base package for Wutta Framework
|
|
||||||
# Copyright © 2023 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Wutta Framework.
|
|
||||||
#
|
|
||||||
# Wutta Framework 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 3 of the License, or (at your option) any
|
|
||||||
# later version.
|
|
||||||
#
|
|
||||||
# Wutta Framework 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
|
|
||||||
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
WuttJamaican - command line interface
|
|
||||||
|
|
||||||
For convenience, from the ``wuttjamaican.cmd`` namespace you can
|
|
||||||
access the following:
|
|
||||||
|
|
||||||
* :class:`~wuttjamaican.cmd.base.Command`
|
|
||||||
* :class:`~wuttjamaican.cmd.base.Subcommand`
|
|
||||||
"""
|
|
||||||
|
|
||||||
from .base import Command, Subcommand
|
|
|
@ -1,441 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# WuttJamaican -- Base package for Wutta Framework
|
|
||||||
# Copyright © 2023-2024 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Wutta Framework.
|
|
||||||
#
|
|
||||||
# Wutta Framework 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 3 of the License, or (at your option) any
|
|
||||||
# later version.
|
|
||||||
#
|
|
||||||
# Wutta Framework 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
|
|
||||||
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
WuttJamaican - command framework
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
from wuttjamaican import __version__
|
|
||||||
from wuttjamaican.conf import make_config
|
|
||||||
from wuttjamaican.util import load_entry_points
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Command:
|
|
||||||
"""
|
|
||||||
Primary command for the application.
|
|
||||||
|
|
||||||
A primary command will usually have multiple subcommands it can
|
|
||||||
run. The typical command line interface is like:
|
|
||||||
|
|
||||||
.. code-block:: none
|
|
||||||
|
|
||||||
<command> [command-options] <subcommand> [subcommand-options]
|
|
||||||
|
|
||||||
:class:`Subcommand` will contain most of the logic, in terms of
|
|
||||||
what actually happens when it runs. Top-level commands are mostly
|
|
||||||
a stub for sake of logically grouping the subcommands.
|
|
||||||
|
|
||||||
:param config: Optional config object to use.
|
|
||||||
|
|
||||||
Usually a command is being ran via actual command line, and
|
|
||||||
there is no config object yet so it must create one. (It does
|
|
||||||
this within its :meth:`run()` method.)
|
|
||||||
|
|
||||||
But if you already have a config object you can specify it here
|
|
||||||
and it will be used instead.
|
|
||||||
|
|
||||||
:param name: Optional value to assign to :attr:`name`. Usually
|
|
||||||
this is declared within the command class definition, but if
|
|
||||||
needed it can be provided dynamically.
|
|
||||||
|
|
||||||
:param stdout: Optional replacement to use for :attr:`stdout`.
|
|
||||||
|
|
||||||
:param stderr: Optional replacement to use for :attr:`stderr`.
|
|
||||||
|
|
||||||
:param subcommands: Optional dictionary to use for
|
|
||||||
:attr:`subcommands`, instead of loading those via entry points.
|
|
||||||
|
|
||||||
This base class also serves as the primary ``wutta`` command for
|
|
||||||
WuttJamaican. Most apps will subclass this and register their own
|
|
||||||
top-level command, then create subcommands as needed.
|
|
||||||
|
|
||||||
For more info see :doc:`/narr/cli/commands`.
|
|
||||||
|
|
||||||
.. attribute:: name
|
|
||||||
|
|
||||||
Name of the primary command, e.g. ``wutta``
|
|
||||||
|
|
||||||
.. attribute:: description
|
|
||||||
|
|
||||||
Description of the app itself or the primary command.
|
|
||||||
|
|
||||||
.. attribute:: version
|
|
||||||
|
|
||||||
Version string for the app or primary command.
|
|
||||||
|
|
||||||
.. attribute:: stdout
|
|
||||||
|
|
||||||
Reference to file-like object which should be used for writing
|
|
||||||
to STDOUT. By default this is just ``sys.stdout``.
|
|
||||||
|
|
||||||
.. attribute:: stderr
|
|
||||||
|
|
||||||
Reference to file-like object which should be used for writing
|
|
||||||
to STDERR. By default this is just ``sys.stderr``.
|
|
||||||
|
|
||||||
.. attribute:: subcommands
|
|
||||||
|
|
||||||
Dictionary of available subcommand classes, keyed by subcommand
|
|
||||||
name. These are usually loaded from setuptools entry points.
|
|
||||||
"""
|
|
||||||
name = 'wutta'
|
|
||||||
version = __version__
|
|
||||||
description = "Wutta Software Framework"
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
config=None,
|
|
||||||
name=None,
|
|
||||||
stdout=None,
|
|
||||||
stderr=None,
|
|
||||||
subcommands=None):
|
|
||||||
|
|
||||||
self.config = config
|
|
||||||
self.name = name or self.name
|
|
||||||
self.stdout = stdout or sys.stdout
|
|
||||||
self.stderr = stderr or sys.stderr
|
|
||||||
|
|
||||||
# nb. default entry point is like 'wutta_poser.subcommands'
|
|
||||||
safe_name = self.name.replace('-', '_')
|
|
||||||
self.subcommands = subcommands or load_entry_points(f'{safe_name}.subcommands')
|
|
||||||
if not self.subcommands:
|
|
||||||
|
|
||||||
# nb. legacy entry point is like 'wutta_poser.commands'
|
|
||||||
self.subcommands = load_entry_points(f'{safe_name}.commands')
|
|
||||||
if self.subcommands:
|
|
||||||
msg = (f"entry point group '{safe_name}.commands' uses deprecated name; "
|
|
||||||
f"please define '{safe_name}.subcommands' instead")
|
|
||||||
warnings.warn(msg, DeprecationWarning, stacklevel=2)
|
|
||||||
log.warning(msg)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def sorted_subcommands(self):
|
|
||||||
"""
|
|
||||||
Get the list of subcommand classes, sorted by name.
|
|
||||||
"""
|
|
||||||
return [self.subcommands[name]
|
|
||||||
for name in sorted(self.subcommands)]
|
|
||||||
|
|
||||||
def print_help(self):
|
|
||||||
"""
|
|
||||||
Print usage help text for the main command.
|
|
||||||
"""
|
|
||||||
self.parser.print_help()
|
|
||||||
|
|
||||||
def run(self, *args):
|
|
||||||
"""
|
|
||||||
Parse command line arguments and execute appropriate
|
|
||||||
subcommand.
|
|
||||||
|
|
||||||
Or, if requested, or args are ambiguous, show help for either
|
|
||||||
the top-level or subcommand.
|
|
||||||
|
|
||||||
Usually of course this method is invoked by way of command
|
|
||||||
line. But if you need to run it programmatically, you must
|
|
||||||
specify the full command line args *except* not the top-level
|
|
||||||
command name. So for example to run the equivalent of this
|
|
||||||
command line:
|
|
||||||
|
|
||||||
.. code-block:: sh
|
|
||||||
|
|
||||||
wutta setup --help
|
|
||||||
|
|
||||||
You could do this in Python::
|
|
||||||
|
|
||||||
from wuttjamaican.cmd import Command
|
|
||||||
|
|
||||||
cmd = Command()
|
|
||||||
assert cmd.name == 'wutta'
|
|
||||||
cmd.run('setup', '--help')
|
|
||||||
"""
|
|
||||||
# build arg parser
|
|
||||||
self.parser = self.make_arg_parser()
|
|
||||||
self.add_args()
|
|
||||||
|
|
||||||
# primary parser gets first pass at full args, and stores
|
|
||||||
# everything not used within args.argv
|
|
||||||
args = self.parser.parse_args(args)
|
|
||||||
if not args or not args.argv:
|
|
||||||
self.print_help()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# then argv should include <subcommand> [subcommand-options]
|
|
||||||
subcmd = args.argv[0]
|
|
||||||
if subcmd in self.subcommands:
|
|
||||||
if '-h' in args.argv or '--help' in args.argv:
|
|
||||||
subcmd = self.subcommands[subcmd](self)
|
|
||||||
subcmd.print_help()
|
|
||||||
sys.exit(0)
|
|
||||||
else:
|
|
||||||
self.print_help()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# we should be done needing to print help messages. now it's
|
|
||||||
# safe to redirect STDOUT/STDERR, if necessary
|
|
||||||
if args.stdout:
|
|
||||||
self.stdout = args.stdout
|
|
||||||
if args.stderr:
|
|
||||||
self.stderr = args.stderr
|
|
||||||
|
|
||||||
# make the config object
|
|
||||||
if not self.config:
|
|
||||||
self.config = self.make_config(args)
|
|
||||||
|
|
||||||
# invoke subcommand
|
|
||||||
log.debug("running command line: %s", sys.argv)
|
|
||||||
subcmd = self.subcommands[subcmd](self)
|
|
||||||
self.prep_subcommand(subcmd, args)
|
|
||||||
subcmd._run(*args.argv[1:])
|
|
||||||
|
|
||||||
# nb. must flush these in case they are file objects
|
|
||||||
self.stdout.flush()
|
|
||||||
self.stderr.flush()
|
|
||||||
|
|
||||||
def make_arg_parser(self):
|
|
||||||
"""
|
|
||||||
Must return a new :class:`argparse.ArgumentParser` instance
|
|
||||||
for use by the main command.
|
|
||||||
|
|
||||||
This will use :class:`CommandArgumentParser` by default.
|
|
||||||
"""
|
|
||||||
subcommands = ""
|
|
||||||
for subcmd in self.sorted_subcommands():
|
|
||||||
subcommands += f" {subcmd.name:<20s} {subcmd.description}\n"
|
|
||||||
|
|
||||||
epilog = f"""\
|
|
||||||
subcommands:
|
|
||||||
{subcommands}
|
|
||||||
|
|
||||||
also try: {self.name} <subcommand> -h
|
|
||||||
"""
|
|
||||||
|
|
||||||
return CommandArgumentParser(
|
|
||||||
prog=self.name,
|
|
||||||
description=self.description,
|
|
||||||
add_help=False,
|
|
||||||
usage=f"{self.name} [options] <subcommand> [subcommand-options]",
|
|
||||||
epilog=epilog,
|
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
||||||
|
|
||||||
def add_args(self):
|
|
||||||
"""
|
|
||||||
Configure args for the main command arg parser.
|
|
||||||
|
|
||||||
Anything you setup here will then be available when the
|
|
||||||
command runs. You can add arguments directly to
|
|
||||||
``self.parser``, e.g.::
|
|
||||||
|
|
||||||
self.parser.add_argument('--foo', default='bar', help="Foo value")
|
|
||||||
|
|
||||||
See also docs for :meth:`python:argparse.ArgumentParser.add_argument()`.
|
|
||||||
"""
|
|
||||||
parser = self.parser
|
|
||||||
|
|
||||||
parser.add_argument('-c', '--config', metavar='PATH',
|
|
||||||
action='append', dest='config_paths',
|
|
||||||
help="Config path (may be specified more than once)")
|
|
||||||
|
|
||||||
parser.add_argument('--plus-config', metavar='PATH',
|
|
||||||
action='append', dest='plus_config_paths',
|
|
||||||
help="Extra configs to load in addition to normal config")
|
|
||||||
|
|
||||||
parser.add_argument('-P', '--progress', action='store_true', default=False,
|
|
||||||
help="Report progress when relevant")
|
|
||||||
|
|
||||||
parser.add_argument('-V', '--version', action='version',
|
|
||||||
version=f"%(prog)s {self.version}")
|
|
||||||
|
|
||||||
parser.add_argument('--stdout', metavar='PATH', type=argparse.FileType('w'),
|
|
||||||
help="Optional path to which STDOUT should be written")
|
|
||||||
parser.add_argument('--stderr', metavar='PATH', type=argparse.FileType('w'),
|
|
||||||
help="Optional path to which STDERR should be written")
|
|
||||||
|
|
||||||
def make_config(self, args):
|
|
||||||
"""
|
|
||||||
Make the config object in preparation for running a subcommand.
|
|
||||||
|
|
||||||
By default this is a straightforward wrapper around
|
|
||||||
:func:`wuttjamaican.conf.make_config()`.
|
|
||||||
|
|
||||||
:returns: The new config object.
|
|
||||||
"""
|
|
||||||
return make_config(args.config_paths,
|
|
||||||
plus_files=args.plus_config_paths)
|
|
||||||
|
|
||||||
def prep_subcommand(self, subcommand, args):
|
|
||||||
"""
|
|
||||||
Prepare the subcommand for running, as needed.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class CommandArgumentParser(argparse.ArgumentParser):
|
|
||||||
"""
|
|
||||||
Custom argument parser for use with :class:`Command`.
|
|
||||||
|
|
||||||
This is based on standard :class:`python:argparse.ArgumentParser`
|
|
||||||
but overrides some of the parsing logic which is specific to the
|
|
||||||
primary command object, to separate command options from
|
|
||||||
subcommand options.
|
|
||||||
|
|
||||||
This is documented as FYI but you probably should not need to know
|
|
||||||
about or try to use this yourself. It will be used automatically
|
|
||||||
by :class:`Command` or a subclass thereof.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def parse_args(self, args=None, namespace=None):
|
|
||||||
args, argv = self.parse_known_args(args, namespace)
|
|
||||||
args.argv = argv
|
|
||||||
return args
|
|
||||||
|
|
||||||
|
|
||||||
class Subcommand:
|
|
||||||
"""
|
|
||||||
Base class for application subcommands.
|
|
||||||
|
|
||||||
Subcommands are where the real action happens. Each must define
|
|
||||||
the :meth:`run()` method with whatever logic is needed. They can
|
|
||||||
also define :meth:`add_args()` to expose options.
|
|
||||||
|
|
||||||
Subcommands always belong to a top-level command - the association
|
|
||||||
is made by way of :term:`entry point` registration, and the
|
|
||||||
constructor for this class.
|
|
||||||
|
|
||||||
:param command: Reference to top-level :class:`Command` object.
|
|
||||||
|
|
||||||
Note that unlike :class:`Command`, the base ``Subcommand`` does
|
|
||||||
not correspond to any real subcommand for WuttJamaican. (It's
|
|
||||||
*only* a base class.) For a real example see
|
|
||||||
:class:`~wuttjamaican.cmd.make_appdir.MakeAppDir`.
|
|
||||||
|
|
||||||
.. attribute:: stdout
|
|
||||||
|
|
||||||
Reference to file-like object which should be used for writing
|
|
||||||
to STDOUT. This is inherited from :attr:`Command.stdout`.
|
|
||||||
|
|
||||||
.. attribute:: stderr
|
|
||||||
|
|
||||||
Reference to file-like object which should be used for writing
|
|
||||||
to STDERR. This is inherited from :attr:`Command.stderr`.
|
|
||||||
"""
|
|
||||||
name = 'UNDEFINED'
|
|
||||||
description = "TODO: not defined"
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
command,
|
|
||||||
):
|
|
||||||
self.command = command
|
|
||||||
self.stdout = self.command.stdout
|
|
||||||
self.stderr = self.command.stderr
|
|
||||||
self.config = self.command.config
|
|
||||||
if self.config:
|
|
||||||
self.app = self.config.get_app()
|
|
||||||
|
|
||||||
# build arg parser
|
|
||||||
self.parser = self.make_arg_parser()
|
|
||||||
self.add_args()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"Subcommand(name={self.name})"
|
|
||||||
|
|
||||||
def make_arg_parser(self):
|
|
||||||
"""
|
|
||||||
Must return a new :class:`argparse.ArgumentParser` instance
|
|
||||||
for use by the subcommand.
|
|
||||||
"""
|
|
||||||
return argparse.ArgumentParser(
|
|
||||||
prog=f'{self.command.name} {self.name}',
|
|
||||||
description=self.description)
|
|
||||||
|
|
||||||
def add_args(self):
|
|
||||||
"""
|
|
||||||
Configure additional args for the subcommand arg parser.
|
|
||||||
|
|
||||||
Anything you setup here will then be available within
|
|
||||||
:meth:`run()`. You can add arguments directly to
|
|
||||||
``self.parser``, e.g.::
|
|
||||||
|
|
||||||
self.parser.add_argument('--foo', default='bar', help="Foo value")
|
|
||||||
|
|
||||||
See also docs for :meth:`python:argparse.ArgumentParser.add_argument()`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def print_help(self):
|
|
||||||
"""
|
|
||||||
Print usage help text for the subcommand.
|
|
||||||
"""
|
|
||||||
self.parser.print_help()
|
|
||||||
|
|
||||||
def _run(self, *args):
|
|
||||||
args = self.parser.parse_args(args)
|
|
||||||
return self.run(args)
|
|
||||||
|
|
||||||
def run(self, args):
|
|
||||||
"""
|
|
||||||
Run the subcommand logic. Subclass should override this.
|
|
||||||
|
|
||||||
:param args: Reference to the
|
|
||||||
:class:`python:argparse.Namespace` object, as returned by
|
|
||||||
the subcommand arg parser.
|
|
||||||
|
|
||||||
The ``args`` should have values for everything setup in
|
|
||||||
:meth:`add_args()`. For example if you added the ``--foo``
|
|
||||||
arg then here in ``run()`` you can do::
|
|
||||||
|
|
||||||
print("foo value is:", args.foo)
|
|
||||||
|
|
||||||
Usually of course this method is invoked by way of command
|
|
||||||
line. But if you need to run it programmatically, you should
|
|
||||||
*not* try to invoke this method directly. Instead create the
|
|
||||||
``Command`` object and invoke its :meth:`~Command.run()`
|
|
||||||
method.
|
|
||||||
|
|
||||||
For a command line like ``bin/poser hello --foo=baz`` then,
|
|
||||||
you might do this::
|
|
||||||
|
|
||||||
from poser.commands import PoserCommand
|
|
||||||
|
|
||||||
cmd = PoserCommand()
|
|
||||||
assert cmd.name == 'poser'
|
|
||||||
cmd.run('hello', '--foo=baz')
|
|
||||||
"""
|
|
||||||
self.stdout.write("TODO: command logic not yet implemented\n")
|
|
||||||
|
|
||||||
|
|
||||||
def main(*args):
|
|
||||||
"""
|
|
||||||
Primary entry point for the ``wutta`` command.
|
|
||||||
"""
|
|
||||||
args = list(args) or sys.argv[1:]
|
|
||||||
|
|
||||||
cmd = Command()
|
|
||||||
cmd.run(*args)
|
|
|
@ -1,59 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# WuttJamaican -- Base package for Wutta Framework
|
|
||||||
# Copyright © 2023 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Wutta Framework.
|
|
||||||
#
|
|
||||||
# Wutta Framework 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 3 of the License, or (at your option) any
|
|
||||||
# later version.
|
|
||||||
#
|
|
||||||
# Wutta Framework 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
|
|
||||||
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
WuttJamaican - subcommand ``date-organize``
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from .base import Subcommand
|
|
||||||
|
|
||||||
|
|
||||||
class DateOrganize(Subcommand):
|
|
||||||
"""
|
|
||||||
Organize files into subfolders according to date
|
|
||||||
"""
|
|
||||||
name = 'date-organize'
|
|
||||||
description = __doc__.strip()
|
|
||||||
|
|
||||||
def add_args(self):
|
|
||||||
""" """
|
|
||||||
self.parser.add_argument('folder', metavar='PATH',
|
|
||||||
help="Path to directory containing files which are "
|
|
||||||
"to be organized by date.")
|
|
||||||
|
|
||||||
def run(self, args):
|
|
||||||
""" """
|
|
||||||
today = datetime.date.today()
|
|
||||||
for filename in sorted(os.listdir(args.folder)):
|
|
||||||
path = os.path.join(args.folder, filename)
|
|
||||||
if os.path.isfile(path):
|
|
||||||
mtime = datetime.datetime.fromtimestamp(os.path.getmtime(path))
|
|
||||||
if mtime.date() < today:
|
|
||||||
datedir = mtime.strftime(os.sep.join(('%Y', '%m', '%d')))
|
|
||||||
datedir = os.path.join(args.folder, datedir)
|
|
||||||
if not os.path.exists(datedir):
|
|
||||||
os.makedirs(datedir)
|
|
||||||
shutil.move(path, datedir)
|
|
|
@ -1,68 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# WuttJamaican -- Base package for Wutta Framework
|
|
||||||
# Copyright © 2023 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Wutta Framework.
|
|
||||||
#
|
|
||||||
# Wutta Framework 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 3 of the License, or (at your option) any
|
|
||||||
# later version.
|
|
||||||
#
|
|
||||||
# Wutta Framework 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
|
|
||||||
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
WuttJamaican - subcommand ``make-appdir``
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from .base import Subcommand
|
|
||||||
|
|
||||||
|
|
||||||
class MakeAppDir(Subcommand):
|
|
||||||
"""
|
|
||||||
Make or refresh the "app dir" for virtual environment
|
|
||||||
"""
|
|
||||||
name = 'make-appdir'
|
|
||||||
description = __doc__.strip()
|
|
||||||
|
|
||||||
def add_args(self):
|
|
||||||
""" """
|
|
||||||
self.parser.add_argument('--path', metavar='APPDIR',
|
|
||||||
help="Optional path to desired app dir. If not specified "
|
|
||||||
"it will be named ``app`` and placed in the root of the "
|
|
||||||
"virtual environment.")
|
|
||||||
|
|
||||||
def run(self, args):
|
|
||||||
"""
|
|
||||||
This may be used during setup to establish the :term:`app dir`
|
|
||||||
for a virtual environment. This folder will contain config
|
|
||||||
files, log files etc. used by the app.
|
|
||||||
"""
|
|
||||||
if args.path:
|
|
||||||
appdir = os.path.abspath(args.path)
|
|
||||||
else:
|
|
||||||
appdir = os.path.join(sys.prefix, 'app')
|
|
||||||
|
|
||||||
self.make_appdir(appdir, args)
|
|
||||||
self.stdout.write(f"established appdir: {appdir}\n")
|
|
||||||
|
|
||||||
def make_appdir(self, appdir, args):
|
|
||||||
"""
|
|
||||||
Make the :term:`app dir` for the given path.
|
|
||||||
|
|
||||||
Calls :meth:`~wuttjamaican.app.AppHandler.make_appdir()` to do
|
|
||||||
the heavy lifting.
|
|
||||||
"""
|
|
||||||
self.app.make_appdir(appdir)
|
|
|
@ -1,42 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# WuttJamaican -- Base package for Wutta Framework
|
|
||||||
# Copyright © 2023 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Wutta Framework.
|
|
||||||
#
|
|
||||||
# Wutta Framework 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 3 of the License, or (at your option) any
|
|
||||||
# later version.
|
|
||||||
#
|
|
||||||
# Wutta Framework 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
|
|
||||||
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
WuttJamaican - subcommand ``setup``
|
|
||||||
"""
|
|
||||||
|
|
||||||
from .base import Subcommand
|
|
||||||
|
|
||||||
|
|
||||||
class Setup(Subcommand):
|
|
||||||
"""
|
|
||||||
Install and configure various software
|
|
||||||
"""
|
|
||||||
name = 'setup'
|
|
||||||
description = __doc__.strip()
|
|
||||||
|
|
||||||
def run(self, args):
|
|
||||||
"""
|
|
||||||
TODO: Eventually this will run an interactive setup utility.
|
|
||||||
But it doesn't yet.
|
|
||||||
"""
|
|
||||||
self.stderr.write("TODO: setup is not yet implemented\n")
|
|
|
@ -1,33 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# WuttJamaican -- Base package for Wutta Framework
|
|
||||||
# Copyright © 2023 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Wutta Framework.
|
|
||||||
#
|
|
||||||
# Wutta Framework 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 3 of the License, or (at your option) any
|
|
||||||
# later version.
|
|
||||||
#
|
|
||||||
# Wutta Framework 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
|
|
||||||
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
WuttJamaican - command line interface
|
|
||||||
"""
|
|
||||||
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn("wuttjamaican.commands package is deprecated; "
|
|
||||||
"please use wuttjamaican.cmd instead",
|
|
||||||
DeprecationWarning, stacklevel=2)
|
|
||||||
|
|
||||||
from wuttjamaican.cmd import *
|
|
|
@ -1,33 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# WuttJamaican -- Base package for Wutta Framework
|
|
||||||
# Copyright © 2023 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Wutta Framework.
|
|
||||||
#
|
|
||||||
# Wutta Framework 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 3 of the License, or (at your option) any
|
|
||||||
# later version.
|
|
||||||
#
|
|
||||||
# Wutta Framework 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
|
|
||||||
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
WuttJamaican - base classes for cli
|
|
||||||
"""
|
|
||||||
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn("wuttjamaican.commands package is deprecated; "
|
|
||||||
"please use wuttjamaican.cmd instead",
|
|
||||||
DeprecationWarning, stacklevel=2)
|
|
||||||
|
|
||||||
from wuttjamaican.cmd.base import *
|
|
|
@ -1,33 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# WuttJamaican -- Base package for Wutta Framework
|
|
||||||
# Copyright © 2023 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Wutta Framework.
|
|
||||||
#
|
|
||||||
# Wutta Framework 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 3 of the License, or (at your option) any
|
|
||||||
# later version.
|
|
||||||
#
|
|
||||||
# Wutta Framework 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
|
|
||||||
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
WuttJamaican - subcommand ``make-appdir``
|
|
||||||
"""
|
|
||||||
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn("wuttjamaican.commands package is deprecated; "
|
|
||||||
"please use wuttjamaican.cmd instead",
|
|
||||||
DeprecationWarning, stacklevel=2)
|
|
||||||
|
|
||||||
from wuttjamaican.cmd.make_appdir import *
|
|
|
@ -1,33 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
################################################################################
|
|
||||||
#
|
|
||||||
# WuttJamaican -- Base package for Wutta Framework
|
|
||||||
# Copyright © 2023 Lance Edgar
|
|
||||||
#
|
|
||||||
# This file is part of Wutta Framework.
|
|
||||||
#
|
|
||||||
# Wutta Framework 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 3 of the License, or (at your option) any
|
|
||||||
# later version.
|
|
||||||
#
|
|
||||||
# Wutta Framework 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
|
|
||||||
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
################################################################################
|
|
||||||
"""
|
|
||||||
WuttJamaican - subcommand ``setup``
|
|
||||||
"""
|
|
||||||
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn("wuttjamaican.commands package is deprecated; "
|
|
||||||
"please use wuttjamaican.cmd instead",
|
|
||||||
DeprecationWarning, stacklevel=2)
|
|
||||||
|
|
||||||
from wuttjamaican.cmd.setup import *
|
|
|
@ -1,240 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import sys
|
|
||||||
from unittest import TestCase
|
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
|
|
||||||
from wuttjamaican.cmd import base
|
|
||||||
from wuttjamaican.cmd.setup import Setup
|
|
||||||
from wuttjamaican.testing import FileConfigTestCase
|
|
||||||
|
|
||||||
|
|
||||||
# nb. do this just for coverage
|
|
||||||
from wuttjamaican.commands.base import Command as legacy
|
|
||||||
|
|
||||||
|
|
||||||
class TestCommand(FileConfigTestCase):
|
|
||||||
|
|
||||||
def test_base(self):
|
|
||||||
# base command is for 'wutta' and has a 'setup' subcommand
|
|
||||||
cmd = base.Command()
|
|
||||||
self.assertEqual(cmd.name, 'wutta')
|
|
||||||
self.assertIn('setup', cmd.subcommands)
|
|
||||||
self.assertEqual(str(cmd), 'wutta')
|
|
||||||
|
|
||||||
def test_subcommand_entry_points(self):
|
|
||||||
with patch('wuttjamaican.cmd.base.load_entry_points') as load_entry_points:
|
|
||||||
|
|
||||||
# empty entry points
|
|
||||||
load_entry_points.side_effect = lambda group: {}
|
|
||||||
cmd = base.Command()
|
|
||||||
self.assertEqual(cmd.subcommands, {})
|
|
||||||
|
|
||||||
# typical entry points
|
|
||||||
load_entry_points.side_effect = lambda group: {'setup': Setup}
|
|
||||||
cmd = base.Command()
|
|
||||||
self.assertEqual(cmd.subcommands, {'setup': Setup})
|
|
||||||
self.assertEqual(cmd.subcommands['setup'].name, 'setup')
|
|
||||||
|
|
||||||
# legacy entry points
|
|
||||||
# nb. mock returns entry points only when legacy name is used
|
|
||||||
load_entry_points.side_effect = lambda group: {} if 'subcommands' in group else {'setup': Setup}
|
|
||||||
cmd = base.Command()
|
|
||||||
self.assertEqual(cmd.subcommands, {'setup': Setup})
|
|
||||||
self.assertEqual(cmd.subcommands['setup'].name, 'setup')
|
|
||||||
|
|
||||||
def test_sorted_subcommands(self):
|
|
||||||
cmd = base.Command(subcommands={'foo': 'FooSubcommand',
|
|
||||||
'bar': 'BarSubcommand'})
|
|
||||||
|
|
||||||
srtd = cmd.sorted_subcommands()
|
|
||||||
self.assertEqual(srtd, ['BarSubcommand', 'FooSubcommand'])
|
|
||||||
|
|
||||||
def test_run_may_print_help(self):
|
|
||||||
|
|
||||||
class Hello(base.Subcommand):
|
|
||||||
name = 'hello'
|
|
||||||
|
|
||||||
cmd = base.Command(subcommands={'hello': Hello})
|
|
||||||
|
|
||||||
# first run is not "tested" per se but gives us some coverage.
|
|
||||||
# (this will *actually* print help, b/c no args specified)
|
|
||||||
try:
|
|
||||||
cmd.run()
|
|
||||||
except SystemExit:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# from now on we mock the help
|
|
||||||
print_help = MagicMock()
|
|
||||||
cmd.print_help = print_help
|
|
||||||
|
|
||||||
# help is shown if no subcommand is given
|
|
||||||
try:
|
|
||||||
cmd.run()
|
|
||||||
except SystemExit:
|
|
||||||
pass
|
|
||||||
print_help.assert_called_once_with()
|
|
||||||
|
|
||||||
# help is shown if -h is given
|
|
||||||
print_help.reset_mock()
|
|
||||||
try:
|
|
||||||
cmd.run('-h')
|
|
||||||
except SystemExit:
|
|
||||||
pass
|
|
||||||
print_help.assert_called_once_with()
|
|
||||||
|
|
||||||
# help is shown if --help is given
|
|
||||||
print_help.reset_mock()
|
|
||||||
try:
|
|
||||||
cmd.run('--help')
|
|
||||||
except SystemExit:
|
|
||||||
pass
|
|
||||||
print_help.assert_called_once_with()
|
|
||||||
|
|
||||||
# help is shown if bad arg is given
|
|
||||||
print_help.reset_mock()
|
|
||||||
try:
|
|
||||||
cmd.run('--this-means-nothing')
|
|
||||||
except SystemExit:
|
|
||||||
pass
|
|
||||||
print_help.assert_called_once_with()
|
|
||||||
|
|
||||||
# help is shown if bad subcmd is given
|
|
||||||
print_help.reset_mock()
|
|
||||||
try:
|
|
||||||
cmd.run('make-sandwich')
|
|
||||||
except SystemExit:
|
|
||||||
pass
|
|
||||||
print_help.assert_called_once_with()
|
|
||||||
|
|
||||||
# main help is *not* shown if subcommand *and* -h are given
|
|
||||||
# (sub help is shown instead in that case)
|
|
||||||
print_help.reset_mock()
|
|
||||||
try:
|
|
||||||
cmd.run('hello', '-h')
|
|
||||||
except SystemExit:
|
|
||||||
pass
|
|
||||||
print_help.assert_not_called()
|
|
||||||
|
|
||||||
def test_run_invokes_subcommand(self):
|
|
||||||
|
|
||||||
class Hello(base.Subcommand):
|
|
||||||
name = 'hello'
|
|
||||||
def add_args(self):
|
|
||||||
self.parser.add_argument('--foo', action='store_true')
|
|
||||||
def run(self, args):
|
|
||||||
self.run_with(foo=args.foo)
|
|
||||||
|
|
||||||
run_with = Hello.run_with = MagicMock()
|
|
||||||
cmd = base.Command(subcommands={'hello': Hello})
|
|
||||||
|
|
||||||
# omit --foo in which case that is false by default
|
|
||||||
cmd.run('hello')
|
|
||||||
run_with.assert_called_once_with(foo=False)
|
|
||||||
|
|
||||||
# specify --foo in which case that is true
|
|
||||||
run_with.reset_mock()
|
|
||||||
cmd.run('hello', '--foo')
|
|
||||||
run_with.assert_called_once_with(foo=True)
|
|
||||||
|
|
||||||
def test_run_uses_stdout_stderr_params(self):
|
|
||||||
myout = self.write_file('my.out', '')
|
|
||||||
myerr = self.write_file('my.err', '')
|
|
||||||
|
|
||||||
class Hello(base.Subcommand):
|
|
||||||
name = 'hello'
|
|
||||||
def run(self, args):
|
|
||||||
self.stdout.write("hello world")
|
|
||||||
self.stderr.write("error text")
|
|
||||||
|
|
||||||
with patch('wuttjamaican.cmd.base.sys') as sys:
|
|
||||||
|
|
||||||
cmd = base.Command(subcommands={'hello': Hello})
|
|
||||||
|
|
||||||
# sys.stdout and sys.stderr should be used by default
|
|
||||||
cmd.run('hello')
|
|
||||||
sys.exit.assert_not_called()
|
|
||||||
sys.stdout.write.assert_called_once_with('hello world')
|
|
||||||
sys.stderr.write.assert_called_once_with('error text')
|
|
||||||
|
|
||||||
# but our files may be used instead if specified
|
|
||||||
sys.reset_mock()
|
|
||||||
cmd.run('hello', '--stdout', myout, '--stderr', myerr)
|
|
||||||
sys.exit.assert_not_called()
|
|
||||||
sys.stdout.write.assert_not_called()
|
|
||||||
sys.stderr.write.assert_not_called()
|
|
||||||
with open(myout, 'rt') as f:
|
|
||||||
self.assertEqual(f.read(), 'hello world')
|
|
||||||
with open(myerr, 'rt') as f:
|
|
||||||
self.assertEqual(f.read(), 'error text')
|
|
||||||
|
|
||||||
|
|
||||||
class TestCommandArgumentParser(TestCase):
|
|
||||||
|
|
||||||
def test_parse_args(self):
|
|
||||||
|
|
||||||
kw = {
|
|
||||||
'prog': 'wutta',
|
|
||||||
'add_help': False,
|
|
||||||
}
|
|
||||||
|
|
||||||
# nb. examples below assume a command line like:
|
|
||||||
# bin/wutta foo --bar
|
|
||||||
|
|
||||||
# first here is what default parser does
|
|
||||||
parser = argparse.ArgumentParser(**kw)
|
|
||||||
parser.add_argument('subcommand', nargs='*')
|
|
||||||
try:
|
|
||||||
args = parser.parse_args(['foo', '--bar'])
|
|
||||||
except SystemExit:
|
|
||||||
# nb. parser was not happy, tried to exit process
|
|
||||||
args = None
|
|
||||||
else:
|
|
||||||
self.assertEqual(args.subcommand, ['foo', '--bar'])
|
|
||||||
self.assertFalse(hasattr(args, 'argv'))
|
|
||||||
|
|
||||||
# now here is was custom parser does
|
|
||||||
# (moves extras to argv for subcommand parser)
|
|
||||||
parser = base.CommandArgumentParser(**kw)
|
|
||||||
parser.add_argument('subcommand', nargs='*')
|
|
||||||
args = parser.parse_args(['foo', '--bar'])
|
|
||||||
self.assertEqual(args.subcommand, ['foo'])
|
|
||||||
self.assertEqual(args.argv, ['--bar'])
|
|
||||||
|
|
||||||
|
|
||||||
class TestSubcommand(TestCase):
|
|
||||||
|
|
||||||
def test_basic(self):
|
|
||||||
cmd = base.Command()
|
|
||||||
subcmd = base.Subcommand(cmd)
|
|
||||||
subcmd.name = 'foobar'
|
|
||||||
self.assertEqual(repr(subcmd), 'Subcommand(name=foobar)')
|
|
||||||
# TODO: this doesn't really test anything per se, but at least
|
|
||||||
# gives us the coverage..
|
|
||||||
subcmd._run()
|
|
||||||
|
|
||||||
|
|
||||||
class TestMain(TestCase):
|
|
||||||
|
|
||||||
# nb. this doesn't test anything per se but gives coverage
|
|
||||||
|
|
||||||
def test_explicit_args(self):
|
|
||||||
try:
|
|
||||||
base.main('--help')
|
|
||||||
except SystemExit:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_implicit_args(self):
|
|
||||||
|
|
||||||
def true_exit(*args):
|
|
||||||
sys.exit(*args)
|
|
||||||
|
|
||||||
with patch('wuttjamaican.cmd.base.sys') as mocksys:
|
|
||||||
mocksys.argv = ['wutta', '--help']
|
|
||||||
mocksys.exit = true_exit
|
|
||||||
|
|
||||||
try:
|
|
||||||
base.main()
|
|
||||||
except SystemExit:
|
|
||||||
pass
|
|
|
@ -1,34 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import os
|
|
||||||
|
|
||||||
from wuttjamaican.conf import WuttaConfig
|
|
||||||
from wuttjamaican.cmd import Command, date_organize
|
|
||||||
from wuttjamaican.testing import FileConfigTestCase
|
|
||||||
|
|
||||||
|
|
||||||
class TestDateOrganize(FileConfigTestCase):
|
|
||||||
|
|
||||||
def test_run(self):
|
|
||||||
dates = [
|
|
||||||
datetime.date(2023, 11, 21),
|
|
||||||
datetime.date(2023, 11, 20),
|
|
||||||
datetime.date(2023, 10, 15),
|
|
||||||
datetime.date(2023, 9, 10),
|
|
||||||
]
|
|
||||||
|
|
||||||
for date in dates:
|
|
||||||
dt = datetime.datetime.combine(date, datetime.time(0))
|
|
||||||
filename = date.strftime('%Y%m%d.txt')
|
|
||||||
path = self.write_file(filename, '')
|
|
||||||
os.utime(path, (dt.timestamp(), dt.timestamp()))
|
|
||||||
|
|
||||||
cmd = Command(subcommands={
|
|
||||||
'date-organize': date_organize.DateOrganize,
|
|
||||||
})
|
|
||||||
cmd.run('date-organize', self.tempdir)
|
|
||||||
|
|
||||||
self.assertEqual(os.listdir(self.tempdir), ['2023'])
|
|
||||||
self.assertEqual(sorted(os.listdir(os.path.join(self.tempdir, '2023'))),
|
|
||||||
['09', '10', '11'])
|
|
|
@ -1,53 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import tempfile
|
|
||||||
from unittest import TestCase
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from wuttjamaican.conf import WuttaConfig
|
|
||||||
from wuttjamaican.cmd import Command, make_appdir
|
|
||||||
|
|
||||||
|
|
||||||
# nb. do this just for coverage
|
|
||||||
from wuttjamaican.commands.make_appdir import MakeAppDir as legacy
|
|
||||||
|
|
||||||
|
|
||||||
class TestMakeAppDir(TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.config = WuttaConfig(appname='wuttatest')
|
|
||||||
self.command = Command(self.config, subcommands={
|
|
||||||
'make-appdir': make_appdir.MakeAppDir,
|
|
||||||
})
|
|
||||||
|
|
||||||
def test_run(self):
|
|
||||||
|
|
||||||
# appdir is created, and 3 subfolders added by default
|
|
||||||
tempdir = tempfile.mkdtemp()
|
|
||||||
appdir = os.path.join(tempdir, 'app')
|
|
||||||
self.assertFalse(os.path.exists(appdir))
|
|
||||||
self.command.run('make-appdir', '--path', appdir)
|
|
||||||
self.assertTrue(os.path.exists(appdir))
|
|
||||||
self.assertEqual(len(os.listdir(appdir)), 3)
|
|
||||||
shutil.rmtree(tempdir)
|
|
||||||
|
|
||||||
# subfolders still added if appdir already exists
|
|
||||||
tempdir = tempfile.mkdtemp()
|
|
||||||
self.assertTrue(os.path.exists(tempdir))
|
|
||||||
self.assertEqual(len(os.listdir(tempdir)), 0)
|
|
||||||
self.command.run('make-appdir', '--path', tempdir)
|
|
||||||
self.assertEqual(len(os.listdir(tempdir)), 3)
|
|
||||||
shutil.rmtree(tempdir)
|
|
||||||
|
|
||||||
# mock out sys.prefix to get coverage
|
|
||||||
with patch('wuttjamaican.cmd.make_appdir.sys') as sys:
|
|
||||||
tempdir = tempfile.mkdtemp()
|
|
||||||
appdir = os.path.join(tempdir, 'app')
|
|
||||||
sys.prefix = tempdir
|
|
||||||
self.assertFalse(os.path.exists(appdir))
|
|
||||||
self.command.run('make-appdir')
|
|
||||||
self.assertTrue(os.path.exists(appdir))
|
|
||||||
self.assertEqual(len(os.listdir(appdir)), 3)
|
|
||||||
shutil.rmtree(tempdir)
|
|
|
@ -1,20 +0,0 @@
|
||||||
# -*- coding: utf-8; -*-
|
|
||||||
|
|
||||||
from unittest import TestCase
|
|
||||||
|
|
||||||
from wuttjamaican.cmd import Command, setup
|
|
||||||
|
|
||||||
|
|
||||||
# nb. do this just for coverage
|
|
||||||
from wuttjamaican.commands.setup import Setup as legacy
|
|
||||||
|
|
||||||
|
|
||||||
class TestSetup(TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.command = Command()
|
|
||||||
self.subcommand = setup.Setup(self.command)
|
|
||||||
|
|
||||||
def test_run(self):
|
|
||||||
# TODO: this doesn't really test anything yet
|
|
||||||
self.subcommand._run()
|
|
Loading…
Reference in a new issue