Compare commits

...

11 Commits

Author SHA1 Message Date
Zach White 5a777c5de9 New release: 0.0.34 → 0.0.35 2020-05-27 20:58:33 -07:00
Zach White c51c898562 use cli.echo, not cli.args.echo. 2020-05-27 20:51:28 -07:00
Erovia 08ea23b087 Fix possibility of recursive execution 2020-05-26 21:43:15 +02:00
Erovia 0105405d83 Checking for the 'quantum' directory is better for long term 2020-05-10 17:41:30 +02:00
Erovia 92c57e2a46 Fix possibility of recursive execution 2020-05-10 17:30:30 +02:00
skullY 30c89ec8e0 New release: 0.0.33 → 0.0.34 2020-04-29 10:39:57 -07:00
Zach White a7a7bd9f08 improve the qmk setup flow (#20)
* improve the qmk setup flow

* Apply suggestions from code review

Co-Authored-By: Erovia <Erovia@users.noreply.github.com>

* Update qmk_cli/subcommands/setup.py

* explicity set encoding to utf-8

* polishing

Co-authored-by: Erovia <Erovia@users.noreply.github.com>
2020-04-29 10:38:56 -07:00
skullY 5229ce14e3 New release: 0.0.32 → 0.0.33 2020-04-15 13:53:35 -07:00
Erovia 8fa960f25e Merge pull request #19 from qmk/fix_store_true_false 2020-04-15 22:49:17 +02:00
skullY 1481dfe157 Make arg_only apply only to the subcommand it is part of 2020-04-15 12:07:15 -07:00
skullY ac39b9bb0f Fix setting config values for store_true and store_false 2020-04-15 09:30:49 -07:00
8 changed files with 171 additions and 120 deletions
+124 -81
View File
@@ -242,15 +242,24 @@ class SubparserWrapper(object):
This also stores the default for the argument in `self.cli.default_arguments`. This also stores the default for the argument in `self.cli.default_arguments`.
""" """
if 'action' in kwargs and kwargs['action'] == 'store_boolean': if kwargs.get('action') == 'store_boolean':
# Store boolean will call us again with the enable/disable flag arguments # 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()
argument_name = self.cli.get_argument_name(*args, **kwargs)
self.subparser.add_argument(*args, **kwargs) self.subparser.add_argument(*args, **kwargs)
if kwargs.get('action') == 'store_false':
self.cli._config_store_false.append(argument_name)
if kwargs.get('action') == 'store_true':
self.cli._config_store_true.append(argument_name)
if self.submodule not in self.cli.default_arguments: if self.submodule not in self.cli.default_arguments:
self.cli.default_arguments[self.submodule] = {} self.cli.default_arguments[self.submodule] = {}
self.cli.default_arguments[self.submodule][self.cli.get_argument_name(*args, **kwargs)] = kwargs.get('default') self.cli.default_arguments[self.submodule][argument_name] = kwargs.get('default')
self.cli.release_lock() self.cli.release_lock()
@@ -268,11 +277,13 @@ class MILC(object):
# Define some basic info # Define some basic info
self.acquire_lock() self.acquire_lock()
self._config_store_true = []
self._config_store_false = []
self._description = None self._description = None
self._entrypoint = None self._entrypoint = None
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 = self.config_source = None self.config = self.config_source = None
self.config_file = None self.config_file = None
self.default_arguments = {} self.default_arguments = {}
@@ -284,7 +295,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_config()
self.initialize_argparse() self.initialize_argparse()
self.initialize_logging() self.initialize_logging()
@@ -377,7 +388,7 @@ class MILC(object):
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('--config-file', help='The location for the configuration file') self.add_argument('--config-file', help='The location for the configuration file')
self.arg_only.append('config_file') self.arg_only['config_file'] = ['general']
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:
@@ -427,17 +438,20 @@ class MILC(object):
raise RuntimeError('You must run this before the with statement!') raise RuntimeError('You must run this before the with statement!')
def argument_function(handler): def argument_function(handler):
if 'arg_only' in kwargs and kwargs['arg_only']: subcommand_name = handler.__name__.replace("_", "-")
if kwargs.get('arg_only'):
arg_name = self.get_argument_name(*args, **kwargs) arg_name = self.get_argument_name(*args, **kwargs)
self.arg_only.append(arg_name) if arg_name not in self.arg_only:
self.arg_only[arg_name] = []
self.arg_only[arg_name].append(subcommand_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 name in self.subcommands: elif subcommand_name in self.subcommands:
self.subcommands[name].add_argument(*args, **kwargs) self.subcommands[subcommand_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!')
@@ -468,22 +482,20 @@ class MILC(object):
self.release_lock() self.release_lock()
def read_config_file(self): def read_config_file(self, config_file):
"""Read in the configuration file and store it in self.config. """Read in the configuration file and return Configuration objects for it and the config_source.
""" """
self.acquire_lock() config = Configuration()
self.config = Configuration() config_source = Configuration()
self.config_source = Configuration()
self.config_file = self.find_config_file()
if self.config_file and self.config_file.exists(): if config_file.exists():
config = RawConfigParser(self.config) raw_config = RawConfigParser()
config.read(str(self.config_file)) raw_config.read(str(config_file))
# Iterate over the config file options and write them into self.config # Iterate over the config file options and write them into config
for section in config.sections(): for section in raw_config.sections():
for option in config.options(section): for option in raw_config.options(section):
value = config.get(section, option) value = raw_config.get(section, option)
# 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']:
@@ -498,9 +510,17 @@ class MILC(object):
else: else:
value = int(value) value = int(value)
self.config[section][option] = value config[section][option] = value
self.config_source[section][option] = 'config_file' config_source[section][option] = 'config_file'
return config, config_source
def initialize_config(self):
"""Read in the configuration file and store it in self.config.
"""
self.acquire_lock()
self.config_file = self.find_config_file()
self.config, self.config_source = self.read_config_file(self.config_file)
self.release_lock() self.release_lock()
def merge_args_into_config(self): def merge_args_into_config(self):
@@ -511,38 +531,88 @@ class MILC(object):
if argument in ('subparsers', 'entrypoint'): if argument in ('subparsers', 'entrypoint'):
continue continue
if argument not in self.arg_only: # Find the argument's section
# Find the argument's section # Underscores in command's names are converted to dashes during initialization.
# Underscores in command's names are converted to dashes during initialization. # TODO(Erovia) Find a better solution
# TODO(Erovia) Find a better solution entrypoint_name = self._entrypoint.__name__.replace("_", "-")
entrypoint_name = self._entrypoint.__name__.replace("_", "-") if entrypoint_name in self.default_arguments and argument in self.default_arguments[entrypoint_name]:
if entrypoint_name in self.default_arguments and argument in self.default_arguments[entrypoint_name]: argument_found = True
argument_found = True section = self._entrypoint.__name__
section = self._entrypoint.__name__ if argument in self.default_arguments['general']:
if argument in self.default_arguments['general']: argument_found = True
argument_found = True section = 'general'
section = 'general'
if not argument_found: if not argument_found:
raise RuntimeError('Could not find argument in `self.default_arguments`. This should be impossible!') raise RuntimeError('Could not find argument in `self.default_arguments`. This should be impossible!')
exit(1) exit(1)
if argument not in self.arg_only or section not in self.arg_only[argument]:
# Determine the arg value and source
arg_value = getattr(self.args, argument)
if argument in self._config_store_true and arg_value:
passed_on_cmdline = True
elif argument in self._config_store_false and not arg_value:
passed_on_cmdline = True
elif arg_value is not None:
passed_on_cmdline = True
else:
passed_on_cmdline = False
# Merge this argument into self.config # Merge this argument into self.config
if argument in self.default_arguments['general'] or argument in self.default_arguments[entrypoint_name]: if passed_on_cmdline and (argument in self.default_arguments['general'] or argument in self.default_arguments[entrypoint_name] or argument not in self.config[entrypoint_name]):
arg_value = getattr(self.args, argument) self.config[section][argument] = arg_value
if arg_value is not None: self.config_source[section][argument] = 'argument'
self.config[section][argument] = arg_value
self.config_source[section][argument] = 'argument'
else:
if argument not in self.config[entrypoint_name]:
# Check if the argument exist for this section
arg = getattr(self.args, argument)
if arg is not None:
self.config[section][argument] = arg
self.config_source[section][argument] = 'argument'
self.release_lock() self.release_lock()
def _save_config_file(self, config):
"""Write config to disk.
"""
# Generate a sanitized version of our running configuration
sane_config = RawConfigParser()
for section_name, section in config._config.items():
sane_config.add_section(section_name)
for option_name, value in section.items():
if section_name == 'general':
if option_name in ['config_file']:
continue
if value is not None:
sane_config.set(section_name, option_name, str(value))
config_dir = self.config_file.parent
if not config_dir.exists():
config_dir.mkdir(parents=True, exist_ok=True)
# Write the config file atomically.
self.acquire_lock()
with NamedTemporaryFile(mode='w', dir=str(config_dir), delete=False) as tmpfile:
sane_config.write(tmpfile)
if os.path.getsize(tmpfile.name) > 0:
os.replace(tmpfile.name, str(self.config_file))
else:
self.log.warning('Config file saving failed, not replacing %s with %s.', str(self.config_file), tmpfile.name)
self.release_lock()
def write_config_option(self, section, option):
"""Save a single config option to the config file.
"""
if not self.config_file:
self.log.warning('%s.config_file not set, not saving config!', self.__class__.__name__)
return
config, config_source = self.read_config_file(self.config_file)
if section in config and option in config[section] and config[section][option] is None:
del(config[section][option])
else:
config[section][option] = str(self.config[section][option])
self._save_config_file(config)
# Housekeeping
self.log.info('Wrote configuration to %s', shlex.quote(str(self.config_file)))
def save_config(self): def save_config(self):
"""Save the current configuration to the config file. """Save the current configuration to the config file.
""" """
@@ -552,36 +622,9 @@ class MILC(object):
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__)
return return
self.acquire_lock() # Write config to disk
self._save_config_file(self.config)
# Generate a sanitized version of our running configuration self.log.info('Wrote configuration to %s', shlex.quote(str(self.config_file)))
config = RawConfigParser()
for section_name, section in self.config._config.items():
config.add_section(section_name)
for option_name, value in section.items():
if section_name == 'general':
if option_name in ['config_file']:
continue
if value is not None:
config.set(section_name, option_name, str(value))
# Write out the config file
config_dir = self.config_file.parent
if not config_dir.exists():
config_dir.mkdir(parents=True, exist_ok=True)
with NamedTemporaryFile(mode='w', dir=str(config_dir), delete=False) as tmpfile:
config.write(tmpfile)
# Move the new config file into place atomically
if os.path.getsize(tmpfile.name) > 0:
os.replace(tmpfile.name, str(self.config_file))
else:
self.log.warning('Config file saving failed, not replacing %s with %s.', str(self.config_file), tmpfile.name)
# Housekeeping
self.release_lock()
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.
+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.32' __version__ = '0.0.35'
+5 -5
View File
@@ -9,7 +9,7 @@ default_fork = 'qmk/' + default_repo
default_branch = 'master' default_branch = 'master'
def clone(url, destination, branch): def git_clone(url, destination, branch):
git_clone = [ git_clone = [
'git', 'git',
'clone', 'clone',
@@ -20,13 +20,13 @@ def clone(url, destination, branch):
] ]
cli.log.debug('Git clone command: %s', git_clone) cli.log.debug('Git clone command: %s', git_clone)
with subprocess.Popen(git_clone, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) as p: with subprocess.Popen(git_clone, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, bufsize=1, universal_newlines=True, encoding='utf-8') as p:
for line in p.stdout: for line in p.stdout:
print(line, end='') print(line, end='')
if p.returncode != 0: if p.returncode != 0:
cli.log.error('git clone exited %d', p.returncode) cli.log.error('git clone exited %d', p.returncode)
return False
else: else:
cli.log.info('Successfully cloned %s to %s!', cli.args.fork, cli.args.destination) cli.log.info('Successfully cloned %s to %s!', url, destination)
return True
return p.returncode
+4 -3
View File
@@ -1,6 +1,6 @@
"""Useful helper functions. """Useful helper functions.
""" """
from milc import cli from milc import cli, format_ansi
def question(question, boolean=True, default=''): def question(question, boolean=True, default=''):
@@ -18,10 +18,11 @@ def question(question, boolean=True, default=''):
else: else:
answer_key = 'y/n' answer_key = 'y/n'
prompt = '*** %s [%s] ' % (question, answer_key) prompt = format_ansi('\n*** %s [%s] ' % (question, answer_key))
while True: while True:
answer = input(prompt) answer = input(prompt)
print()
if answer == '' and default.lower() == 'y': if answer == '' and default.lower() == 'y':
answer = 'y' answer = 'y'
elif answer == '' and default.lower() == 'n': elif answer == '' and default.lower() == 'n':
@@ -32,4 +33,4 @@ def question(question, boolean=True, default=''):
elif answer.lower() in ['n', 'no']: elif answer.lower() in ['n', 'no']:
return False return False
else: else:
cli.args.echo('Invalid answer!') cli.echo('Invalid answer!')
+6 -1
View File
@@ -30,7 +30,12 @@ def in_qmk_firmware():
cur_dir = Path.cwd() cur_dir = Path.cwd()
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(): # An additional check for something that exists in the root of qmk_firmware,
# but not in the script's install directory, to avoid recursive execution,
# if started from install directory.
# e.g.: cd ~/.local/bin && ./qmk
found_quantum = cur_dir / 'quantum'
if found_bin.is_file() and found_quantum.is_dir():
command = [sys.executable, found_bin.as_posix()] command = [sys.executable, found_bin.as_posix()]
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+2 -3
View File
@@ -4,7 +4,7 @@ import os
from pathlib import Path from pathlib import Path
from milc import cli from milc import cli
from qmk_cli.git import clone as git_clone from qmk_cli.git import git_clone
default_repo = 'qmk_firmware' default_repo = 'qmk_firmware'
default_fork = 'qmk/' + default_repo default_fork = 'qmk/' + default_repo
@@ -27,5 +27,4 @@ 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 = git_clone(git_url, cli.args.destination, cli.config.clone.branch) return git_clone(git_url, cli.args.destination, cli.config.clone.branch)
exit(0 if success else 1)
+28 -25
View File
@@ -1,12 +1,13 @@
"""Setup qmk_firmware on your computer. """Setup qmk_firmware on your computer.
""" """
import os import os
import shlex
import subprocess import subprocess
import sys import sys
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 git_clone
from qmk_cli.helpers import question from qmk_cli.helpers import question
default_repo = 'qmk_firmware' default_repo = 'qmk_firmware'
@@ -18,11 +19,14 @@ default_branch = 'master'
@cli.argument('-y', '--yes', arg_only=True, action='store_true', help='Answer yes to all questions') @cli.argument('-y', '--yes', arg_only=True, action='store_true', help='Answer yes to all questions')
@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')
@cli.argument('-b', '--branch', default=default_branch, help='The branch to clone') @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('-H', '--home', default=Path(os.environ['QMK_HOME']), type=Path, help='The location for QMK Firmware. Defaults to %s' % (os.environ['QMK_HOME'],))
@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')
@cli.subcommand('Setup your computer for qmk_firmware.') @cli.subcommand('Setup your computer for qmk_firmware.')
def setup(cli): def setup(cli):
qmk_firmware = Path(cli.args.destination) """Guide the user through setting up their QMK environment.
"""
clone_prompt = 'Would you like to clone {fg_cyan}%s{fg_reset} to {fg_cyan}%s{fg_reset}?' % (cli.args.fork, shlex.quote(str(cli.args.home)))
home_prompt = 'Would you like to set {fg_cyan}%s{fg_reset} as your QMK home?' % (shlex.quote(str(cli.args.home)),)
# Sanity checks # Sanity checks
if cli.args.yes and cli.args.no: if cli.args.yes and cli.args.no:
@@ -30,30 +34,29 @@ def setup(cli):
exit(1) exit(1)
# Check on qmk_firmware, and if it doesn't exist offer to check it out. # Check on qmk_firmware, and if it doesn't exist offer to check it out.
if (qmk_firmware / 'Makefile').exists(): if (cli.args.home / 'Makefile').exists():
cli.log.info('Found qmk_firmware at %s.', str(qmk_firmware)) cli.log.info('Found qmk_firmware at %s.', str(cli.args.home))
else: else:
cli.log.error('qmk_firmware not found!') cli.log.error('Could not find qmk_firmware!')
if question('Would you like to clone %s?' % cli.args.fork): if question(clone_prompt):
git_url = '/'.join((cli.config.setup.baseurl, cli.args.fork)) git_url = '/'.join((cli.config.setup.baseurl, cli.args.fork))
clone(git_url, cli.args.destination, cli.config.setup.branch) result = git_clone(git_url, cli.args.home, cli.config.setup.branch)
if result != 0:
exit(1)
# Offer to set `user.qmk_home` for them.
if cli.args.home != os.environ['QMK_HOME'] and question(home_prompt):
cli.config['user']['qmk_home'] = str(cli.args.home.absolute())
cli.write_config_option('user', 'qmk_home')
# 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(): qmk_bin = cli.args.home / 'bin/qmk'
qmk_bin = qmk_firmware / 'bin' / 'qmk' doctor_cmd = [sys.executable, qmk_bin, 'doctor']
doctor_cmd = [sys.executable, str(qmk_bin), 'doctor']
if cli.args.yes:
doctor_cmd.append('--yes')
if cli.args.no:
doctor_cmd.append('--no')
doctor = subprocess.run(doctor_cmd)
if doctor.returncode != 0:
cli.log.error('Your build environment is not setup completely.')
if question('Would you like to run util/qmk_install?'): if cli.args.yes:
curdir = os.getcwd() doctor_cmd.append('--yes')
os.chdir(str(qmk_firmware))
process = subprocess.run(['util/qmk_install.sh']) if cli.args.no:
os.chdir(curdir) doctor_cmd.append('--no')
if process.returncode == 0:
cli.log.info('QMK setup complete!') subprocess.run(doctor_cmd)
+1 -1
View File
@@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 0.0.32 current_version = 0.0.35
commit = True commit = True
tag = True tag = True
tag_name = {new_version} tag_name = {new_version}