diff --git a/docs/api/wuttjamaican/cmd.base.rst b/docs/api/wuttjamaican/cmd.base.rst deleted file mode 100644 index 5a02ec8..0000000 --- a/docs/api/wuttjamaican/cmd.base.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``wuttjamaican.cmd.base`` -========================= - -.. automodule:: wuttjamaican.cmd.base - :members: diff --git a/docs/api/wuttjamaican/cmd.date_organize.rst b/docs/api/wuttjamaican/cmd.date_organize.rst deleted file mode 100644 index 35ec416..0000000 --- a/docs/api/wuttjamaican/cmd.date_organize.rst +++ /dev/null @@ -1,8 +0,0 @@ - -``wuttjamaican.cmd.date_organize`` -================================== - -.. automodule:: wuttjamaican.cmd.date_organize - :members: - -.. program-output:: wutta date-organize -h diff --git a/docs/api/wuttjamaican/cmd.make_appdir.rst b/docs/api/wuttjamaican/cmd.make_appdir.rst deleted file mode 100644 index 235af56..0000000 --- a/docs/api/wuttjamaican/cmd.make_appdir.rst +++ /dev/null @@ -1,8 +0,0 @@ - -``wuttjamaican.cmd.make_appdir`` -================================ - -.. automodule:: wuttjamaican.cmd.make_appdir - :members: - -.. program-output:: wutta make-appdir -h diff --git a/docs/api/wuttjamaican/cmd.rst b/docs/api/wuttjamaican/cmd.rst deleted file mode 100644 index 5bec70c..0000000 --- a/docs/api/wuttjamaican/cmd.rst +++ /dev/null @@ -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` but it also functions as -the top-level ``wutta`` command. - -Some :term:`subcommands` are available as well; these are -registered under the ``wutta`` command. - -.. toctree:: - :maxdepth: 1 - - cmd.base - cmd.date_organize - cmd.make_appdir - cmd.setup diff --git a/docs/api/wuttjamaican/cmd.setup.rst b/docs/api/wuttjamaican/cmd.setup.rst deleted file mode 100644 index 140599c..0000000 --- a/docs/api/wuttjamaican/cmd.setup.rst +++ /dev/null @@ -1,8 +0,0 @@ - -``wuttjamaican.cmd.setup`` -========================== - -.. automodule:: wuttjamaican.cmd.setup - :members: - -.. program-output:: wutta setup -h diff --git a/docs/api/wuttjamaican/index.rst b/docs/api/wuttjamaican/index.rst index 4b0aec8..753b81f 100644 --- a/docs/api/wuttjamaican/index.rst +++ b/docs/api/wuttjamaican/index.rst @@ -8,7 +8,6 @@ :maxdepth: 1 app - cmd conf db db.conf diff --git a/docs/narr/config/files.rst b/docs/narr/config/files.rst index 514da48..0a6eafe 100644 --- a/docs/narr/config/files.rst +++ b/docs/narr/config/files.rst @@ -43,13 +43,16 @@ be used to avoid unwanted splits. Specifying via Command Line --------------------------- -All :term:`commands` accept the ``-c`` or ``--config`` params: +While no :term:`commands` 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 - 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 diff --git a/docs/narr/install/quickstart.rst b/docs/narr/install/quickstart.rst index f92b1dc..821957e 100644 --- a/docs/narr/install/quickstart.rst +++ b/docs/narr/install/quickstart.rst @@ -43,7 +43,5 @@ For more info see: * :class:`~wuttjamaican.conf.WuttaConfig` and especially :meth:`~wuttjamaican.conf.WuttaConfig.get()` -You can also define your own command line interface; see: - -* :class:`~wuttjamaican.commands.base.Command` -* :class:`~wuttjamaican.commands.base.Subcommand` +You can also define your own command line interface; see +:doc:`/narr/cli/index`. diff --git a/pyproject.toml b/pyproject.toml index 7657806..2abf661 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,16 +39,6 @@ docs = ["Sphinx", "sphinxcontrib-programoutput", "furo"] 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] Homepage = "https://rattailproject.org/" Repository = "https://kallithea.rattailproject.org/rattail-project/wuttjamaican" diff --git a/src/wuttjamaican/cmd/__init__.py b/src/wuttjamaican/cmd/__init__.py deleted file mode 100644 index 4323656..0000000 --- a/src/wuttjamaican/cmd/__init__.py +++ /dev/null @@ -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 . -# -################################################################################ -""" -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 diff --git a/src/wuttjamaican/cmd/base.py b/src/wuttjamaican/cmd/base.py deleted file mode 100644 index 50d96d8..0000000 --- a/src/wuttjamaican/cmd/base.py +++ /dev/null @@ -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 . -# -################################################################################ -""" -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-options] [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-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} -h -""" - - return CommandArgumentParser( - prog=self.name, - description=self.description, - add_help=False, - usage=f"{self.name} [options] [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) diff --git a/src/wuttjamaican/cmd/date_organize.py b/src/wuttjamaican/cmd/date_organize.py deleted file mode 100644 index c87f6e8..0000000 --- a/src/wuttjamaican/cmd/date_organize.py +++ /dev/null @@ -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 . -# -################################################################################ -""" -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) diff --git a/src/wuttjamaican/cmd/make_appdir.py b/src/wuttjamaican/cmd/make_appdir.py deleted file mode 100644 index e2c1dba..0000000 --- a/src/wuttjamaican/cmd/make_appdir.py +++ /dev/null @@ -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 . -# -################################################################################ -""" -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) diff --git a/src/wuttjamaican/cmd/setup.py b/src/wuttjamaican/cmd/setup.py deleted file mode 100644 index 5df6143..0000000 --- a/src/wuttjamaican/cmd/setup.py +++ /dev/null @@ -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 . -# -################################################################################ -""" -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") diff --git a/src/wuttjamaican/commands/__init__.py b/src/wuttjamaican/commands/__init__.py deleted file mode 100644 index 72d43cd..0000000 --- a/src/wuttjamaican/commands/__init__.py +++ /dev/null @@ -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 . -# -################################################################################ -""" -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 * diff --git a/src/wuttjamaican/commands/base.py b/src/wuttjamaican/commands/base.py deleted file mode 100644 index 38014c1..0000000 --- a/src/wuttjamaican/commands/base.py +++ /dev/null @@ -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 . -# -################################################################################ -""" -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 * diff --git a/src/wuttjamaican/commands/make_appdir.py b/src/wuttjamaican/commands/make_appdir.py deleted file mode 100644 index 15b6f34..0000000 --- a/src/wuttjamaican/commands/make_appdir.py +++ /dev/null @@ -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 . -# -################################################################################ -""" -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 * diff --git a/src/wuttjamaican/commands/setup.py b/src/wuttjamaican/commands/setup.py deleted file mode 100644 index 8f71954..0000000 --- a/src/wuttjamaican/commands/setup.py +++ /dev/null @@ -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 . -# -################################################################################ -""" -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 * diff --git a/tests/cmd/__init__.py b/tests/cmd/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/cmd/test_base.py b/tests/cmd/test_base.py deleted file mode 100644 index 0b18796..0000000 --- a/tests/cmd/test_base.py +++ /dev/null @@ -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 diff --git a/tests/cmd/test_date_organize.py b/tests/cmd/test_date_organize.py deleted file mode 100644 index 400b3dd..0000000 --- a/tests/cmd/test_date_organize.py +++ /dev/null @@ -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']) diff --git a/tests/cmd/test_make_appdir.py b/tests/cmd/test_make_appdir.py deleted file mode 100644 index a19cb6b..0000000 --- a/tests/cmd/test_make_appdir.py +++ /dev/null @@ -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) diff --git a/tests/cmd/test_setup.py b/tests/cmd/test_setup.py deleted file mode 100644 index 8989f92..0000000 --- a/tests/cmd/test_setup.py +++ /dev/null @@ -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()