diff options
Diffstat (limited to 'bin/toxcmd.py')
-rwxr-xr-x | bin/toxcmd.py | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/bin/toxcmd.py b/bin/toxcmd.py new file mode 100755 index 0000000..38bb6d5 --- /dev/null +++ b/bin/toxcmd.py @@ -0,0 +1,252 @@ +#!/usr/bin/env python +# -*- coding: UTF-8 -*- +""" +Provides a command container for additional tox commands, used in "tox.ini". + +COMMANDS: + + * copytree + * copy + * py2to3 + +REQUIRES: + * argparse +""" + +from glob import glob +import argparse +import inspect +import os.path +import shutil +import sys + +__author__ = "Jens Engel" +__copyright__ = "(c) 2013 by Jens Engel" +__license__ = "BSD" + +# ----------------------------------------------------------------------------- +# CONSTANTS: +# ----------------------------------------------------------------------------- +VERSION = "0.1.0" +FORMATTER_CLASS = argparse.RawDescriptionHelpFormatter + + +# ----------------------------------------------------------------------------- +# SUBCOMMAND: copytree +# ----------------------------------------------------------------------------- +def command_copytree(args): + """ + Copy one or more source directory(s) below a destination directory. + Parts of the destination directory path are created if needed. + Similar to the UNIX command: 'cp -R srcdir destdir' + """ + for srcdir in args.srcdirs: + basename = os.path.basename(srcdir) + destdir2 = os.path.normpath(os.path.join(args.destdir, basename)) + if os.path.exists(destdir2): + shutil.rmtree(destdir2) + sys.stdout.write("copytree: %s => %s\n" % (srcdir, destdir2)) + shutil.copytree(srcdir, destdir2) + return 0 + + +def setup_parser_copytree(parser): + parser.add_argument("srcdirs", nargs="+", help="Source directory(s)") + parser.add_argument("destdir", help="Destination directory") + + +command_copytree.usage = "%(prog)s srcdir... destdir" +command_copytree.short = "Copy source dir(s) below a destination directory." +command_copytree.setup_parser = setup_parser_copytree + + +# ----------------------------------------------------------------------------- +# SUBCOMMAND: copy +# ----------------------------------------------------------------------------- +def command_copy(args): + """ + Copy one or more source-files(s) to a destpath (destfile or destdir). + Destdir mode is used if: + * More than one srcfile is provided + * Last parameter ends with a slash ("/"). + * Last parameter is an existing directory + + Destination directory path is created if needed. + Similar to the UNIX command: 'cp srcfile... destpath' + """ + sources = args.sources + destpath = args.destpath + source_files = [] + for file_ in sources: + if "*" in file_: + selected = glob(file_) + source_files.extend(selected) + elif os.path.isfile(file_): + source_files.append(file_) + + if destpath.endswith("/") or os.path.isdir(destpath) or len(sources) > 1: + # -- DESTDIR-MODE: Last argument is a directory. + destdir = destpath + else: + # -- DESTFILE-MODE: Copy (and rename) one file. + assert len(source_files) == 1 + destdir = os.path.dirname(destpath) + + # -- WORK-HORSE: Copy one or more files to destpath. + if not os.path.isdir(destdir): + sys.stdout.write("copy: Create dir %s\n" % destdir) + os.makedirs(destdir) + for source in source_files: + destname = os.path.join(destdir, os.path.basename(source)) + sys.stdout.write("copy: %s => %s\n" % (source, destname)) + shutil.copy(source, destname) + return 0 + + +def setup_parser_copy(parser): + parser.add_argument("sources", nargs="+", help="Source files.") + parser.add_argument("destpath", help="Destination path") + + +command_copy.usage = "%(prog)s sources... destpath" +command_copy.short = "Copy one or more source files to a destinition." +command_copy.setup_parser = setup_parser_copy + + +# ----------------------------------------------------------------------------- +# SUBCOMMAND: mkdir +# ----------------------------------------------------------------------------- +def command_mkdir(args): + """ + Create a non-existing directory (or more ...). + If the directory exists, the step is skipped. + Similar to the UNIX command: 'mkdir -p dir' + """ + errors = 0 + for directory in args.dirs: + if os.path.exists(directory): + if not os.path.isdir(directory): + # -- SANITY CHECK: directory exists, but as file... + sys.stdout.write("mkdir: %s\n" % directory) + sys.stdout.write("ERROR: Exists already, but as file...\n") + errors += 1 + else: + # -- NORMAL CASE: Directory does not exits yet. + assert not os.path.isdir(directory) + sys.stdout.write("mkdir: %s\n" % directory) + os.makedirs(directory) + return errors + + +def setup_parser_mkdir(parser): + parser.add_argument("dirs", nargs="+", help="Directory(s)") + +command_mkdir.usage = "%(prog)s dir..." +command_mkdir.short = "Create non-existing directory (or more...)." +command_mkdir.setup_parser = setup_parser_mkdir + +# ----------------------------------------------------------------------------- +# SUBCOMMAND: py2to3 +# ----------------------------------------------------------------------------- +def command_py2to3(args): + """ + Apply '2to3' tool (Python2 to Python3 conversion tool) to Python sources. + """ + from lib2to3.main import main + sys.exit(main("lib2to3.fixes", args=args.sources)) + + +def setup_parser4py2to3(parser): + parser.add_argument("sources", nargs="+", help="Source files.") + + +command_py2to3.name = "2to3" +command_py2to3.usage = "%(prog)s sources..." +command_py2to3.short = "Apply python's 2to3 tool to Python sources." +command_py2to3.setup_parser = setup_parser4py2to3 + + +# ----------------------------------------------------------------------------- +# COMMAND HELPERS/UTILS: +# ----------------------------------------------------------------------------- +def discover_commands(): + commands = [] + for name, func in inspect.getmembers(inspect.getmodule(toxcmd_main)): + if name.startswith("__"): + continue + if name.startswith("command_") and callable(func): + command_name0 = name.replace("command_", "") + command_name = getattr(func, "name", command_name0) + commands.append(Command(command_name, func)) + return commands + + +class Command(object): + def __init__(self, name, func): + assert isinstance(name, basestring) + assert callable(func) + self.name = name + self.func = func + self.parser = None + + def setup_parser(self, command_parser): + setup_parser = getattr(self.func, "setup_parser", None) + if setup_parser and callable(setup_parser): + setup_parser(command_parser) + else: + command_parser.add_argument("args", nargs="*") + + @property + def usage(self): + usage = getattr(self.func, "usage", None) + return usage + + @property + def short_description(self): + short_description = getattr(self.func, "short", "") + return short_description + + @property + def description(self): + return inspect.getdoc(self.func) + + def __call__(self, args): + return self.func(args) + + +# ----------------------------------------------------------------------------- +# MAIN-COMMAND: +# ----------------------------------------------------------------------------- +def toxcmd_main(args=None): + """Command util with subcommands for tox environments.""" + usage = "USAGE: %(prog)s [OPTIONS] COMMAND args..." + if args is None: + args = sys.argv[1:] + + # -- STEP: Build command-line parser. + parser = argparse.ArgumentParser(description=inspect.getdoc(toxcmd_main), + formatter_class=FORMATTER_CLASS) + common_parser = parser.add_argument_group("Common options") + common_parser.add_argument("--version", action="version", version=VERSION) + subparsers = parser.add_subparsers(help="commands") + for command in discover_commands(): + command_parser = subparsers.add_parser(command.name, + usage=command.usage, + description=command.description, + help=command.short_description, + formatter_class=FORMATTER_CLASS) + command_parser.set_defaults(func=command) + command.setup_parser(command_parser) + command.parser = command_parser + + # -- STEP: Process command-line and run command. + options = parser.parse_args(args) + command_function = options.func + return command_function(options) + + +# ----------------------------------------------------------------------------- +# MAIN: +# ----------------------------------------------------------------------------- +if __name__ == "__main__": + sys.exit(toxcmd_main()) |