1use std::io::Write;
4use std::path::{Path, PathBuf};
5
6use ktrs_project::migrate::{Migration, plan};
7
8pub const HELP: &str = "\
9Usage: ktrs migrate [--write] [PATH ...]
10
11Switches the build's ktfmt / ktlint setup to the ktrs drop-ins (default PATH: .): ktfmt-gradle and
12ktlint-gradle plugin ids (plugins {}, the version catalog, buildscript or convention-build
13dependencies) and the plugin repository, gantsign's ktlint-maven-plugin, and Spotless's ktfmt/ktlint
14steps (Gradle and Maven). Edits only the ids, coordinates and versions, plus the lines the README
15adds; setups with no drop-in (kotlinter, ktlint run from its jar) get a `note:` on stderr.
16
17Without --write, prints the edits as a unified diff and exits 1 if there are any (0 if none).
18
19Options:
20 --write Apply the edits and list the files changed";
21
22pub fn run(args: &[String], out: &mut dyn Write, err: &mut dyn Write) -> i32 {
23 let mut write = false;
24 let mut paths = Vec::new();
25 for arg in args {
26 match arg.as_str() {
27 "--write" => write = true,
28 "-h" | "--help" => {
29 let _ = writeln!(out, "{HELP}");
30 return 0;
31 }
32 _ if arg.starts_with('-') => {
33 let _ = writeln!(err, "error: unknown option {arg}\n\n{HELP}");
34 return 2;
35 }
36 _ => paths.push(PathBuf::from(arg)),
37 }
38 }
39 if paths.is_empty() {
40 paths.push(PathBuf::from("."));
41 }
42 let mut migrations: Vec<Migration> = Vec::new();
43 for path in &paths {
44 let m = plan(path, env!("CARGO_PKG_VERSION"));
45 if !migrations.iter().any(|seen| seen.root == m.root) {
46 migrations.push(m);
47 }
48 }
49 let cwd = std::env::current_dir().unwrap_or_default();
50 let mut changed = 0;
51 for m in &migrations {
52 for note in &m.notes {
53 let _ = writeln!(err, "note: {note}");
54 }
55 for change in &m.changes {
56 let label = label(&cwd, &change.path);
57 if write {
58 if let Err(e) = change.write() {
59 let _ = writeln!(err, "error: {label}: {e}");
60 return 2;
61 }
62 let _ = writeln!(out, "migrated {label}");
63 } else {
64 let _ = write!(out, "{}", change.unified_diff(&label));
65 }
66 changed += 1;
67 }
68 }
69 let files = if changed == 1 { "1 file".to_string() } else { format!("{changed} files") };
70 match (changed, write) {
71 (0, _) => {
72 let _ = writeln!(out, "Nothing to migrate.");
73 0
74 }
75 (_, true) => {
76 let _ = writeln!(out, "{files} changed.");
77 0
78 }
79 (_, false) => {
80 let _ = writeln!(out, "{files} would change; run `ktrs migrate --write` to apply.");
81 1
82 }
83 }
84}
85
86fn label(cwd: &Path, path: &Path) -> String {
88 path.strip_prefix(cwd).unwrap_or(path).to_string_lossy().replace('\\', "/")
89}