add multiline-hidden prompt for both ui and cli
This commit is contained in:
@@ -364,11 +364,13 @@ in
|
|||||||
- hidden: A hidden text (e.g. password)
|
- hidden: A hidden text (e.g. password)
|
||||||
- line: A single line of text
|
- line: A single line of text
|
||||||
- multiline: A multiline text
|
- multiline: A multiline text
|
||||||
|
- multiline-hidden: A multiline text
|
||||||
'';
|
'';
|
||||||
type = enum [
|
type = enum [
|
||||||
"hidden"
|
"hidden"
|
||||||
"line"
|
"line"
|
||||||
"multiline"
|
"multiline"
|
||||||
|
"multiline-hidden"
|
||||||
];
|
];
|
||||||
default = "line";
|
default = "line";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import enum
|
import enum
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
import termios
|
||||||
|
import tty
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -15,6 +17,7 @@ class PromptType(enum.Enum):
|
|||||||
LINE = "line"
|
LINE = "line"
|
||||||
HIDDEN = "hidden"
|
HIDDEN = "hidden"
|
||||||
MULTILINE = "multiline"
|
MULTILINE = "multiline"
|
||||||
|
MULTILINE_HIDDEN = "multiline-hidden"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -36,6 +39,63 @@ class Prompt:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_multiline_hidden_input() -> str:
|
||||||
|
"""
|
||||||
|
Get multiline input from the user without echoing the input.
|
||||||
|
This function allows the user to enter multiple lines of text,
|
||||||
|
and it will return the concatenated string of all lines entered.
|
||||||
|
The user can finish the input by pressing Ctrl-D (EOF).
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Save terminal settings
|
||||||
|
fd = sys.stdin.fileno()
|
||||||
|
old_settings = termios.tcgetattr(fd)
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
current_line: list[str] = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Change terminal settings - disable echo
|
||||||
|
tty.setraw(fd)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
char = sys.stdin.read(1)
|
||||||
|
|
||||||
|
# Check for Ctrl-D (ASCII value 4 or EOF)
|
||||||
|
if not char or ord(char) == 4:
|
||||||
|
# Add last line if not empty
|
||||||
|
if current_line:
|
||||||
|
lines.append("".join(current_line))
|
||||||
|
break
|
||||||
|
|
||||||
|
# Check for Ctrl-C (KeyboardInterrupt)
|
||||||
|
if ord(char) == 3:
|
||||||
|
raise KeyboardInterrupt
|
||||||
|
|
||||||
|
# Handle Enter key
|
||||||
|
if char == "\r" or char == "\n":
|
||||||
|
lines.append("".join(current_line))
|
||||||
|
current_line = []
|
||||||
|
# Print newline for visual feedback
|
||||||
|
sys.stdout.write("\r\n")
|
||||||
|
sys.stdout.flush()
|
||||||
|
# Handle backspace
|
||||||
|
elif ord(char) == 127 or ord(char) == 8:
|
||||||
|
if current_line:
|
||||||
|
current_line.pop()
|
||||||
|
# Regular character
|
||||||
|
else:
|
||||||
|
current_line.append(char)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Restore terminal settings
|
||||||
|
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||||
|
# Print a final newline for clean display
|
||||||
|
print()
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
def ask(
|
def ask(
|
||||||
ident: str,
|
ident: str,
|
||||||
input_type: PromptType,
|
input_type: PromptType,
|
||||||
@@ -53,6 +113,9 @@ def ask(
|
|||||||
case PromptType.MULTILINE:
|
case PromptType.MULTILINE:
|
||||||
print(f"{text} (Finish with Ctrl-D): ")
|
print(f"{text} (Finish with Ctrl-D): ")
|
||||||
result = sys.stdin.read()
|
result = sys.stdin.read()
|
||||||
|
case PromptType.MULTILINE_HIDDEN:
|
||||||
|
print("Enter multiple lines (press Ctrl-D to finish or Ctrl-C to cancel):")
|
||||||
|
result = get_multiline_hidden_input()
|
||||||
case PromptType.HIDDEN:
|
case PromptType.HIDDEN:
|
||||||
result = getpass(f"{text} (hidden): ")
|
result = getpass(f"{text} (hidden): ")
|
||||||
|
|
||||||
|
|||||||
@@ -33,19 +33,19 @@ def set_var(machine: str | Machine, var: str | Var, value: bytes, flake: Flake)
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def set_via_stdin(machine: str, var_id: str, flake: Flake) -> None:
|
def set_via_stdin(machine_name: str, var_id: str, flake: Flake) -> None:
|
||||||
var = get_var(str(flake.path), machine, var_id)
|
machine = Machine(name=machine_name, flake=flake)
|
||||||
|
var = get_var(str(flake.path), machine_name, var_id)
|
||||||
if sys.stdin.isatty():
|
if sys.stdin.isatty():
|
||||||
new_value = ask(
|
new_value = ask(
|
||||||
var.id,
|
var.id,
|
||||||
PromptType.HIDDEN,
|
PromptType.MULTILINE_HIDDEN,
|
||||||
None,
|
None,
|
||||||
).encode("utf-8")
|
).encode("utf-8")
|
||||||
else:
|
else:
|
||||||
new_value = sys.stdin.buffer.read()
|
new_value = sys.stdin.buffer.read()
|
||||||
|
|
||||||
_machine = Machine(name=machine, flake=flake)
|
set_var(machine, var, new_value, flake)
|
||||||
set_var(_machine, var, new_value, flake)
|
|
||||||
|
|
||||||
|
|
||||||
def _set_command(args: argparse.Namespace) -> None:
|
def _set_command(args: argparse.Namespace) -> None:
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { callApi } from "@/src/api";
|
import { callApi } from "@/src/api";
|
||||||
import {
|
import {
|
||||||
createForm,
|
createForm,
|
||||||
|
FieldValues,
|
||||||
SubmitHandler,
|
SubmitHandler,
|
||||||
validate,
|
validate,
|
||||||
FieldValues,
|
|
||||||
} from "@modular-forms/solid";
|
} from "@modular-forms/solid";
|
||||||
import { createQuery, useQueryClient } from "@tanstack/solid-query";
|
import { createQuery, useQueryClient } from "@tanstack/solid-query";
|
||||||
import { Typography } from "@/src/components/Typography";
|
import { Typography } from "@/src/components/Typography";
|
||||||
@@ -113,21 +113,43 @@ export const VarsStep = (props: VarsStepProps) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
{/* Avoid nesting issue in case of a "." */}
|
{/* Avoid nesting issue in case of a "." */}
|
||||||
<Field
|
<Field
|
||||||
name={`${generator.name.replaceAll(".", "__dot__")}.${prompt.name.replaceAll(".", "__dot__")}`}
|
name={`${generator.name.replaceAll(
|
||||||
|
".",
|
||||||
|
"__dot__",
|
||||||
|
)}.${prompt.name.replaceAll(".", "__dot__")}`}
|
||||||
>
|
>
|
||||||
{(field, props) => (
|
{(field, props) => (
|
||||||
<TextInput
|
<Switch
|
||||||
inputProps={{
|
fallback={
|
||||||
...props,
|
<TextInput
|
||||||
type:
|
inputProps={{
|
||||||
prompt.prompt_type === "hidden"
|
...props,
|
||||||
? "password"
|
type:
|
||||||
: "text",
|
prompt.prompt_type === "hidden"
|
||||||
}}
|
? "password"
|
||||||
label={prompt.description}
|
: "text",
|
||||||
value={prompt.previous_value ?? ""}
|
}}
|
||||||
error={field.error}
|
label={prompt.description}
|
||||||
/>
|
value={prompt.previous_value ?? ""}
|
||||||
|
error={field.error}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Match
|
||||||
|
when={
|
||||||
|
prompt.prompt_type === "multiline" ||
|
||||||
|
prompt.prompt_type === "multiline-hidden"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
{...props}
|
||||||
|
class="w-full h-32 border border-gray-300 rounded-md p-2"
|
||||||
|
placeholder={prompt.description}
|
||||||
|
value={prompt.previous_value ?? ""}
|
||||||
|
name={prompt.description}
|
||||||
|
/>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
Reference in New Issue
Block a user