Refactor CLI to interact with new dungeon code
This commit is contained in:
parent
6b2f898d36
commit
f284faee92
|
@ -4,93 +4,81 @@ import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from core import load_all_sources
|
import dungeon as dungeonlib
|
||||||
from dungeon import Dungeon
|
|
||||||
|
|
||||||
# Globals
|
# Globals
|
||||||
logger = logging.getLogger("inquisitor.cli")
|
logger = logging.getLogger("inquisitor.cli")
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def parse_args(commands):
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("command", default="help", help="The command to execute", choices=list(commands.keys()))
|
||||||
|
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.add_argument("--log", default="INFO", help="Set the log level (default: INFO)")
|
||||||
subparsers = parser.add_subparsers(help="Command to execute", dest="command")
|
|
||||||
subparsers.required = True
|
|
||||||
|
|
||||||
update_parser = subparsers.add_parser("update", help="Fetch new items")
|
|
||||||
update_parser.add_argument("--srcdir", help="Path to sources folder (default ./sources)",
|
|
||||||
default="./sources")
|
|
||||||
update_parser.add_argument("--dungeon", help="Path to item cache folder (default ./dungeon)",
|
|
||||||
default="./dungeon")
|
|
||||||
update_parser.add_argument("--sources", help="Sources to update, by name",
|
|
||||||
nargs="*")
|
|
||||||
update_parser.set_defaults(func=update)
|
|
||||||
|
|
||||||
deactivate_parser = subparsers.add_parser("deactivate", help="Deactivate items by source")
|
|
||||||
deactivate_parser.add_argument("--srcdir", help="Path to sources folder (default ./sources)",
|
|
||||||
default="./sources")
|
|
||||||
deactivate_parser.add_argument("--dungeon", help="Path to item cache folder (default ./dungeon)",
|
|
||||||
default="./dungeon")
|
|
||||||
deactivate_parser.add_argument("--sources", help="Sources to deactivate, by name",
|
|
||||||
nargs="*")
|
|
||||||
deactivate_parser.set_defaults(func=deactivate)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Configure logging
|
if not os.path.isdir(args.srcdir):
|
||||||
|
print("Error: srcdir must be a directory")
|
||||||
|
exit(-1)
|
||||||
|
if not os.path.isdir(args.dungeon):
|
||||||
|
logger.error("Error: dungeon must be a directory")
|
||||||
|
exit(-1)
|
||||||
|
if not args.sources:
|
||||||
|
logger.error("Error: No sources specified")
|
||||||
|
exit(-1)
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
# Enumerate valid commands.
|
||||||
|
g = globals()
|
||||||
|
commands = {
|
||||||
|
name[8:] : g[name]
|
||||||
|
for name in g
|
||||||
|
if name.startswith("command_")}
|
||||||
|
|
||||||
|
args = parse_args(commands)
|
||||||
|
|
||||||
|
# Configure logging.
|
||||||
loglevel = getattr(logging, args.log.upper())
|
loglevel = getattr(logging, args.log.upper())
|
||||||
if not isinstance(loglevel, int):
|
if not isinstance(loglevel, int):
|
||||||
raise ValueError("Invalid log level: {}".format(args.log))
|
raise ValueError("Invalid log level: {}".format(args.log))
|
||||||
logging.basicConfig(format='[%(levelname)s:%(filename)s:%(lineno)d] %(message)s', level=loglevel)
|
logging.basicConfig(format='[%(levelname)s:%(filename)s:%(lineno)d] %(message)s', level=loglevel)
|
||||||
|
|
||||||
args.func(args)
|
# Execute command.
|
||||||
|
commands[args.command](args)
|
||||||
|
|
||||||
|
|
||||||
def update(args):
|
def command_update(args):
|
||||||
"""Fetches new items from sources and stores them in the dungeon."""
|
"""Fetches new items from sources and stores them in the dungeon."""
|
||||||
if not os.path.isdir(args.srcdir):
|
# Initialize dungeon.
|
||||||
logger.error("srcdir must be a directory")
|
dungeon = dungeonlib.Dungeon(args.dungeon)
|
||||||
exit(-1)
|
|
||||||
if not os.path.isdir(args.dungeon):
|
|
||||||
logger.error("dungeon must be a directory")
|
|
||||||
exit(-1)
|
|
||||||
sources = load_all_sources(args.srcdir)
|
|
||||||
source_names = [s.SOURCE for s in sources]
|
|
||||||
logger.debug("Known sources: {}".format(source_names))
|
|
||||||
if args.sources:
|
|
||||||
names = args.sources
|
|
||||||
for name in names:
|
|
||||||
if name not in source_names:
|
|
||||||
logger.error("Source not found: {}".format(name))
|
|
||||||
else:
|
|
||||||
names = source_names
|
|
||||||
dungeon = Dungeon(args.dungeon)
|
|
||||||
for itemsource in sources:
|
|
||||||
if itemsource.SOURCE in names:
|
|
||||||
new_items = dungeon.update(itemsource)
|
|
||||||
items = dungeon.get_active_items_for_folder(itemsource.SOURCE)
|
|
||||||
logger.info("{} new item{}".format(new_items, "s" if new_items != 1 else ""))
|
|
||||||
|
|
||||||
def deactivate(args):
|
# Process each source argument.
|
||||||
|
for source_arg in args.sources:
|
||||||
|
dungeon.update(source_arg, args)
|
||||||
|
|
||||||
|
|
||||||
|
def command_deactivate(args):
|
||||||
"""Deactivates all items in the given sources."""
|
"""Deactivates all items in the given sources."""
|
||||||
if not os.path.isdir(args.dungeon):
|
# Initialize dungeon.
|
||||||
logger.error("dungeon must be a directory")
|
dungeon = dungeonlib.Dungeon(args.dungeon)
|
||||||
exit(-1)
|
|
||||||
sources = load_all_sources(args.srcdir)
|
# Deactivate all items in each source.
|
||||||
source_names = [s.SOURCE for s in sources]
|
for source_name in args.sources:
|
||||||
logger.debug("Known sources: {}".format(source_names))
|
if source_name not in dungeon:
|
||||||
if args.sources:
|
print("Error: No source named '{}'".format(source_name))
|
||||||
names = args.sources
|
print("Valid source names are: " + " ".join([s for s in dungeon]))
|
||||||
for name in names:
|
continue
|
||||||
if name not in source_names:
|
cell = dungeon[source_name]
|
||||||
logger.error("Source not found: {}".format(name))
|
count = 0
|
||||||
else:
|
for item_id in cell:
|
||||||
names = source_names
|
item = cell[item_id]
|
||||||
dungeon = Dungeon(args.dungeon)
|
if item['active']:
|
||||||
for name in names:
|
item.deactivate()
|
||||||
if os.path.isdir(os.path.join(args.dungeon, name)):
|
count += 1
|
||||||
items = dungeon.get_active_items_for_folder(name)
|
logger.info("Deactivated {} items in '{}'".format(count, source_name))
|
||||||
for item in items:
|
|
||||||
dungeon.deactivate_item(name, item['id'])
|
|
||||||
else:
|
|
||||||
logger.error("Folder not found: {}".format(name))
|
|
||||||
|
|
|
@ -7,10 +7,8 @@ import time
|
||||||
import random
|
import random
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from item import create_item
|
import item # This will define create_item as a builtin
|
||||||
|
|
||||||
|
|
||||||
# Globals
|
# Globals
|
||||||
logger = logging.getLogger("inquisitor.dungeon")
|
logger = logging.getLogger("inquisitor.dungeon")
|
||||||
|
@ -85,7 +83,7 @@ class DungeonCell():
|
||||||
with open(state_path, 'r', encoding='utf-8') as f:
|
with open(state_path, 'r', encoding='utf-8') as f:
|
||||||
self.state = ast.literal_eval(f.read())
|
self.state = ast.literal_eval(f.read())
|
||||||
|
|
||||||
def _item_path(key):
|
def _item_path(self, key):
|
||||||
return os.path.join(self.path, key + ".item")
|
return os.path.join(self.path, key + ".item")
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
|
@ -95,7 +93,7 @@ class DungeonCell():
|
||||||
return ReadableItem(self.dungeon_path, self.name, key)
|
return ReadableItem(self.dungeon_path, self.name, key)
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
logger.info("Setting item {} in cell {}".format(key, self.name))
|
logger.debug("Setting item {} in cell {}".format(key, self.name))
|
||||||
if type(value) is ReadableItem:
|
if type(value) is ReadableItem:
|
||||||
value = value.item
|
value = value.item
|
||||||
if type(value) is not dict:
|
if type(value) is not dict:
|
||||||
|
@ -105,7 +103,7 @@ class DungeonCell():
|
||||||
f.write(str(value))
|
f.write(str(value))
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
logger.info("Deleting item '{}' in cell '{}'".format(key, self.name))
|
logger.debug("Deleting item '{}' in cell '{}'".format(key, self.name))
|
||||||
filepath = self._item_path(key)
|
filepath = self._item_path(key)
|
||||||
if os.path.isfile(filepath):
|
if os.path.isfile(filepath):
|
||||||
os.remove(filepath)
|
os.remove(filepath)
|
||||||
|
@ -131,7 +129,7 @@ class DungeonCell():
|
||||||
# Get the ids of the existing items.
|
# Get the ids of the existing items.
|
||||||
prior_item_ids = [item_id for item_id in self]
|
prior_item_ids = [item_id for item_id in self]
|
||||||
# Get the new items.
|
# Get the new items.
|
||||||
new_items = itemsource.fetch_new(state, args)
|
new_items = source.fetch_new(self.state, args)
|
||||||
self.save_state()
|
self.save_state()
|
||||||
new_count = del_count = 0
|
new_count = del_count = 0
|
||||||
for item in new_items:
|
for item in new_items:
|
||||||
|
@ -171,7 +169,7 @@ class Dungeon():
|
||||||
for filename in os.listdir(self.path):
|
for filename in os.listdir(self.path):
|
||||||
if not os.path.isdir(os.path.join(self.path, filename)):
|
if not os.path.isdir(os.path.join(self.path, filename)):
|
||||||
continue
|
continue
|
||||||
if not os.path.isfile(os.path.join(self.path, filename, 'status')):
|
if not os.path.isfile(os.path.join(self.path, filename, 'state')):
|
||||||
continue
|
continue
|
||||||
self.cells[filename] = DungeonCell(self.path, filename)
|
self.cells[filename] = DungeonCell(self.path, filename)
|
||||||
# Ensure Inquisitor's source is present
|
# Ensure Inquisitor's source is present
|
||||||
|
@ -194,7 +192,6 @@ class Dungeon():
|
||||||
yield name
|
yield name
|
||||||
|
|
||||||
def push_error_item(self, title, body=None):
|
def push_error_item(self, title, body=None):
|
||||||
logger.error(title)
|
|
||||||
item = create_item(
|
item = create_item(
|
||||||
'inquisitor',
|
'inquisitor',
|
||||||
'{:x}'.format(random.getrandbits(16 * 4)),
|
'{:x}'.format(random.getrandbits(16 * 4)),
|
||||||
|
@ -208,25 +205,33 @@ class Dungeon():
|
||||||
necessary. Returns the source and its DungeonCell if the source is
|
necessary. Returns the source and its DungeonCell if the source is
|
||||||
valid and None, None otherwise.
|
valid and None, None otherwise.
|
||||||
"""
|
"""
|
||||||
# Check if the named source is present in the sources directory.
|
# Move to the sources directory.
|
||||||
source_file_path = os.path.join(sources_path, source_name + ".py")
|
cwd = os.getcwd()
|
||||||
if not os.path.isfile(source_file_path):
|
os.chdir(sources_path)
|
||||||
|
# Check if the named source is present.
|
||||||
|
source_file_name = source_name + ".py"
|
||||||
|
if not os.path.isfile(source_file_name):
|
||||||
|
os.chdir(cwd)
|
||||||
msg = "Could not find source '{}'".format(source_name)
|
msg = "Could not find source '{}'".format(source_name)
|
||||||
|
logger.error(msg)
|
||||||
self.push_error_item(msg)
|
self.push_error_item(msg)
|
||||||
return None, None
|
return None, None
|
||||||
# Try to import the source module.
|
# Try to import the source module.
|
||||||
try:
|
try:
|
||||||
logger.debug("Loading module {}".format(source_file_path))
|
logger.debug("Loading module {}".format(source_file_name))
|
||||||
spec = importlib.util.spec_from_file_location("itemsource", source_file_path)
|
spec = importlib.util.spec_from_file_location("itemsource", source_file_name)
|
||||||
itemsource = importlib.util.module_from_spec(spec)
|
itemsource = importlib.util.module_from_spec(spec)
|
||||||
spec.loader.exec_module(itemsource)
|
spec.loader.exec_module(itemsource)
|
||||||
if not hasattr(itemsource, 'fetch_new'):
|
if not hasattr(itemsource, 'fetch_new'):
|
||||||
raise ImportError("fetch_new missing")
|
raise ImportError("fetch_new missing")
|
||||||
except Exception:
|
except Exception:
|
||||||
|
os.chdir(cwd)
|
||||||
msg = "Error importing source '{}'".format(source_name)
|
msg = "Error importing source '{}'".format(source_name)
|
||||||
|
logger.error("{}\n{}".format(msg, traceback.format_exc()))
|
||||||
self.push_error_item(msg, traceback.format_exc())
|
self.push_error_item(msg, traceback.format_exc())
|
||||||
return None, None
|
return None, None
|
||||||
# Since the source is valid, get or create the source cell.
|
# Since the source is valid, get or create the source cell.
|
||||||
|
os.chdir(cwd)
|
||||||
if source_name not in self:
|
if source_name not in self:
|
||||||
self[source_name] = DungeonCell(self.path, source_name)
|
self[source_name] = DungeonCell(self.path, source_name)
|
||||||
|
|
||||||
|
@ -255,5 +260,6 @@ class Dungeon():
|
||||||
deleted_count, "s" if deleted_count != 1 else ""))
|
deleted_count, "s" if deleted_count != 1 else ""))
|
||||||
except:
|
except:
|
||||||
msg = "Error fetching items from source '{}'".format(source_name)
|
msg = "Error fetching items from source '{}'".format(source_name)
|
||||||
|
logger.error("{}\n{}".format(msg, traceback.format_exc()))
|
||||||
self.push_error_item(msg, traceback.format_exc())
|
self.push_error_item(msg, traceback.format_exc())
|
||||||
return
|
return
|
||||||
|
|
|
@ -2,14 +2,13 @@
|
||||||
import importlib.util
|
import importlib.util
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
# Globals
|
# Globals
|
||||||
logger = logging.getLogger("inquisitor.item")
|
logger = logging.getLogger("inquisitor.item")
|
||||||
|
|
||||||
|
|
||||||
def create_item(source, item_id, title, link=None, time=None, author=None, body=None):
|
def create_item(source, item_id, title, link=None, time=None, author=None, body=None):
|
||||||
|
import time
|
||||||
item = {
|
item = {
|
||||||
'id': item_id,
|
'id': item_id,
|
||||||
'source': source,
|
'source': source,
|
||||||
|
@ -26,3 +25,6 @@ def create_item(source, item_id, title, link=None, time=None, author=None, body=
|
||||||
if body is not None:
|
if body is not None:
|
||||||
item['body'] = body
|
item['body'] = body
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
import builtins
|
||||||
|
builtins.create_item = create_item
|
||||||
|
|
Loading…
Reference in New Issue