diff --git a/fastcore/script.py b/fastcore/script.py index 67b075ae..fc221f7d 100644 --- a/fastcore/script.py +++ b/fastcore/script.py @@ -51,7 +51,7 @@ def main(msg:Annotated[str, "The message"], ## Short flags -A capital letter in a parameter name declares a short flag: the capitalized letter becomes the short spelling and the lowercased name the long one, so `Resume:int=None` gets both `-r` and `--resume`. The capital can be any letter (`sUggest:str=None` gives `-u/--suggest`), only the first capital counts, and names without capitals get a long flag only. Since the flags are lowercased, the parameter's actual name keeps its capital -- so a Python caller writes `main(Resume=3)`, which usefully advertises that it's invoking a CLI entry point. Positional (default-less) parameters have no flags, so capitals there are left alone. +A capital letter in a parameter name declares a short flag: the capitalized letter becomes the short spelling and the lowercased name the long one, so `Resume:int=None` gets both `-r` and `--resume`. The capital can be any letter (`sUggest:str=None` gives `-u/--suggest`), only the first capital counts, and names without capitals get a long flag only. Underscores in optional parameter names appear as hyphens (`cache_dir` becomes `--cache-dir`), while the Python argument keeps its underscore. Since the flags are lowercased, the parameter's actual name keeps its capital -- so a Python caller writes `main(Resume=3)`, which usefully advertises that it's invoking a CLI entry point. Positional (default-less) parameters have no flags, so capitals there are left alone. ## Positional params @@ -141,7 +141,7 @@ def _arg_kw(k, anno, doc, default, extra, mv=None): else: action,d = 'store_true',False if action=='version': if 'version' not in extra and d is not inspect.Parameter.empty: extra['version'] = d - return f'--{k}', {'help':doc or '', **extra} + return f'--{k.replace("_", "-")}', {'help':doc or '', **extra} kw = {} if action and 'action' not in extra: kw['action'] = action if anno is not None: @@ -154,6 +154,7 @@ def _arg_kw(k, anno, doc, default, extra, mv=None): kw['help'] = (doc or '') + (f" (default: {dshow})" if 'default' in kw else '') if negated: kw['dest'] = k name = f'no-{k}' if negated else k + if opt: name = name.replace('_', '-') short = first(c for c in k if c.isupper()) if opt else None if short is None: return f"{'--' if opt else ''}{name}", {**kw, **extra} kw['dest'] = k # flags are lowercased, so argparse's derived dest would drop the capital diff --git a/nbs/06_script.ipynb b/nbs/06_script.ipynb index 07522680..c896ec15 100644 --- a/nbs/06_script.ipynb +++ b/nbs/06_script.ipynb @@ -140,7 +140,7 @@ "#| export\n", "## Short flags\n", "\n", - "A capital letter in a parameter name declares a short flag: the capitalized letter becomes the short spelling and the lowercased name the long one, so `Resume:int=None` gets both `-r` and `--resume`. The capital can be any letter (`sUggest:str=None` gives `-u/--suggest`), only the first capital counts, and names without capitals get a long flag only. Since the flags are lowercased, the parameter's actual name keeps its capital -- so a Python caller writes `main(Resume=3)`, which usefully advertises that it's invoking a CLI entry point. Positional (default-less) parameters have no flags, so capitals there are left alone." + "A capital letter in a parameter name declares a short flag: the capitalized letter becomes the short spelling and the lowercased name the long one, so `Resume:int=None` gets both `-r` and `--resume`. The capital can be any letter (`sUggest:str=None` gives `-u/--suggest`), only the first capital counts, and names without capitals get a long flag only. Underscores in optional parameter names appear as hyphens (`cache_dir` becomes `--cache-dir`), while the Python argument keeps its underscore. Since the flags are lowercased, the parameter's actual name keeps its capital -- so a Python caller writes `main(Resume=3)`, which usefully advertises that it's invoking a CLI entry point. Positional (default-less) parameters have no flags, so capitals there are left alone." ] }, { @@ -295,7 +295,7 @@ " else: action,d = 'store_true',False\n", " if action=='version':\n", " if 'version' not in extra and d is not inspect.Parameter.empty: extra['version'] = d\n", - " return f'--{k}', {'help':doc or '', **extra}\n", + " return f'--{k.replace(\"_\", \"-\")}', {'help':doc or '', **extra}\n", " kw = {}\n", " if action and 'action' not in extra: kw['action'] = action\n", " if anno is not None:\n", @@ -308,6 +308,7 @@ " kw['help'] = (doc or '') + (f\" (default: {dshow})\" if 'default' in kw else '')\n", " if negated: kw['dest'] = k\n", " name = f'no-{k}' if negated else k\n", + " if opt: name = name.replace('_', '-')\n", " short = first(c for c in k if c.isupper()) if opt else None\n", " if short is None: return f\"{'--' if opt else ''}{name}\", {**kw, **extra}\n", " kw['dest'] = k # flags are lowercased, so argparse's derived dest would drop the capital\n", @@ -371,9 +372,9 @@ "metadata": {}, "outputs": [], "source": [ - "test_eq(_arg_kw('a', int, 'help', 1, {}), ('--a', dict(type=int, default=1, help='help (default: 1)')))\n", + "test_eq(_arg_kw('some_flag', int, 'help', 1, {}), ('--some-flag', dict(type=int, default=1, help='help (default: 1)')))\n", "test_eq(_arg_kw('a', int, 'help', inspect.Parameter.empty, {}), ('a', dict(type=int, help='help')))\n", - "test_eq(_arg_kw('up', bool, 'upper?', True, {}), ('--no-up', dict(action='store_false', default=True, help='upper? (default: True)', dest='up')))\n", + "test_eq(_arg_kw('some_flag', bool, 'enabled?', True, {}), ('--no-some-flag', dict(action='store_false', default=True, help='enabled? (default: True)', dest='some_flag')))\n", "test_eq(_arg_kw('p', str, 'path', inspect.Parameter.empty, dict(opt=False, nargs='?', default='-')),\n", " ('p', dict(type=str, default='-', help='path (default: -)', nargs='?')))\n", "test_eq(_arg_kw('a', int, 'help', 1, {}, 'int'), ('--a', dict(type=int, default=1, help='help (default: 1)', metavar='(int)')))\n",