Skip to content

Commit d32e516

Browse files
committed
better codegen_py UX
1 parent 1d71307 commit d32e516

8 files changed

Lines changed: 139 additions & 238 deletions

File tree

json_explorer/codegen/interactive.py

Lines changed: 9 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@
1616
from rich.table import Table
1717
from rich import box
1818

19-
from prompt_toolkit import prompt
20-
from prompt_toolkit.history import FileHistory
21-
from prompt_toolkit.completion import PathCompleter, WordCompleter, FuzzyCompleter
22-
2319
from . import (
2420
GeneratorError,
2521
generate_from_analysis,
@@ -29,6 +25,7 @@
2925
load_config,
3026
)
3127
from json_explorer.analyzer import analyze_json
28+
from json_explorer.utils import prompt_input, prompt_input_path
3229

3330
logger = logging.getLogger(__name__)
3431

@@ -131,87 +128,15 @@ def run_interactive(self) -> bool:
131128
# ========================================================================
132129

133130
def _input(self, message: str, default: str | None = None, **kwargs) -> str:
134-
"""
135-
User friendly input with optional choices and autocompletion.
136-
Falls back to Prompt.ask if prompt_toolkit is unavailable.
137-
138-
Args:
139-
message: Prompt message.
140-
default: Default value if user enters nothing.
141-
kwargs: May include 'choices' (list of strings) for tab completion.
142-
143-
Returns:
144-
User input as string (or default if empty).
145-
"""
146-
choices = kwargs.get("choices")
147-
try:
148-
149-
history = FileHistory(".json_explorer_input_history")
150-
151-
if choices:
152-
str_choices = [str(c) for c in choices]
153-
completer = FuzzyCompleter(WordCompleter(str_choices, ignore_case=True))
154-
# Only show options in prompt, not above it
155-
display_message = f"{message} ({'/'.join(str_choices)})"
156-
157-
while True:
158-
text = prompt(
159-
f"{display_message} > ",
160-
default=default or "",
161-
history=history,
162-
completer=completer,
163-
complete_while_typing=True,
164-
).strip()
165-
166-
if not text and default is not None:
167-
return default
168-
169-
# Exact or case-insensitive match
170-
if text in str_choices:
171-
return text
172-
lowered = text.lower()
173-
ci_matches = [c for c in str_choices if c.lower() == lowered]
174-
if ci_matches:
175-
return ci_matches[0]
176-
177-
# Prefix match
178-
prefix_matches = [
179-
c for c in str_choices if c.lower().startswith(lowered)
180-
]
181-
if len(prefix_matches) == 1:
182-
return prefix_matches[0]
183-
184-
self.console.print(f"[red]Invalid choice: {text}[/red]")
185-
186-
# Free text input
187-
return prompt(
188-
f"{message} > ", default=default or "", history=history
189-
).strip() or (default or "")
190-
191-
except Exception:
192-
return self._input(message, default=default, **kwargs)
193-
194-
def _input_path(self, message: str, **kwargs) -> str:
195-
"""
196-
Input for file paths with autocompletion.
197-
Falls back to Prompt.ask if prompt_toolkit is unavailable.
198-
"""
199-
default = kwargs.get("default")
200-
try:
201-
202-
history = FileHistory(".json_explorer_path_history")
203-
completer = PathCompleter(expanduser=True)
204-
205-
return prompt(
206-
f"{message} > ",
207-
default=default,
208-
history=history,
209-
completer=completer,
210-
complete_while_typing=True,
211-
).strip()
131+
return prompt_input(
132+
message,
133+
default=default,
134+
choices=kwargs.get("choices"),
135+
console=self.console,
136+
)
212137

213-
except Exception:
214-
return self._input(message)
138+
def _input_path(self, message: str, default: str = "") -> str:
139+
return prompt_input_path(message, default=default)
215140

216141
# ========================================================================
217142
# Main Menu

json_explorer/codegen/languages/go/interactive.py

Lines changed: 11 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,9 @@
1010

1111
from rich.console import Console
1212
from rich.panel import Panel
13-
from rich.prompt import Prompt, Confirm
14-
15-
from prompt_toolkit import prompt
16-
from prompt_toolkit.history import FileHistory
17-
from prompt_toolkit.completion import WordCompleter, FuzzyCompleter
13+
from rich.prompt import Confirm
1814

