Compare commits

...

15 Commits

Author SHA1 Message Date
skullY be0dd95e46 New release: 0.0.13 → 0.0.14 2019-09-25 12:00:35 -07:00
skullY 30031a8cd7 Fix qmk clone 2019-09-25 12:00:32 -07:00
skullY 94adba52c6 New release: 0.0.12 → 0.0.13 2019-09-25 09:54:23 -07:00
skullY 38aefc9716 Fix the environment 2019-09-25 09:54:16 -07:00
skullY 081fa0c8a6 New release: 0.0.11 → 0.0.12 2019-09-22 14:56:52 -07:00
skullY 8a03e2ad46 Add milc to setup.py 2019-09-22 14:56:44 -07:00
skullY c8d966afa7 New release: 0.0.10 → 0.0.11 2019-09-22 14:43:32 -07:00
skullY 1ea8f02927 Rework how we import subcommands 2019-09-09 10:30:10 -07:00
skullY 3683f86b4d update milc 2019-09-09 10:30:01 -07:00
skullY 1d622467e4 Fix logging so certain modules don't output unwanted debug messages 2019-09-08 21:21:04 -07:00
skullY a5c8efddb4 Rework how the CLI calls subcommands 2019-09-08 19:58:56 -07:00
skullY 8d75fd3dca Sync milc with qmk_firmware 2019-09-08 19:56:54 -07:00
skullY 72c46efc74 change the order of qmk doctor checks 2019-07-15 13:09:30 -07:00
skullY fdc42051d5 New release: 0.0.9 → 0.0.10 2019-07-15 11:28:10 -07:00
skullY a4859aa15c Remove old dists before building 2019-07-15 11:28:00 -07:00
13 changed files with 144 additions and 256 deletions
+71 -45
View File
@@ -17,6 +17,7 @@ import argparse
import logging
import os
import re
import shlex
import sys
from decimal import Decimal
from tempfile import NamedTemporaryFile
@@ -35,6 +36,10 @@ except ImportError:
import argcomplete
import colorama
from appdirs import user_config_dir
# Disable logging until we can configure it how the user wants
logging.basicConfig(filename='/dev/null')
# Log Level Representations
EMOJI_LOGLEVELS = {
@@ -47,6 +52,7 @@ EMOJI_LOGLEVELS = {
}
EMOJI_LOGLEVELS['FATAL'] = EMOJI_LOGLEVELS['CRITICAL']
EMOJI_LOGLEVELS['WARN'] = EMOJI_LOGLEVELS['WARNING']
UNICODE_SUPPORT = sys.stdout.encoding.lower().startswith('utf')
# ANSI Color setup
# Regex was gratefully borrowed from kfir on stackoverflow:
@@ -97,11 +103,12 @@ class ANSIFormatter(logging.Formatter):
class ANSIEmojiLoglevelFormatter(ANSIFormatter):
"""A log formatter that makes the loglevel an emoji.
"""A log formatter that makes the loglevel an emoji on UTF capable terminals.
"""
def format(self, record):
record.levelname = EMOJI_LOGLEVELS[record.levelname].format(**ansi_colors)
if UNICODE_SUPPORT:
record.levelname = EMOJI_LOGLEVELS[record.levelname].format(**ansi_colors)
return super(ANSIEmojiLoglevelFormatter, self).format(record)
@@ -144,13 +151,15 @@ class Configuration(object):
def __init__(self, *args, **kwargs):
self._config = {}
self.default_container = ConfigurationOption
def __getattr__(self, key):
return self.__getitem__(key)
def __getitem__(self, key):
"""Returns a config section, creating it if it doesn't exist yet.
"""
if key not in self._config:
self.__dict__[key] = self._config[key] = ConfigurationOption()
self.__dict__[key] = self._config[key] = ConfigurationSection(self)
return self._config[key]
@@ -161,30 +170,34 @@ class Configuration(object):
def __delitem__(self, key):
if key in self.__dict__ and key[0] != '_':
del self.__dict__[key]
del self._config[key]
if key in self._config:
del self._config[key]
class ConfigurationOption(Configuration):
def __init__(self, *args, **kwargs):
super(ConfigurationOption, self).__init__(*args, **kwargs)
self.default_container = dict
class ConfigurationSection(Configuration):
def __init__(self, parent, *args, **kwargs):
super(ConfigurationSection, self).__init__(*args, **kwargs)
self.parent = parent
def __getitem__(self, key):
"""Returns a config section, creating it if it doesn't exist yet.
"""Returns a config value, pulling from the `default` section as a fallback.
"""
if key not in self._config:
self.__dict__[key] = self._config[key] = None
if key in self._config:
return self._config[key]
return self._config[key]
elif key in self.parent.default:
return self.parent.default[key]
return None
def handle_store_boolean(self, *args, **kwargs):
"""Does the add_argument for action='store_boolean'.
"""
kwargs['add_dest'] = False
disabled_args = None
disabled_kwargs = kwargs.copy()
disabled_kwargs['action'] = 'store_false'
disabled_kwargs['dest'] = self.get_argument_name(*args, **kwargs)
disabled_kwargs['help'] = 'Disable ' + kwargs['help']
kwargs['action'] = 'store_true'
kwargs['help'] = 'Enable ' + kwargs['help']
@@ -219,11 +232,6 @@ class SubparserWrapper(object):
self.subparser.completer = completer
def add_argument(self, *args, **kwargs):
if kwargs.get('add_dest', True):
kwargs['dest'] = self.submodule + '_' + self.cli.get_argument_name(*args, **kwargs)
if 'add_dest' in kwargs:
del kwargs['add_dest']
if 'action' in kwargs and kwargs['action'] == 'store_boolean':
return handle_store_boolean(self, *args, **kwargs)
@@ -254,12 +262,16 @@ class MILC(object):
self._entrypoint = None
self._inside_context_manager = False
self.ansi = ansi_colors
self.arg_only = []
self.config = Configuration()
self.config_file = None
self.prog_name = sys.argv[0][:-3] if sys.argv[0].endswith('.py') else sys.argv[0]
self.version = os.environ.get('QMK_VERSION', 'unknown')
self.release_lock()
# Figure out our program name
self.prog_name = sys.argv[0][:-3] if sys.argv[0].endswith('.py') else sys.argv[0]
self.prog_name = self.prog_name.split('/')[-1]
# Initialize all the things
self.initialize_argparse()
self.initialize_logging()
@@ -273,7 +285,7 @@ class MILC(object):
self._description = self._arg_parser.description = self._arg_defaults.description = value
def echo(self, text, *args, **kwargs):
"""Print colorized text to stdout, as long as stdout is a tty.
"""Print colorized text to stdout.
ANSI color strings (such as {fg-blue}) will be converted into ANSI
escape sequences, and the ANSI reset sequence will be added to all
@@ -284,11 +296,10 @@ class MILC(object):
if args and kwargs:
raise RuntimeError('You can only specify *args or **kwargs, not both!')
if sys.stdout.isatty():
args = args or kwargs
text = format_ansi(text)
args = args or kwargs
text = format_ansi(text)
print(text % args)
print(text % args)
def initialize_argparse(self):
"""Prepare to process arguments from sys.argv.
@@ -313,21 +324,21 @@ class MILC(object):
self.release_lock()
def completer(self, completer):
"""Add an arpcomplete completer to this subcommand.
"""Add an argcomplete completer to this subcommand.
"""
self._arg_parser.completer = completer
def add_argument(self, *args, **kwargs):
"""Wrapper to add arguments to both the main and the shadow argparser.
"""
if 'action' in kwargs and kwargs['action'] == 'store_boolean':
return handle_store_boolean(self, *args, **kwargs)
if kwargs.get('add_dest', True) and args[0][0] == '-':
kwargs['dest'] = 'general_' + self.get_argument_name(*args, **kwargs)
if 'add_dest' in kwargs:
del kwargs['add_dest']
if 'action' in kwargs and kwargs['action'] == 'store_boolean':
return handle_store_boolean(self, *args, **kwargs)
self.acquire_lock()
self._arg_parser.add_argument(*args, **kwargs)
@@ -396,7 +407,7 @@ class MILC(object):
if self.args and self.args.general_config_file:
return self.args.general_config_file
return os.path.abspath(os.path.expanduser('~/.%s.ini' % self.prog_name))
return os.path.join(user_config_dir(appname='qmk', appauthor='QMK'), '%s.ini' % self.prog_name)
def get_argument_name(self, *args, **kwargs):
"""Takes argparse arguments and returns the dest name.
@@ -413,6 +424,11 @@ class MILC(object):
raise RuntimeError('You must run this before the with statement!')
def argument_function(handler):
if 'arg_only' in kwargs and kwargs['arg_only']:
arg_name = self.get_argument_name(*args, **kwargs)
self.arg_only.append(arg_name)
del kwargs['arg_only']
if handler is self._entrypoint:
self.add_argument(*args, **kwargs)
@@ -485,15 +501,20 @@ class MILC(object):
if argument in ('subparsers', 'entrypoint'):
continue
if '_' not in argument:
continue
section, option = argument.split('_', 1)
if hasattr(self.args_passed, argument):
self.config[section][option] = getattr(self.args, argument)
if '_' in argument:
section, option = argument.split('_', 1)
else:
if option not in self.config[section]:
self.config[section][option] = getattr(self.args, argument)
section = self._entrypoint.__name__
option = argument
if option not in self.arg_only:
if hasattr(self.args_passed, argument):
arg_value = getattr(self.args, argument)
if arg_value:
self.config[section][option] = arg_value
else:
if option not in self.config[section]:
self.config[section][option] = getattr(self.args, argument)
self.release_lock()
@@ -509,6 +530,8 @@ class MILC(object):
self.acquire_lock()
config = RawConfigParser()
config_dir = os.path.dirname(self.config_file)
for section_name, section in self.config._config.items():
config.add_section(section_name)
for option_name, value in section.items():
@@ -517,7 +540,10 @@ class MILC(object):
continue
config.set(section_name, option_name, str(value))
with NamedTemporaryFile(mode='w', dir=os.path.dirname(self.config_file), delete=False) as tmpfile:
if not os.path.exists(config_dir):
os.makedirs(config_dir)
with NamedTemporaryFile(mode='w', dir=config_dir, delete=False) as tmpfile:
config.write(tmpfile)
# Move the new config file into place atomically
@@ -527,6 +553,7 @@ class MILC(object):
self.log.warning('Config file saving failed, not replacing %s with %s.', self.config_file, tmpfile.name)
self.release_lock()
cli.log.info('Wrote configuration to %s', shlex.quote(self.config_file))
def __call__(self):
"""Execute the entrypoint function.
@@ -534,8 +561,7 @@ class MILC(object):
if not self._inside_context_manager:
# If they didn't use the context manager use it ourselves
with self:
self.__call__()
return
return self.__call__()
if not self._entrypoint:
raise RuntimeError('No entrypoint provided!')
@@ -603,8 +629,8 @@ class MILC(object):
"""Called by __enter__() to setup the logging configuration.
"""
if len(logging.root.handlers) != 0:
# This is not a design decision. This is what I'm doing for now until I can examine and think about this situation in more detail.
raise RuntimeError('MILC should be the only system installing root log handlers!')
# MILC is the only thing that should have root log handlers
logging.root.handlers = []
self.acquire_lock()
@@ -649,8 +675,9 @@ class MILC(object):
self.read_config()
self.setup_logging()
if self.config.general.save_config:
if 'save_config' in self.config.general and self.config.general.save_config:
self.save_config()
exit(0)
return self
@@ -713,4 +740,3 @@ if __name__ == '__main__':
cli.goodbye.add_argument('-n', '--name', help='Name to bid farewell to', default='World')
cli() # Automatically picks between main(), hello() and goodbye()
print(sorted(ansi_colors.keys()))
+1 -1
View File
@@ -1,3 +1,3 @@
"""A program to help you work with qmk_firmware."""
__version__ = '0.0.9'
__version__ = '0.0.14'
-96
View File
@@ -1,96 +0,0 @@
"""QMK Python Doctor
Check up for QMK environment.
"""
import shutil
import platform
import os
from pathlib import Path
from qmk_cli.milc import cli
def check_qmk_firmware():
"""Make sure qmk_firmware and the qmk cli are there.
"""
qmk_firmware = Path(os.environ['QMK_HOME'])
qmk_cli = qmk_firmware / 'bin' / 'qmk'
if qmk_firmware.exists() and not qmk_cli.exists():
cli.log.error("{fg_red}Can't find %s/bin/qmk! You need to `git pull`.", os.environ['QMK_HOME'])
return False
elif not qmk_firmware.exists():
cli.log.error("{fg_red}Can't find the qmk_firmware checkout! %s does not exist!", os.environ['QMK_HOME'])
return False
cli.log.info('Found qmk_firmware checkout in {fg_cyan}%s', str(qmk_firmware))
return True
def check_vital_programs():
"""Make sure the software we need has been installed.
TODO(unclaimed):
* [ ] Run the binaries to make sure they work
* [ ] Compile a trivial program with each compiler
"""
ok = True
binaries = ['dfu-programmer', 'avrdude', 'dfu-util', 'avr-gcc', 'arm-none-eabi-gcc']
for binary in binaries:
res = shutil.which(binary)
if res is None:
cli.log.error("{fg_red}QMK can't find %s in your path", binary)
ok = False
if ok:
cli.log.info("All necessary software is installed.")
return ok
def check_platform_tests():
"""Dispatch to platform specific tests.
"""
OS = platform.system()
if OS == "Darwin":
cli.log.info("Detected {fg_cyan}macOS")
return check_mac_os()
elif OS == "Linux":
cli.log.info("Detected {fg_cyan}linux")
return check_linux()
else:
cli.log.info("Assuming {fg_cyan}Windows")
return check_windows()
def check_mac_os():
"""Run macOS specific tests.
There aren't any yet.
"""
return True
def check_linux():
"""Run Linux specific tests.
TODO(unclaimed):
* [ ] Check for udev entries on linux
"""
test = 'systemctl list-unit-files | grep enabled | grep -i ModemManager'
if os.system(test) == 0:
cli.log.warn("{bg_yellow}Detected modem manager. Please disable it if you are using Pro Micros")
def check_windows():
"""Run Windows specific tests.
TODO(unclaimed):
* [ ] Check out the driver situation
"""
return True
+1 -1
View File
@@ -2,7 +2,7 @@
"""
import subprocess
from qmk_cli.milc import cli
from milc import cli
default_repo = 'qmk_firmware'
default_fork = 'qmk/' + default_repo
+2 -2
View File
@@ -1,6 +1,6 @@
"""Useful helper functions.
"""
from qmk_cli.milc import cli
from milc import cli
def question(question, boolean=True, default=''):
@@ -8,7 +8,7 @@ def question(question, boolean=True, default=''):
This keeps re-asking until it gets acceptible input.
"""
if cli.args.general_yes:
if cli.args.yes:
return True
if default and default.lower() == 'y':
+27 -48
View File
@@ -1,16 +1,29 @@
#!/usr/bin/env python3
"""CLI wrapper for running QMK commands.
This program can be run from anywhere, with or without a qmk_firmware checkout. It provides a small set of subcommands for working with QMK and otherwise dispatches to the `qmk_firmware/bin/qmk` script for the repo you are currently in, or your default repo if you are not currently in a qmk_firmware checkout.
This program can be run from anywhere, with or without a qmk_firmware repository. If a qmk_firmware repository can be located we will use that to augment our available subcommands.
FIXME(skullydazed/anyone): --help shows underscores where we want dashes in subcommands (EG json_keymap instead of json-keymap)
TODO(skullydazed/anyone): Need a way to filter some subcommands from --help (EG `qmk hello`)
"""
import argparse
import os
import subprocess
import sys
from functools import lru_cache
from importlib import import_module
from pathlib import Path
import milc
milc.EMOJI_LOGLEVELS['INFO'] = '{fg_blue}Ψ{style_reset_all}'
@milc.cli.entrypoint('CLI wrapper for running QMK commands.')
def qmk_main(cli):
"""The function that gets run when there's no subcommand.
"""
cli.print_help()
@lru_cache(maxsize=2)
def in_qmk_firmware():
@@ -21,7 +34,7 @@ def in_qmk_firmware():
found_bin = cur_dir / 'bin' / 'qmk'
if found_bin.is_file():
command = [found_bin, '--version']
result = subprocess.run(command)
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode == 0:
return cur_dir
@@ -47,55 +60,21 @@ def find_qmk_firmware():
return Path.home() / 'qmk_firmware'
def parse_args():
"""Process arguments outside milc.
"""
parser = argparse.ArgumentParser(description='CLI wrapper for running QMK commands.')
parser.add_argument('-H', '--home', help='Path to the qmk_firmware directory.')
parser.add_argument('subcommand', help='Subcommand to run')
parser.add_argument('subcommand_args', nargs=argparse.REMAINDER, help='Arguments to pass to the subcommand.')
args = parser.parse_args()
if args.home:
os.environ['QMK_HOME'] = args.home
return (args.subcommand, args.subcommand_args)
def main():
"""Dispatch the CLI subcommand to the proper place.
We first check to see if the subcommand was provided by the global `qmk`. If it was we import that module and hand control over to the entrypoint.
All other subcommands are dispatched to the local `qmk`, either the one we are currently in or whatever the user's default qmk_firmware is.
"""Setup the environment before dispatching to the entrypoint.
"""
subcommand, subcommand_args = parse_args()
subcommand_module = 'qmk_cli.subcommands.' + subcommand
sys.argv = ['qmk-'+subcommand] + subcommand_args
# Environment setup
qmk_firmware = find_qmk_firmware()
qmk_bin = qmk_firmware / 'bin' / 'qmk'
os.environ['QMK_HOME'] = str(qmk_firmware)
os.environ['ORIG_CWD'] = os.getcwd()
try:
# Attempt to import the subcommand from qmk_cli first
import qmk_cli.milc
import_module(subcommand_module)
qmk_cli.milc.cli()
# Import the subcommand modules
import qmk_cli.subcommands
except ImportError as e:
# Check to make sure there's not a bad import statement in qmk_cli
if e.name != subcommand_module:
raise
if qmk_firmware.exists():
os.chdir(str(qmk_firmware))
sys.path.append(str(qmk_firmware / 'lib' / 'python'))
import qmk.cli
# Dispatch to the underlying `qmk_firmware/bin/qmk`
if qmk_bin.is_file() and os.access(str(qmk_bin), os.X_OK):
argv = ['python3', str(qmk_bin), subcommand] + subcommand_args
os.execvp('python3', argv)
# Tell the user we can't continue
print('*** Could not locate qmk_firmware directory!')
exit(255)
if __name__ == '__main__':
main()
# Call the entrypoint
milc.cli()
+6
View File
@@ -0,0 +1,6 @@
"""QMK CLI Subcommands
We list each subcommand here explicitly because all the reliable ways of searching for modules are slow and delay startup.
"""
from . import clone
from . import setup
+13 -10
View File
@@ -3,26 +3,29 @@
import os
from pathlib import Path
from qmk_cli.milc import cli
from qmk.cli.git import clone
from milc import cli
from qmk_cli.git import clone as git_clone
default_repo = 'qmk_firmware'
default_fork = 'qmk/' + default_repo
default_branch = 'master'
@cli.argument('--baseurl', default='https://github.com', help='The URL all git operations start from.')
@cli.argument('-b', '--branch', default=default_branch, help='The branch to clone.')
@cli.argument('destination', default=os.environ['QMK_HOME'], nargs='?', help='The directory to clone to.')
@cli.argument('fork', default=default_fork, nargs='?', help='The qmk_firmware fork to clone')
@cli.entrypoint('Clone a qmk_firmware fork.')
def main(cli):
@cli.argument('--baseurl', default='https://github.com', help='The URL all git operations start from (Default: https://github.com)')
@cli.argument('-b', '--branch', default=default_branch, help='The branch to clone (Default: %s)' % default_branch)
@cli.argument('destination', default=None, nargs='?', help='The directory to clone to (Default: Current Directory)')
@cli.argument('fork', default=default_fork, nargs='?', help='The qmk_firmware fork to clone (Default: %s)' % default_fork)
@cli.subcommand('Clone a qmk_firmware fork.')
def clone(cli):
if not cli.args.destination:
cli.args.destination = os.path.join(os.environ['ORIG_CWD'], default_fork.split('/')[-1])
qmk_firmware = Path(cli.args.destination)
git_url = '/'.join((cli.config.general.baseurl, cli.args.fork))
git_url = '/'.join((cli.config.clone.baseurl, cli.args.fork))
if qmk_firmware.exists():
cli.log.error('Destination already exists: %s', cli.args.destination)
exit(1)
success = clone(git_url, cli.args.destination, cli.config.general.branch)
success = git_clone(git_url, cli.args.destination, cli.config.clone.branch)
exit(0 if success else 1)
-29
View File
@@ -1,29 +0,0 @@
"""QMK Python Doctor
Check up for QMK environment.
"""
import qmk_cli.doctor
from qmk_cli.milc import cli
@cli.entrypoint('Basic QMK environment checks')
def main(cli):
"""Basic QMK environment checks.
This is currently very simple, it just checks that all the expected binaries are on your system.
"""
cli.log.info('QMK Doctor is checking your environment')
funcs = (
qmk_cli.doctor.check_qmk_firmware,
qmk_cli.doctor.check_vital_programs,
qmk_cli.doctor.check_platform_tests,
)
ok = True
for func in funcs:
if not func():
ok = False
if ok:
cli.log.info('{fg_green}QMK is ready to go')
+17 -20
View File
@@ -2,12 +2,12 @@
"""
import os
import subprocess
import sys
from pathlib import Path
from qmk_cli.doctor import check_vital_programs
from milc import cli
from qmk_cli.git import clone
from qmk_cli.helpers import question
from qmk_cli.milc import cli
default_repo = 'qmk_firmware'
default_fork = 'qmk/' + default_repo
@@ -19,9 +19,8 @@ default_branch = 'master'
@cli.argument('-b', '--branch', default=default_branch, help='The branch to clone')
@cli.argument('destination', default=os.environ['QMK_HOME'], nargs='?', help='The directory to clone to')
@cli.argument('fork', default=default_fork, nargs='?', help='The qmk_firmware fork to clone')
@cli.entrypoint('Setup your computer for qmk_firmware.')
def main(cli):
setup_successful = False
@cli.subcommand('Setup your computer for qmk_firmware.')
def setup(cli):
qmk_firmware = Path(cli.args.destination)
# Check on qmk_firmware, and if it doesn't exist offer to check it out.
@@ -33,19 +32,17 @@ def main(cli):
git_url = '/'.join((cli.config.general.baseurl, cli.args.fork))
clone(git_url, cli.args.destination, cli.config.general.branch)
# Check if the build environment is setup, and if not offer to set it up
if check_vital_programs():
cli.log.info('Your build environment is ready!')
else:
cli.log.error('Your build environment is not setup completely.')
if qmk_firmware.exists() and question('Would you like to run util/qmk_install?'):
curdir = os.getcwd()
os.chdir(str(qmk_firmware))
process = subprocess.run(['util/qmk_install.sh'])
os.chdir(curdir)
if process.returncode == 0:
setup_successful = True
# Run `qmk_firmware/bin/qmk doctor` to check the rest of the environment out
if qmk_firmware.exists():
qmk_bin = qmk_firmware / 'bin' / 'qmk'
doctor = subprocess.run([sys.executable, qmk_bin, 'doctor'])
if doctor.returncode != 0:
cli.log.error('Your build environment is not setup completely.')
# fin
if setup_successful:
cli.log.info('QMK setup complete!')
if question('Would you like to run util/qmk_install?'):
curdir = os.getcwd()
os.chdir(str(qmk_firmware))
process = subprocess.run(['util/qmk_install.sh'])
os.chdir(curdir)
if process.returncode == 0:
cli.log.info('QMK setup complete!')
+1
View File
@@ -9,6 +9,7 @@ TWINE_USERNAME=$PYPI_USERNAME
export FLIT_USERNAME TWINE_USERNAME
rm dist/*
bumpversion patch
git push origin master --tags
+1 -1
View File
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.0.9
current_version = 0.0.14
commit = True
tag = True
tag_name = {new_version}
+4 -3
View File
@@ -10,7 +10,7 @@ if __name__ == "__main__":
name=metadata['dist-name'],
description='A program to help users work with QMK Firmware.',
entry_points={
'console_scripts': ['%s = %s' % l for l in setup_cfg['entry_points'].items()]
'console_scripts': ['%s = %s' % l for l in setup_cfg['entry_points'].items()],
},
license='MIT License',
url=metadata['home-page'],
@@ -22,6 +22,7 @@ if __name__ == "__main__":
long_description=open('README.md').read(),
long_description_content_type="text/markdown",
packages=find_packages(),
py_modules = ['milc'],
classifiers=[
'Development Status :: 3 - Alpha',
'Environment :: Console',
@@ -38,8 +39,8 @@ if __name__ == "__main__":
requires_python=metadata['requires-python'],
install_requires=[
#"milc", #FIXME(skullydazed): Included in the repo for now.
"appdirs",
"argcomplete",
"colorama",
#"halo"
"colorama"
],
)