Skip to content

Commit 998de4a

Browse files
Merge pull request #1261 from element-hq/gaelg/prompt-generic-function
Refactor to use a helper function for prompting
2 parents 09f1d75 + 249285b commit 998de4a

5 files changed

Lines changed: 287 additions & 170 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Refactor internal prompting logic.

packages/ess-migration-tool/src/ess_migration_tool/extra_files.py

Lines changed: 90 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
from .interfaces import ExtraFilesDiscoveryStrategy, SecretDiscoveryStrategy
1717
from .models import DiscoveredExtraFile, DiscoveredPath, SecretConfig
18-
from .utils import is_quiet_mode
18+
from .utils import is_quiet_mode, prompt_choice, prompt_value, prompt_yes_no
1919

2020
logger = logging.getLogger("migration")
2121

@@ -247,117 +247,111 @@ def prompt_for_missing_files(self) -> None:
247247
self.pretty_logger.info(" 2. Skip these files and continue")
248248
self.pretty_logger.info(" 3. Provide a directory to search for files")
249249

250-
while True:
251-
try:
252-
choice = input(" Enter your choice (1/2/3): ").strip()
253-
254-
if choice == "1":
255-
# Provide alternative paths
256-
self._prompt_for_individual_alternatives(file_path)
257-
break
258-
elif choice == "2":
259-
# Skip missing files
260-
self.pretty_logger.info(" ⚠️ Skipping missing files...")
261-
file_path.skipped_reason = "Skipped by user"
262-
break
263-
elif choice == "3":
264-
# Provide directory to search
265-
self._prompt_for_directory_search(file_path)
266-
break
267-
else:
268-
self.pretty_logger.info(" ❌ Invalid choice. Please enter 1, 2, or 3.")
269-
270-
except KeyboardInterrupt as err:
271-
self.pretty_logger.info("\n ❌ Operation cancelled by user")
272-
raise ExtraFilesError("User cancelled file input") from err
273-
except EOFError as err:
274-
self.pretty_logger.info("\n ❌ End of input reached")
275-
raise ExtraFilesError("End of input reached during file prompt") from err
250+
choice = prompt_choice(
251+
self.pretty_logger,
252+
"Enter your choice (1/2/3):",
253+
[
254+
"Provide alternative path for this file",
255+
"Skip these files and continue",
256+
"Provide a directory to search for files",
257+
],
258+
)
259+
260+
if choice == "Provide alternative path for this file":
261+
# Provide alternative paths
262+
self._prompt_for_individual_alternatives(file_path)
263+
elif choice == "Skip these files and continue":
264+
# Skip missing files
265+
self.pretty_logger.info(" ⚠️ Skipping missing files...")
266+
file_path.skipped_reason = "Skipped by user"
267+
elif choice == "Provide a directory to search for files":
268+
# Provide directory to search
269+
self._prompt_for_directory_search(file_path)
276270

277271
def _prompt_for_individual_alternatives(self, discovered_path: DiscoveredPath) -> None:
278272
"""
279273
Prompt user for alternative paths for each missing file.
280274
"""
281-
while True:
275+
276+
def validate_file_path(path: str) -> tuple[bool, str]:
277+
if path.lower() == "skip":
278+
return True, "" # Special case for skip
279+
if not path:
280+
return False, "File path cannot be empty. Please try again."
282281
try:
283-
new_path = input(" Please enter the correct file path (or 'skip' to ignore): ").strip()
282+
new_path_obj = Path(path)
283+
if new_path_obj.is_dir():
284+
return False, "Targetted file is a directory."
285+
discovered_extra_file = self._discover_extra_file(discovered_path, new_path_obj)
286+
if not discovered_extra_file:
287+
return False, "File not found or invalid."
288+
return True, ""
289+
except ExtraFilesError as e:
290+
return False, str(e)
284291

285-
if new_path.lower() == "skip":
286-
self.pretty_logger.info(f" ⚠️ Skipping file: {discovered_path.source_path}")
287-
break
292+
while True:
293+
new_path = prompt_value(
294+
self.pretty_logger,
295+
"Please enter the correct file path (or 'skip' to ignore):",
296+
validator=validate_file_path,
297+
)
288298

289-
if not new_path:
290-
self.pretty_logger.info(" ❌ File path cannot be empty. Please try again.")
291-
continue
292-
293-
# Validate the new path
294-
try:
295-
new_path_obj = Path(new_path)
296-
if new_path_obj.is_dir():
297-
raise ExtraFilesError("Targetted file is a directory.")
298-
else:
299-
discovered_extra_file = self._discover_extra_file(discovered_path, new_path_obj)
300-
if discovered_extra_file:
301-
self.discovered_extra_files[new_path_obj] = discovered_extra_file
302-
self.pretty_logger.info(f" ✅ File validated: {new_path}")
303-
break
304-
except ExtraFilesError as e:
305-
self.pretty_logger.info(f" ❌ Invalid file path: {e}")
306-
self.pretty_logger.info(" Please try again or enter 'skip' to ignore.")
307-
308-
except KeyboardInterrupt as err:
309-
self.pretty_logger.info("\n ❌ Operation cancelled by user")
310-
raise ExtraFilesError("User cancelled file input") from err
311-
except EOFError as err:
312-
self.pretty_logger.info("\n ❌ End of input reached")
313-
raise ExtraFilesError("End of input reached during file prompt") from err
299+
if new_path.lower() == "skip":
300+
self.pretty_logger.info(f" ⚠️ Skipping file: {discovered_path.source_path}")
301+
break
302+
303+
new_path_obj = Path(new_path)
304+
discovered_extra_file = self._discover_extra_file(discovered_path, new_path_obj)
305+
if discovered_extra_file:
306+
self.discovered_extra_files[new_path_obj] = discovered_extra_file
307+
self.pretty_logger.info(f" ✅ File validated: {new_path}")
308+
break
314309

