diff --git a/tabcmd/execution/global_options.py b/tabcmd/execution/global_options.py index 1a24133c..46a277dc 100644 --- a/tabcmd/execution/global_options.py +++ b/tabcmd/execution/global_options.py @@ -111,11 +111,34 @@ def set_embedded_datasources_options(parser): # used in create extract. listed in delete-extract but makes no sense there +def _parse_yes_no(value: str) -> bool: + # tabcmd Classic accepts exactly `yes`/`y`/`no`/`n` (case-insensitive) on + # --encrypt — see workgroup/src/silos/tableau-server/applications/ + # app-tabcmd/src/com/tableausoftware/tabcmd/commands/CreateExtracts.java + # lines 79-101 in the monolith. tabcmd 2 keeps Classic's set intact (so + # ported scripts run) and adds `true`/`false`/`1`/`0` as a forgiving + # superset for users who type boolean-shaped values. + lowered = value.strip().lower() + if lowered in ("yes", "y", "true", "1"): + return True + if lowered in ("no", "n", "false", "0"): + return False + import argparse + + raise argparse.ArgumentTypeError("Expected yes/y/no/n/true/false/1/0 for --encrypt, got {!r}".format(value)) + + def set_encryption_option(parser): parser.add_argument( "--encrypt", dest="encrypt", - action="store_true", # set to true IF user passes in option --encrypt + # Classic parity: --encrypt yes / --encrypt no. Bare --encrypt still + # means True (default tabcmd 2 behavior). Omitted -> False. + nargs="?", + const=True, + default=False, + type=_parse_yes_no, + metavar="yes|y|no|n|true|false|1|0", help=_("createextracts.options.encrypt"), ) return parser diff --git a/tests/parsers/test_parser_create_extracts.py b/tests/parsers/test_parser_create_extracts.py index 94ca7a7e..505cee14 100644 --- a/tests/parsers/test_parser_create_extracts.py +++ b/tests/parsers/test_parser_create_extracts.py @@ -106,3 +106,62 @@ def test_create_extract_parser_missing_embedded_datasources(self): ] with self.assertRaises(SystemExit): args = self.parser_under_test.parse_args(mock_args) + + # --encrypt Classic set is yes|y|no|n (case-insensitive) per + # app-tabcmd/.../CreateExtracts.java:79-101; tabcmd 2 adds + # true|false|1|0 on top as a forgiving superset. Bare flag still True, + # omitted False. + def _base_args(self): + return [commandname, "--datasource", "ds", "--project", "p", "--parent-project-path", "pp"] + + def test_encrypt_omitted_is_false(self): + args = self.parser_under_test.parse_args(self._base_args()) + assert args.encrypt is False, args + + def test_encrypt_bare_flag_is_true(self): + args = self.parser_under_test.parse_args(self._base_args() + ["--encrypt"]) + assert args.encrypt is True, args + + def test_encrypt_yes_is_true(self): + args = self.parser_under_test.parse_args(self._base_args() + ["--encrypt", "yes"]) + assert args.encrypt is True, args + + def test_encrypt_no_is_false(self): + args = self.parser_under_test.parse_args(self._base_args() + ["--encrypt", "no"]) + assert args.encrypt is False, args + + def test_encrypt_y_is_true(self): + # Classic shortcut; ported scripts must keep working. + args = self.parser_under_test.parse_args(self._base_args() + ["--encrypt", "y"]) + assert args.encrypt is True, args + + def test_encrypt_n_is_false(self): + # Classic shortcut; ported scripts must keep working. + args = self.parser_under_test.parse_args(self._base_args() + ["--encrypt", "n"]) + assert args.encrypt is False, args + + def test_encrypt_true_is_true(self): + args = self.parser_under_test.parse_args(self._base_args() + ["--encrypt", "true"]) + assert args.encrypt is True, args + + def test_encrypt_false_is_false(self): + args = self.parser_under_test.parse_args(self._base_args() + ["--encrypt", "false"]) + assert args.encrypt is False, args + + def test_encrypt_1_is_true(self): + # Numeric alias in tabcmd 2's forgiving superset. + args = self.parser_under_test.parse_args(self._base_args() + ["--encrypt", "1"]) + assert args.encrypt is True, args + + def test_encrypt_0_is_false(self): + # Numeric alias in tabcmd 2's forgiving superset. + args = self.parser_under_test.parse_args(self._base_args() + ["--encrypt", "0"]) + assert args.encrypt is False, args + + def test_encrypt_case_insensitive(self): + args = self.parser_under_test.parse_args(self._base_args() + ["--encrypt", "YES"]) + assert args.encrypt is True, args + + def test_encrypt_bad_value_rejected(self): + with self.assertRaises(SystemExit): + self.parser_under_test.parse_args(self._base_args() + ["--encrypt", "maybe"])