15+
from json_explorer.utils import prompt_input
1916
from ...core.config import GeneratorConfig
2017
from .config import get_web_api_config, get_strict_config, get_modern_config
2118

@@ -144,6 +141,7 @@ def configure_language_specific(
144141
"JSON tag case style",
145142
choices=["original", "snake", "camel"],
146143
default="original",
144+
console=console,
147145
)
148146

149147
# Optional fields
@@ -158,16 +156,19 @@ def configure_language_specific(
158156
"Integer type",
159157
choices=["int", "int32", "int64"],
160158
default="int64",
159+
console=console,
161160
)
162161
go_config["float_type"] = self._input(
163162
"Float type",
164163
choices=["float32", "float64"],
165164
default="float64",
165+
console=console,
166166
)
167167
go_config["unknown_type"] = self._input(
168168
"Unknown type representation",
169169
choices=["interface{}", "any"],
170170
default="interface{}",
171+
console=console,
171172
)
172173

173174
# Advanced options
@@ -176,6 +177,7 @@ def configure_language_specific(
176177
"Time type for timestamps",
177178
choices=["time.Time", "string", "int64"],
178179
default="time.Time",
180+
console=console,
179181
)
180182

181183
logger.info(f"Go-specific config collected: {len(go_config)} options")
@@ -284,62 +286,7 @@ def validate_config(self, config: dict[str, Any]) -> list[str]:
284286
# ========================================================================
285287

286288
def _input(self, message: str, default: str | None = None, **kwargs) -> str:
287-
"""
288-
User friendly input with optional choices and autocompletion.
289-
Falls back to Prompt.ask if prompt_toolkit is unavailable.
290-
291-
Args:
292-
message: Prompt message.
293-
default: Default value if user enters nothing.
294-
kwargs: May include 'choices' (list of strings) for tab completion.
295-
296-
Returns:
297-
User input as string (or default if empty).
298-
"""
299-
choices = kwargs.get("choices")
300-
try:
301-
302-
history = FileHistory(".json_explorer_input_history")
303-
304-
if choices:
305-
str_choices = [str(c) for c in choices]
306-
completer = FuzzyCompleter(WordCompleter(str_choices, ignore_case=True))
307-
# Only show options in prompt, not above it
308-
display_message = f"{message} ({'/'.join(str_choices)})"
309-
310-
while True:
311-
text = prompt(
312-
f"{display_message} > ",
313-
default=default or "",
314-
history=history,
315-
completer=completer,
316-
complete_while_typing=True,
317-
).strip()
318-
319-
if not text and default is not None:
320-
return default
321-
322-
# Exact or case-insensitive match
323-
if text in str_choices:
324-
return text
325-
lowered = text.lower()
326-
ci_matches = [c for c in str_choices if c.lower() == lowered]
327-
if ci_matches:
328-
return ci_matches[0]
329-
330-
# Prefix match
331-
prefix_matches = [
332-
c for c in str_choices if c.lower().startswith(lowered)
333-
]
334-
if len(prefix_matches) == 1:
335-
return prefix_matches[0]
336-
337-
self.console.print(f"[red]Invalid choice: {text}[/red]")
338-
339-
# Free text input
340-
return prompt(
341-
f"{message} > ", default=default or "", history=history
342-
).strip() or (default or "")
343-
344-
except Exception:
345-
return self._input(message, default=default, **kwargs)
289+
console = kwargs.get("console") or None
290+
return prompt_input(
291+
message, default=default, choices=kwargs.get("choices"), console=console
292+
)

json_explorer/codegen/languages/python/interactive.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010

1111
from rich.console import Console
1212
from rich.panel import Panel
13-
from rich.prompt import Prompt, Confirm
13+
from rich.prompt import Confirm
1414

