From 532f5d98c15f091a270e68450112e91ad085f6bf Mon Sep 17 00:00:00 2001 From: kennyballou Date: Sat, 9 Mar 2013 18:33:32 -0700 Subject: Refactor/ Move Program flow to use `argparse` --- pylint.conf | 2 +- xnt/basecommand.py | 32 --------- xnt/cmdoptions.py | 31 --------- xnt/commands/__init__.py | 41 ------------ xnt/commands/help.py | 42 ------------ xnt/commands/listtargets.py | 56 ---------------- xnt/commands/target.py | 78 ---------------------- xnt/commands/version.py | 34 ---------- xnt/tasks.py | 6 +- xnt/tests/xenantargparsertests.py | 132 ++++++++++++++++++++++++++++++++++++++ xnt/version.py | 2 +- xnt/xenant.py | 126 ++++++++++++++++++++++++++++-------- 12 files changed, 235 insertions(+), 347 deletions(-) delete mode 100644 xnt/basecommand.py delete mode 100644 xnt/cmdoptions.py delete mode 100644 xnt/commands/__init__.py delete mode 100644 xnt/commands/help.py delete mode 100644 xnt/commands/listtargets.py delete mode 100644 xnt/commands/target.py delete mode 100644 xnt/commands/version.py create mode 100644 xnt/tests/xenantargparsertests.py diff --git a/pylint.conf b/pylint.conf index 5282466..a56e0a5 100644 --- a/pylint.conf +++ b/pylint.conf @@ -249,7 +249,7 @@ max-attributes=7 min-public-methods=1 # Maximum number of public methods for a class (see R0904). -max-public-methods=50 +max-public-methods=55 [EXCEPTIONS] diff --git a/xnt/basecommand.py b/xnt/basecommand.py deleted file mode 100644 index 6433918..0000000 --- a/xnt/basecommand.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python -"""Basecommand class for xnt commands""" - -# Xnt -- A Wrapper Build Tool -# Copyright (C) 2012 Kenny Ballou - -# This program 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. - -# This program 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 this program. If not, see . - -class Command(object): - """Base Command Class Definition""" - name = None - usage = None - hidden = False - need_build = False - - def __init__(self): - pass - - def run(self, arguments=None): - """Invoke the Command""" - pass diff --git a/xnt/cmdoptions.py b/xnt/cmdoptions.py deleted file mode 100644 index 529f060..0000000 --- a/xnt/cmdoptions.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python -"""Xenant Options Defintion - -All available options for Xenant (and there actions) -""" - -# Xnt -- A Wrapper Build Tool -# Copyright (C) 2012 Kenny Ballou - -# This program 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. - -# This program 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 this program. If not, see . - -import logging - -def __flip_verbose_flag(): - """Turn on logging for xnt (and submodules)""" - logging.getLogger("xnt").setLevel(logging.INFO) - -OPTIONS = { - "-v": __flip_verbose_flag, -} diff --git a/xnt/commands/__init__.py b/xnt/commands/__init__.py deleted file mode 100644 index 583558b..0000000 --- a/xnt/commands/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python -"""Commands Module""" - -# Xnt -- A Wrapper Build Tool -# Copyright (C) 2012 Kenny Ballou - -# This program 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. - -# This program 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 this program. If not, see . - -from xnt.commands.help import HelpCommand -from xnt.commands.listtargets import ListTargetsCommand -from xnt.commands.version import VersionCommand -from xnt.commands.target import TargetCommand - -COMMANDS = { - HelpCommand.name: HelpCommand, - ListTargetsCommand.name: ListTargetsCommand, - VersionCommand.name: VersionCommand, - TargetCommand.name: TargetCommand, -} - -def get_summaries(ignore_hidden=True): - """Return a list of summaries about each command""" - items = [] - - for name, command_class in COMMANDS.items(): - if ignore_hidden and command_class.hidden: - continue - items.append((name, command_class.summary)) - - return sorted(items) diff --git a/xnt/commands/help.py b/xnt/commands/help.py deleted file mode 100644 index 79584be..0000000 --- a/xnt/commands/help.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python -"""Xnt Help Command""" - -# Xnt -- A Wrapper Build Tool -# Copyright (C) 2012 Kenny Ballou - -# This program 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. - -# This program 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 this program. If not, see . - -from xnt.basecommand import Command -from xnt.status_codes import SUCCESS - -class HelpCommand(Command): - """Help Command""" - name = 'help' - usage = """""" - summary = 'Print Usage Summary' - needs_build = False - - def run(self, arguments=None): - """Invoke Help""" - from xnt.commands import get_summaries - from xnt import __version__, __license__ - commands = get_summaries() - print(__version__) - print(__license__) - print("Available Commands:") - for name, summary in commands: - print(name) - print("\t" + summary) - - return SUCCESS diff --git a/xnt/commands/listtargets.py b/xnt/commands/listtargets.py deleted file mode 100644 index 2885905..0000000 --- a/xnt/commands/listtargets.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python -"""List Targets Xnt Command""" - -# Xnt -- A Wrapper Build Tool -# Copyright (C) 2012 Kenny Ballou - -# This program 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. - -# This program 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 this program. If not, see . - -from xnt.basecommand import Command -from xnt.status_codes import SUCCESS, ERROR -import logging - -LOGGER = logging.getLogger(__name__) - -class ListTargetsCommand(Command): - """List Targets Command""" - name = 'list-targets' - usage = """""" - summary = "Prints targets in build file" - needs_build = True - - def __init__(self, build): - """Initialization""" - Command.__init__(self) - self.build = build - - def run(self, arguments=None): - """Invoke ListTargets""" - LOGGER.debug("build is null? %s", self.build == None) - try: - for attr in dir(self.build): - LOGGER.debug("Attribute %s:", attr) - try: - func = getattr(self.build, attr) - if func.decorator == "target": - print(attr + ":") - if func.__doc__: - print(func.__doc__) - print("\n") - except AttributeError: - pass - except AttributeError as ex: - LOGGER.error(ex) - return ERROR - return SUCCESS diff --git a/xnt/commands/target.py b/xnt/commands/target.py deleted file mode 100644 index 0520c74..0000000 --- a/xnt/commands/target.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python -"""(Generic) Target Xnt Command for invoking build targets""" - -# Xnt -- A Wrapper Build Tool -# Copyright (C) 2012 Kenny Ballou - -# This program 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. - -# This program 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 this program. If not, see . - -from xnt.basecommand import Command -from xnt.status_codes import SUCCESS, ERROR, UNKNOWN_ERROR -import logging - -LOGGER = logging.getLogger("xnt") - -class TargetCommand(Command): - """Target Command""" - name = '' - usage = """""" - summary = "Invokes target(s) in build.py" - needs_build = True - - def __init__(self, build): - """Initialization""" - Command.__init__(self) - self.build = build - - def run(self, targets=None, props=None): #pylint: disable-msg=W0221 - """Invoke Target Command""" - if targets: - for target in targets: - error_code = self.call_target(target, props) - if error_code: - return error_code - return SUCCESS - else: - return self.call_target("default", props) - - def call_target(self, target_name, props): - """Invoke build target""" - def process_params(params, buildproperties=None): - """Parse the passed properties and append to build properties""" - properties = buildproperties if buildproperties is not None else {} - for param in params: - name, value = param[2:].split("=") - properties[name] = value - return properties - def __get_properties(): - """Return the properties dictionary of the build module""" - try: - return getattr(self.build, "properties") - except AttributeError: - return None - try: - if props and len(props) > 0: - setattr(self.build, - "properties", - process_params(props, __get_properties())) - target = getattr(self.build, target_name) - error_code = target() - return error_code if error_code else 0 - except AttributeError: - LOGGER.warning("There was no target: %s", target_name) - return ERROR - except Exception as ex: - LOGGER.error(ex) - return UNKNOWN_ERROR - diff --git a/xnt/commands/version.py b/xnt/commands/version.py deleted file mode 100644 index 9c2e302..0000000 --- a/xnt/commands/version.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python -"""Version Xnt Command""" - -# Xnt -- A Wrapper Build Tool -# Copyright (C) 2012 Kenny Ballou - -# This program 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. - -# This program 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 this program. If not, see . - -from xnt.basecommand import Command -from xnt.status_codes import SUCCESS - -class VersionCommand(Command): - """Version Command""" - name = 'version' - usage = """""" - summary = "Print Version of Xnt" - needs_build = False - - def run(self, arguments=None): - """Invoke Version""" - from xnt import __version__ - print(__version__) - return SUCCESS diff --git a/xnt/tasks.py b/xnt/tasks.py index 7a3e79d..679701c 100644 --- a/xnt/tasks.py +++ b/xnt/tasks.py @@ -127,12 +127,10 @@ def xntcall(path, targets=None, props=None): param: targets - list of targets to execute param: props - dictionary of properties to pass to the build module """ - import xnt.xenant - from xnt.commands.target import TargetCommand + from xnt.xenant import invoke_build, load_build cwd = os.getcwd() - command = TargetCommand(xnt.xenant.load_build(path)) os.chdir(path) - error_code = command.run(targets=targets, props=props) + error_code = invoke_build(load_build(path), targets=targets, props=props) os.chdir(cwd) return error_code diff --git a/xnt/tests/xenantargparsertests.py b/xnt/tests/xenantargparsertests.py new file mode 100644 index 0000000..3a22b33 --- /dev/null +++ b/xnt/tests/xenantargparsertests.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python +"""Xenant Arg Parser Tests""" + +# Xnt -- A Wrapper Build Tool +# Copyright (C) 2012 Kenny Ballou + +# This program 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. + +# This program 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 this program. If not, see . + +import xnt.xenant +import unittest + +#pylint: disable-msg=C0103 +class XenantArgParserTests(unittest.TestCase): + """Test Cases for Xenant Args Parsing""" + + def test_nil_args(self): + """Test the empty case (no arguements)""" + args_in = [] + args = xnt.xenant.parse_args(args_in) + self.assertIsNotNone(args) + self.assertFalse(args["verbose"]) + self.assertFalse(args["list-targets"]) + self.assertIsNone(args["properties"]) + self.assertIsNotNone(args["targets"]) + self.assertEqual(len(args["targets"]), 0) + + def test_single_target(self): + """Test the single target case""" + args_in = ["my_target"] + args = xnt.xenant.parse_args(args_in) + self.assertIsNotNone(args) + self.assertFalse(args["verbose"]) + self.assertFalse(args["list-targets"]) + self.assertIsNone(args["properties"]) + self.assertIsNotNone(args["targets"]) + self.assertEqual(len(args["targets"]), 1) + + def test_verbose(self): + """Test verbose flag""" + args_in = ["-v"] + args = xnt.xenant.parse_args(args_in) + self.assertIsNotNone(args) + self.assertTrue(args["verbose"]) + self.assertFalse(args["list-targets"]) + self.assertIsNone(args["properties"]) + self.assertIsNotNone(args["targets"]) + self.assertEqual(len(args["targets"]), 0) + + def test_list_targets_short(self): + """Test list targets flag, short hand""" + args_in = ["-l"] + args = xnt.xenant.parse_args(args_in) + self.assertIsNotNone(args) + self.assertFalse(args["verbose"]) + self.assertTrue(args["list-targets"]) + self.assertIsNone(args["properties"]) + self.assertIsNotNone(args["targets"]) + self.assertEqual(len(args["targets"]), 0) + + def test_list_targets_long(self): + """Test list targets flag, long hand""" + args_in = ["--list-targets"] + args = xnt.xenant.parse_args(args_in) + self.assertIsNotNone(args) + self.assertFalse(args["verbose"]) + self.assertTrue(args["list-targets"]) + self.assertIsNone(args["properties"]) + self.assertIsNotNone(args["targets"]) + self.assertEqual(len(args["targets"]), 0) + + def test_single_verbose(self): + """Test the verbose single case""" + args_in = ["-v", "my_verbose_target"] + args = xnt.xenant.parse_args(args_in) + self.assertIsNotNone(args) + self.assertTrue(args["verbose"]) + self.assertFalse(args["list-targets"]) + self.assertIsNone(args["properties"]) + self.assertIsNotNone(args["targets"]) + self.assertEqual(len(args["targets"]), 1) + + def test_multi_target(self): + """Test the verbose single case""" + args_in = ["my_first_target", "my_second_target"] + args = xnt.xenant.parse_args(args_in) + self.assertIsNotNone(args) + self.assertFalse(args["verbose"]) + self.assertFalse(args["list-targets"]) + self.assertIsNone(args["properties"]) + self.assertIsNotNone(args["targets"]) + self.assertEqual(len(args["targets"]), 2) + + def test_properties_no_target(self): + """Test property parsing""" + args_in = ["-Dmyvar=myvalue"] + args = xnt.xenant.parse_args(args_in) + self.assertIsNotNone(args) + self.assertFalse(args["verbose"]) + self.assertFalse(args["list-targets"]) + self.assertIsNotNone(args["properties"]) + self.assertEqual(len(args["properties"]), 1) + self.assertEqual(args["properties"][0], "myvar=myvalue") + self.assertIsNotNone(args["targets"]) + self.assertEqual(len(args["targets"]), 0) + + def test_more_properties(self): + """Test property parsing""" + args_in = ["-Dmyvar=myvalue", "-Dmyothervar=myothervalue"] + args = xnt.xenant.parse_args(args_in) + self.assertIsNotNone(args) + self.assertFalse(args["verbose"]) + self.assertFalse(args["list-targets"]) + self.assertIsNotNone(args["properties"]) + self.assertEqual(len(args["properties"]), 2) + self.assertEqual(args["properties"][0], "myvar=myvalue") + self.assertEqual(args["properties"][1], "myothervar=myothervalue") + self.assertIsNotNone(args["targets"]) + self.assertEqual(len(args["targets"]), 0) + +if __name__ == "__main__": + unittest.main() diff --git a/xnt/version.py b/xnt/version.py index 3562358..77cf994 100644 --- a/xnt/version.py +++ b/xnt/version.py @@ -17,5 +17,5 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -__version_info__ = (0, 5, 2) +__version_info__ = (0, 5, 3) __version__ = '.'.join(list(str(i) for i in __version_info__ if True)) diff --git a/xnt/xenant.py b/xnt/xenant.py index e67ef7c..9e5a3df 100644 --- a/xnt/xenant.py +++ b/xnt/xenant.py @@ -21,9 +21,9 @@ import os import sys import time import logging -from xnt.cmdoptions import OPTIONS -from xnt.commands import COMMANDS -from xnt.commands.target import TargetCommand +import argparse +from xnt import __version__ +from xnt.status_codes import SUCCESS, ERROR, UNKNOWN_ERROR logging.basicConfig(format="%(asctime)s:%(levelname)s:%(message)s") LOGGER = logging.Logger(name=__name__) @@ -32,30 +32,15 @@ LOGGER.addHandler(logging.StreamHandler()) def main(): """Xnt Entry Point""" start_time = time.time() - params = list(p for p in sys.argv[1:] if p.startswith('-D')) - flags = list(o for o in sys.argv[1:] - if o.startswith('-') and o not in params) - cmds = list(c for c in sys.argv[1:] - if c not in flags and c not in params) - #Loop flags and apply them - for flag in flags: - if flag in OPTIONS: - OPTIONS[flag]() - else: - LOGGER.debug("%s is not a vaild option", flag) - #run things - cmd_found = False - for cmd in cmds: - if cmd in COMMANDS: - cmd_found = True - if COMMANDS[cmd].needs_build: - command = COMMANDS[cmd](load_build()) - else: - command = COMMANDS[cmd]() - error_code = command.run() - if cmd_found == False: - command = TargetCommand(load_build()) - error_code = command.run(targets=cmds, props=params) + args = parse_args(sys.argv[1:]) + if args["verbose"]: + LOGGER.setLevel(logging.DEBUG) + if args["list-targets"]: + error_code = list_targets(load_build()) + else: + error_code = invoke_build(load_build(), + args["targets"], + args["properties"]) elapsed_time = time.time() - start_time LOGGER.info("Execution time: %.3f", elapsed_time) if error_code != 0: @@ -91,5 +76,92 @@ def load_build(path=""): del sys.modules["build"] os.chdir(cwd) +def invoke_build(build, targets=None, props=None): + """Invoke Build with `targets` passing `props`""" + def call_target(target_name, props): + """Call target on build module""" + def process_params(params, existing_props=None): + """Parse and separate properties and append to build module""" + properties = existing_props if existing_props is not None else {} + for param in params: + name, value = param.split("=") + properties[name] = value + return properties + def __get_properties(): + """Return the properties dictionary of the build module""" + try: + return getattr(build, "PROPERTIES") + except AttributeError: + LOGGER.warning("Build file specifies no properties") + return None + try: + if props and len(props) > 0: + setattr(build, + "PROPERTIES", + process_params(props, __get_properties())) + target = getattr(build, target_name) + error_code = target() + return error_code if error_code else 0 + except AttributeError: + LOGGER.error("There was no target: %s", target_name) + return ERROR + except Exception as ex: + LOGGER.critical(ex) + return UNKNOWN_ERROR + if targets and len(targets) > 0: + for target in targets: + error_code = call_target(target, props) + if error_code: + return error_code + return SUCCESS + else: + return call_target("default", props) + +def list_targets(build): + """List targets (and doctstrings) of the provided build module""" + try: + for attr in dir(build): + try: + func = getattr(build, attr) + if func.decorator == "target": + print(attr + ":") + if func.__doc__: + print(func.__doc__) + print("") + except AttributeError: + pass + except AttributeError as ex: + LOGGER.error(ex) + return ERROR + return SUCCESS + +def parse_args(args_in): + """Parse and group arguments""" + parser = argparse.ArgumentParser(prog="Xnt") + parser.add_argument("-v", "--verbose", + help="Enable verbose output", + action="store_true", + dest="verbose") + parser.add_argument( + "--version", + action="version", + version=__version__, + help="Print Xnt Version and quit") + parser.add_argument("-l", "--list-targets", + action="store_true", + dest="list-targets", + help="Print build targets") + # Properties Group + params_group = parser.add_argument_group("Properties") + params_group.add_argument( + "-D", dest="properties", action="append", + help="Property values to be passed to the build module") + target_group = parser.add_argument_group("Targets") + + # Targets Group + target_group.add_argument("targets", nargs=argparse.REMAINDER, + help="Name(s) of targets to invoke") + return vars(parser.parse_args(args_in)) + if __name__ == "__main__": main() -- cgit v1.2.1