fix(parser,selection): correct None handling and Path type checks

- Ensure required argument validation treats only `None` as missing
  instead of falsy values (e.g., 0, False, empty string)
- Guard SelectionAction default resolution against `None` results
- Replace direct `type == Path` checks with `issubclass(..., Path)`
  for proper handling of Path subclasses across suggestions logic

Improves correctness in argument parsing and selection defaults,
aligning with Falyx’s explicit and predictable behavior goals.
This commit is contained in:
2026-03-29 13:21:28 -04:00
parent 79f7bd6a60
commit 8ce0ffa18e
4 changed files with 7 additions and 7 deletions

View File

@@ -344,7 +344,7 @@ class SelectionAction(BaseAction):
selection = [ selection = [
key key
for key, sel in self.selections.items() for key, sel in self.selections.items()
if sel.value == maybe_result if sel.value == maybe_result and maybe_result is not None
] ]
if selection: if selection:
effective_default = selection[0] effective_default = selection[0]

View File

@@ -1155,7 +1155,7 @@ class CommandArgumentParser:
for spec in self._arguments: for spec in self._arguments:
if spec.dest == "help" or spec.dest == "tldr": if spec.dest == "help" or spec.dest == "tldr":
continue continue
if spec.required and not result.get(spec.dest): if spec.required and result.get(spec.dest) is None:
help_text = f" help: {spec.help}" if spec.help else "" help_text = f" help: {spec.help}" if spec.help else ""
if ( if (
spec.action == ArgumentAction.ACTION spec.action == ArgumentAction.ACTION
@@ -1316,7 +1316,7 @@ class CommandArgumentParser:
for suggestion in arg.suggestions for suggestion in arg.suggestions
if suggestion_filter(suggestion) if suggestion_filter(suggestion)
] ]
if arg.type is Path: if issubclass(arg.type, Path):
return self._suggest_paths(prefix if not cursor_at_end_of_token else ".") return self._suggest_paths(prefix if not cursor_at_end_of_token else ".")
return [] return []
@@ -1391,7 +1391,7 @@ class CommandArgumentParser:
): ):
return [] return []
return sorted(next_non_consumed_positional_arg.suggestions) return sorted(next_non_consumed_positional_arg.suggestions)
if next_non_consumed_positional_arg.type == Path: if issubclass(next_non_consumed_positional_arg.type, Path):
if cursor_at_end_of_token: if cursor_at_end_of_token:
return self._suggest_paths(".") return self._suggest_paths(".")
else: else:
@@ -1536,7 +1536,7 @@ class CommandArgumentParser:
if suggestion.startswith(last) if suggestion.startswith(last)
) )
) )
elif arg.type == Path and not cursor_at_end_of_token: elif issubclass(arg.type, Path) and not cursor_at_end_of_token:
suggestions.extend(self._suggest_paths(last)) suggestions.extend(self._suggest_paths(last))
elif last_keyword_state_in_args and not last_keyword_state_in_args.consumed: elif last_keyword_state_in_args and not last_keyword_state_in_args.consumed:
suggestions.extend( suggestions.extend(

View File

@@ -1 +1 @@
__version__ = "0.1.86" __version__ = "0.1.87"

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "falyx" name = "falyx"
version = "0.1.86" version = "0.1.87"
description = "Reliable and introspectable async CLI action framework." description = "Reliable and introspectable async CLI action framework."
authors = ["Roland Thomas Jr <roland@rtj.dev>"] authors = ["Roland Thomas Jr <roland@rtj.dev>"]
license = "MIT" license = "MIT"