Compare commits

...

32 Commits

Author SHA1 Message Date
skullY ebea7f7ea0 New release: 0.0.25 → 0.0.26 2019-11-28 08:42:55 -08:00
skullY 49b288786a Add support for hidden subcommands 2019-11-28 08:42:49 -08:00
skullY e75b4d4ed1 New release: 0.0.24 → 0.0.25 2019-11-18 22:33:19 -08:00
skullY 7f56cfd781 Add flake8 to requirements 2019-11-18 22:33:14 -08:00
skullY b04bf7a4da New release: 0.0.23 → 0.0.24 2019-11-15 23:25:13 -08:00
skullY fb260f29e1 Fix commands with no arguments 2019-11-15 23:15:07 -08:00
skullY e8e98ca15a New release: 0.0.22 → 0.0.23 2019-11-15 22:21:33 -08:00
skullY 4d1302696e Make folding arguments into config operate more correctly. 2019-11-15 22:21:26 -08:00
skullY 4d78df2cbc New release: 0.0.21 → 0.0.22 2019-11-12 16:54:28 -08:00
skullY 4a45081412 filter None from config files 2019-11-12 16:53:32 -08:00
skullY 9cab9ea610 New release: 0.0.20 → 0.0.21 2019-11-11 13:04:13 -08:00
skullY 177fa55bce fix pytest in qmkfm/base_container 2019-11-11 13:03:39 -08:00
skullY 0783864629 New release: 0.0.19 → 0.0.20 2019-11-11 11:56:35 -08:00
skullY 8ec66dd599 Improve --version 2019-11-11 11:56:20 -08:00
skullY 790536eca4 New release: 0.0.18 → 0.0.19 2019-11-11 09:17:31 -08:00
skullY 6f1ec49a9a Add support for specifying user.qmk_home in config 2019-11-11 09:15:24 -08:00
skullY c7f37a0e1b MILC improvements-
* Remove the shadow argparser
* Make it easier to reason about arguments and how they're translated into the config tree
* Populate self.config during init to support setting user.qmk_home for the global CLI
* Remove the short argument -c so that we can unambiguously determine the config file location without doing full argument processing
* Remove the --save-config option as it's a little confusing anyway
* Use Pathlib for path manipulation
2019-11-11 09:14:24 -08:00
skullY 5001c627ef New release: 0.0.17 → 0.0.18 2019-11-10 11:11:14 -08:00
Erovia 490c94e282 Fix command line variable access for baseurl and branch 2019-11-10 11:10:49 -08:00
skullY 212e185b62 New release: 0.0.16 → 0.0.17 2019-10-23 22:54:53 -07:00
zvecr b7d0a65bdf Fix No such file or directory: 'C:\\dev\\null' 2019-10-23 22:51:53 -07:00
skullY 221d42fec1 sync milc.py with qmk_firmware 2019-10-23 22:47:50 -07:00
skullY f3e83794c9 New release: 0.0.15 → 0.0.16 2019-10-12 13:18:31 -07:00
zvecr 73628cc6b0 Fix in_qmk_firmware for Python 3.5 2019-10-12 13:15:15 -07:00
skullY e077319a16 New release: 0.0.14 → 0.0.15 2019-10-08 07:23:09 -07:00
skullY 5b0ba9a25d Document release requirements 2019-10-08 07:22:56 -07:00
skullY bd4e9bf9cf Add hjson to requirements 2019-10-08 07:20:26 -07:00
Nikita Titov b2974b4219 read README with utf-8 and close file after reading 2019-10-04 07:45:02 -07:00
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
8 changed files with 134 additions and 119 deletions
+96 -96
View File
@@ -20,6 +20,7 @@ import re
import shlex import shlex
import sys import sys
from decimal import Decimal from decimal import Decimal
from pathlib import Path
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from time import sleep from time import sleep
@@ -39,7 +40,7 @@ import colorama
from appdirs import user_config_dir from appdirs import user_config_dir
# Disable logging until we can configure it how the user wants # Disable logging until we can configure it how the user wants
logging.basicConfig(filename='/dev/null') logging.basicConfig(stream=os.devnull)
# Log Level Representations # Log Level Representations
EMOJI_LOGLEVELS = { EMOJI_LOGLEVELS = {
@@ -96,7 +97,6 @@ def format_ansi(text):
class ANSIFormatter(logging.Formatter): class ANSIFormatter(logging.Formatter):
"""A log formatter that inserts ANSI color. """A log formatter that inserts ANSI color.
""" """
def format(self, record): def format(self, record):
msg = super(ANSIFormatter, self).format(record) msg = super(ANSIFormatter, self).format(record)
return format_ansi(msg) return format_ansi(msg)
@@ -105,7 +105,6 @@ class ANSIFormatter(logging.Formatter):
class ANSIEmojiLoglevelFormatter(ANSIFormatter): class ANSIEmojiLoglevelFormatter(ANSIFormatter):
"""A log formatter that makes the loglevel an emoji on UTF capable terminals. """A log formatter that makes the loglevel an emoji on UTF capable terminals.
""" """
def format(self, record): def format(self, record):
if UNICODE_SUPPORT: if UNICODE_SUPPORT:
record.levelname = EMOJI_LOGLEVELS[record.levelname].format(**ansi_colors) record.levelname = EMOJI_LOGLEVELS[record.levelname].format(**ansi_colors)
@@ -115,7 +114,6 @@ class ANSIEmojiLoglevelFormatter(ANSIFormatter):
class ANSIStrippingFormatter(ANSIFormatter): class ANSIStrippingFormatter(ANSIFormatter):
"""A log formatter that strips ANSI. """A log formatter that strips ANSI.
""" """
def format(self, record): def format(self, record):
msg = super(ANSIStrippingFormatter, self).format(record) msg = super(ANSIStrippingFormatter, self).format(record)
return ansi_escape.sub('', msg) return ansi_escape.sub('', msg)
@@ -127,7 +125,6 @@ class Configuration(object):
This class never raises IndexError, instead it will return None if a This class never raises IndexError, instead it will return None if a
section or option does not yet exist. section or option does not yet exist.
""" """
def __contains__(self, key): def __contains__(self, key):
return self._config.__contains__(key) return self._config.__contains__(key)
@@ -180,13 +177,13 @@ class ConfigurationSection(Configuration):
self.parent = parent self.parent = parent
def __getitem__(self, key): def __getitem__(self, key):
"""Returns a config value, pulling from the `default` section as a fallback. """Returns a config value, pulling from the `user` section as a fallback.
""" """
if key in self._config: if key in self._config:
return self._config[key] return self._config[key]
elif key in self.parent.default: elif key in self.parent.user:
return self.parent.default[key] return self.parent.user[key]
return None return None
@@ -214,9 +211,8 @@ def handle_store_boolean(self, *args, **kwargs):
class SubparserWrapper(object): class SubparserWrapper(object):
"""Wrap subparsers so we can populate the normal and the shadow parser. """Wrap subparsers so we can track what options the user passed.
""" """
def __init__(self, cli, submodule, subparser): def __init__(self, cli, submodule, subparser):
self.cli = cli self.cli = cli
self.submodule = submodule self.submodule = submodule
@@ -232,26 +228,30 @@ class SubparserWrapper(object):
self.subparser.completer = completer self.subparser.completer = completer
def add_argument(self, *args, **kwargs): def add_argument(self, *args, **kwargs):
"""Add an argument for this subcommand.
This also stores the default for the argument in `self.cli.default_arguments`.
"""
if 'action' in kwargs and kwargs['action'] == 'store_boolean': if 'action' in kwargs and kwargs['action'] == 'store_boolean':
# Store boolean will call us again with the enable/disable flag arguments
return handle_store_boolean(self, *args, **kwargs) return handle_store_boolean(self, *args, **kwargs)
self.cli.acquire_lock() self.cli.acquire_lock()
self.subparser.add_argument(*args, **kwargs) self.subparser.add_argument(*args, **kwargs)
if self.submodule not in self.cli.default_arguments:
if 'default' in kwargs: self.cli.default_arguments[self.submodule] = {}
del kwargs['default'] self.cli.default_arguments[self.submodule][self.cli.get_argument_name(*args, **kwargs)] = kwargs.get('default')
if 'action' in kwargs and kwargs['action'] == 'store_false':
kwargs['action'] == 'store_true'
self.cli.subcommands_default[self.submodule].add_argument(*args, **kwargs)
self.cli.release_lock() self.cli.release_lock()
class MILC(object): class MILC(object):
"""MILC - An Opinionated Batteries Included Framework """MILC - An Opinionated Batteries Included Framework
""" """
def __init__(self): def __init__(self):
"""Initialize the MILC object. """Initialize the MILC object.
version
The version string to associate with your CLI program
""" """
# Setup a lock for thread safety # Setup a lock for thread safety
self._lock = threading.RLock() if thread else None self._lock = threading.RLock() if thread else None
@@ -263,9 +263,10 @@ class MILC(object):
self._inside_context_manager = False self._inside_context_manager = False
self.ansi = ansi_colors self.ansi = ansi_colors
self.arg_only = [] self.arg_only = []
self.config = Configuration() self.config = None
self.config_file = None self.config_file = None
self.version = os.environ.get('QMK_VERSION', 'unknown') self.default_arguments = {}
self.version = 'unknown'
self.release_lock() self.release_lock()
# Figure out our program name # Figure out our program name
@@ -273,6 +274,7 @@ class MILC(object):
self.prog_name = self.prog_name.split('/')[-1] self.prog_name = self.prog_name.split('/')[-1]
# Initialize all the things # Initialize all the things
self.read_config_file()
self.initialize_argparse() self.initialize_argparse()
self.initialize_logging() self.initialize_logging()
@@ -282,7 +284,7 @@ class MILC(object):
@description.setter @description.setter
def description(self, value): def description(self, value):
self._description = self._arg_parser.description = self._arg_defaults.description = value self._description = self._arg_parser.description = value
def echo(self, text, *args, **kwargs): def echo(self, text, *args, **kwargs):
"""Print colorized text to stdout. """Print colorized text to stdout.
@@ -311,12 +313,9 @@ class MILC(object):
self.acquire_lock() self.acquire_lock()
self.subcommands = {} self.subcommands = {}
self.subcommands_default = {}
self._subparsers = None self._subparsers = None
self._subparsers_default = None
self.argwarn = argcomplete.warn self.argwarn = argcomplete.warn
self.args = None self.args = None
self._arg_defaults = argparse.ArgumentParser(**kwargs)
self._arg_parser = argparse.ArgumentParser(**kwargs) self._arg_parser = argparse.ArgumentParser(**kwargs)
self.set_defaults = self._arg_parser.set_defaults self.set_defaults = self._arg_parser.set_defaults
self.print_usage = self._arg_parser.print_usage self.print_usage = self._arg_parser.print_usage
@@ -329,25 +328,18 @@ class MILC(object):
self._arg_parser.completer = completer self._arg_parser.completer = completer
def add_argument(self, *args, **kwargs): def add_argument(self, *args, **kwargs):
"""Wrapper to add arguments to both the main and the shadow argparser. """Wrapper to add arguments and track whether they were passed on the command line.
""" """
if 'action' in kwargs and kwargs['action'] == 'store_boolean': if 'action' in kwargs and kwargs['action'] == 'store_boolean':
return handle_store_boolean(self, *args, **kwargs) 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']
self.acquire_lock() self.acquire_lock()
self._arg_parser.add_argument(*args, **kwargs)
# Populate the shadow parser self._arg_parser.add_argument(*args, **kwargs)
if 'default' in kwargs: if 'general' not in self.default_arguments:
del kwargs['default'] self.default_arguments['general'] = {}
if 'action' in kwargs and kwargs['action'] == 'store_false': self.default_arguments['general'][self.get_argument_name(*args, **kwargs)] = kwargs.get('default')
kwargs['action'] == 'store_true'
self._arg_defaults.add_argument(*args, **kwargs)
self.release_lock() self.release_lock()
def initialize_logging(self): def initialize_logging(self):
@@ -374,15 +366,14 @@ class MILC(object):
self.add_argument('--log-file-fmt', default='[%(levelname)s] [%(asctime)s] [file:%(pathname)s] [line:%(lineno)d] %(message)s', help='Format string for log file.') self.add_argument('--log-file-fmt', default='[%(levelname)s] [%(asctime)s] [file:%(pathname)s] [line:%(lineno)d] %(message)s', help='Format string for log file.')
self.add_argument('--log-file', help='File to write log messages to') self.add_argument('--log-file', help='File to write log messages to')
self.add_argument('--color', action='store_boolean', default=True, help='color in output') self.add_argument('--color', action='store_boolean', default=True, help='color in output')
self.add_argument('-c', '--config-file', help='The config file to read and/or write') self.add_argument('--config-file', help='The location for the configuration file')
self.add_argument('--save-config', action='store_true', help='Save the running configuration to the config file') self.arg_only.append('config_file')
def add_subparsers(self, title='Sub-commands', **kwargs): def add_subparsers(self, title='Sub-commands', **kwargs):
if self._inside_context_manager: if self._inside_context_manager:
raise RuntimeError('You must run this before the with statement!') raise RuntimeError('You must run this before the with statement!')
self.acquire_lock() self.acquire_lock()
self._subparsers_default = self._arg_defaults.add_subparsers(title=title, dest='subparsers', **kwargs)
self._subparsers = self._arg_parser.add_subparsers(title=title, dest='subparsers', **kwargs) self._subparsers = self._arg_parser.add_subparsers(title=title, dest='subparsers', **kwargs)
self.release_lock() self.release_lock()
@@ -404,10 +395,12 @@ class MILC(object):
if self.config_file: if self.config_file:
return self.config_file return self.config_file
if self.args and self.args.general_config_file: if '--config-file' in sys.argv:
return self.args.general_config_file return Path(sys.argv[sys.argv.index('--config-file') + 1]).expanduser().resolve()
return os.path.join(user_config_dir(appname='qmk', appauthor='QMK'), '%s.ini' % self.prog_name) filedir = user_config_dir(appname='qmk', appauthor='QMK')
filename = '%s.ini' % self.prog_name
return Path(filedir) / filename
def get_argument_name(self, *args, **kwargs): def get_argument_name(self, *args, **kwargs):
"""Takes argparse arguments and returns the dest name. """Takes argparse arguments and returns the dest name.
@@ -429,11 +422,12 @@ class MILC(object):
self.arg_only.append(arg_name) self.arg_only.append(arg_name)
del kwargs['arg_only'] del kwargs['arg_only']
name = handler.__name__.replace("_", "-")
if handler is self._entrypoint: if handler is self._entrypoint:
self.add_argument(*args, **kwargs) self.add_argument(*args, **kwargs)
elif handler.__name__ in self.subcommands: elif name in self.subcommands:
self.subcommands[handler.__name__].add_argument(*args, **kwargs) self.subcommands[name].add_argument(*args, **kwargs)
else: else:
raise RuntimeError('Decorated function is not entrypoint or subcommand!') raise RuntimeError('Decorated function is not entrypoint or subcommand!')
@@ -445,7 +439,7 @@ class MILC(object):
def arg_passed(self, arg): def arg_passed(self, arg):
"""Returns True if arg was passed on the command line. """Returns True if arg was passed on the command line.
""" """
return self.args_passed[arg] in (None, False) return self.default_arguments.get(arg) != self.args[arg]
def parse_args(self): def parse_args(self):
"""Parse the CLI args. """Parse the CLI args.
@@ -458,25 +452,22 @@ class MILC(object):
self.acquire_lock() self.acquire_lock()
self.args = self._arg_parser.parse_args() self.args = self._arg_parser.parse_args()
self.args_passed = self._arg_defaults.parse_args()
if 'entrypoint' in self.args: if 'entrypoint' in self.args:
self._entrypoint = self.args.entrypoint self._entrypoint = self.args.entrypoint
if self.args.general_config_file:
self.config_file = self.args.general_config_file
self.release_lock() self.release_lock()
def read_config(self): def read_config_file(self):
"""Parse the configuration file and determine the runtime configuration. """Read in the configuration file and store it in self.config.
""" """
self.acquire_lock() self.acquire_lock()
self.config = Configuration()
self.config_file = self.find_config_file() self.config_file = self.find_config_file()
if self.config_file and os.path.exists(self.config_file): if self.config_file and self.config_file.exists():
config = RawConfigParser(self.config) config = RawConfigParser(self.config)
config.read(self.config_file) config.read(str(self.config_file))
# Iterate over the config file options and write them into self.config # Iterate over the config file options and write them into self.config
for section in config.sections(): for section in config.sections():
@@ -486,8 +477,10 @@ class MILC(object):
# Coerce values into useful datatypes # Coerce values into useful datatypes
if value.lower() in ['1', 'yes', 'true', 'on']: if value.lower() in ['1', 'yes', 'true', 'on']:
value = True value = True
elif value.lower() in ['0', 'no', 'false', 'none', 'off']: elif value.lower() in ['0', 'no', 'false', 'off']:
value = False value = False
elif value.lower() in ['none']:
continue
elif value.replace('.', '').isdigit(): elif value.replace('.', '').isdigit():
if '.' in value: if '.' in value:
value = Decimal(value) value = Decimal(value)
@@ -496,32 +489,44 @@ class MILC(object):
self.config[section][option] = value self.config[section][option] = value
# Fold the CLI args into self.config self.release_lock()
def merge_args_into_config(self):
"""Merge CLI arguments into self.config to create the runtime configuration.
"""
self.acquire_lock()
for argument in vars(self.args): for argument in vars(self.args):
if argument in ('subparsers', 'entrypoint'): if argument in ('subparsers', 'entrypoint'):
continue continue
if '_' in argument: if argument not in self.arg_only:
section, option = argument.split('_', 1) # Find the argument's section
else: if self._entrypoint.__name__ in self.default_arguments and argument in self.default_arguments[self._entrypoint.__name__]:
section = self._entrypoint.__name__ argument_found = True
option = argument section = self._entrypoint.__name__
if argument in self.default_arguments['general']:
argument_found = True
section = 'general'
if option not in self.arg_only: if not argument_found:
if hasattr(self.args_passed, argument): raise RuntimeError('Could not find argument in `self.default_arguments`. This should be impossible!')
exit(1)
# Merge this argument into self.config
if argument in self.default_arguments:
arg_value = getattr(self.args, argument) arg_value = getattr(self.args, argument)
if arg_value: if arg_value:
self.config[section][option] = arg_value self.config[section][argument] = arg_value
else: else:
if option not in self.config[section]: if argument not in self.config[section]:
self.config[section][option] = getattr(self.args, argument) self.config[section][argument] = getattr(self.args, argument)
self.release_lock() self.release_lock()
def save_config(self): def save_config(self):
"""Save the current configuration to the config file. """Save the current configuration to the config file.
""" """
self.log.debug("Saving config file to '%s'", self.config_file) self.log.debug("Saving config file to '%s'", str(self.config_file))
if not self.config_file: if not self.config_file:
self.log.warning('%s.config_file file not set, not saving config!', self.__class__.__name__) self.log.warning('%s.config_file file not set, not saving config!', self.__class__.__name__)
@@ -529,31 +534,34 @@ class MILC(object):
self.acquire_lock() self.acquire_lock()
# Generate a sanitized version of our running configuration
config = RawConfigParser() config = RawConfigParser()
config_dir = os.path.dirname(self.config_file)
for section_name, section in self.config._config.items(): for section_name, section in self.config._config.items():
config.add_section(section_name) config.add_section(section_name)
for option_name, value in section.items(): for option_name, value in section.items():
if section_name == 'general': if section_name == 'general':
if option_name in ['save_config']: if option_name in ['config_file']:
continue continue
config.set(section_name, option_name, str(value)) if value is not None:
config.set(section_name, option_name, str(value))
if not os.path.exists(config_dir): # Write out the config file
os.makedirs(config_dir) config_dir = self.config_file.parent
if not config_dir.exists():
config_dir.mkdir(parents=True, exist_ok=True)
with NamedTemporaryFile(mode='w', dir=config_dir, delete=False) as tmpfile: with NamedTemporaryFile(mode='w', dir=str(config_dir), delete=False) as tmpfile:
config.write(tmpfile) config.write(tmpfile)
# Move the new config file into place atomically # Move the new config file into place atomically
if os.path.getsize(tmpfile.name) > 0: if os.path.getsize(tmpfile.name) > 0:
os.rename(tmpfile.name, self.config_file) os.rename(tmpfile.name, str(self.config_file))
else: else:
self.log.warning('Config file saving failed, not replacing %s with %s.', self.config_file, tmpfile.name) self.log.warning('Config file saving failed, not replacing %s with %s.', str(self.config_file), tmpfile.name)
# Housekeeping
self.release_lock() self.release_lock()
cli.log.info('Wrote configuration to %s', shlex.quote(self.config_file)) cli.log.info('Wrote configuration to %s', shlex.quote(str(self.config_file)))
def __call__(self): def __call__(self):
"""Execute the entrypoint function. """Execute the entrypoint function.
@@ -587,41 +595,37 @@ class MILC(object):
return entrypoint_func return entrypoint_func
def add_subcommand(self, handler, description, name=None, **kwargs): def add_subcommand(self, handler, description, name=None, hidden=False, **kwargs):
"""Register a subcommand. """Register a subcommand.
If name is not provided we use `handler.__name__`. If name is not provided we use `handler.__name__`.
""" """
if self._inside_context_manager: if self._inside_context_manager:
raise RuntimeError('You must run this before the with statement!') raise RuntimeError('You must run this before the with statement!')
if self._subparsers is None: if self._subparsers is None:
self.add_subparsers() self.add_subparsers(metavar="")
if not name: if not name:
name = handler.__name__ name = handler.__name__.replace("_", "-")
self.acquire_lock() self.acquire_lock()
kwargs['help'] = description if not hidden:
self.subcommands_default[name] = self._subparsers_default.add_parser(name, **kwargs) self._subparsers.metavar = "{%s,%s}" % (self._subparsers.metavar[1:-1], name) if self._subparsers.metavar else "{%s%s}" % (self._subparsers.metavar[1:-1], name)
kwargs['help'] = description
self.subcommands[name] = SubparserWrapper(self, name, self._subparsers.add_parser(name, **kwargs)) self.subcommands[name] = SubparserWrapper(self, name, self._subparsers.add_parser(name, **kwargs))
self.subcommands[name].set_defaults(entrypoint=handler) self.subcommands[name].set_defaults(entrypoint=handler)
if name not in self.__dict__:
self.__dict__[name] = self.subcommands[name]
else:
self.log.debug("Could not add subcommand '%s' to attributes, key already exists!", name)
self.release_lock() self.release_lock()
return handler return handler
def subcommand(self, description, **kwargs): def subcommand(self, description, hidden=False, **kwargs):
"""Decorator to register a subcommand. """Decorator to register a subcommand.
""" """
def subcommand_function(handler): def subcommand_function(handler):
return self.add_subcommand(handler, description, **kwargs) return self.add_subcommand(handler, description, hidden=hidden, **kwargs)
return subcommand_function return subcommand_function
@@ -643,9 +647,9 @@ class MILC(object):
self.log_format = self.config['general']['log_fmt'] self.log_format = self.config['general']['log_fmt']
if self.config.general.color: if self.config.general.color:
self.log_format = ANSIEmojiLoglevelFormatter(self.args.general_log_fmt, self.config.general.datetime_fmt) self.log_format = ANSIEmojiLoglevelFormatter(self.args.log_fmt, self.config.general.datetime_fmt)
else: else:
self.log_format = ANSIStrippingFormatter(self.args.general_log_fmt, self.config.general.datetime_fmt) self.log_format = ANSIStrippingFormatter(self.args.log_fmt, self.config.general.datetime_fmt)
if self.log_file: if self.log_file:
self.log_file_handler = logging.FileHandler(self.log_file, self.log_file_mode) self.log_file_handler = logging.FileHandler(self.log_file, self.log_file_mode)
@@ -672,13 +676,9 @@ class MILC(object):
colorama.init() colorama.init()
self.parse_args() self.parse_args()
self.read_config() self.merge_args_into_config()
self.setup_logging() self.setup_logging()
if 'save_config' in self.config.general and self.config.general.save_config:
self.save_config()
exit(0)
return self return self
def __exit__(self, exc_type, exc_val, exc_tb): def __exit__(self, exc_type, exc_val, exc_tb):
+1 -1
View File
@@ -1,3 +1,3 @@
"""A program to help you work with qmk_firmware.""" """A program to help you work with qmk_firmware."""
__version__ = '0.0.12' __version__ = '0.0.26'
+15 -11
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""CLI wrapper for running QMK commands. """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) 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 subcommands`) TODO(skullydazed/anyone): Need a way to filter some subcommands from --help (EG `qmk hello`)
""" """
import argparse import argparse
import os import os
@@ -33,7 +33,7 @@ def in_qmk_firmware():
while len(cur_dir.parents) > 0: while len(cur_dir.parents) > 0:
found_bin = cur_dir / 'bin' / 'qmk' found_bin = cur_dir / 'bin' / 'qmk'
if found_bin.is_file(): if found_bin.is_file():
command = [found_bin, '--version'] command = [found_bin.as_posix(), '--version']
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode == 0: if result.returncode == 0:
@@ -48,33 +48,37 @@ def find_qmk_firmware():
"""Look for qmk_firmware in the usual places. """Look for qmk_firmware in the usual places.
This function returns the path to qmk_firmware, or the default location if one does not exist. This function returns the path to qmk_firmware, or the default location if one does not exist.
FIXME(skullydazed): add config file support
""" """
if in_qmk_firmware(): if in_qmk_firmware():
return in_qmk_firmware() return in_qmk_firmware()
if milc.cli.config.user.qmk_home:
return Path(milc.cli.config.user.qmk_home).expanduser().resolve()
if 'QMK_HOME' in os.environ: if 'QMK_HOME' in os.environ:
return Path(os.environ['QMK_HOME']) path = Path(os.environ['QMK_HOME']).expanduser()
if path.exists():
return path.resolve()
return path
return Path.home() / 'qmk_firmware' return Path.home() / 'qmk_firmware'
def main(): def main():
"""Dispatch the CLI subcommand to the proper place. """Setup the environment before dispatching to the entrypoint.
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.
""" """
# Environment setup # Environment setup
import qmk_cli
milc.cli.version = qmk_cli.__version__
qmk_firmware = find_qmk_firmware() qmk_firmware = find_qmk_firmware()
os.environ['QMK_HOME'] = str(qmk_firmware) os.environ['QMK_HOME'] = str(qmk_firmware)
os.environ['ORIG_CWD'] = os.getcwd()
# Import the subcommand modules # Import the subcommand modules
import qmk_cli.subcommands import qmk_cli.subcommands
if qmk_firmware.exists(): if qmk_firmware.exists():
os.chdir(str(qmk_firmware))
sys.path.append(str(qmk_firmware / 'lib' / 'python')) sys.path.append(str(qmk_firmware / 'lib' / 'python'))
import qmk.cli import qmk.cli
+9 -6
View File
@@ -4,19 +4,22 @@ import os
from pathlib import Path from pathlib import Path
from milc import cli from milc import cli
from qmk_cli.git import clone from qmk_cli.git import clone as git_clone
default_repo = 'qmk_firmware' default_repo = 'qmk_firmware'
default_fork = 'qmk/' + default_repo default_fork = 'qmk/' + default_repo
default_branch = 'master' default_branch = 'master'
@cli.argument('--baseurl', default='https://github.com', help='The URL all git operations start from.') @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.') @cli.argument('-b', '--branch', default=default_branch, help='The branch to clone (Default: %s)' % default_branch)
@cli.argument('destination', default=os.environ['QMK_HOME'], nargs='?', help='The directory to clone to.') @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') @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.') @cli.subcommand('Clone a qmk_firmware fork.')
def clone(cli): 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) qmk_firmware = Path(cli.args.destination)
git_url = '/'.join((cli.config.clone.baseurl, cli.args.fork)) git_url = '/'.join((cli.config.clone.baseurl, cli.args.fork))
@@ -24,5 +27,5 @@ def clone(cli):
cli.log.error('Destination already exists: %s', cli.args.destination) cli.log.error('Destination already exists: %s', cli.args.destination)
exit(1) exit(1)
success = clone(git_url, cli.args.destination, cli.config.clone.branch) success = git_clone(git_url, cli.args.destination, cli.config.clone.branch)
exit(0 if success else 1) exit(0 if success else 1)
+2 -2
View File
@@ -29,8 +29,8 @@ def setup(cli):
else: else:
cli.log.error('qmk_firmware not found!') cli.log.error('qmk_firmware not found!')
if question('Would you like to clone %s?' % cli.args.fork): if question('Would you like to clone %s?' % cli.args.fork):
git_url = '/'.join((cli.config.general.baseurl, cli.args.fork)) git_url = '/'.join((cli.config.setup.baseurl, cli.args.fork))
clone(git_url, cli.args.destination, cli.config.general.branch) clone(git_url, cli.args.destination, cli.config.setup.branch)
# Run `qmk_firmware/bin/qmk doctor` to check the rest of the environment out # Run `qmk_firmware/bin/qmk doctor` to check the rest of the environment out
if qmk_firmware.exists(): if qmk_firmware.exists():
+3
View File
@@ -1,5 +1,8 @@
#!/bin/sh #!/bin/sh
# Increment the verison number and release a new version of qmk_cli. # Increment the verison number and release a new version of qmk_cli.
#
# Required packages: pip3 install bumpversion twine
set -e set -e
set -x set -x
+1 -1
View File
@@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.0.12 current_version = 0.0.26
commit = True commit = True
tag = True tag = True
tag_name = {new_version} tag_name = {new_version}
+7 -2
View File
@@ -6,6 +6,8 @@ setup_cfg.read('setup.cfg')
metadata = setup_cfg['metadata'] metadata = setup_cfg['metadata']
if __name__ == "__main__": if __name__ == "__main__":
with open('README.md', encoding='utf-8') as readme_file:
long_description=readme_file.read()
setup( setup(
name=metadata['dist-name'], name=metadata['dist-name'],
description='A program to help users work with QMK Firmware.', description='A program to help users work with QMK Firmware.',
@@ -19,7 +21,7 @@ if __name__ == "__main__":
author_email=metadata['author-email'], author_email=metadata['author-email'],
maintainer=metadata['author'], maintainer=metadata['author'],
maintainer_email=metadata['author-email'], maintainer_email=metadata['author-email'],
long_description=open('README.md').read(), long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
packages=find_packages(), packages=find_packages(),
py_modules = ['milc'], py_modules = ['milc'],
@@ -41,6 +43,9 @@ if __name__ == "__main__":
#"milc", #FIXME(skullydazed): Included in the repo for now. #"milc", #FIXME(skullydazed): Included in the repo for now.
"appdirs", "appdirs",
"argcomplete", "argcomplete",
"colorama" "colorama",
"flake8",
"hjson",
"yapf"
], ],
) )