diff --git a/radicale/__main__.py b/radicale/__main__.py index f7aa48d..67da92b 100644 --- a/radicale/__main__.py +++ b/radicale/__main__.py @@ -75,7 +75,14 @@ def run() -> None: continue assert ":" not in section # check field separator assert "-" not in section and "_" not in section # not implemented - group = parser.add_argument_group(section) + group_description = None + if section_data.get("_allow_extra"): + group_description = "additional options allowed" + if section == "headers": + group_description += " (e.g. --headers-Pragma=no-cache)" + elif "type" in section_data: + group_description = "backend specific options omitted" + group = parser.add_argument_group(section, group_description) for option, data in section_data.items(): if option.startswith("_"): continue @@ -106,7 +113,32 @@ def run() -> None: del kwargs["type"] group.add_argument(*args, **kwargs) - args_ns = parser.parse_args() + args_ns, remaining_args = parser.parse_known_args() + unrecognized_args = [] + while remaining_args: + arg = remaining_args.pop(0) + for section, data in config.DEFAULT_CONFIG_SCHEMA.items(): + if "type" not in data and not data.get("_allow_extra"): + continue + prefix = "--%s-" % section + if arg.startswith(prefix): + arg = arg[len(prefix):] + break + else: + unrecognized_args.append(arg) + continue + value = "" + if "=" in arg: + arg, value = arg.split("=", maxsplit=1) + elif remaining_args and not remaining_args[0].startswith("-"): + value = remaining_args.pop(0) + option = arg + if not data.get("_allow_extra"): # preserve dash in HTTP header names + option = option.replace("-", "_") + vars(args_ns)["c:%s:%s" % (section, option)] = value + if unrecognized_args: + parser.error("unrecognized arguments: %s" % + " ".join(unrecognized_args)) # Preliminary configure logging with contextlib.suppress(ValueError): diff --git a/radicale/tests/test_server.py b/radicale/tests/test_server.py index 956bc85..d2b0243 100644 --- a/radicale/tests/test_server.py +++ b/radicale/tests/test_server.py @@ -188,15 +188,17 @@ class TestBaseServerRequests(BaseTest): self.get("/", check=302) def test_command_line_interface(self) -> None: + self.configuration.update({"headers": {"Test-Server": "test"}}) config_args = [] - for section, values in config.DEFAULT_CONFIG_SCHEMA.items(): + for section in self.configuration.sections(): if section.startswith("_"): continue - for option, data in values.items(): + for option in self.configuration.options(section): if option.startswith("_"): continue long_name = "--%s-%s" % (section, option.replace("_", "-")) - if data["type"] == bool: + if config.DEFAULT_CONFIG_SCHEMA.get( + section, {}).get(option, {}).get("type") == bool: if not cast(bool, self.configuration.get(section, option)): long_name = "--no%s" % long_name[1:] config_args.append(long_name) @@ -205,11 +207,17 @@ class TestBaseServerRequests(BaseTest): raw_value = self.configuration.get_raw(section, option) assert isinstance(raw_value, str) config_args.append(raw_value) + config_args.append("--headers-Test-Header=test") p = subprocess.Popen( [sys.executable, "-m", "radicale"] + config_args, env={**os.environ, "PYTHONPATH": os.pathsep.join(sys.path)}) try: - self.get("/", is_alive_fn=lambda: p.poll() is None, check=302) + status, headers, _ = self.request( + "GET", "/", is_alive_fn=lambda: p.poll() is None) + self._check_status(status, 302) + for key in self.configuration.options("headers"): + assert headers.get(key) == self.configuration.get( + "headers", key) finally: p.terminate() p.wait()