315310
def _prompt_for_directory_search(self, discovered_path: DiscoveredPath) -> None:
316311
"""
317312
Prompt user for a directory to search for missing files.
318313
"""
319314
self.pretty_logger.info("\n📁 Please provide a directory to search for missing files:")
320315

321-
while True:
316+
def validate_directory(path: str) -> tuple[bool, str]:
317+
if not path:
318+
return False, "Directory path cannot be empty. Please try again."
322319
try:
323-
search_dir = input(" Enter directory path: ").strip()
324-
325-
if not search_dir:
326-
self.pretty_logger.info(" ❌ Directory path cannot be empty. Please try again.")
327-
continue
328-
329-
# Validate the directory
330-
try:
331-
dir_path = Path(search_dir)
332-
if not dir_path.is_dir():
333-
raise ExtraFilesError(f"Path is not a directory: {search_dir}")
334-
335-
# Search for missing files in the directory
336-
found_files = self._handle_directory(discovered_path, dir_path)
337-
338-
if found_files:
339-
self.pretty_logger.info(f" ✅ Found {len(found_files)} matching files in {search_dir}")
340-
for file_path in found_files:
341-
self.pretty_logger.info(f" 📄 {Path(file_path).name}")
342-
break
343-
else:
344-
self.pretty_logger.info(f" ⚠️ No matching files found in {search_dir}")
345-
self.pretty_logger.info(" Would you like to try another directory?")
346-
retry = input(" Enter 'yes' to try again or any other key to skip: ").strip().lower()
347-
if retry != "yes":
348-
self.pretty_logger.info(" ⚠️ Skipping directory search...")
349-
break
350-
351-
except Exception as e:
352-
self.pretty_logger.info(f" ❌ Error searching directory: {e}")
353-
self.pretty_logger.info(" Please try again.")
354-
355-
except KeyboardInterrupt as err:
356-
self.pretty_logger.info("\n ❌ Operation cancelled by user")
357-
raise ExtraFilesError("User cancelled file input") from err
358-
except EOFError as err:
359-
self.pretty_logger.info("\n ❌ End of input reached")
360-
raise ExtraFilesError("End of input reached during file prompt") from err
320+
dir_path = Path(path)
321+
if not dir_path.is_dir():
322+
return False, f"Path is not a directory: {path}"
323+
return True, ""
324+
except Exception as e:
325+
return False, str(e)
326+
327+
while True:
328+
search_dir = prompt_value(
329+
self.pretty_logger,
330+
"Enter directory path:",
331+
validator=validate_directory,
332+
)
333+
334+
dir_path = Path(search_dir)
335+
336+
# Search for missing files in the directory
337+
found_files = self._handle_directory(discovered_path, dir_path)
338+
339+
if found_files:
340+
self.pretty_logger.info(f" ✅ Found {len(found_files)} matching files in {search_dir}")
341+
for file_path in found_files:
342+
self.pretty_logger.info(f" 📄 {Path(file_path).name}")
343+
break
344+
else:
345+
self.pretty_logger.info(f" ⚠️ No matching files found in {search_dir}")
346+
self.pretty_logger.info(" Would you like to try another directory?")
347+
retry = prompt_yes_no(
348+
self.pretty_logger,
349+
"Try another directory?",
350+
default=False,
351+
)
352+
if not retry:
353+
self.pretty_logger.info(" ⚠️ Skipping directory search...")
354+
break
361355