15+
from json_explorer.utils import prompt_input
1516
from ...core.config import GeneratorConfig
1617
from .config import (
1718
get_dataclass_config,
@@ -136,7 +137,7 @@ def configure_language_specific(
136137
console.print("\n[bold]Python-Specific Options:[/bold]")
137138

138139
# Style selection
139-
style = Prompt.ask(
140+
style = self._input(
140141
"Select Python style",
141142
choices=["dataclass", "pydantic", "typeddict"],
142143
default="dataclass",
@@ -338,3 +339,13 @@ def validate_config(self, config: dict[str, Any]) -> list[str]:
338339
logger.info(f"Config validation: {len(warnings)} warnings")
339340

340341
return warnings
342+
343+
# ========================================================================
344+
# prompt_toolkit integration
345+
# ========================================================================
346+
347+
def _input(self, message: str, default: str | None = None, **kwargs) -> str:
348+
console = kwargs.get("console") or None
349+
return prompt_input(
350+
message, default=default, choices=kwargs.get("choices"), console=console
351+
)

json_explorer/interactive.py

Lines changed: 9 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,11 @@
99
from rich.panel import Panel
1010
from rich.table import Table
1111

12-
from prompt_toolkit import prompt
13-
from prompt_toolkit.history import FileHistory
14-
from prompt_toolkit.completion import PathCompleter, WordCompleter, FuzzyCompleter
15-
1612
from .tree_view import print_json_analysis, print_compact_tree
1713
from .search import JsonSearcher
1814
from .stats import DataStatsAnalyzer
1915
from .visualizer import JSONVisualizer
20-
from .utils import load_json
16+
from .utils import load_json, prompt_input, prompt_input_path
2117
from .codegen.interactive import CodegenInteractiveHandler
2218
from .logging_config import get_logger
2319

@@ -94,87 +90,15 @@ def run(self) -> int:
9490
return 0
9591

9692
def _input(self, message: str, default: str | None = None, **kwargs) -> str:
97-
"""
98-
User friendly input with optional choices and autocompletion.
99-
Falls back to Prompt.ask if prompt_toolkit is unavailable.
100-
101-
Args:
102-
message: Prompt message.
103-
default: Default value if user enters nothing.
104-
kwargs: May include 'choices' (list of strings) for tab completion.
105-
106-
Returns:
107-
User input as string (or default if empty).
108-
"""
109-
choices = kwargs.get("choices")
110-
try:
111-
112-
history = FileHistory(".json_explorer_input_history")
113-
114-
if choices:
115-
str_choices = [str(c) for c in choices]
116-
completer = FuzzyCompleter(WordCompleter(str_choices, ignore_case=True))
117-
# Only show options in prompt, not above it
118-
display_message = f"{message} ({'/'.join(str_choices)})"
119-
120-
while True:
121-
text = prompt(
122-
f"{display_message} > ",
123-
default=default or "",
124-
history=history,
125-
completer=completer,
126-
complete_while_typing=True,
127-
).strip()
128-
129-
if not text and default is not None:
130-
return default
131-
132-
# Exact or case-insensitive match
133-
if text in str_choices:
134-
return text
135-
lowered = text.lower()
136-
ci_matches = [c for c in str_choices if c.lower() == lowered]
137-
if ci_matches:
138-
return ci_matches[0]
139-
140-
# Prefix match
141-
prefix_matches = [
142-
c for c in str_choices if c.lower().startswith(lowered)
143-
]
144-
if len(prefix_matches) == 1:
145-
return prefix_matches[0]
146-
147-
self.console.print(f"[red]Invalid choice: {text}[/red]")
148-
149-
# Free text input
150-
return prompt(
151-
f"{message} > ", default=default or "", history=history
152-
).strip() or (default or "")
153-
154-
except Exception:
155-
return Prompt.ask(message, default=default, **kwargs)
156-
157-
def _input_path(self, message: str, **kwargs) -> str:
158-
"""
159-
Input for file paths with autocompletion.
160-
Falls back to Prompt.ask if prompt_toolkit is unavailable.
161-
"""
162-
default = kwargs.get("default")
163-
try:
164-
165-
history = FileHistory(".json_explorer_path_history")
166-
completer = PathCompleter(expanduser=True)
167-
168-
return prompt(
169-
f"{message} > ",
170-
default=default,
171-
history=history,
172-
completer=completer,
173-
complete_while_typing=True,
174-
).strip()
93+
return prompt_input(
94+
message,
95+
default=default,
96+
choices=kwargs.get("choices"),
97+
console=self.console,
98+
)
17599

176-
except Exception:
177-
return self._input(message)
100+
def _input_path(self, message: str, default: str = "") -> str:
101+
return prompt_input_path(message, default=default)
178102

179103
def _show_main_menu(self) -> None:
180104
"""Display the main menu."""

0 commit comments

Comments
 (0)