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 /lib/python/milc.py | |
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
Diffstat (limited to 'lib/python/milc.py')
-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): |