diff --git a/.gitignore b/.gitignore index 94b65a47bef7b53ece3c0622771eed35da3804a1..6eb6012acdd1e3006b1d4c508c0d35bbea10d7a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ venv/* __pycache__/* -dist/* \ No newline at end of file +dist/* +__pycache__/ \ No newline at end of file diff --git a/src/__init__.py b/src/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..918e8289956f10fd166504696758c5edd60bbe8f 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -0,0 +1 @@ +from main import * diff --git a/src/bus.py b/src/bus.py new file mode 100644 index 0000000000000000000000000000000000000000..3ef95aa8835277d8c0ddb7cc376298842f01f8dc --- /dev/null +++ b/src/bus.py @@ -0,0 +1,91 @@ +import collections +import pydash + + +class BusError(Exception): + pass + + +class Bus: + + def __init__(self): + self.ID = 0 + self.providers = {} + self.listeners = collections.defaultdict(list) + + + def provide(self, topic, callback): + '''Provide an RPC to be used by the bus.''' + self.providers[topic] = callback + + + def call(self, topic, *args, **kwargs): + '''Call an RPC that exists on the bus and any listeners.''' + + # Check if RPC exists, throw error on fault. + def non_existant_rpc(): + return topic not in self.providers + + def raise_RPC_error(): + raise BusError('No RPC Provider: {}'.format(topic)) + + + # Else call listeners and then return with RPC result. + def call_listeners(): + if topic in self.listeners: + for _, listener in self.listeners[topic]: + listener(*args, **kwargs) + + def call_rpc(): + return self.providers[topic](*args, **kwargs) + + call_listeners_and_rpc = pydash.flow(call_listeners, call_rpc) + + return pydash.cond([ + (non_existant_rpc, raise_RPC_error), + (pydash.stub_true, call_listeners_and_rpc) + ])() + + + def listen(self, topic, callback): + '''Listen to a specific topic and receive a callback for when it is called.''' + ID = self.ID + self.ID += 1 + self.listeners[topic].append((ID, callback)) + return ID + + + def unlisten(self, topic, ID): + '''Stop recieving updates for a topic.''' + + def has_id(): + return any(i == ID for i, _ in self.listeners[topic]) + + def remove_id(): + items = self.listeners[topic] + keep = lambda i, _: i != ID + self.listeners[topic] = [(a, b) for a, b in items if keep(a, b)] + + return pydash.cond([ + (has_id, remove_id) + ])() + + + def unprovide(self, topic): + '''Unregister a topic.''' + + def has_rpc(): + return topic in self.providers + + def raise_RPC_error(): + raise BusError('No RPC Provider: {}'.format(topic)) + + + def remove_RPC(): + del self.providers[topic] + + + return pydash.cond([ + (has_rpc, raise_RPC_error), + (pydash.stub_true, remove_RPC) + ])() diff --git a/src/core.py b/src/core.py new file mode 100644 index 0000000000000000000000000000000000000000..5ba35da24e4f1e6c6a1208220cc9dce714b1485a --- /dev/null +++ b/src/core.py @@ -0,0 +1,50 @@ +import os +import configparser + + +class CredentialsException(Exception): + '''An exception type for missing credentials.''' + + +def TouchStructure(): + '''Initializes the .kattcmd file in the users home directory.''' + # Create ~/.kattcmd and store: + # - Path to .kattisrc file + # - Empty list of plugins + + # [options] + # kattisrc=~/.kattisrc (check that it exists) + # plugins=[] + + user_folder = os.path.expanduser('~') + kattcmd_file = os.path.join(user_folder, '.kattcmd') + if not os.path.isfile(kattcmd_file): + config = configparser.ConfigParser() + config['options'] = { + 'kattisrc': os.path.expanduser('~/.kattisrc'), + 'plugins': [] + } + + with open(kattcmd_file, 'w') as configfile: + configfile.write(config) + + return True + return False + + +def _ListBuiltins(): + '''Returns a list of all the builtin plugins.''' + return [] + + +def _ListExternals(): + '''Returns a list of all user-added plugins.''' + config_path = os.path.expanduser('~/.kattcmd') + config = configparser.ConfigParser() + config.read(config_path) + return config['options']['plugins'] + + +def ListPlugins(): + '''Returns a list of all plugins, which is all available commands used by the program.''' + return _ListBuiltins() + _ListExternals() diff --git a/src/doc.py b/src/doc.py new file mode 100644 index 0000000000000000000000000000000000000000..854eb38644ca5f455193c58096fa64b81f46c910 --- /dev/null +++ b/src/doc.py @@ -0,0 +1,6 @@ + +Interactive = '''If the session should be interactive or not. Brings up the prompt.''' + +Command = '''What command to run, cannot be used with --interactive''' + + diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000000000000000000000000000000000000..64d49afb5f6bf770710bed2c10412aa5b8e9b6b8 --- /dev/null +++ b/src/main.py @@ -0,0 +1,41 @@ +import os +import pydash +import click + +import doc +import bus +import core + + +def interactive_mode(bus, plugins): + pass + + +def command_mode(bus, plugins, command): + pass + + +@click.command() +@click.option('--interactive', default=True, type=bool, help=doc.Interactive) +@click.option('--command', default='', help=doc.Command) +def main(interactive, command): + '''Command line tool for helping with administrative tasks around Kattis.''' + the_bus = bus.Bus() + + core.TouchStructure() + plugins = core.ListPlugins() + for plugin in plugins: + plugin.Init(the_bus) + + + # Check if interactive or not, if yes: run as interactive, if not then run command + if interactive: + interactive_mode(the_bus, plugins) + elif not command: + raise ValueError('Either you must specify a command or use interactive mode.') + else: + command_mode(the_bus, plugins, command) + + +if __name__ == '__main__': + main()