11use anyhow:: { anyhow, Context , Result } ;
22use clap:: { Parser , Subcommand , ValueEnum } ;
3- use refaktor_core:: {
4- apply_plan, format_history, get_status, redo_refactoring, scan_repository_multi,
5- undo_refactoring, write_plan, write_preview, ApplyOptions , Config , History , LockFile , Plan ,
6- PlanOptions , Preview , Style ,
7- } ;
3+ use refaktor_core:: { Config , Preview , Style } ;
84use std:: io:: { self , IsTerminal } ;
95use std:: path:: PathBuf ;
106use std:: process;
117use std:: str:: FromStr ;
128
9+ mod apply;
10+ mod history;
11+ mod plan;
12+ mod redo;
1313mod rename;
14+ mod status;
15+ mod undo;
1416
15- /// Returns the default styles used by refaktor CLI
16- pub ( crate ) fn get_default_styles ( ) -> Vec < StyleArg > {
17- vec ! [
18- StyleArg :: Original ,
19- StyleArg :: Snake ,
20- StyleArg :: Kebab ,
21- StyleArg :: Camel ,
22- StyleArg :: Pascal ,
23- StyleArg :: ScreamingSnake ,
24- StyleArg :: Train , // Include Train-Case in CLI defaults
25- StyleArg :: ScreamingTrain , // Include ScreamingTrain for ALL-CAPS-PATTERNS
26- ]
27- }
2817
2918/// Smart search & replace for code and files with case-aware transformations
3019#[ derive( Parser , Debug ) ]
@@ -478,7 +467,7 @@ fn main() {
478467 Preview :: from_str ( & config. defaults . preview_format ) . unwrap_or ( Preview :: Diff )
479468 } ) ;
480469
481- handle_plan (
470+ plan :: handle_plan (
482471 & old,
483472 & new,
484473 paths,
@@ -492,7 +481,7 @@ fn main() {
492481 include_styles,
493482 only_styles,
494483 exclude_match,
495- format,
484+ Some ( format) ,
496485 plan_out,
497486 dry_run,
498487 use_color,
@@ -519,7 +508,7 @@ fn main() {
519508 Preview :: from_str ( & config. defaults . preview_format ) . unwrap_or ( Preview :: Diff )
520509 } ) ;
521510
522- handle_plan (
511+ plan :: handle_plan (
523512 & old,
524513 & new,
525514 paths,
@@ -533,7 +522,7 @@ fn main() {
533522 include_styles,
534523 only_styles,
535524 exclude_match,
536- format,
525+ Some ( format) ,
537526 PathBuf :: from ( ".refaktor/plan.json" ) ,
538527 true , // Always dry-run
539528 use_color,
@@ -543,21 +532,18 @@ fn main() {
543532 Commands :: Apply {
544533 plan,
545534 id,
546- atomic,
535+ atomic : _ ,
547536 commit,
548537 force_with_conflicts,
549- } => handle_apply ( plan, id, atomic , commit, force_with_conflicts) ,
538+ } => apply :: handle_apply ( plan, id, commit, force_with_conflicts) ,
550539
551- Commands :: Undo { id } => handle_undo ( id) ,
540+ Commands :: Undo { id } => undo :: handle_undo ( & id) ,
552541
553- Commands :: Redo { id } => {
554- let refaktor_dir = PathBuf :: from ( ".refaktor" ) ;
555- redo_refactoring ( & id, & refaktor_dir) . context ( "Failed to redo refactoring" )
556- } ,
542+ Commands :: Redo { id } => redo:: handle_redo ( & id) ,
557543
558- Commands :: Status => handle_status ( ) ,
544+ Commands :: Status => status :: handle_status ( ) ,
559545
560- Commands :: History { limit } => handle_history ( limit) ,
546+ Commands :: History { limit } => history :: handle_history ( limit) ,
561547
562548 Commands :: Init {
563549 local,
@@ -636,255 +622,7 @@ fn main() {
636622 }
637623}
638624
639- #[ allow( clippy:: too_many_arguments) ]
640- fn handle_plan (
641- old : & str ,
642- new : & str ,
643- paths : Vec < PathBuf > ,
644- include : Vec < String > ,
645- exclude : Vec < String > ,
646- respect_gitignore : bool ,
647- unrestricted : u8 ,
648- rename_files : bool ,
649- rename_dirs : bool ,
650- exclude_styles : Vec < StyleArg > ,
651- include_styles : Vec < StyleArg > ,
652- only_styles : Vec < StyleArg > ,
653- exclude_match : Vec < String > ,
654- preview : Preview ,
655- plan_out : PathBuf ,
656- dry_run : bool ,
657- use_color : bool ,
658- ) -> Result < ( ) > {
659- let current_dir = std:: env:: current_dir ( ) . context ( "Failed to get current directory" ) ?;
660-
661- // Use provided paths or default to current directory
662- let search_paths = if paths. is_empty ( ) {
663- vec ! [ PathBuf :: from( "." ) ]
664- } else {
665- paths
666- } ;
667-
668- // Acquire lock
669- let refaktor_dir = current_dir. join ( ".refaktor" ) ;
670- let _lock = LockFile :: acquire ( & refaktor_dir)
671- . context ( "Failed to acquire lock for refaktor operation" ) ?;
672-
673- // Build the list of styles to use based on exclude, include, and only options
674- let styles = {
675- if only_styles. is_empty ( ) {
676- // Start with the default styles
677- let default_styles = get_default_styles ( ) ;
678-
679- // Remove excluded styles from defaults
680- let mut active_styles: Vec < StyleArg > = default_styles
681- . into_iter ( )
682- . filter ( |s| !exclude_styles. contains ( s) )
683- . collect ( ) ;
684-
685- // Add included styles (Title, Train, Dot)
686- for style in include_styles {
687- if !active_styles. contains ( & style) {
688- active_styles. push ( style) ;
689- }
690- }
691-
692- if active_styles. is_empty ( ) {
693- eprintln ! ( "Warning: All styles have been excluded, using default styles" ) ;
694- None // Use default styles
695- } else {
696- Some ( active_styles. into_iter ( ) . map ( Into :: into) . collect ( ) )
697- }
698- } else {
699- // If --only-styles is specified, use only those styles
700- Some ( only_styles. into_iter ( ) . map ( Into :: into) . collect ( ) )
701- }
702- } ;
703-
704- let options = PlanOptions {
705- includes : include,
706- excludes : exclude,
707- respect_gitignore,
708- unrestricted_level : unrestricted. min ( 3 ) , // Cap at 3 for safety
709- styles,
710- rename_files,
711- rename_dirs,
712- rename_root : false , // Default: do not allow root directory renames in plan
713- plan_out : plan_out. clone ( ) ,
714- coerce_separators : refaktor_core:: scanner:: CoercionMode :: Auto , // TODO: make configurable
715- exclude_match,
716- } ;
717-
718- // Resolve all search paths to absolute paths and canonicalize them
719- let resolved_paths: Vec < PathBuf > = search_paths
720- . iter ( )
721- . map ( |path| {
722- let absolute_path = if path. is_absolute ( ) {
723- path. clone ( )
724- } else {
725- current_dir. join ( path)
726- } ;
727- // Canonicalize to remove . and .. components
728- absolute_path. canonicalize ( ) . unwrap_or ( absolute_path)
729- } )
730- . collect ( ) ;
731-
732- let plan = scan_repository_multi ( & resolved_paths, old, new, & options)
733- . context ( "Failed to scan repository" ) ?;
734-
735- // Show preview
736- write_preview ( & plan, preview, Some ( use_color) ) . context ( "Failed to write preview" ) ?;
737-
738- // Write plan unless dry-run
739- if !dry_run {
740- write_plan ( & plan, & plan_out) . context ( "Failed to write plan" ) ?;
741-
742- if preview != Preview :: Json {
743- eprintln ! ( "\n Plan written to: {}" , plan_out. display( ) ) ;
744- }
745- }
746-
747- // Check for conflicts and return appropriate exit code
748- if let Some ( conflicts) = check_for_conflicts ( & plan) {
749- eprintln ! ( "\n Warning: {conflicts} conflicts detected" ) ;
750- if !dry_run {
751- eprintln ! ( "Use --force-with-conflicts to apply anyway" ) ;
752- }
753- // We don't exit with error here, just warn
754- }
755-
756- Ok ( ( ) )
757- }
758-
759- const fn check_for_conflicts ( _plan : & refaktor_core:: Plan ) -> Option < usize > {
760- // Check if there are any rename conflicts
761- // This is a placeholder - would need to check the actual conflicts
762- // from the rename module
763- None
764- }
765625
766- fn handle_apply (
767- plan_path : Option < PathBuf > ,
768- id : Option < String > ,
769- atomic : bool ,
770- commit : bool ,
771- force_with_conflicts : bool ,
772- ) -> Result < ( ) > {
773- let root = std:: env:: current_dir ( ) . context ( "Failed to get current directory" ) ?;
774-
775- // Acquire lock
776- let refaktor_dir = root. join ( ".refaktor" ) ;
777- let _lock = LockFile :: acquire ( & refaktor_dir)
778- . context ( "Failed to acquire lock for refaktor operation" ) ?;
779-
780- // Determine which plan to load
781- let plan_path = if let Some ( path) = plan_path {
782- path
783- } else if let Some ( id) = id {
784- // Load from history by ID (placeholder for now)
785- eprintln ! ( "Loading plan from history ID {id} not yet implemented" ) ;
786- return Ok ( ( ) ) ;
787- } else {
788- // Default to last plan
789- PathBuf :: from ( ".refaktor/plan.json" )
790- } ;
791-
792- // Load the plan
793- let plan_json = std:: fs:: read_to_string ( & plan_path)
794- . with_context ( || format ! ( "Failed to read plan from {}" , plan_path. display( ) ) ) ?;
795-
796- let plan: Plan = serde_json:: from_str ( & plan_json) . context ( "Failed to parse plan JSON" ) ?;
797-
798- // Check for conflicts if not forcing
799- if !force_with_conflicts {
800- if let Some ( conflicts) = check_for_conflicts ( & plan) {
801- eprintln ! (
802- "Error: {conflicts} conflicts detected. Use --force-with-conflicts to apply anyway"
803- ) ;
804- return Err ( anyhow ! ( "Conflicts detected" ) ) ;
805- }
806- }
807-
808- // Set up apply options
809- let options = ApplyOptions {
810- atomic,
811- commit,
812- force : force_with_conflicts,
813- ..Default :: default ( )
814- } ;
815-
816- eprintln ! (
817- "Applying plan {} ({} edits, {} renames)..." ,
818- plan. id,
819- plan. matches. len( ) ,
820- plan. renames. len( )
821- ) ;
822-
823- // Apply the plan
824- apply_plan ( & plan, & options) . context ( "Failed to apply plan" ) ?;
825-
826- eprintln ! ( "Plan applied successfully!" ) ;
827-
828- // Delete the plan.json file after successful apply (only if using default path)
829- if plan_path == PathBuf :: from ( ".refaktor/plan.json" ) {
830- if let Err ( e) = std:: fs:: remove_file ( & plan_path) {
831- eprintln ! (
832- "Warning: Failed to delete plan file {}: {}" ,
833- plan_path. display( ) ,
834- e
835- ) ;
836- } else {
837- eprintln ! ( "Deleted plan file: {}" , plan_path. display( ) ) ;
838- }
839- }
840-
841- if commit {
842- eprintln ! ( "Changes committed to git" ) ;
843- }
844-
845- Ok ( ( ) )
846- }
847-
848- fn handle_status ( ) -> Result < ( ) > {
849- let refaktor_dir = PathBuf :: from ( ".refaktor" ) ;
850- let status = get_status ( & refaktor_dir) . context ( "Failed to get status" ) ?;
851-
852- print ! ( "{}" , status. format( ) ) ;
853- Ok ( ( ) )
854- }
855-
856- fn handle_undo ( id : String ) -> Result < ( ) > {
857- let refaktor_dir = PathBuf :: from ( ".refaktor" ) ;
858-
859- // Handle "latest" keyword
860- let actual_id = if id == "latest" {
861- // Load history and get the most recent non-revert entry
862- let history = History :: load ( & refaktor_dir) . context ( "Failed to load history" ) ?;
863- let entries = history. list_entries ( None ) ;
864-
865- // Find the most recent non-revert entry
866- entries
867- . iter ( )
868- . find ( |e| e. revert_of . is_none ( ) )
869- . map ( |e| e. id . clone ( ) )
870- . ok_or_else ( || anyhow ! ( "No entries to undo" ) ) ?
871- } else {
872- id
873- } ;
874-
875- undo_refactoring ( & actual_id, & refaktor_dir) . context ( "Failed to undo refactoring" )
876- }
877-
878- fn handle_history ( limit : Option < usize > ) -> Result < ( ) > {
879- let refaktor_dir = PathBuf :: from ( ".refaktor" ) ;
880- let history = History :: load ( & refaktor_dir) . context ( "Failed to load history" ) ?;
881-
882- let entries = history. list_entries ( limit) ;
883- let formatted = format_history ( & entries, false ) ?;
884-
885- println ! ( "{formatted}" ) ;
886- Ok ( ( ) )
887- }
888626
889627fn is_refaktor_ignored ( ) -> Result < bool > {
890628 // Check if .refaktor is already ignored in any ignore file
0 commit comments