diff options
| author | skullydazed <skullydazed@users.noreply.github.com> | 2019-11-18 14:54:50 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-11-18 14:54:50 -0800 |
| commit | 9c58da6b121ca36f616f8a9a78dd4c2234bd5942 (patch) | |
| tree | 3ea17693bdc689f5174aefb9dbfd908130761c1f | |
| parent | b608bddc5edae7ccce8108d9c7777437160f3cb3 (diff) | |
| download | qmk_firmware-9c58da6b121ca36f616f8a9a78dd4c2234bd5942.tar.gz qmk_firmware-9c58da6b121ca36f616f8a9a78dd4c2234bd5942.zip | |
Improve a number of things about how MILC operates (#7344)
* Pull in updates for MILC
* 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
* Fix commands with no arguments
| -rw-r--r-- | lib/python/milc.py | 167 |
1 files changed, 82 insertions, 85 deletions
diff --git a/lib/python/milc.py b/lib/python/milc.py index 7b130bdea..e8599eff3 100644 --- a/lib/python/milc.py +++ b/lib/python/milc.py | |||
| @@ -20,6 +20,7 @@ import re | |||
| 20 | import shlex | 20 | import shlex |
| 21 | import sys | 21 | import sys |
| 22 | from decimal import Decimal | 22 | from decimal import Decimal |
| 23 | from pathlib import Path | ||
| 23 | from tempfile import NamedTemporaryFile | 24 | from tempfile import NamedTemporaryFile |
| 24 | from time import sleep | 25 | from time import sleep |
| 25 | 26 | ||
| @@ -39,7 +40,7 @@ import colorama | |||
| 39 | from appdirs import user_config_dir | 40 | from appdirs import user_config_dir |
| 40 | 41 | ||
| 41 | # Disable logging until we can configure it how the user wants | 42 | # Disable logging until we can configure it how the user wants |
| 42 | logging.basicConfig(filename='/dev/null') | 43 | logging.basicConfig(stream=os.devnull) |
| 43 | 44 | ||
| 44 | # Log Level Representations | 45 | # Log Level Representations |
| 45 | EMOJI_LOGLEVELS = { | 46 | EMOJI_LOGLEVELS = { |
| @@ -96,7 +97,6 @@ def format_ansi(text): | |||
| 96 | class ANSIFormatter(logging.Formatter): | 97 | class ANSIFormatter(logging.Formatter): |
| 97 | """A log formatter that inserts ANSI color. | 98 | """A log formatter that inserts ANSI color. |
| 98 | """ | 99 | """ |
| 99 | |||
| 100 | def format(self, record): | 100 | def format(self, record): |
| 101 | msg = super(ANSIFormatter, self).format(record) | 101 | msg = super(ANSIFormatter, self).format(record) |
| 102 | return format_ansi(msg) | 102 | return format_ansi(msg) |
| @@ -105,7 +105,6 @@ class ANSIFormatter(logging.Formatter): | |||
| 105 | class ANSIEmojiLoglevelFormatter(ANSIFormatter): | 105 | class ANSIEmojiLoglevelFormatter(ANSIFormatter): |
| 106 | """A log formatter that makes the loglevel an emoji on UTF capable terminals. | 106 | """A log formatter that makes the loglevel an emoji on UTF capable terminals. |
| 107 | """ | 107 | """ |
| 108 | |||
| 109 | def format(self, record): | 108 | def format(self, record): |
| 110 | if UNICODE_SUPPORT: | 109 | if UNICODE_SUPPORT: |
| 111 | record.levelname = EMOJI_LOGLEVELS[record.levelname].format(**ansi_colors) | 110 | record.levelname = EMOJI_LOGLEVELS[record.levelname].format(**ansi_colors) |
| @@ -115,7 +114,6 @@ class ANSIEmojiLoglevelFormatter(ANSIFormatter): | |||
| 115 | class ANSIStrippingFormatter(ANSIFormatter): | 114 | class ANSIStrippingFormatter(ANSIFormatter): |
| 116 | """A log formatter that strips ANSI. | 115 | """A log formatter that strips ANSI. |
| 117 | """ | 116 | """ |
| 118 | |||
| 119 | def format(self, record): | 117 | def format(self, record): |
| 120 | msg = super(ANSIStrippingFormatter, self).format(record) | 118 | msg = super(ANSIStrippingFormatter, self).format(record) |
| 121 | return ansi_escape.sub('', msg) | 119 | return ansi_escape.sub('', msg) |
| @@ -127,7 +125,6 @@ class Configuration(object): | |||
| 127 | This class never raises IndexError, instead it will return None if a | 125 | This class never raises IndexError, instead it will return None if a |
| 128 | section or option does not yet exist. | 126 | section or option does not yet exist. |
| 129 | """ | 127 | """ |
| 130 | |||
| 131 | def __contains__(self, key): | 128 | def __contains__(self, key): |
| 132 | return self._config.__contains__(key) | 129 | return self._config.__contains__(key) |
| 133 | 130 | ||
| @@ -214,9 +211,8 @@ def handle_store_boolean(self, *args, **kwargs): | |||
| 214 | 211 | ||
| 215 | 212 | ||
| 216 | class SubparserWrapper(object): | 213 | class SubparserWrapper(object): |
| 217 | """Wrap subparsers so we can populate the normal and the shadow parser. | 214 | """Wrap subparsers so we can track what options the user passed. |
| 218 | """ | 215 | """ |
| 219 | |||
| 220 | def __init__(self, cli, submodule, subparser): | 216 | def __init__(self, cli, submodule, subparser): |
| 221 | self.cli = cli | 217 | self.cli = cli |
| 222 | self.submodule = submodule | 218 | self.submodule = submodule |
| @@ -232,26 +228,30 @@ class SubparserWrapper(object): | |||
| 232 | self.subparser.completer = completer | 228 | self.subparser.completer = completer |
| 233 | 229 | ||
| 234 | def add_argument(self, *args, **kwargs): | 230 | def add_argument(self, *args, **kwargs): |
| 231 | """Add an argument for this subcommand. | ||
| 232 | |||
| 233 | This also stores the default for the argument in `self.cli.default_arguments`. | ||
| 234 | """ | ||
| 235 | if 'action' in kwargs and kwargs['action'] == 'store_boolean': | 235 | if 'action' in kwargs and kwargs['action'] == 'store_boolean': |
| 236 | # Store boolean will call us again with the enable/disable flag arguments | ||
| 236 | return handle_store_boolean(self, *args, **kwargs) | 237 | return handle_store_boolean(self, *args, **kwargs) |
| 237 | 238 | ||
| 238 | self.cli.acquire_lock() | 239 | self.cli.acquire_lock() |
| 239 | self.subparser.add_argument(*args, **kwargs) | 240 | self.subparser.add_argument(*args, **kwargs) |
| 240 | 241 | if self.submodule not in self.cli.default_arguments: | |
| 241 | if 'default' in kwargs: | 242 | self.cli.default_arguments[self.submodule] = {} |
| 242 | del kwargs['default'] | 243 | self.cli.default_arguments[self.submodule][self.cli.get_argument_name(*args, **kwargs)] = kwargs.get('default') |
| 243 | if 'action' in kwargs and kwargs['action'] == 'store_false': | ||
| 244 | kwargs['action'] == 'store_true' | ||
| 245 | self.cli.subcommands_default[self.submodule].add_argument(*args, **kwargs) | ||
| 246 | self.cli.release_lock() | 244 | self.cli.release_lock() |
| 247 | 245 | ||
| 248 | 246 | ||
| 249 | class MILC(object): | 247 | class MILC(object): |
| 250 | """MILC - An Opinionated Batteries Included Framework | 248 | """MILC - An Opinionated Batteries Included Framework |
| 251 | """ | 249 | """ |
| 252 | |||
| 253 | def __init__(self): | 250 | def __init__(self): |
| 254 | """Initialize the MILC object. | 251 | """Initialize the MILC object. |
| 252 | |||
| 253 | version | ||
| 254 | The version string to associate with your CLI program | ||
| 255 | """ | 255 | """ |
| 256 | # Setup a lock for thread safety | 256 | # Setup a lock for thread safety |
| 257 | self._lock = threading.RLock() if thread else None | 257 | self._lock = threading.RLock() if thread else None |
| @@ -263,9 +263,10 @@ class MILC(object): | |||
| 263 | self._inside_context_manager = False | 263 | self._inside_context_manager = False |
| 264 | self.ansi = ansi_colors | 264 | self.ansi = ansi_colors |
| 265 | self.arg_only = [] | 265 | self.arg_only = [] |
| 266 | self.config = Configuration() | 266 | self.config = None |
| 267 | self.config_file = None | 267 | self.config_file = None |
| 268 | self.version = os.environ.get('QMK_VERSION', 'unknown') | 268 | self.default_arguments = {} |
| 269 | self.version = 'unknown' | ||
| 269 | self.release_lock() | 270 | self.release_lock() |
| 270 | 271 | ||
| 271 | # Figure out our program name | 272 | # Figure out our program name |
| @@ -273,6 +274,7 @@ class MILC(object): | |||
| 273 | self.prog_name = self.prog_name.split('/')[-1] | 274 | self.prog_name = self.prog_name.split('/')[-1] |
| 274 | 275 | ||
| 275 | # Initialize all the things | 276 | # Initialize all the things |
| 277 | self.read_config_file() | ||
| 276 | self.initialize_argparse() | 278 | self.initialize_argparse() |
| 277 | self.initialize_logging() | 279 | self.initialize_logging() |
| 278 | 280 | ||
| @@ -282,7 +284,7 @@ class MILC(object): | |||
| 282 | 284 | ||
| 283 | @description.setter | 285 | @description.setter |
| 284 | def description(self, value): | 286 | def description(self, value): |
| 285 | self._description = self._arg_parser.description = self._arg_defaults.description = value | 287 | self._description = self._arg_parser.description = value |
| 286 | 288 | ||
| 287 | def echo(self, text, *args, **kwargs): | 289 | def echo(self, text, *args, **kwargs): |
| 288 | """Print colorized text to stdout. | 290 | """Print colorized text to stdout. |
| @@ -311,12 +313,9 @@ class MILC(object): | |||
| 311 | 313 | ||
| 312 | self.acquire_lock() | 314 | self.acquire_lock() |
| 313 | self.subcommands = {} | 315 | self.subcommands = {} |
| 314 | self.subcommands_default = {} | ||
| 315 | self._subparsers = None | 316 | self._subparsers = None |
| 316 | self._subparsers_default = None | ||
| 317 | self.argwarn = argcomplete.warn | 317 | self.argwarn = argcomplete.warn |
| 318 | self.args = None | 318 | self.args = None |
| 319 | self._arg_defaults = argparse.ArgumentParser(**kwargs) | ||
| 320 | self._arg_parser = argparse.ArgumentParser(**kwargs) | 319 | self._arg_parser = argparse.ArgumentParser(**kwargs) |
| 321 | self.set_defaults = self._arg_parser.set_defaults | 320 | self.set_defaults = self._arg_parser.set_defaults |
| 322 | self.print_usage = self._arg_parser.print_usage | 321 | self.print_usage = self._arg_parser.print_usage |
| @@ -329,25 +328,18 @@ class MILC(object): | |||
| 329 | self._arg_parser.completer = completer | 328 | self._arg_parser.completer = completer |
| 330 | 329 | ||
| 331 | def add_argument(self, *args, **kwargs): | 330 | def add_argument(self, *args, **kwargs): |
| 332 | """Wrapper to add arguments to both the main and the shadow argparser. | 331 | """Wrapper to add arguments and track whether they were passed on the command line. |
| 333 | """ | 332 | """ |
| 334 | if 'action' in kwargs and kwargs['action'] == 'store_boolean': | 333 | if 'action' in kwargs and kwargs['action'] == 'store_boolean': |
| 335 | return handle_store_boolean(self, *args, **kwargs) | 334 | return handle_store_boolean(self, *args, **kwargs) |
| 336 | 335 | ||
| 337 | if kwargs.get('add_dest', True) and args[0][0] == '-': | ||
| 338 | kwargs['dest'] = 'general_' + self.get_argument_name(*args, **kwargs) | ||
| 339 | if 'add_dest' in kwargs: | ||
| 340 | del kwargs['add_dest'] | ||
| 341 | |||
| 342 | self.acquire_lock() | 336 | self.acquire_lock() |
| 337 | |||
| 343 | self._arg_parser.add_argument(*args, **kwargs) | 338 | self._arg_parser.add_argument(*args, **kwargs) |
| 339 | if 'general' not in self.default_arguments: | ||
| 340 | self.default_arguments['general'] = {} | ||
| 341 | self.default_arguments['general'][self.get_argument_name(*args, **kwargs)] = kwargs.get('default') | ||
| 344 | 342 | ||
| 345 | # Populate the shadow parser | ||
| 346 | if 'default' in kwargs: | ||
| 347 | del kwargs['default'] | ||
| 348 | if 'action' in kwargs and kwargs['action'] == 'store_false': | ||
| 349 | kwargs['action'] == 'store_true' | ||
| 350 | self._arg_defaults.add_argument(*args, **kwargs) | ||
| 351 | self.release_lock() | 343 | self.release_lock() |
| 352 | 344 | ||
| 353 | def initialize_logging(self): | 345 | def initialize_logging(self): |
| @@ -374,15 +366,14 @@ class MILC(object): | |||
| 374 | 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.') | 366 | 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.') |
| 375 | self.add_argument('--log-file', help='File to write log messages to') | 367 | self.add_argument('--log-file', help='File to write log messages to') |
| 376 | self.add_argument('--color', action='store_boolean', default=True, help='color in output') | 368 | self.add_argument('--color', action='store_boolean', default=True, help='color in output') |
| 377 | self.add_argument('-c', '--config-file', help='The config file to read and/or write') | 369 | self.add_argument('--config-file', help='The location for the configuration file') |
| 378 | self.add_argument('--save-config', action='store_true', help='Save the running configuration to the config file') | 370 | self.arg_only.append('config_file') |
| 379 | 371 | ||
| 380 | def add_subparsers(self, title='Sub-commands', **kwargs): | 372 | def add_subparsers(self, title='Sub-commands', **kwargs): |
| 381 | if self._inside_context_manager: | 373 | if self._inside_context_manager: |
| 382 | raise RuntimeError('You must run this before the with statement!') | 374 | raise RuntimeError('You must run this before the with statement!') |
| 383 | 375 | ||
| 384 | self.acquire_lock() | 376 | self.acquire_lock() |
| 385 | self._subparsers_default = self._arg_defaults.add_subparsers(title=title, dest='subparsers', **kwargs) | ||
| 386 | self._subparsers = self._arg_parser.add_subparsers(title=title, dest='subparsers', **kwargs) | 377 | self._subparsers = self._arg_parser.add_subparsers(title=title, dest='subparsers', **kwargs) |
| 387 | self.release_lock() | 378 | self.release_lock() |
| 388 | 379 | ||
| @@ -404,10 +395,12 @@ class MILC(object): | |||
| 404 | if self.config_file: | 395 | if self.config_file: |
| 405 | return self.config_file | 396 | return self.config_file |
| 406 | 397 | ||
| 407 | if self.args and self.args.general_config_file: | 398 | if '--config-file' in sys.argv: |
| 408 | return self.args.general_config_file | 399 | return Path(sys.argv[sys.argv.index('--config-file') + 1]).expanduser().resolve() |
| 409 | 400 | ||
| 410 | return os.path.join(user_config_dir(appname='qmk', appauthor='QMK'), '%s.ini' % self.prog_name) | 401 | filedir = user_config_dir(appname='qmk', appauthor='QMK') |
| 402 | filename = '%s.ini' % self.prog_name | ||
| 403 | return Path(filedir) / filename | ||
| 411 | 404 | ||
| 412 | def get_argument_name(self, *args, **kwargs): | 405 | def get_argument_name(self, *args, **kwargs): |
| 413 | """Takes argparse arguments and returns the dest name. | 406 | """Takes argparse arguments and returns the dest name. |
| @@ -446,7 +439,7 @@ class MILC(object): | |||
| 446 | def arg_passed(self, arg): | 439 | def arg_passed(self, arg): |
| 447 | """Returns True if arg was passed on the command line. | 440 | """Returns True if arg was passed on the command line. |
| 448 | """ | 441 | """ |
| 449 | return self.args_passed[arg] in (None, False) | 442 | return self.default_arguments.get(arg) != self.args[arg] |
| 450 | 443 | ||
| 451 | def parse_args(self): | 444 | def parse_args(self): |
| 452 | """Parse the CLI args. | 445 | """Parse the CLI args. |
| @@ -459,25 +452,22 @@ class MILC(object): | |||
| 459 | 452 | ||
| 460 | self.acquire_lock() | 453 | self.acquire_lock() |
| 461 | self.args = self._arg_parser.parse_args() | 454 | self.args = self._arg_parser.parse_args() |
| 462 | self.args_passed = self._arg_defaults.parse_args() | ||
| 463 | 455 | ||
| 464 | if 'entrypoint' in self.args: | 456 | if 'entrypoint' in self.args: |
| 465 | self._entrypoint = self.args.entrypoint | 457 | self._entrypoint = self.args.entrypoint |
| 466 | 458 | ||
| 467 | if self.args.general_config_file: | ||
| 468 | self.config_file = self.args.general_config_file | ||
| 469 | |||
| 470 | self.release_lock() | 459 | self.release_lock() |
| 471 | 460 | ||
| 472 | def read_config(self): | 461 | def read_config_file(self): |
| 473 | """Parse the configuration file and determine the runtime configuration. | 462 | """Read in the configuration file and store it in self.config. |
| 474 | """ | 463 | """ |
| 475 | self.acquire_lock() | 464 | self.acquire_lock() |
| 465 | self.config = Configuration() | ||
| 476 | self.config_file = self.find_config_file() | 466 | self.config_file = self.find_config_file() |
| 477 | 467 | ||
| 478 | if self.config_file and os.path.exists(self.config_file): | 468 | if self.config_file and self.config_file.exists(): |
| 479 | config = RawConfigParser(self.config) | 469 | config = RawConfigParser(self.config) |
| 480 | config.read(self.config_file) | 470 | config.read(str(self.config_file)) |
| 481 | 471 | ||
| 482 | # Iterate over the config file options and write them into self.config | 472 | # Iterate over the config file options and write them into self.config |
| 483 | for section in config.sections(): | 473 | for section in config.sections(): |
| @@ -487,8 +477,10 @@ class MILC(object): | |||
| 487 | # Coerce values into useful datatypes | 477 | # Coerce values into useful datatypes |
| 488 | if value.lower() in ['1', 'yes', 'true', 'on']: | 478 | if value.lower() in ['1', 'yes', 'true', 'on']: |
| 489 | value = True | 479 | value = True |
| 490 | elif value.lower() in ['0', 'no', 'false', 'none', 'off']: | 480 | elif value.lower() in ['0', 'no', 'false', 'off']: |
| 491 | value = False | 481 | value = False |
| 482 | elif value.lower() in ['none']: | ||
| 483 | continue | ||
| 492 | elif value.replace('.', '').isdigit(): | 484 | elif value.replace('.', '').isdigit(): |
| 493 | if '.' in value: | 485 | if '.' in value: |
| 494 | value = Decimal(value) | 486 | value = Decimal(value) |
| @@ -497,32 +489,44 @@ class MILC(object): | |||
| 497 | 489 | ||
| 498 | self.config[section][option] = value | 490 | self.config[section][option] = value |
| 499 | 491 | ||
| 500 | # Fold the CLI args into self.config | 492 | self.release_lock() |
| 493 | |||
| 494 | def merge_args_into_config(self): | ||
| 495 | """Merge CLI arguments into self.config to create the runtime configuration. | ||
| 496 | """ | ||
| 497 | self.acquire_lock() | ||
| 501 | for argument in vars(self.args): | 498 | for argument in vars(self.args): |
| 502 | if argument in ('subparsers', 'entrypoint'): | 499 | if argument in ('subparsers', 'entrypoint'): |
| 503 | continue | 500 | continue |
| 504 | 501 | ||
| 505 | if '_' in argument: | 502 | if argument not in self.arg_only: |
| 506 | section, option = argument.split('_', 1) | 503 | # Find the argument's section |
| 507 | else: | 504 | if self._entrypoint.__name__ in self.default_arguments and argument in self.default_arguments[self._entrypoint.__name__]: |
| 508 | section = self._entrypoint.__name__ | 505 | argument_found = True |
| 509 | option = argument | 506 | section = self._entrypoint.__name__ |
| 510 | 507 | if argument in self.default_arguments['general']: | |
| 511 | if option not in self.arg_only: | 508 | argument_found = True |
| 512 | if hasattr(self.args_passed, argument): | 509 | section = 'general' |
| 510 | |||
| 511 | if not argument_found: | ||
| 512 | raise RuntimeError('Could not find argument in `self.default_arguments`. This should be impossible!') | ||
| 513 | exit(1) | ||
| 514 | |||
| 515 | # Merge this argument into self.config | ||
| 516 | if argument in self.default_arguments: | ||
| 513 | arg_value = getattr(self.args, argument) | 517 | arg_value = getattr(self.args, argument) |
| 514 | if arg_value: | 518 | if arg_value: |
| 515 | self.config[section][option] = arg_value | 519 | self.config[section][argument] = arg_value |
| 516 | else: | 520 | else: |
| 517 | if option not in self.config[section]: | 521 | if argument not in self.config[section]: |
| 518 | self.config[section][option] = getattr(self.args, argument) | 522 | self.config[section][argument] = getattr(self.args, argument) |
| 519 | 523 | ||
| 520 | self.release_lock() | 524 | self.release_lock() |
| 521 | 525 | ||
| 522 | def save_config(self): | 526 | def save_config(self): |
| 523 | """Save the current configuration to the config file. | 527 | """Save the current configuration to the config file. |
| 524 | """ | 528 | """ |
| 525 | self.log.debug("Saving config file to '%s'", self.config_file) | 529 | self.log.debug("Saving config file to '%s'", str(self.config_file)) |
| 526 | 530 | ||
| 527 | if not self.config_file: | 531 | if not self.config_file: |
| 528 | self.log.warning('%s.config_file file not set, not saving config!', self.__class__.__name__) | 532 | self.log.warning('%s.config_file file not set, not saving config!', self.__class__.__name__) |
| @@ -530,31 +534,34 @@ class MILC(object): | |||
| 530 | 534 | ||
| 531 | self.acquire_lock() | 535 | self.acquire_lock() |
| 532 | 536 | ||
| 537 | # Generate a sanitized version of our running configuration | ||
| 533 | config = RawConfigParser() | 538 | config = RawConfigParser() |
| 534 | config_dir = os.path.dirname(self.config_file) | ||
| 535 | |||
| 536 | for section_name, section in self.config._config.items(): | 539 | for section_name, section in self.config._config.items(): |
| 537 | config.add_section(section_name) | 540 | config.add_section(section_name) |
| 538 | for option_name, value in section.items(): | 541 | for option_name, value in section.items(): |
| 539 | if section_name == 'general': | 542 | if section_name == 'general': |
| 540 | if option_name in ['save_config']: | 543 | if option_name in ['config_file']: |
| 541 | continue | 544 | continue |
| 542 | config.set(section_name, option_name, str(value)) | 545 | if value is not None: |
| 546 | config.set(section_name, option_name, str(value)) | ||
| 543 | 547 | ||
| 544 | if not os.path.exists(config_dir): | 548 | # Write out the config file |
| 545 | os.makedirs(config_dir) | 549 | config_dir = self.config_file.parent |
| 550 | if not config_dir.exists(): | ||
| 551 | config_dir.mkdir(parents=True, exist_ok=True) | ||
| 546 | 552 | ||
| 547 | with NamedTemporaryFile(mode='w', dir=config_dir, delete=False) as tmpfile: | 553 | with NamedTemporaryFile(mode='w', dir=str(config_dir), delete=False) as tmpfile: |
| 548 | config.write(tmpfile) | 554 | config.write(tmpfile) |
| 549 | 555 | ||
| 550 | # Move the new config file into place atomically | 556 | # Move the new config file into place atomically |
| 551 | if os.path.getsize(tmpfile.name) > 0: | 557 | if os.path.getsize(tmpfile.name) > 0: |
| 552 | os.rename(tmpfile.name, self.config_file) | 558 | os.rename(tmpfile.name, str(self.config_file)) |
| 553 | else: | 559 | else: |
| 554 | self.log.warning('Config file saving failed, not replacing %s with %s.', self.config_file, tmpfile.name) | 560 | self.log.warning('Config file saving failed, not replacing %s with %s.', str(self.config_file), tmpfile.name) |
| 555 | 561 | ||
| 562 | # Housekeeping | ||
| 556 | self.release_lock() | 563 | self.release_lock() |
| 557 | cli.log.info('Wrote configuration to %s', shlex.quote(self.config_file)) | 564 | cli.log.info('Wrote configuration to %s', shlex.quote(str(self.config_file))) |
| 558 | 565 | ||
| 559 | def __call__(self): | 566 | def __call__(self): |
| 560 | """Execute the entrypoint function. | 567 | """Execute the entrypoint function. |
| @@ -603,16 +610,11 @@ class MILC(object): | |||
| 603 | name = handler.__name__.replace("_", "-") | 610 | name = handler.__name__.replace("_", "-") |
| 604 | 611 | ||
| 605 | self.acquire_lock() | 612 | self.acquire_lock() |
| 613 | |||
| 606 | kwargs['help'] = description | 614 | kwargs['help'] = description |
| 607 | self.subcommands_default[name] = self._subparsers_default.add_parser(name, **kwargs) | ||
| 608 | self.subcommands[name] = SubparserWrapper(self, name, self._subparsers.add_parser(name, **kwargs)) | 615 | self.subcommands[name] = SubparserWrapper(self, name, self._subparsers.add_parser(name, **kwargs)) |
| 609 | self.subcommands[name].set_defaults(entrypoint=handler) | 616 | self.subcommands[name].set_defaults(entrypoint=handler) |
| 610 | 617 | ||
| 611 | if name not in self.__dict__: | ||
| 612 | self.__dict__[name] = self.subcommands[name] | ||
| 613 | else: | ||
| 614 | self.log.debug("Could not add subcommand '%s' to attributes, key already exists!", name) | ||
| 615 | |||
| 616 | self.release_lock() | 618 | self.release_lock() |
| 617 | 619 | ||
| 618 | return handler | 620 | return handler |
| @@ -620,7 +622,6 @@ class MILC(object): | |||
| 620 | def subcommand(self, description, **kwargs): | 622 | def subcommand(self, description, **kwargs): |
| 621 | """Decorator to register a subcommand. | 623 | """Decorator to register a subcommand. |
| 622 | """ | 624 | """ |
| 623 | |||
| 624 | def subcommand_function(handler): | 625 | def subcommand_function(handler): |
| 625 | return self.add_subcommand(handler, description, **kwargs) | 626 | return self.add_subcommand(handler, description, **kwargs) |
| 626 | 627 | ||
| @@ -644,9 +645,9 @@ class MILC(object): | |||
| 644 | self.log_format = self.config['general']['log_fmt'] | 645 | self.log_format = self.config['general']['log_fmt'] |
| 645 | 646 | ||
| 646 | if self.config.general.color: | 647 | if self.config.general.color: |
| 647 | self.log_format = ANSIEmojiLoglevelFormatter(self.args.general_log_fmt, self.config.general.datetime_fmt) | 648 | self.log_format = ANSIEmojiLoglevelFormatter(self.args.log_fmt, self.config.general.datetime_fmt) |
| 648 | else: | 649 | else: |
| 649 | self.log_format = ANSIStrippingFormatter(self.args.general_log_fmt, self.config.general.datetime_fmt) | 650 | self.log_format = ANSIStrippingFormatter(self.args.log_fmt, self.config.general.datetime_fmt) |
| 650 | 651 | ||
| 651 | if self.log_file: | 652 | if self.log_file: |
| 652 | self.log_file_handler = logging.FileHandler(self.log_file, self.log_file_mode) | 653 | self.log_file_handler = logging.FileHandler(self.log_file, self.log_file_mode) |
| @@ -673,13 +674,9 @@ class MILC(object): | |||
| 673 | 674 | ||
| 674 | colorama.init() | 675 | colorama.init() |
| 675 | self.parse_args() | 676 | self.parse_args() |
| 676 | self.read_config() | 677 | self.merge_args_into_config() |
| 677 | self.setup_logging() | 678 | self.setup_logging() |
| 678 | 679 | ||
| 679 | if 'save_config' in self.config.general and self.config.general.save_config: | ||
| 680 | self.save_config() | ||
| 681 | exit(0) | ||
| 682 | |||
| 683 | return self | 680 | return self |
| 684 | 681 | ||
| 685 | def __exit__(self, exc_type, exc_val, exc_tb): | 682 | def __exit__(self, exc_type, exc_val, exc_tb): |
