From 5cd4662367e7b931dbc79c9655e157f10fc7f426 Mon Sep 17 00:00:00 2001 From: Aradhya Tripathi <67282231+Aradhya-Tripathi@users.noreply.github.com> Date: Sun, 28 May 2023 18:27:53 +0530 Subject: [PATCH 1/4] feat: Added support for hidden inputs --- trogon/introspect.py | 4 +++ trogon/run_command.py | 10 ++++-- trogon/trogon.py | 8 +++-- trogon/widgets/parameter_controls.py | 47 +++++++++++++++++++++++----- 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/trogon/introspect.py b/trogon/introspect.py index b0855ed..652b604 100644 --- a/trogon/introspect.py +++ b/trogon/introspect.py @@ -41,6 +41,8 @@ class OptionSchema: required: bool = False is_flag: bool = False is_boolean_flag: bool = False + hidden: bool = (False,) + hide_input: bool = (False,) flag_value: Any = "" opts: list = field(default_factory=list) counting: bool = False @@ -134,6 +136,8 @@ def process_command( type=param.type, is_flag=param.is_flag, is_boolean_flag=param.is_bool_flag, + hidden=param.hidden, + hide_input=param.hide_input, flag_value=param.flag_value, counting=param.count, opts=param.opts, diff --git a/trogon/run_command.py b/trogon/run_command.py index 0cf6cc0..d871917 100644 --- a/trogon/run_command.py +++ b/trogon/run_command.py @@ -15,7 +15,7 @@ ArgumentSchema, MultiValueParamData, ) -from trogon.widgets.parameter_controls import ValueNotSupplied +from trogon.widgets.parameter_controls import ValueNotSupplied, HiddenValueInput @dataclass @@ -231,7 +231,13 @@ def to_cli_string(self, include_root_command: bool = False) -> Text: text_renderables = [] for arg in args: text_renderables.append( - Text(shlex.quote(str(arg))) + Text( + shlex.quote( + arg.display + if isinstance(arg, HiddenValueInput) + else str(arg) + ) + ) if arg != ValueNotSupplied() else Text("???", style="bold black on red") ) diff --git a/trogon/trogon.py b/trogon/trogon.py index c97d0b1..65148f6 100644 --- a/trogon/trogon.py +++ b/trogon/trogon.py @@ -34,6 +34,7 @@ from trogon.widgets.command_tree import CommandTree from trogon.widgets.form import CommandForm from trogon.widgets.multiple_choice import NonFocusableVerticalScroll +from trogon.widgets.parameter_controls import HiddenValueInput try: from importlib import metadata # type: ignore @@ -251,9 +252,12 @@ def run( console = Console() if self.post_run_command and self.execute_on_exit: console.print( - f"Running [b cyan]{self.app_name} {' '.join(shlex.quote(s) for s in self.post_run_command)}[/]" + f"Running [b cyan]{self.app_name} {' '.join(shlex.quote(s.display if isinstance(s, HiddenValueInput) else s) for s in self.post_run_command)}[/]" ) - + self.post_run_command = [ + cmd.value if isinstance(cmd, HiddenValueInput) else cmd + for cmd in self.post_run_command + ] split_app_name = shlex.split(self.app_name) program_name = shlex.split(self.app_name)[0] arguments = [*split_app_name, *self.post_run_command] diff --git a/trogon/widgets/parameter_controls.py b/trogon/widgets/parameter_controls.py index 3ea3b13..4db997c 100644 --- a/trogon/widgets/parameter_controls.py +++ b/trogon/widgets/parameter_controls.py @@ -47,6 +47,12 @@ def __bool__(self): return False +class HiddenValueInput: + def __init__(self, value: str) -> None: + self.value = value + self.display = len(value) * "*" + + class ParameterControls(Widget): def __init__( self, @@ -84,9 +90,7 @@ def apply_filter(self, filter_query: str) -> bool: name_contains_query = any( filter_query in name.casefold() for name in self.schema.name ) - help_contains_query = ( - filter_query in help_text.casefold() - ) + help_contains_query = filter_query in help_text.casefold() should_be_visible = name_contains_query or help_contains_query self.display = should_be_visible @@ -115,10 +119,17 @@ def compose(self) -> ComposeResult: help_text = getattr(schema, "help", "") or "" multiple = schema.multiple is_option = isinstance(schema, OptionSchema) + hidden = getattr(schema, "hidden", False) + hide_input = getattr(schema, "hide_input", False) nargs = schema.nargs label = self._make_command_form_control_label( - name, argument_type, is_option, schema.required, multiple=multiple + name=name, + type=argument_type, + is_option=is_option, + hidden=hidden, + is_required=schema.required, + multiple=multiple, ) first_focus_control: Widget | None = ( None # The widget that will be focused when the form is focused. @@ -139,6 +150,7 @@ def compose(self) -> ComposeResult: multiple_choice_widget = control_method( default=default, label=label, + hide_input=hide_input, multiple=multiple, schema=schema, control_id=schema.key, @@ -204,8 +216,15 @@ def make_widget_group(self) -> Iterable[Widget]: multiple = schema.multiple required = schema.required is_option = isinstance(schema, OptionSchema) + hidden = getattr(schema, "hidden", False) + hide_input = getattr(schema, "hide_input", False) label = self._make_command_form_control_label( - name, parameter_type, is_option, required, multiple + name=name, + type=parameter_type, + is_option=is_option, + hidden=hidden, + is_required=required, + multiple=multiple, ) # Get the types of the parameter. We can map these types on to widgets that will be rendered. @@ -220,7 +239,12 @@ def make_widget_group(self) -> Iterable[Widget]: for _type in parameter_types: control_method = self.get_control_method(_type) control_widgets = control_method( - default, label, multiple, schema, schema.key + default=default, + label=label, + multiple=multiple, + hide_input=hide_input, + schema=schema, + control_id=schema.key, ) yield from control_widgets @@ -256,6 +280,8 @@ def _get_form_control_value(control: ControlWidgetType) -> Any: return ValueNotSupplied() return control.value elif isinstance(control, Input): + if control.password and control.value != "": + return HiddenValueInput(control.value) return ( ValueNotSupplied() if control.value == "" else control.value ) # TODO: We should only return "" when user selects a checkbox - needs custom widget. @@ -339,10 +365,12 @@ def make_text_control( label: Text | None, multiple: bool, schema: OptionSchema | ArgumentSchema, + hide_input: bool, control_id: str, ) -> Widget: control = Input( classes=f"command-form-input {control_id}", + password=hide_input, ) yield control return control @@ -353,6 +381,7 @@ def make_checkbox_control( label: Text | None, multiple: bool, schema: OptionSchema | ArgumentSchema, + hide_input: bool, control_id: str, ) -> Widget: if default.values: @@ -375,6 +404,7 @@ def make_choice_control( label: Text | None, multiple: bool, schema: OptionSchema | ArgumentSchema, + hide_input: bool, control_id: str, choices: list[str], ) -> Widget: @@ -403,17 +433,18 @@ def _make_command_form_control_label( name: str | list[str], type: click.ParamType, is_option: bool, + hidden: bool, is_required: bool, multiple: bool, ) -> Text: if isinstance(name, str): text = Text.from_markup( - f"{name}[dim]{' multiple' if multiple else ''} {type.name}[/] {' [b red]*[/]required' if is_required else ''}" + f"{name}[dim]{' multiple' if multiple else ''} {type.name}[/] {' [b red]*[/]required' if is_required else ''} {'[dim] (hidden)' if hidden else ''}" ) else: names = Text(" / ", style="dim").join([Text(n) for n in name]) text = Text.from_markup( - f"{names}[dim]{' multiple' if multiple else ''} {type.name}[/] {' [b red]*[/]required' if is_required else ''}" + f"{names}[dim]{' multiple' if multiple else ''} {type.name}[/] {' [b red]*[/]required' if is_required else ''} {'[dim] (hidden)' if hidden else ''}" ) if isinstance(type, (click.IntRange, click.FloatRange)): From 079c2513dd2cca152d4c1424e884c3aab12cb629 Mon Sep 17 00:00:00 2001 From: Aradhya Tripathi <67282231+Aradhya-Tripathi@users.noreply.github.com> Date: Sun, 28 May 2023 18:38:13 +0530 Subject: [PATCH 2/4] fix: removed tuple from optionschema --- trogon/introspect.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trogon/introspect.py b/trogon/introspect.py index 652b604..dcc49fe 100644 --- a/trogon/introspect.py +++ b/trogon/introspect.py @@ -41,8 +41,8 @@ class OptionSchema: required: bool = False is_flag: bool = False is_boolean_flag: bool = False - hidden: bool = (False,) - hide_input: bool = (False,) + hidden: bool = False + hide_input: bool = False flag_value: Any = "" opts: list = field(default_factory=list) counting: bool = False From bcacb3ce97bd169406a6331910c28dac9e5a45d3 Mon Sep 17 00:00:00 2001 From: Aradhya Tripathi <67282231+Aradhya-Tripathi@users.noreply.github.com> Date: Mon, 29 May 2023 16:09:13 +0530 Subject: [PATCH 3/4] refactor: removed single quotes from hidden input --- trogon/run_command.py | 8 +++----- trogon/trogon.py | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/trogon/run_command.py b/trogon/run_command.py index d871917..75e6157 100644 --- a/trogon/run_command.py +++ b/trogon/run_command.py @@ -232,11 +232,9 @@ def to_cli_string(self, include_root_command: bool = False) -> Text: for arg in args: text_renderables.append( Text( - shlex.quote( - arg.display - if isinstance(arg, HiddenValueInput) - else str(arg) - ) + str(arg.display) + if isinstance(arg, HiddenValueInput) + else shlex.quote(str(arg)) ) if arg != ValueNotSupplied() else Text("???", style="bold black on red") diff --git a/trogon/trogon.py b/trogon/trogon.py index 65148f6..e508d36 100644 --- a/trogon/trogon.py +++ b/trogon/trogon.py @@ -252,7 +252,7 @@ def run( console = Console() if self.post_run_command and self.execute_on_exit: console.print( - f"Running [b cyan]{self.app_name} {' '.join(shlex.quote(s.display if isinstance(s, HiddenValueInput) else s) for s in self.post_run_command)}[/]" + f"Running [b cyan]{self.app_name} {' '.join(s.display if isinstance(s, HiddenValueInput) else shlex.quote(s) for s in self.post_run_command)}[/]" ) self.post_run_command = [ cmd.value if isinstance(cmd, HiddenValueInput) else cmd From 84d34b45f0ad2cd5d705d1d856ededfeac311d2d Mon Sep 17 00:00:00 2001 From: Aradhya Tripathi <67282231+Aradhya-Tripathi@users.noreply.github.com> Date: Wed, 14 Jun 2023 14:03:30 +0530 Subject: [PATCH 4/4] feat: Skipping hide_input flag options --- trogon/run_command.py | 8 ++------ trogon/trogon.py | 8 ++------ trogon/widgets/form.py | 5 +++++ trogon/widgets/parameter_controls.py | 20 +------------------- 4 files changed, 10 insertions(+), 31 deletions(-) diff --git a/trogon/run_command.py b/trogon/run_command.py index 75e6157..0cf6cc0 100644 --- a/trogon/run_command.py +++ b/trogon/run_command.py @@ -15,7 +15,7 @@ ArgumentSchema, MultiValueParamData, ) -from trogon.widgets.parameter_controls import ValueNotSupplied, HiddenValueInput +from trogon.widgets.parameter_controls import ValueNotSupplied @dataclass @@ -231,11 +231,7 @@ def to_cli_string(self, include_root_command: bool = False) -> Text: text_renderables = [] for arg in args: text_renderables.append( - Text( - str(arg.display) - if isinstance(arg, HiddenValueInput) - else shlex.quote(str(arg)) - ) + Text(shlex.quote(str(arg))) if arg != ValueNotSupplied() else Text("???", style="bold black on red") ) diff --git a/trogon/trogon.py b/trogon/trogon.py index e508d36..c97d0b1 100644 --- a/trogon/trogon.py +++ b/trogon/trogon.py @@ -34,7 +34,6 @@ from trogon.widgets.command_tree import CommandTree from trogon.widgets.form import CommandForm from trogon.widgets.multiple_choice import NonFocusableVerticalScroll -from trogon.widgets.parameter_controls import HiddenValueInput try: from importlib import metadata # type: ignore @@ -252,12 +251,9 @@ def run( console = Console() if self.post_run_command and self.execute_on_exit: console.print( - f"Running [b cyan]{self.app_name} {' '.join(s.display if isinstance(s, HiddenValueInput) else shlex.quote(s) for s in self.post_run_command)}[/]" + f"Running [b cyan]{self.app_name} {' '.join(shlex.quote(s) for s in self.post_run_command)}[/]" ) - self.post_run_command = [ - cmd.value if isinstance(cmd, HiddenValueInput) else cmd - for cmd in self.post_run_command - ] + split_app_name = shlex.split(self.app_name) program_name = shlex.split(self.app_name)[0] arguments = [*split_app_name, *self.post_run_command] diff --git a/trogon/widgets/form.py b/trogon/widgets/form.py index b084c3e..364fc63 100644 --- a/trogon/widgets/form.py +++ b/trogon/widgets/form.py @@ -132,6 +132,9 @@ def compose(self) -> ComposeResult: if options: yield Label(f"Options", classes="command-form-heading") for option in options: + if option.hide_input: + continue + controls = ParameterControls(option, id=option.key) if self.first_control is None: self.first_control = controls @@ -172,6 +175,8 @@ def _form_changed(self) -> None: # For each of the options in the schema for this command, # lets grab the values the user has supplied for them in the form. for option in command.options: + if option.hide_input: + continue parameter_control = self.query_one(f"#{option.key}", ParameterControls) value = parameter_control.get_values() for v in value.values: diff --git a/trogon/widgets/parameter_controls.py b/trogon/widgets/parameter_controls.py index 4db997c..77b2f0d 100644 --- a/trogon/widgets/parameter_controls.py +++ b/trogon/widgets/parameter_controls.py @@ -47,12 +47,6 @@ def __bool__(self): return False -class HiddenValueInput: - def __init__(self, value: str) -> None: - self.value = value - self.display = len(value) * "*" - - class ParameterControls(Widget): def __init__( self, @@ -120,7 +114,6 @@ def compose(self) -> ComposeResult: multiple = schema.multiple is_option = isinstance(schema, OptionSchema) hidden = getattr(schema, "hidden", False) - hide_input = getattr(schema, "hide_input", False) nargs = schema.nargs label = self._make_command_form_control_label( @@ -150,7 +143,6 @@ def compose(self) -> ComposeResult: multiple_choice_widget = control_method( default=default, label=label, - hide_input=hide_input, multiple=multiple, schema=schema, control_id=schema.key, @@ -217,7 +209,6 @@ def make_widget_group(self) -> Iterable[Widget]: required = schema.required is_option = isinstance(schema, OptionSchema) hidden = getattr(schema, "hidden", False) - hide_input = getattr(schema, "hide_input", False) label = self._make_command_form_control_label( name=name, type=parameter_type, @@ -242,7 +233,6 @@ def make_widget_group(self) -> Iterable[Widget]: default=default, label=label, multiple=multiple, - hide_input=hide_input, schema=schema, control_id=schema.key, ) @@ -280,8 +270,6 @@ def _get_form_control_value(control: ControlWidgetType) -> Any: return ValueNotSupplied() return control.value elif isinstance(control, Input): - if control.password and control.value != "": - return HiddenValueInput(control.value) return ( ValueNotSupplied() if control.value == "" else control.value ) # TODO: We should only return "" when user selects a checkbox - needs custom widget. @@ -365,13 +353,9 @@ def make_text_control( label: Text | None, multiple: bool, schema: OptionSchema | ArgumentSchema, - hide_input: bool, control_id: str, ) -> Widget: - control = Input( - classes=f"command-form-input {control_id}", - password=hide_input, - ) + control = Input(classes=f"command-form-input {control_id}") yield control return control @@ -381,7 +365,6 @@ def make_checkbox_control( label: Text | None, multiple: bool, schema: OptionSchema | ArgumentSchema, - hide_input: bool, control_id: str, ) -> Widget: if default.values: @@ -404,7 +387,6 @@ def make_choice_control( label: Text | None, multiple: bool, schema: OptionSchema | ArgumentSchema, - hide_input: bool, control_id: str, choices: list[str], ) -> Widget: