From 69627d3fdac901abb78a8ab216da5dab6608ca32 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Mon, 16 Dec 2019 23:40:46 -0800 Subject: [PATCH] Revamp CLI and add add command --- inquisitor/__main__.py | 63 +++++++++--------- inquisitor/cli.py | 143 ++++++++++++++++++++++++++++++----------- inquisitor/configs.py | 18 ++++-- inquisitor/importer.py | 2 +- 4 files changed, 153 insertions(+), 73 deletions(-) diff --git a/inquisitor/__main__.py b/inquisitor/__main__.py index af2b57a..d460a87 100644 --- a/inquisitor/__main__.py +++ b/inquisitor/__main__.py @@ -1,42 +1,43 @@ # Standard library imports import argparse -import os -import logging -import sys # Application imports import cli - -# Globals -logger = logging.getLogger("inquisitor.__main__") +import configs def parse_args(valid_commands): command_descs = "\n".join([ "- {0}: {1}".format(name, func.__doc__) for name, func in valid_commands.items()]) - parser = argparse.ArgumentParser(description="Available commands:\n{}\n".format(command_descs), formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument("command", default="help", help="The command to execute", choices=valid_commands, metavar="COMMAND") - parser.add_argument("--srcdir", help="Path to sources folder (default ./sources)", default="./sources") - parser.add_argument("--dungeon", help="Path to item cache folder (default ./dungeon)", default="./dungeon") - parser.add_argument("--sources", help="Sources to update, by name", nargs="*") - parser.add_argument("--log", default="INFO", help="Set the log level (default: INFO)") + parser = argparse.ArgumentParser( + description="Available commands:\n{}\n".format(command_descs), + formatter_class=argparse.RawDescriptionHelpFormatter, + add_help=False) + parser.add_argument("command", + nargs="?", + default="help", + help="The command to execute", + choices=valid_commands, + metavar="command") + parser.add_argument("args", + nargs=argparse.REMAINDER, + help="Command arguments", + metavar="args") + parser.add_argument("-v", + action="store_true", + dest="verbose", + help="Enable debug logging") - args = parser.parse_args() + global print_usage + print_usage = parser.print_help - return args - - -def run_flask_server(args): - """Run the default flask server serving from the specified dungeon.""" - try: - from app import app - app.run() - return 0 - except Exception as e: - logger.error(e) - return (-1) + return parser.parse_args() +def command_help(args): + """Print this help message and exit.""" + print_usage() + return 0 def main(): # Enumerate valid commands. @@ -44,21 +45,19 @@ def main(): name[8:] : func for name, func in vars(cli).items() if name.startswith("command_")} - commands["run"] = run_flask_server + commands['help'] = command_help args = parse_args(commands) # Configure logging. - if args.command != 'run': - loglevel = getattr(logging, args.log.upper()) - if not isinstance(loglevel, int): - raise ValueError("Invalid log level: {}".format(args.log)) - logging.basicConfig(format='[%(levelname)s:%(filename)s:%(lineno)d] %(message)s', level=loglevel) + if args.verbose: + configs.log_verbose() # Execute command. if args.command: - return commands[args.command](args) + return commands[args.command](args.args) if __name__ == "__main__": + import sys sys.exit(main()) diff --git a/inquisitor/cli.py b/inquisitor/cli.py index e7aefa2..084357c 100644 --- a/inquisitor/cli.py +++ b/inquisitor/cli.py @@ -1,60 +1,131 @@ # Standard library imports +import argparse +import json import logging import os +import random # Application imports -import dungeon as dungeonlib - -# Globals -logger = logging.getLogger("inquisitor.cli") +from configs import logger, DUNGEON_PATH, SOURCES_PATH def command_update(args): """Fetch and store new items from the specified sources.""" - if not os.path.isdir(args.srcdir): - print("update: Error: srcdir must be a directory") - return (-1) - if not os.path.isdir(args.dungeon): - logger.error("update: Error: dungeon must be a directory") - return (-1) - if not args.sources: - logger.error("update: Error: No sources specified") - return (-1) + parser = argparse.ArgumentParser( + prog="inquisitor update", + description=command_update.__doc__, + add_help=False) + parser.add_argument("source", + nargs="*", + help="Sources to update.") + args = parser.parse_args(args) - # Initialize dungeon. - dungeon = dungeonlib.Dungeon(args.dungeon) - - # Process each source argument. - for source_arg in args.sources: - dungeon.update(source_arg, args) + if len(args.source) == 0: + parser.print_help() + return 0 + if not os.path.isdir(DUNGEON_PATH): + logger.error("Couldn't find dungeon. Set INQUISITOR_DUNGEON or cd to parent folder of ./dungeon") + return -1 + if not os.path.isdir(SOURCES_PATH): + logger.error("Couldn't find sources. Set INQUISITOR_SOURCES or cd to parent folder of ./sources") + # Update sources + from importer import update_sources + update_sources(*args.source) return 0 + def command_deactivate(args): """Deactivate all items in the specified dungeon cells.""" - if not os.path.isdir(args.dungeon): - logger.error("deactivate: Error: dungeon must be a directory") - return (-1) - if not args.sources: - logger.error("deactivate: Error: No sources specified") - return (-1) + parser = argparse.ArgumentParser( + prog="inquisitor deactivate", + description=command_deactivate.__doc__, + add_help=False) + parser.add_argument("source", + nargs="*", + help="Cells to deactivate.") + args = parser.parse_args(args) - # Initialize dungeon. - dungeon = dungeonlib.Dungeon(args.dungeon) + if len(args.source) == 0: + parser.print_help() + return 0 + if not os.path.isdir(DUNGEON_PATH): + logger.error("Couldn't find dungeon. Set INQUISITOR_DUNGEON or cd to parent folder of ./dungeon") + return -1 # Deactivate all items in each source. - for source_name in args.sources: - if source_name not in dungeon: - print("Error: No source named '{}'".format(source_name)) - print("Valid source names are: " + " ".join([s for s in dungeon])) - continue - cell = dungeon[source_name] + from loader import load_items + for source_name in args.source: + path = os.path.join(DUNGEON_PATH, source_name) + if not os.path.isdir(path): + logger.warning("'{}' is not an extant source".format(source_name)) count = 0 - for item_id in cell: - item = cell[item_id] + items, _ = load_items(source_name) + for item in items.values(): if item['active']: - item.deactivate() + item['active'] = False count += 1 logger.info("Deactivated {} items in '{}'".format(count, source_name)) return 0 + + +def command_add(args): + """Creates an item.""" + parser = argparse.ArgumentParser( + prog="inquisitor add", + description=command_add.__doc__, + add_help=False) + parser.add_argument("--id", help="String") + parser.add_argument("--source", help="String") + parser.add_argument("--title", help="String") + parser.add_argument("--link", help="URL") + parser.add_argument("--time", type=int, help="Unix timestmap") + parser.add_argument("--author", help="String") + parser.add_argument("--body", help="HTML") + parser.add_argument("--tags", help="Comma-separated list") + parser.add_argument("--ttl", type=int, help="Cleanup protection in seconds") + args = parser.parse_args(args) + + if not args.title: + parser.print_help() + return 0 + if not os.path.isdir(DUNGEON_PATH): + logger.error("Couldn't find dungeon. Set INQUISITOR_DUNGEON or cd to parent folder of ./dungeon") + return -1 + + from importer import populate_new + item = { + 'id': '{:x}'.format(random.getrandbits(16 * 4)), + 'source': 'inquisitor' + } + if args.id: item['id'] = str(args.id) + if args.source: item['source'] = str(args.source) + if args.title: item['title'] = str(args.title) + if args.link: item['link'] = str(args.link) + if args.time: item['time'] = int(args.time) + if args.author: item['author'] = str(args.author) + if args.body: item['body'] = str(args.body) + if args.tags: item['tags'] = [str(tag) for tag in args.tags] + if args.ttl: item['ttl'] = int(args.ttl) + populate_new(item) + s = json.dumps(item, indent=2) + path = os.path.join(DUNGEON_PATH, item['source'], item['id'] + '.item') + with open(path, 'w', encoding='utf8') as f: + f.write(s) + logger.info(item) + + +# def command_run(args): +# """Run the default Flask server.""" +# pass + +# def run_flask_server(args): +# """Run the default flask server serving from the specified dungeon.""" +# try: +# from app import app +# app.run() +# return 0 +# except Exception as e: +# logger.error(e) +# return (-1) diff --git a/inquisitor/configs.py b/inquisitor/configs.py index 38fc850..918d42b 100644 --- a/inquisitor/configs.py +++ b/inquisitor/configs.py @@ -5,9 +5,19 @@ DUNGEON_PATH = os.path.abspath(os.environ.get("INQUISITOR_DUNGEON") or "./dungeo SOURCES_PATH = os.path.abspath(os.environ.get("INQUISITOR_SOURCES") or "./sources") logger = logging.getLogger("inquisitor") -logger.setLevel(logging.INFO) handler = logging.StreamHandler() -handler.setLevel(logging.INFO) -formatter = logging.Formatter('[{levelname}] {message}', style="{") -handler.setFormatter(formatter) logger.addHandler(handler) + +def log_normal(): + logger.setLevel(logging.INFO) + handler.setLevel(logging.INFO) + formatter = logging.Formatter('[{levelname}] {message}', style="{") + handler.setFormatter(formatter) + +def log_verbose(): + logger.setLevel(logging.DEBUG) + handler.setLevel(logging.DEBUG) + formatter = logging.Formatter('[{asctime}] [{levelname}:{filename}:{lineno}] {message}', style="{") + handler.setFormatter(formatter) + +log_normal() diff --git a/inquisitor/importer.py b/inquisitor/importer.py index 62f025e..6341fa0 100644 --- a/inquisitor/importer.py +++ b/inquisitor/importer.py @@ -74,7 +74,7 @@ def update_source(source_name, fetch_new): # If the item is new, write it. new_count += 1 s = json.dumps(item) - path = os.path.join(DUNGEON_PATH, item['source'], item['id']) + path = os.path.join(DUNGEON_PATH, item['source'], item['id'] + ".item") with open(path, 'w', encoding="utf8") as f: f.write(s)