#!/usr/bin/env nu # Automated try-catch to Result pattern refactorer # Refactors 276+ try-catch blocks to use Result pattern helpers # Version: 1.0 use std log # Configuration let config = { dry_run: false backup: true verbose: true patterns: [ "bash_check" # try { bash -c ... | complete } catch { ... } "bash_or" # try { bash ... } catch { fallback } "json_read" # try { open file | from json } catch { ... } "bash_wrap" # try { bash -c ... } catch { ... } ] } # Report structure mut report = { total_files: 0 files_processed: 0 patterns_found: {} errors: [] changes_by_file: {} } # Add result.nu import if not present def ensure-result-import [file_path: string] { let content_result = (do { open $file_path } | complete) if $content_result.exit_code != 0 { return false } let content = $content_result.stdout # Check if already imported if ($content | str contains "use.*result.nu") { return false } # Check where to insert import let lines = ($content | lines) let insert_pos = ( $lines | enumerate | find -a {|x| $x.item =~ "^(use|def|export)" } | get 0?.index | default 0 ) # Insert import let new_lines = ( $lines | enumerate | each {|x| if $x.index == $insert_pos { ["use lib_provisioning/result.nu *", $x.item] } else { $x.item } } | flatten ) true } # Pattern 1: bash-check (try { bash -c ... | complete } catch { {exit_code: 1, stderr: $err} }) def refactor-bash-check [content: string] -> {changed: bool, content: string} { # Match pattern: try { bash -c $"..." | complete } catch {|err| {exit_code: 1, stderr: $err} } let pattern = 'try\s*\{\s*bash\s+-c\s+\$"([^"]+)"\s*\|\s*complete\s*\}\s*catch\s*\{\|err\|\s*\{exit_code:\s*1,\s*stderr:\s*\$err\s*\}\s*\}' if not ($content =~ $pattern) { return {changed: false, content: $content} } # Replace with bash-check helper let new_content = ( $content | str replace -a -m $pattern 'bash-check $"$1"' ) {changed: true, content: $new_content} } # Pattern 2: bash-or (try { bash -c ... } catch { fallback }) def refactor-bash-or [content: string] -> {changed: bool, content: string} { # Match pattern: try { bash -c $"..." } catch { fallback_value } let pattern = 'try\s*\{\s*bash\s+-c\s+\$"([^"]+)"\s*\}\s*catch\s*\{\s*([^}]+)\s*\}' if not ($content =~ $pattern) { return {changed: false, content: $content} } # Replace with bash-or helper let new_content = ( $content | str replace -a -m $pattern 'bash-or $"$1" $2' ) {changed: true, content: $new_content} } # Pattern 3: json-read (try { open file | from json } catch { ... }) def refactor-json-read [content: string] -> {changed: bool, content: string} { # Match pattern: try { open $path | from json } catch { default_value } let pattern = 'try\s*\{\s*open\s+(\$\w+)\s*\|\s*from\s+json\s*\}\s*catch\s*\{\s*([^}]+)\s*\}' if not ($content =~ $pattern) { return {changed: false, content: $content} } # Replace with json-read helper + match-result let new_content = ( $content | str replace -a -m $pattern '(json-read $1) | match-result {|data| $data} {|_err| $2}' ) {changed: true, content: $new_content} } # Pattern 4: bash-wrap (try { bash -c ... } catch { error_record }) def refactor-bash-wrap [content: string] -> {changed: bool, content: string} { # Match pattern: try { bash -c $"..." } catch {|err| error_record } let pattern = 'try\s*\{\s*bash\s+-c\s+\$"([^"]+)"\s*\}\s*catch\s*\{\|err\|\s*([^}]+)\s*\}' if not ($content =~ $pattern) { return {changed: false, content: $content} } # Replace with bash-wrap helper let new_content = ( $content | str replace -a -m $pattern '(bash-wrap $"$1") | match-result {|output| output} {|err| $2}' ) {changed: true, content: $new_content} } # Apply all refactoring patterns def apply-patterns [content: string] -> {changed: bool, content: string, patterns_applied: list} { mut result = {changed: false, content: $content, patterns_applied: []} # Apply each pattern for pattern in ["bash_check", "bash_or", "json_read", "bash_wrap"] { let pattern_result = ( match $pattern { "bash_check" => (refactor-bash-check $result.content) "bash_or" => (refactor-bash-or $result.content) "json_read" => (refactor-json-read $result.content) "bash_wrap" => (refactor-bash-wrap $result.content) _ => {changed: false, content: $result.content} } ) if $pattern_result.changed { $result.changed = true $result.content = $pattern_result.content $result.patterns_applied = ($result.patterns_applied | append $pattern) } } $result } # Refactor single file def refactor-file [file_path: string] -> record { let content_result = (do { open $file_path } | complete) if $content_result.exit_code != 0 { return { file: $file_path changed: false patterns_applied: [] import_added: false backup_created: false } } let original_content = $content_result.stdout # Ensure result.nu import let import_added = (ensure-result-import $file_path) # Apply refactoring patterns let refactor_result = (apply-patterns $original_content) # Check if any changes let has_changes = ($refactor_result.changed or $import_added) if $has_changes and (not $config.dry_run) { # Create backup if $config.backup { let backup_result = (do { bash -c $"cp ($file_path) ($file_path).bak" } | complete) if $backup_result.exit_code != 0 { # Log but continue if $config.verbose { print $"Warning: backup failed for ($file_path)" } } } # Write new content let save_result = (do { $refactor_result.content | save -f $file_path } | complete) if $save_result.exit_code != 0 { if $config.verbose { print $"Warning: save failed for ($file_path)" } } } { file: $file_path changed: $has_changes patterns_applied: $refactor_result.patterns_applied import_added: $import_added backup_created: ($has_changes and $config.backup) } } # Main refactoring loop def main [] { print "🔧 Automated try-catch → Result Pattern Refactorer" print "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" print $"Dry run: ($config.dry_run)" print $"Backup enabled: ($config.backup)" print "" # Find all .nu files with try-catch print "📁 Scanning for try-catch patterns..." let files = ( glob "provisioning/core/nulib/**/*.nu" | par-each {|f| if (open $f | str contains "try\s*{") { $f } else { null } } | filter {|x| $x != null} ) $report.total_files = ($files | length) print $"Found ($($files | length)) files with try-catch patterns" print "" # Process files print "🔄 Processing files..." let results = ( $files | par-each {|file| refactor-file $file } ) # Generate report mut changed_count = 0 mut pattern_counts = {} for result in $results { if $result.changed { $changed_count += 1 $report.changes_by_file = ($report.changes_by_file | insert $result.file { patterns: $result.patterns_applied backup: $result.backup_created }) for pattern in $result.patterns_applied { let current = ($pattern_counts | get -i $pattern | default 0) $pattern_counts = ($pattern_counts | insert $pattern ($current + 1)) } } if $config.verbose { let status = (if $result.changed { "✅ CHANGED" } else { "⏭️ SKIPPED" }) print $"($status): ($result.file | path basename)" } } $report.files_processed = $changed_count $report.patterns_found = $pattern_counts # Final report print "" print "📊 REFACTORING REPORT" print "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" print $"Total files scanned: ($report.total_files)" print $"Files changed: ($report.files_processed)" print "" print "Patterns refactored:" for {pattern, count} in ($report.patterns_found | to entries) { print $" • ($pattern): ($count) occurrences" } if $config.dry_run { print "" print "⚠️ DRY RUN MODE - No files were modified" print "Run with --no-dry-run to apply changes" } else if $config.backup { print "" print "✅ Backups created for all changed files (.bak)" } print "" print "Next steps:" print "1. Review changes: git diff" print "2. Verify helpers are imported: grep 'use lib_provisioning/result.nu' *.nu" print "3. Test: cargo test (if applicable)" print "4. Commit: git add -A && git commit -m 'refactor: eliminate try-catch blocks'" } # Parse command line arguments let args = $env.ARGS.positional if ($args | any {|arg| $arg == "--apply"}) { $config.dry_run = false } if ($args | any {|arg| $arg == "--verbose"}) { $config.verbose = true } # Run main main