Skip to content

Commit b2979ba

Browse files
committed
Overhauled legacy ctrl handling to cover config file + args (fixes #20), increased debug verbosity
1 parent 2c05f82 commit b2979ba

10 files changed

Lines changed: 248 additions & 86 deletions

File tree

remove-json-keys/src/remove_json_keys/assets/data/messages.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@
2828
"warn_NO_PROJECT_ROOT_FOUND_IN": { "message": "No project root detected in" },
2929
"warn_SPECIFIED_CONFIG": { "message": "Specified config" },
3030
"warn_NOT_FOUND": { "message": "Not found" },
31-
"warn_OPTION": { "message": "Option" },
31+
"warn_CONFIG_FILE_KEY": { "message": "Config file key" },
32+
"warn_CLI_OPTION": { "message": "CLI option" },
33+
"warn_HAS_BEEN_REPLACED_BY": { "message": "has been replaced by" },
34+
"warn_AND_WILL_BE_REMOVED": { "message": "and will be removed" },
3235
"warn_NO_LONGER_HAS_ANY_EFFECT": { "message": "no longer has any effect" },
3336
"err_UNRECOGNIZED_ARGS": { "message": "Unrecognized argument(s)" },
3437
"err_INVALID_KEY": { "message": "Invalid key" },

remove-json-keys/src/remove_json_keys/lib/log.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import os, sys
2+
from pathlib import Path
23
from types import SimpleNamespace as sn
34
if sys.platform == 'win32' : import colorama ; colorama.init() # enable ANSI color support
45

6+
from . import data as datalib, pkg, settings
7+
58
try : terminal_width = os.get_terminal_size()[0]
69
except OSError : terminal_width = 80
710

11+
current_ver = datalib.json.read(Path(__file__).parent.parent / 'assets/data/package_data.json')['version']
12+
next_maj_ver = pkg.get_next_maj_ver(current_ver)
13+
_warned_keys = { 'cli': set(), 'config': set() }
14+
815
colors = sn(
916
nc='\x1b[0m', # no color
1017
br='\x1b[1;91m', # bright red
@@ -33,6 +40,28 @@ def version(cli):
3340
print(f'\n{colors.by}{cli.name}\n{colors.bw}{cli.msgs.log_VERSION.lower()}: {cli.version}{colors.nc}')
3441
def warn(msg, *args, **kwargs) : print(f'\n{colors.bo}WARNING: {msg.format(*args, **kwargs)}{colors.nc}')
3542

43+
def warn_legacy_option(cli, flag: str, source: str) -> None:
44+
warned_set = _warned_keys[source]
45+
if flag in warned_set : return
46+
canonical_key = settings.get_canonical_key(flag)
47+
msg = f"{ cli.msgs.warn_CONFIG_FILE_KEY if source == 'config' else cli.msgs.warn_CLI_OPTION } {flag!r}"
48+
if canonical_key:
49+
canonical_ctrl = getattr(settings.controls, canonical_key, None)
50+
if source == 'cli' and canonical_ctrl:
51+
flags = [arg for arg in getattr(canonical_ctrl, 'args', []) if arg.startswith('-')]
52+
if flag.startswith('-') and len(flag) == 2: # show short flag replacement
53+
display_key = min(flags, key=len) if flags else f"--{canonical_key.replace('_', '-')}"
54+
else: # show long flag replacement
55+
long_flags = [flag for flag in flags if flag.startswith('--')]
56+
display_key = long_flags[0] if long_flags else f"--{canonical_key.replace('_', '-')}"
57+
else:
58+
display_key = canonical_key
59+
msg += f' {cli.msgs.warn_HAS_BEEN_REPLACED_BY} {display_key!r}'
60+
else:
61+
msg += f' {cli.msgs.warn_NO_LONGER_HAS_ANY_EFFECT}'
62+
msg += f' {cli.msgs.warn_AND_WILL_BE_REMOVED} @ v{next_maj_ver}'
63+
warn(msg) ; warned_set.add(flag)
64+
3665
def cmd_docs_url_exit(cli, msg='', cmd='help'):
3766
if msg : error(msg)
3867
help_cmd(cli) if cmd == 'help' else init_cmd(cli)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import re
2+
3+
def get_next_maj_ver(version: str) -> str: # e.g. '1.2.3' -> '2.0.0'
4+
major = re.match(r'^(\d+)\..*', version)
5+
if not major : raise ValueError(f'Invalid version string: {version!r}')
6+
return f'{ int(major.group(1)) +1 }.0.0'
Lines changed: 83 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import argparse, sys
1+
import argparse, re, sys
22
from types import SimpleNamespace as sn
33

4-
from . import data, init, log, url
4+
from . import data, init, log, string, url
55

66
controls = sn(
77
json_dir=sn(
@@ -27,66 +27,107 @@
2727
debug=sn(
2828
args=['-V', '--debug'], nargs='?', const=True, metavar='TARGET_KEY'),
2929
legacy_no_wizard=sn(
30-
replaced_by='no_wizard', args=['-W'])
30+
args=['-W'], action='store_true', default=None)
3131
)
3232

33+
def get_canonical_key(key: str) -> str | None:
34+
if key.startswith('-'): # convert CLI arg to full key name
35+
for ctrl_key, ctrl in vars(controls).items():
36+
if key in getattr(ctrl, 'args', []):
37+
key = ctrl_key
38+
break
39+
legacy_key = key if key.startswith('legacy_') else f'legacy_{key}'
40+
legacy_ctrl = getattr(controls, legacy_key, None)
41+
stripped_key = string.removeprefix(key, 'legacy_')
42+
return legacy_ctrl.replaced_by if legacy_ctrl and hasattr(legacy_ctrl, 'replaced_by') \
43+
else stripped_key if hasattr(controls, stripped_key) \
44+
else None
45+
46+
def is_neg_key(key: str) -> bool :
47+
return bool(re.match(r'^(?:no|disable|exclude)_', string.removeprefix(key, 'legacy_')))
48+
3349
def load(cli):
3450
cli.config = sn()
3551

3652
# Assign help tips from cli.msgs
3753
for ctrl_key, ctrl in vars(controls).items():
38-
if ctrl_key.startswith('legacy_') : continue
54+
if ctrl_key.startswith('legacy_') or ctrl_key.endswith('entropy') : continue
3955
if not hasattr(ctrl, 'help') : ctrl.help = getattr(cli.msgs, f'help_{ctrl_key.upper()}')
4056

41-
# Parse CLI args
57+
# Load from config file
58+
init.config_filepath(cli)
59+
if getattr(cli, 'config_filepath', None):
60+
config_data = data.json.read(cli.config_filepath)
61+
for config_key in config_data:
62+
if not get_canonical_key(config_key):
63+
log.cmd_docs_url_exit(cli,
64+
f'{cli.msgs.err_INVALID_KEY} {config_key!r} {cli.msgs.err_FOUND_IN}'
65+
f'\n{log.colors.gry}{cli.config_filepath}',
66+
cmd='init')
67+
for config_key, config_val in config_data.items():
68+
canonical_key = get_canonical_key(config_key)
69+
if canonical_key and config_key != canonical_key: # re-map config_key -> canonical_key
70+
log.warn_legacy_option(cli, config_key, source='config')
71+
if is_neg_key(config_key) != is_neg_key(canonical_key):
72+
config_val = not config_val # flip bool val of opposite keys first
73+
config_key = canonical_key
74+
setattr(cli.config, config_key, config_val)
75+
log.debug(f'Config file loaded! {log.colors.dg}{len(config_data)} keys processed', cli)
76+
else:
77+
log.debug('No config file found.')
78+
79+
# Parse CLI args (overriding config file loads)
4280
argp = argparse.ArgumentParser(description=cli.description, add_help=False)
81+
valid_argparse_kwargs = {
82+
'action', 'choices', 'const', 'default', 'dest', 'help', 'metavar', 'nargs', 'required', 'type', 'version'}
4383
for ctrl_key, ctrl in vars(controls).items(): # add args to argp
4484
kwargs = ctrl.__dict__.copy()
4585
args = kwargs.pop('args')
46-
if ctrl_key.startswith('legacy_'):
47-
for arg in args:
48-
if arg in sys.argv:
49-
log.warn(f'{cli.msgs.warn_OPTION} {arg} {cli.msgs.warn_NO_LONGER_HAS_ANY_EFFECT}.')
50-
continue # to parse next arg
51-
valid_argparse_kwargs = {
52-
'action', 'choices', 'const', 'default', 'dest', 'help', 'metavar', 'nargs', 'required', 'type', 'version' }
53-
argparse_kwargs = { key:val for key,val in kwargs.items() if key in valid_argparse_kwargs }
86+
argparse_kwargs = { key:val for key,val in kwargs.items() if key in valid_argparse_kwargs }
87+
if ctrl_key.startswith('legacy_'): # copy canonical attrs first
88+
canonical_key = get_canonical_key(ctrl_key)
89+
if canonical_key: # adjust argparse_kwargs
90+
canonical_ctrl = getattr(controls, canonical_key)
91+
argparse_kwargs.update({
92+
key:val for key,val in canonical_ctrl.__dict__.items() if key in valid_argparse_kwargs })
93+
argparse_kwargs['dest'] = canonical_key
94+
if is_neg_key(ctrl_key) != is_neg_key(canonical_key):
95+
argparse_kwargs['action'] = 'store_false' if argparse_kwargs['action'] == 'store_true' \
96+
else 'store_true'
97+
for arg in args:
98+
if arg in sys.argv:
99+
log.warn_legacy_option(cli, arg, source='cli')
100+
break
54101
argp.add_argument(*args, **argparse_kwargs)
55102
parsed_args, unknown_args = argp.parse_known_args()
56-
exempt_flags = [] # exempt dashless + legacy args from validation
57-
for ctrl_key, ctrl in vars(controls).items():
58-
if getattr(ctrl, 'subcmd', False) or ctrl_key.startswith('legacy_'):
59-
for arg in ctrl.args : exempt_flags.append(arg)
60-
if unknown_args and not all(any(arg.startswith(exempt) for exempt in exempt_flags) for arg in unknown_args):
103+
exempt_flags = [] # exempt valid dash-less args from validation
104+
exempt_flags.extend(arg.lstrip('-') for ctrl_key, ctrl in vars(controls).items()
105+
if getattr(ctrl, 'subcmd', False)
106+
for arg in ctrl.args if len(arg) > 2) # skip short flags
107+
if unknown_args and not all(any(arg == exempt for exempt in exempt_flags) for arg in unknown_args):
61108
log.cmd_docs_url_exit(cli, f"{cli.msgs.err_UNRECOGNIZED_ARGS}: {' '.join(unknown_args)}", cmd='help')
62109
for ctrl_key, ctrl in vars(controls).items(): # process subcmds
63110
if getattr(ctrl, 'subcmd', False) and next(arg for arg in ctrl.args if arg.startswith('--'))[2:] in sys.argv:
64111
setattr(parsed_args, ctrl_key, True)
65-
for key, val in vars(parsed_args).items(): # apply parsed_args to cli.config
66-
setattr(cli.config, key, val)
67-
log.debug('Args parsed!', cli)
68-
69-
# Load from config file (w/o overriding args)
70-
init.config_filepath(cli)
71-
if getattr(cli, 'config_filepath', None):
72-
for key, val in data.json.read(cli.config_filepath).items():
73-
if not getattr(cli.config, key, None):
74-
if hasattr(cli.config, key):
75-
setattr(cli.config, key, val)
76-
else:
77-
log.cmd_docs_url_exit(cli,
78-
f'{cli.msgs.err_INVALID_KEY} {key!r} {cli.msgs.err_FOUND_IN}'
79-
f'\n{log.colors.gry}{cli.config_filepath}',
80-
cmd='init')
81-
log.debug('Config file loaded!', cli)
82-
else:
83-
log.debug('No config file found.')
112+
applied_args = []
113+
for arg in sys.argv[1:]:
114+
if not arg.startswith('-') : continue
115+
base_arg = arg.split('=')[0]
116+
for ctrl_key, ctrl in vars(controls).items():
117+
if base_arg in getattr(ctrl, 'args', []):
118+
dest = get_canonical_key(ctrl_key) or ctrl_key if ctrl_key.startswith('legacy_') else ctrl_key
119+
parsed_val = getattr(parsed_args, dest, None)
120+
if parsed_val is not None:
121+
setattr(cli.config, dest, parsed_val)
122+
applied_args.append(arg)
123+
break
124+
log.debug(f'Args parsed! {log.colors.bg}{len(applied_args)} args applied {applied_args}', cli)
84125

85126
# Apply parsers/default_vals
86127
for ctrl_key, ctrl in vars(controls).items():
87-
val = getattr(cli.config, ctrl_key, '')
88-
if not val and hasattr(ctrl, 'default_val'):
89-
setattr(cli.config, ctrl_key, ctrl.default_val)
90-
if getattr(ctrl, 'parser', '') == 'csv':
91-
setattr(cli.config, ctrl_key, data.csv.parse(val))
128+
if not hasattr(cli.config, ctrl_key):
129+
setattr(cli.config, ctrl_key, ctrl.default_val if hasattr(ctrl, 'default_val') else None)
130+
config_val = getattr(cli.config, ctrl_key)
131+
if getattr(ctrl, 'parser', '') == 'csv' and config_val is not None:
132+
setattr(cli.config, ctrl_key, data.csv.parse(config_val))
92133
log.debug('All cli.config vals set!', cli)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def removeprefix(str: str, prefix: str) -> str:
2+
return str[len(prefix):] if str.startswith(prefix) else str

translate-messages/src/translate_messages/assets/data/messages.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@
3333
"warn_NO_PROJECT_ROOT_FOUND_IN": { "message": "No project root detected in" },
3434
"warn_SPECIFIED_CONFIG": { "message": "Specified config" },
3535
"warn_NOT_FOUND": { "message": "Not found" },
36-
"warn_OPTION": { "message": "Option" },
36+
"warn_CONFIG_FILE_KEY": { "message": "Config file key" },
37+
"warn_CLI_OPTION": { "message": "CLI option" },
38+
"warn_HAS_BEEN_REPLACED_BY": { "message": "has been replaced by" },
39+
"warn_AND_WILL_BE_REMOVED": { "message": "and will be removed" },
3740
"warn_NO_LONGER_HAS_ANY_EFFECT": { "message": "no longer has any effect" },
3841
"err_UNRECOGNIZED_ARGS": { "message": "Unrecognized argument(s)" },
3942
"err_PARSE_FAILED": { "message": "Failed to parse" },

translate-messages/src/translate_messages/lib/log.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import os, sys
2+
from pathlib import Path
23
from types import SimpleNamespace as sn
34
if sys.platform == 'win32' : import colorama ; colorama.init() # enable ANSI color support
45

6+
from . import data as datalib, pkg, settings
7+
58
try : terminal_width = os.get_terminal_size()[0]
69
except OSError : terminal_width = 80
710

11+
current_ver = datalib.json.read(Path(__file__).parent.parent / 'assets/data/package_data.json')['version']
12+
next_maj_ver = pkg.get_next_maj_ver(current_ver)
13+
_warned_keys = { 'cli': set(), 'config': set() }
14+
815
colors = sn(
916
nc='\x1b[0m', # no color
1017
br='\x1b[1;91m', # bright red
@@ -33,6 +40,28 @@ def version(cli):
3340
print(f'\n{colors.by}{cli.name}\n{colors.bw}{cli.msgs.log_VERSION.lower()}: {cli.version}{colors.nc}')
3441
def warn(msg, *args, **kwargs) : print(f'\n{colors.bo}WARNING: {msg.format(*args, **kwargs)}{colors.nc}')
3542

43+
def warn_legacy_option(cli, flag: str, source: str) -> None:
44+
warned_set = _warned_keys[source]
45+
if flag in warned_set : return
46+
canonical_key = settings.get_canonical_key(flag)
47+
msg = f"{ cli.msgs.warn_CONFIG_FILE_KEY if source == 'config' else cli.msgs.warn_CLI_OPTION } {flag!r}"
48+
if canonical_key:
49+
canonical_ctrl = getattr(settings.controls, canonical_key, None)
50+
if source == 'cli' and canonical_ctrl:
51+
flags = [arg for arg in getattr(canonical_ctrl, 'args', []) if arg.startswith('-')]
52+
if flag.startswith('-') and len(flag) == 2: # show short flag replacement
53+
display_key = min(flags, key=len) if flags else f"--{canonical_key.replace('_', '-')}"
54+
else: # show long flag replacement
55+
long_flags = [flag for flag in flags if flag.startswith('--')]
56+
display_key = long_flags[0] if long_flags else f"--{canonical_key.replace('_', '-')}"
57+
else:
58+
display_key = canonical_key
59+
msg += f' {cli.msgs.warn_HAS_BEEN_REPLACED_BY} {display_key!r}'
60+
else:
61+
msg += f' {cli.msgs.warn_NO_LONGER_HAS_ANY_EFFECT}'
62+
msg += f' {cli.msgs.warn_AND_WILL_BE_REMOVED} @ v{next_maj_ver}'
63+
warn(msg) ; warned_set.add(flag)
64+
3665
def cmd_docs_url_exit(cli, msg='', cmd='help'):
3766
if msg : error(msg)
3867
help_cmd(cli) if cmd == 'help' else init_cmd(cli)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import re
2+
3+
def get_next_maj_ver(version: str) -> str: # e.g. '1.2.3' -> '2.0.0'
4+
major = re.match(r'^(\d+)\..*', version)
5+
if not major : raise ValueError(f'Invalid version string: {version!r}')
6+
return f'{ int(major.group(1)) +1 }.0.0'

0 commit comments

Comments
 (0)