1use std::io;
5use std::path::Path;
6
7use ktrs_fmt::{FormattingOptions, GOOGLE_FORMAT, KOTLINLANG_FORMAT, META_FORMAT};
8
9use crate::ktfmt::{KTFMT_VERSION, Main, ParsedArgs, editor_config_resolver};
10
11const HELP: &str = "\
12ktrs - fast Kotlin tooling
13
14Usage:
15 ktrs fmt [OPTIONS] [PATH ...] Format .kt/.kts files in place (default PATH: .)
16 ktrs fmt [OPTIONS] - Format stdin to stdout
17 ktrs serve Format requests framed on stdin until it closes (for build tools;
18 protocol: crates/ktrs-cli/src/serve.rs)
19 ktrs --version
20
21Format options:
22 --style <meta|google|kotlinlang> Code style (default: meta)
23 --check Don't write; list files that would change and exit 1 if any
24 --keep-unused-imports Leave unused imports in place
25 --editorconfig Apply .editorconfig overrides (max_line_length, indent sizes,
26 ktfmt_trailing_comma_management_strategy); for stdin, at
27 --stdin-name
28 --stdin-name <name> Name (path) of the stdin input, for messages and .editorconfig
29 -v, --verbose Report each formatted file
30
31A ktfmt-compatible `ktfmt` binary ships alongside, for existing scripts and integrations.";
32
33pub fn run(args: &[String]) -> i32 {
34 match args.first().map(String::as_str) {
35 Some("fmt") => match parse_fmt_args(&args[1..]) {
36 Ok(parsed) => Main::new(io::stdin(), io::stdout(), io::stderr()).run_parsed(&parsed),
37 Err(message) => {
38 eprintln!("error: {message}\n\n{HELP}");
39 2
40 }
41 },
42 Some("serve") if args.len() == 1 => crate::serve::run(io::stdin().lock(), io::stdout().lock()),
43 Some("--version" | "-V") => {
44 println!("ktrs {} (formats like ktfmt {KTFMT_VERSION})", env!("CARGO_PKG_VERSION"));
45 0
46 }
47 Some("help" | "--help" | "-h") => {
48 println!("{HELP}");
49 0
50 }
51 _ => {
52 eprintln!("{HELP}");
53 2
54 }
55 }
56}
57
58pub fn parse_fmt_args(args: &[String]) -> Result<ParsedArgs, String> {
59 let mut parsed = ParsedArgs {
60 file_names: Vec::new(),
61 formatting_options: META_FORMAT,
62 dry_run: false,
63 set_exit_if_changed: false,
64 stdin_name: None,
65 editor_config: false,
66 quiet: true,
67 };
68 let mut keep_unused_imports = false;
69 let mut args = args.iter();
70 while let Some(arg) = args.next() {
71 let (flag, inline_value) = match arg.split_once('=') {
72 Some((flag, value)) if arg.starts_with("--") => (flag, Some(value.to_owned())),
73 _ => (arg.as_str(), None),
74 };
75 let mut value = || inline_value.clone().or_else(|| args.next().cloned()).ok_or(format!("{flag} needs a value"));
76 match flag {
77 "--style" => parsed.formatting_options = style(&value()?)?,
78 "--check" => (parsed.dry_run, parsed.set_exit_if_changed) = (true, true),
79 "--keep-unused-imports" => keep_unused_imports = true,
80 "--editorconfig" => parsed.editor_config = true,
81 "--stdin-name" => parsed.stdin_name = Some(value()?),
82 "-v" | "--verbose" => parsed.quiet = false,
83 "-" => parsed.file_names.push(arg.clone()),
84 _ if flag.starts_with('-') => return Err(format!("unknown option {arg}")),
85 _ => parsed.file_names.push(arg.clone()),
86 }
87 }
88 parsed.formatting_options.remove_unused_imports = !keep_unused_imports;
89 if parsed.file_names.is_empty() {
90 parsed.file_names.push(".".to_owned());
91 }
92 let reads_stdin = parsed.file_names.iter().any(|f| f == "-");
93 if reads_stdin && parsed.file_names.len() > 1 {
94 return Err("cannot read from stdin and files in the same run".to_owned());
95 }
96 if parsed.stdin_name.is_some() && !reads_stdin {
97 return Err("--stdin-name can only be used when reading from stdin (-)".to_owned());
98 }
99 if let (true, true, Some(name)) = (reads_stdin, parsed.editor_config, &parsed.stdin_name) {
102 parsed.formatting_options =
103 editor_config_resolver::resolve_formatting_options(Path::new(name), &parsed.formatting_options);
104 }
105 Ok(parsed)
106}
107
108pub(crate) fn style(name: &str) -> Result<FormattingOptions, String> {
109 match name {
110 "meta" => Ok(META_FORMAT),
111 "google" => Ok(GOOGLE_FORMAT),
112 "kotlinlang" => Ok(KOTLINLANG_FORMAT),
113 _ => Err(format!("unknown style '{name}' (expected meta, google or kotlinlang)")),
114 }
115}