362356
def validate_extra_files(self) -> None:
363357
"""

packages/ess-migration-tool/src/ess_migration_tool/secrets.py

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@
1111

1212
from .interfaces import SecretDiscoveryStrategy
1313
from .models import DiscoveredSecret, GlobalOptions
14-
from .utils import find_matching_schema_key, get_nested_value, is_quiet_mode, is_wildcard_pattern
14+
from .utils import (
15+
find_matching_schema_key,
16+
get_nested_value,
17+
is_quiet_mode,
18+
is_wildcard_pattern,
19+
prompt_value,
20+
)
1521

1622
logger = logging.getLogger("migration")
1723

@@ -210,28 +216,19 @@ def prompt_for_missing_secrets(self) -> None:
210216
if not config_key:
211217
raise RuntimeError(f"Missing configuration path for {secret_key}")
212218

213-
while True:
214-
try:
215-
value = input(" Please paste the secret value: ").strip()
216-
if value:
217-
self.discovered_secrets[secret_key] = DiscoveredSecret(
218-
source_file=self.source_file,
219-
secret_key=secret_key,
220-
config_key=config_key,
221-
value=value,
222-
)
223-
# Remove from missing_required_secrets after successful prompt
224-
self.missing_required_secrets.remove((discovered_secret, error_message))
225-
self.pretty_logger.info(f" ✅ Secret stored for {secret_key}")
226-
break
227-
else:
228-
self.pretty_logger.info(" ❌ Value cannot be empty. Please try again.")
229-
except KeyboardInterrupt as err:
230-
self.pretty_logger.info("\n ❌ Operation cancelled by user")
231-
raise SecretsError("User cancelled secret input") from err
232-
except EOFError as err:
233-
self.pretty_logger.info("\n ❌ End of input reached")
234-
raise SecretsError("End of input reached during secret prompt") from err
219+
value = prompt_value(
220+
self.pretty_logger,
221+
"Please paste the secret value:",
222+
)
223+
self.discovered_secrets[secret_key] = DiscoveredSecret(
224+
source_file=self.source_file,
225+
secret_key=secret_key,
226+
config_key=config_key,
227+
value=value,
228+
)
229+
# Remove from missing_required_secrets after successful prompt
230+
self.missing_required_secrets.remove((discovered_secret, error_message))
231+
self.pretty_logger.info(f" ✅ Secret stored for {secret_key}")
235232

236233
self.pretty_logger.info(f"\n✅ All required {component_name} secrets have been provided")
237234
self.pretty_logger.info("=" * 60)

packages/ess-migration-tool/src/ess_migration_tool/synapse.py

Lines changed: 11 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from .interfaces import ExtraFilesDiscoveryStrategy, SecretDiscoveryStrategy
1717
from .migration import ConfigValueTransformer, MigrationStrategy, TransformationSpec, additional_config_transformer
1818
from .models import DiscoveredSecret, GlobalOptions, MigrationError, SecretConfig
19-
from .utils import extract_hostname_from_url, yaml_dump_with_pipe_for_multiline
19+
from .utils import extract_hostname_from_url, prompt_choice, prompt_value, yaml_dump_with_pipe_for_multiline
2020

2121
logger = logging.getLogger("migration")
2222

@@ -67,27 +67,12 @@ def prompt_user_for_worker(
6767
for i, worker_type in enumerate(matched_worker_types):
6868
config_value_transformer.pretty_logger.info(f" ❌ {i + 1}. {worker_type}")
6969

70-
while True:
71-
try:
72-
value = input(f" Please select the worker type of instance {instance_name}: ").strip()
73-
if value:
74-
# make sure user selected an valid integer
75-
try:
76-
worker_index = int(value) - 1
77-
if worker_index in range(len(matched_worker_types)):
78-
return matched_worker_types[worker_index]
79-
else:
80-
config_value_transformer.pretty_logger.info(f"Invalid worker type: {value}")
81-
except ValueError:
82-
config_value_transformer.pretty_logger.info(" ❌ Please select a valid worker type.")
83-
else:
84-
config_value_transformer.pretty_logger.info(" ❌ Value cannot be empty. Please try again.")
85-
except KeyboardInterrupt as err:
86-
config_value_transformer.pretty_logger.info("\n ❌ Operation cancelled by user")
87-
raise MigrationError("User cancelled worker input") from err
88-
except EOFError as err:
89-
config_value_transformer.pretty_logger.info("\n ❌ End of input reached")
90-
raise MigrationError("End of input reached during worker prompt") from err
70+
selected_worker = prompt_choice(
71+
config_value_transformer.pretty_logger,
72+
f"Please select the worker type of instance {instance_name}:",
73+
matched_worker_types,
74+
)
75+
return selected_worker
9176

9277

9378
def extract_workers_from_instance_map(
@@ -163,19 +148,10 @@ def prompt_for_ingress_host(
163148
)
164149
config_value_transformer.pretty_logger.info(" ❌ Please provide Synapse ingress host (e.g., matrix.example.com):")
165150

166-
while True:
167-
try:
168-
ingress_host = input(" Enter ingress host: ").strip()
169-
if ingress_host:
170-
return ingress_host
171-
else:
172-
config_value_transformer.pretty_logger.info(" ❌ Ingress host cannot be empty. Please try again.")
173-
except KeyboardInterrupt as err:
174-
config_value_transformer.pretty_logger.info("\n ❌ Operation cancelled by user")
175-
raise MigrationError("User cancelled ingress host input") from err
176-
except EOFError as err:
177-
config_value_transformer.pretty_logger.info("\n ❌ End of input reached")
178-
raise MigrationError("End of input reached during ingress host prompt") from err
151+
return prompt_value(
152+
config_value_transformer.pretty_logger,
153+
"Enter ingress host:",
154+
)
179155

180156

181157
def filter_listeners(_, listeners: list[dict] | None, **kwargs: Any) -> dict[str, Any] | None:

0 commit comments

Comments
 (0)