Skip to content

Commit 0e53e7f

Browse files
committed
checking in all the important fixes so we don't lose anything
1 parent 6e8c658 commit 0e53e7f

25 files changed

+2490
-918
lines changed

FEATURE_IDEAS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ some more ideas please:
44
- [x] lock file in .refaktor/ - abort with error if another process is running (for plan/apply/rename)
55
- homebrew formula
66
- interactive mode like git add -P for accepting/rejecting individual changes
7+
- publish this as a Rust crate that other Rust programs can use directly

refaktor-cli/src/apply.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use anyhow::Result;
2+
use refaktor_core::apply_operation;
3+
use std::path::PathBuf;
4+
5+
pub fn handle_apply(
6+
plan_path: Option<PathBuf>,
7+
plan_id: Option<String>,
8+
commit: bool,
9+
force: bool,
10+
) -> Result<()> {
11+
let result = apply_operation(plan_path, plan_id, commit, force, None)?;
12+
println!("{}", result);
13+
Ok(())
14+
}

refaktor-cli/src/history.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use anyhow::{Context, Result};
2+
use refaktor_core::{format_history, History};
3+
use std::path::PathBuf;
4+
5+
pub fn handle_history(limit: Option<usize>) -> Result<()> {
6+
let refaktor_dir = PathBuf::from(".refaktor");
7+
let history = History::load(&refaktor_dir).context("Failed to load history")?;
8+
9+
let entries = history.list_entries(limit);
10+
let formatted = format_history(&entries, false)?;
11+
12+
println!("{}", formatted);
13+
Ok(())
14+
}

refaktor-cli/src/main.rs

Lines changed: 17 additions & 279 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,19 @@
11
use anyhow::{anyhow, Context, Result};
22
use 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};
84
use std::io::{self, IsTerminal};
95
use std::path::PathBuf;
106
use std::process;
117
use std::str::FromStr;
128

9+
mod apply;
10+
mod history;
11+
mod plan;
12+
mod redo;
1313
mod 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!("\nPlan 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!("\nWarning: {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

889627
fn is_refaktor_ignored() -> Result<bool> {
890628
// Check if .refaktor is already ignored in any ignore file

0 commit comments

Comments
 (0)