Module ahd.cli
This file houses the primary entrypoint, and main business logic of ahd.
Module Variables
usage (str): Used by docopt to setup argument parsing; Defines the actual command line interface
config(dict): The dictionary containing the current configuration once deserialized from CONFIG_FILE_PATH
CONFIG_FILE_PATH(str): The path to the configuration file
CURRENT_PATH(str): Used to keep track of users current directory to cd back into it after script execution
Module Functions
main(): The primary entrypoint for the ahd script handles argument parsing
list_macros(verbose:bool = False, config:dict={}): Lists commands currently in config
docs(api:bool = False, offline:bool = False): Processes incoming arguments when the docs command is invoked
dispatch(name, command:str=False, paths:str=False, config:dict={}): Controls the dispatching of macros
Notes
While you an invoke functions directly it is recommended to use the CLI
Documentation
User docs website: https://ahd.readthedocs.io API Docs website: https://kieranwood.ca/ahd Source Code: https://github.com/Descent098/ahd Roadmap: https://github.com/Descent098/ahd/projects
Expand source code
"""This file houses the primary entrypoint, and main business logic of ahd.
Module Variables
----------------
usage (str):
Used by docopt to setup argument parsing;
Defines the actual command line interface
config(dict):
The dictionary containing the current configuration
once deserialized from CONFIG_FILE_PATH
CONFIG_FILE_PATH(str):
The path to the configuration file
CURRENT_PATH(str):
Used to keep track of users current directory
to cd back into it after script execution
Module Functions
----------------
main():
The primary entrypoint for the ahd script handles argument parsing
list_macros(verbose:bool = False, config:dict={}):
Lists commands currently in config
docs(api:bool = False, offline:bool = False):
Processes incoming arguments when the docs command is invoked
dispatch(name, command:str=False, paths:str=False, config:dict={}):
Controls the dispatching of macros
Notes
-----
While you an invoke functions directly it is recommended to use the CLI
Documentation
-------------
User docs website: https://ahd.readthedocs.io
API Docs website: https://kieranwood.ca/ahd
Source Code: https://github.com/Descent098/ahd
Roadmap: https://github.com/Descent098/ahd/projects
"""
# Standard lib dependencies
import datetime
import os # Used primarily to validate paths
import sys # Used to check length of input arguments
import glob # Used to preprocess wildcard paths
import logging # Used to log valueable logging info
import webbrowser # Used to auto-launch the documentation link
import subprocess # Used to run the dispatched commands
# Internal dependencies
from .configuration import configure, register, CONFIG_FILE_PATH
from .__init__ import __version__ as version
# Third-party dependencies
import colored # Used to colour terminal output
import yaml # Used to handle configuration serialization/deserialization
from docopt import docopt # Used to parse arguments and setup POSIX compliant usage info
from fuzzywuzzy import process as suggest_word # Used to parse word similarity for incorrect spellings
usage = """Add-hoc dispatcher
Create ad-hoc commands to be dispatched within their own namespace.
Usage:
ahd [-h] [-v]
ahd list [-l]
ahd docs [-a] [-o]
ahd config [-e] [-i CONFIG_FILE_PATH]
ahd register <name> [<command>] [<paths>]
ahd <name> [<command>] [<paths>] [-d]
Options:
-h, --help show this help message and exit
-v, --version show program's version number and exit
-l, --long Shows all commands in configuration with paths and commands
-a, --api shows the local API docs
-o, --offline shows the local User docs instead of live ones
-e, --export exports the configuration file
-i CONFIG_FILE_PATH, --import CONFIG_FILE_PATH
imports the configuration file
-d, --details prints the details of a command
"""
config = {} # The dictionary containing the current configuration once deserialized from CONFIG_FILE_PATH
CURRENT_PATH = os.curdir # Keeps track of current directory to return to after executing commands
def main() -> None:
"""The primary entrypoint for the ahd script handles argument parsing
All primary business logic is within this function."""
# Setup arguments for parsing
arguments = docopt(usage, version=f"ahd V{version}")
if len(sys.argv) == 1:
print("\n", usage)
sys.exit()
if os.path.exists(CONFIG_FILE_PATH): # If the file already exists
with open(CONFIG_FILE_PATH, "r") as config_file:
config = yaml.safe_load(config_file)
config = dict(config)
else: # If a file does not exist create one
print(f"{colored.fg(1)}Could not locate valid config file creating new one at {CONFIG_FILE_PATH} {colored.fg(15)}")
with open(CONFIG_FILE_PATH, "w") as config_file:
config_file.write("macros:")
config = {"macros": {}}
# Begin argument parsing
if arguments["list"]:
list_macros(arguments["--long"], config)
sys.exit()
# ========= Docs argument parsing =========
if arguments["docs"]:
docs(arguments["--api"], arguments["--offline"])
sys.exit()
# ========= config argument parsing =========
if arguments["config"]:
configure(arguments["--export"], arguments["--import"], config)
sys.exit()
# ========= preprocessing commands and paths =========
if not arguments["<paths>"]:
logging.debug("No paths argument registered setting to \'\'")
arguments["<paths>"] = ""
else:
arguments["<paths>"] = _preprocess_paths(arguments["<paths>"])
if not arguments["<command>"]:
logging.debug("No command argument registered setting to \'\'")
arguments["<command>"] = ""
if "." == arguments["<command>"]: # If <command> is . set to specified value
logging.debug(f". command registered, setting to {config['macros'][arguments['<name>']]['command']}")
arguments["<command>"] = config["macros"][arguments["<name>"]]["command"]
# ========= register argument parsing =========
if arguments["register"]:
register(arguments["<name>"], arguments["<command>"], arguments["<paths>"], config)
# ========= User command argument parsing =========
if arguments['<name>']:
if arguments["--details"]:
try:
config['macros'][arguments['<name>']] # Force a KeyError if the macro does not exist
print(f"{colored.fg(6)}{arguments['<name>']}{colored.fg(15)}\n")
print(f"\tCommand = {config['macros'][arguments['<name>']]['command']}")
print(f"\tPaths = {config['macros'][arguments['<name>']]['paths']}")
if config['macros'][arguments['<name>']].get("runs", False):
print(f"\tRuns = {config['macros'][arguments['<name>']]['runs']}")
if config['macros'][arguments['<name>']].get("created", False):
print(f"\tCreated = {config['macros'][arguments['<name>']]['created']}")
if config['macros'][arguments['<name>']].get("updated", False):
print(f"\tUpdated = {config['macros'][arguments['<name>']]['updated']}")
if config['macros'][arguments['<name>']].get("last_run", False):
print(f"\tLast Run = {config['macros'][arguments['<name>']]['last_run']}")
exit()
except KeyError:
... # If the command is not registered, do nothing and let the dispatch spellchecker find it
if not arguments['<paths>'] and not arguments['<command>']:
dispatch(arguments['<name>'], config=config)
else:
if arguments['<paths>'] and not arguments['<command>']:
# Process inputted paths
arguments['<paths>'] = _preprocess_paths(arguments['<paths>'])
arguments['<paths>'] = _postprocess_paths(arguments['<paths>'])
dispatch(arguments['<name>'], paths = arguments['<paths>'], config=config)
if arguments['<command>'] and not arguments['<paths>']:
dispatch(arguments['<name>'], command = arguments['<command>'], config=config)
else:
# Process inputted paths
arguments['<paths>'] = _preprocess_paths(arguments['<paths>'])
arguments['<paths>'] = _postprocess_paths(arguments['<paths>'])
dispatch(arguments['<name>'], paths = arguments['<paths>'], command = arguments['<command>'], config=config)
def list_macros(verbose:bool = False, config:dict={}) -> None:
"""Lists commands currently in config
Parameters
----------
verbose: (bool)
When specified will print both the command name and
associated commands + paths. Additionally the dictionary
will only return when this flag is specified.
config: (dict)
The dict that contains the current config
"""
# Iterate over the config, and pull information about the macros
count = 0
for count, macro in enumerate(config["macros"]):
if verbose:
print(f"{colored.fg(6)}{macro}{colored.fg(15)}\n")
try:
print(f"\tCommand = {config['macros'][macro]['command']}")
print(f"\tPaths = {config['macros'][macro]['paths']}")
except KeyError:
print(f"Macro {macro} is not configured correctly, check the command and paths variables")
sys.exit(1)
if config['macros'][macro].get("runs", False):
print(f"\tRuns = {config['macros'][macro]['runs']}")
if config['macros'][macro].get("created", False):
print(f"\tCreated = {config['macros'][macro]['created']}")
if config['macros'][macro].get("updated", False):
print(f"\tUpdated = {config['macros'][macro]['updated']}")
if config['macros'][macro].get("last_run", False):
print(f"\tLast Run = {config['macros'][macro]['last_run']}")
else:
print(f"\n{colored.fg(6)}{macro}{colored.fg(15)}")
print(f"\n\n{count+1} macros detected")
def docs(api:bool = False, offline:bool = False) -> None:
"""Processes incoming arguments when the docs command is invoked
Parameters
----------
api: (bool)
When specified, shows API docs as opposed to user docs.
offline: (bool)
When specified will build local copy of docs instead of going to website
Notes
-----
- By Default user documentation is selected
- By default the online documentation is selected
"""
if not api and not offline:
webbrowser.open_new("https://ahd.readthedocs.io")
else:
if offline and not api:
from mkdocs.commands import serve # Used to serve the user documentation locally
print("Docs available at http://localhost:8000/")
webbrowser.open_new("http://localhost:8000/")
serve.serve()
elif api:
if not offline:
webbrowser.open_new("https://kieranwood.ca/ahd")
else:
# Simulates `pdoc --http : ahd`
from pdoc.cli import main as pdoc_main # Used to serve the api documentation locally
sys.argv = [sys.argv[0], "--http", ":", "ahd"]
webbrowser.open_new("http://localhost:8080/ahd")
pdoc_main()
def dispatch(name, command:str=False, paths:str=False, config:dict={}) -> None:
"""Controls the dispatching of macros
Parameters
----------
name: (str)
The name of the macro to dispatch
command: (str)
Used to override the macros configured command
when set to False, will pull from configuration
paths: (str)
Used to override the macros configured paths
when set to False, will pull from configuration
config: (dict)
The dict that contains the current config"""
if "register" == name:
print(usage)
sys.exit()
logging.info(f"Beggining execution of {name}")
try: # Accessing stored information on the command
config["macros"][name]
if not config["macros"][name].get("runs", False):
config["macros"][name]["runs"] = 1
else:
config["macros"][name]["runs"] += 1
config["macros"][name]["last_run"] = str(datetime.datetime.now())[:10:]
with open(CONFIG_FILE_PATH, "w+") as config_file:
yaml.dump(config, config_file, default_flow_style=False) # Update config file with new metadata
except KeyError: # When command does not exist in config
if not config.get("macros", False):
print(f"{colored.fg(1)}No macros found in current config {colored.fg(15)}\n")
sys.exit(1)
commands = [current_command for current_command in config["macros"]] # Get list of commands in config
error_threshold = 60 # The percentage of likelyhood before similar words will throw out result
similar_words = suggest_word.extractBests(name, commands,score_cutoff=error_threshold , limit=3) # Generate word sugestions
if not similar_words: # If there are not similar commands that exist in the config
print(f"{colored.fg(1)}Could not find macro {colored.fg(15)}{name}{colored.fg(1)} or any valid suggestions with %{error_threshold} or higher likelyhood, please check spelling {colored.fg(15)}\n")
sys.exit(1)
# Suggestions found for similar commands
suggestions = ""
for index, word in enumerate(similar_words):
suggestions+= f"\t{index+1}. {colored.fg(3)}{word[0]}{colored.fg(15)} | %{word[1]} likelyhood\n"
print(f"{colored.fg(1)}No command {name} found {colored.fg(15)} here are some suggestions: \n{suggestions}")
if not command:
command = ""
if not paths:
paths = ""
print(f"Most likely suggestion is {colored.fg(3)}{similar_words[0][0]}{colored.fg(15)} rerun using: \n\t> ahd {similar_words[0][0]} \"{command}\" \"{paths}\"")
sys.exit(1)
if not command or command == ".":
command = config["macros"][name]['command']
if not paths:
paths = _postprocess_paths(config["macros"][name]['paths'])
if len(paths) > 1:
for current_path in paths:
if os.name == "nt":
current_path = current_path.replace("~", os.getenv('USERPROFILE'))
current_path = current_path.replace("/", f"{os.sep}")
if os.path.isdir(current_path):
print(f"Running: cd {current_path} && {command} ".replace("\'",""))
subprocess.Popen(f"cd {current_path} && {command} ".replace("\'",""), shell=True)
elif os.path.isfile(current_path):
print(f"Running: {command} {current_path}".replace("\'",""))
subprocess.Popen(f"{command} {current_path}".replace("\'",""), shell=True)
else: # if only a single path is specified instead of a 'list' of them
current_path = paths[0]
if os.name == "nt":
current_path = current_path.replace("~", os.getenv('USERPROFILE'))
current_path = current_path.replace("/", f"{os.sep}")
if os.path.isdir(current_path):
print(f"Running: cd {paths[0]} && {command} ".replace("\'",""))
subprocess.Popen(f"cd {paths[0]} && {command} ".replace("\'",""), shell=True)
elif os.path.isfile(current_path):
print(f"Running: {command} {current_path}".replace("\'",""))
subprocess.Popen(f"{command} {current_path}".replace("\'",""), shell=True)
def _preprocess_paths(paths:str) -> str:
"""Preprocesses paths from input and splits + formats them
into a useable list for later parsing.
Example
-------
```
paths = '~/Desktop/Development/Canadian Coding/SSB, C:\\Users\\Kieran\\Desktop\\Development\\*, ~\\Desktop\\Development\\Personal\\noter, .'
paths = _preprocess_paths(paths)
print(paths) # Prints: '~/Desktop/Development/Canadian Coding/SSB,~/Desktop/Development/*,~/Desktop/Development/Personal/noter,.'
```
"""
logging.info(f"Beginning path preprocessing on {paths}")
result = paths.split(",")
for index, directory in enumerate(result):
directory = directory.strip()
logging.debug(f"Directory: {directory}")
if directory.startswith(".") and (len(directory) > 1):
directory = os.path.abspath(directory)
if "~" not in directory:
if os.name == "nt":
directory = directory.replace(os.getenv('USERPROFILE'),"~")
else:
directory = directory.replace(os.getenv('HOME'),"~")
directory = directory.replace("\\", "/")
result[index] = directory
else:
directory = directory.replace("\\", "/")
result[index] = directory
logging.debug(f"Result: {result}")
result = ",".join(result)
return result
def _postprocess_paths(paths:str) -> list:
"""Postprocesses existing paths to be used by dispatcher.
This means things like expanding wildcards, and processing correct path seperators.
Example
-------
```
paths = 'C:\\Users\\Kieran\\Desktop\\Development\\Canadian Coding\\SSB, C:\\Users\\Kieran\\Desktop\\Development\\Canadian Coding\\website, ~/Desktop/Development/Personal/noter, C:\\Users\\Kieran\\Desktop\\Development\\*'
paths = _preprocess_paths(paths)
print(_postprocess_paths(paths))
# Prints: ['C:/Users/Kieran/Desktop/Development/Canadian Coding/SSB', ' C:/Users/Kieran/Desktop/Development/Canadian Coding/website', ' C:/Users/Kieran/Desktop/Development/Personal/noter', 'C:/Users/Kieran/Desktop/Development/Canadian Coding', 'C:/Users/Kieran/Desktop/Development/Personal', 'C:/Users/Kieran/Desktop/Development/pystall', 'C:/Users/Kieran/Desktop/Development/python-package-template', 'C:/Users/Kieran/Desktop/Development/Work']
```
"""
logging.info(f"Beginning path postprocessing on {paths}")
paths = paths.split(",")
result = []
for directory in paths:
directory = directory.strip()
if os.name == "nt":
directory = directory.replace("/", "\\")
if directory.startswith("."):
try:
if directory[1] == "/" or directory[1] == "\\":
directory = f"{os.curdir}{directory[1::]}"
except IndexError:
directory = os.path.abspath(".")
if "~" in directory:
if os.name == "nt":
directory = directory.replace("~",f"{os.getenv('USERPROFILE')}")
else:
directory = directory.replace("~", f"{os.getenv('HOME')}")
if "*" in directory:
wildcard_paths = glob.glob(directory.strip())
for wildcard_directory in wildcard_paths:
wildcard_directory = wildcard_directory.replace("\\", "/")
result.append(wildcard_directory)
else:
result.append(directory)
logging.debug(f"Result: {result}")
return result
if __name__ == "__main__":
main()
Functions
def dispatch(name, command: str = False, paths: str = False, config: dict = {}) ‑> None
-
Controls the dispatching of macros
Parameters
name
:(str)
- The name of the macro to dispatch
command
:(str)
- Used to override the macros configured command when set to False, will pull from configuration
paths
:(str)
- Used to override the macros configured paths when set to False, will pull from configuration
config
:(dict)
- The dict that contains the current config
Expand source code
def dispatch(name, command:str=False, paths:str=False, config:dict={}) -> None: """Controls the dispatching of macros Parameters ---------- name: (str) The name of the macro to dispatch command: (str) Used to override the macros configured command when set to False, will pull from configuration paths: (str) Used to override the macros configured paths when set to False, will pull from configuration config: (dict) The dict that contains the current config""" if "register" == name: print(usage) sys.exit() logging.info(f"Beggining execution of {name}") try: # Accessing stored information on the command config["macros"][name] if not config["macros"][name].get("runs", False): config["macros"][name]["runs"] = 1 else: config["macros"][name]["runs"] += 1 config["macros"][name]["last_run"] = str(datetime.datetime.now())[:10:] with open(CONFIG_FILE_PATH, "w+") as config_file: yaml.dump(config, config_file, default_flow_style=False) # Update config file with new metadata except KeyError: # When command does not exist in config if not config.get("macros", False): print(f"{colored.fg(1)}No macros found in current config {colored.fg(15)}\n") sys.exit(1) commands = [current_command for current_command in config["macros"]] # Get list of commands in config error_threshold = 60 # The percentage of likelyhood before similar words will throw out result similar_words = suggest_word.extractBests(name, commands,score_cutoff=error_threshold , limit=3) # Generate word sugestions if not similar_words: # If there are not similar commands that exist in the config print(f"{colored.fg(1)}Could not find macro {colored.fg(15)}{name}{colored.fg(1)} or any valid suggestions with %{error_threshold} or higher likelyhood, please check spelling {colored.fg(15)}\n") sys.exit(1) # Suggestions found for similar commands suggestions = "" for index, word in enumerate(similar_words): suggestions+= f"\t{index+1}. {colored.fg(3)}{word[0]}{colored.fg(15)} | %{word[1]} likelyhood\n" print(f"{colored.fg(1)}No command {name} found {colored.fg(15)} here are some suggestions: \n{suggestions}") if not command: command = "" if not paths: paths = "" print(f"Most likely suggestion is {colored.fg(3)}{similar_words[0][0]}{colored.fg(15)} rerun using: \n\t> ahd {similar_words[0][0]} \"{command}\" \"{paths}\"") sys.exit(1) if not command or command == ".": command = config["macros"][name]['command'] if not paths: paths = _postprocess_paths(config["macros"][name]['paths']) if len(paths) > 1: for current_path in paths: if os.name == "nt": current_path = current_path.replace("~", os.getenv('USERPROFILE')) current_path = current_path.replace("/", f"{os.sep}") if os.path.isdir(current_path): print(f"Running: cd {current_path} && {command} ".replace("\'","")) subprocess.Popen(f"cd {current_path} && {command} ".replace("\'",""), shell=True) elif os.path.isfile(current_path): print(f"Running: {command} {current_path}".replace("\'","")) subprocess.Popen(f"{command} {current_path}".replace("\'",""), shell=True) else: # if only a single path is specified instead of a 'list' of them current_path = paths[0] if os.name == "nt": current_path = current_path.replace("~", os.getenv('USERPROFILE')) current_path = current_path.replace("/", f"{os.sep}") if os.path.isdir(current_path): print(f"Running: cd {paths[0]} && {command} ".replace("\'","")) subprocess.Popen(f"cd {paths[0]} && {command} ".replace("\'",""), shell=True) elif os.path.isfile(current_path): print(f"Running: {command} {current_path}".replace("\'","")) subprocess.Popen(f"{command} {current_path}".replace("\'",""), shell=True)
def docs(api: bool = False, offline: bool = False) ‑> None
-
Processes incoming arguments when the docs command is invoked
Parameters
api
:(bool)
- When specified, shows API docs as opposed to user docs.
offline
:(bool)
- When specified will build local copy of docs instead of going to website
Notes
- By Default user documentation is selected
- By default the online documentation is selected
Expand source code
def docs(api:bool = False, offline:bool = False) -> None: """Processes incoming arguments when the docs command is invoked Parameters ---------- api: (bool) When specified, shows API docs as opposed to user docs. offline: (bool) When specified will build local copy of docs instead of going to website Notes ----- - By Default user documentation is selected - By default the online documentation is selected """ if not api and not offline: webbrowser.open_new("https://ahd.readthedocs.io") else: if offline and not api: from mkdocs.commands import serve # Used to serve the user documentation locally print("Docs available at http://localhost:8000/") webbrowser.open_new("http://localhost:8000/") serve.serve() elif api: if not offline: webbrowser.open_new("https://kieranwood.ca/ahd") else: # Simulates `pdoc --http : ahd` from pdoc.cli import main as pdoc_main # Used to serve the api documentation locally sys.argv = [sys.argv[0], "--http", ":", "ahd"] webbrowser.open_new("http://localhost:8080/ahd") pdoc_main()
def list_macros(verbose: bool = False, config: dict = {}) ‑> None
-
Lists commands currently in config
Parameters
verbose
:(bool)
- When specified will print both the command name and associated commands + paths. Additionally the dictionary will only return when this flag is specified.
config
:(dict)
- The dict that contains the current config
Expand source code
def list_macros(verbose:bool = False, config:dict={}) -> None: """Lists commands currently in config Parameters ---------- verbose: (bool) When specified will print both the command name and associated commands + paths. Additionally the dictionary will only return when this flag is specified. config: (dict) The dict that contains the current config """ # Iterate over the config, and pull information about the macros count = 0 for count, macro in enumerate(config["macros"]): if verbose: print(f"{colored.fg(6)}{macro}{colored.fg(15)}\n") try: print(f"\tCommand = {config['macros'][macro]['command']}") print(f"\tPaths = {config['macros'][macro]['paths']}") except KeyError: print(f"Macro {macro} is not configured correctly, check the command and paths variables") sys.exit(1) if config['macros'][macro].get("runs", False): print(f"\tRuns = {config['macros'][macro]['runs']}") if config['macros'][macro].get("created", False): print(f"\tCreated = {config['macros'][macro]['created']}") if config['macros'][macro].get("updated", False): print(f"\tUpdated = {config['macros'][macro]['updated']}") if config['macros'][macro].get("last_run", False): print(f"\tLast Run = {config['macros'][macro]['last_run']}") else: print(f"\n{colored.fg(6)}{macro}{colored.fg(15)}") print(f"\n\n{count+1} macros detected")
def main() ‑> None
-
The primary entrypoint for the ahd script handles argument parsing
All primary business logic is within this function.
Expand source code
def main() -> None: """The primary entrypoint for the ahd script handles argument parsing All primary business logic is within this function.""" # Setup arguments for parsing arguments = docopt(usage, version=f"ahd V{version}") if len(sys.argv) == 1: print("\n", usage) sys.exit() if os.path.exists(CONFIG_FILE_PATH): # If the file already exists with open(CONFIG_FILE_PATH, "r") as config_file: config = yaml.safe_load(config_file) config = dict(config) else: # If a file does not exist create one print(f"{colored.fg(1)}Could not locate valid config file creating new one at {CONFIG_FILE_PATH} {colored.fg(15)}") with open(CONFIG_FILE_PATH, "w") as config_file: config_file.write("macros:") config = {"macros": {}} # Begin argument parsing if arguments["list"]: list_macros(arguments["--long"], config) sys.exit() # ========= Docs argument parsing ========= if arguments["docs"]: docs(arguments["--api"], arguments["--offline"]) sys.exit() # ========= config argument parsing ========= if arguments["config"]: configure(arguments["--export"], arguments["--import"], config) sys.exit() # ========= preprocessing commands and paths ========= if not arguments["<paths>"]: logging.debug("No paths argument registered setting to \'\'") arguments["<paths>"] = "" else: arguments["<paths>"] = _preprocess_paths(arguments["<paths>"]) if not arguments["<command>"]: logging.debug("No command argument registered setting to \'\'") arguments["<command>"] = "" if "." == arguments["<command>"]: # If <command> is . set to specified value logging.debug(f". command registered, setting to {config['macros'][arguments['<name>']]['command']}") arguments["<command>"] = config["macros"][arguments["<name>"]]["command"] # ========= register argument parsing ========= if arguments["register"]: register(arguments["<name>"], arguments["<command>"], arguments["<paths>"], config) # ========= User command argument parsing ========= if arguments['<name>']: if arguments["--details"]: try: config['macros'][arguments['<name>']] # Force a KeyError if the macro does not exist print(f"{colored.fg(6)}{arguments['<name>']}{colored.fg(15)}\n") print(f"\tCommand = {config['macros'][arguments['<name>']]['command']}") print(f"\tPaths = {config['macros'][arguments['<name>']]['paths']}") if config['macros'][arguments['<name>']].get("runs", False): print(f"\tRuns = {config['macros'][arguments['<name>']]['runs']}") if config['macros'][arguments['<name>']].get("created", False): print(f"\tCreated = {config['macros'][arguments['<name>']]['created']}") if config['macros'][arguments['<name>']].get("updated", False): print(f"\tUpdated = {config['macros'][arguments['<name>']]['updated']}") if config['macros'][arguments['<name>']].get("last_run", False): print(f"\tLast Run = {config['macros'][arguments['<name>']]['last_run']}") exit() except KeyError: ... # If the command is not registered, do nothing and let the dispatch spellchecker find it if not arguments['<paths>'] and not arguments['<command>']: dispatch(arguments['<name>'], config=config) else: if arguments['<paths>'] and not arguments['<command>']: # Process inputted paths arguments['<paths>'] = _preprocess_paths(arguments['<paths>']) arguments['<paths>'] = _postprocess_paths(arguments['<paths>']) dispatch(arguments['<name>'], paths = arguments['<paths>'], config=config) if arguments['<command>'] and not arguments['<paths>']: dispatch(arguments['<name>'], command = arguments['<command>'], config=config) else: # Process inputted paths arguments['<paths>'] = _preprocess_paths(arguments['<paths>']) arguments['<paths>'] = _postprocess_paths(arguments['<paths>']) dispatch(arguments['<name>'], paths = arguments['<paths>'], command = arguments['<command>'], config=config)