Skip to content

Commit fe46549

Browse files
committed
Hide CLI internals
1 parent 78cb0ef commit fe46549

File tree

5 files changed

+157
-143
lines changed

5 files changed

+157
-143
lines changed

src/cli.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use std::io::Result as IoResult;
2+
use std::path::PathBuf;
3+
4+
use clap::builder::{Styles, ValueParser};
5+
use clap::{Arg, ArgGroup, Command, value_parser};
6+
7+
use crate::rule::Rule;
8+
9+
mod commands;
10+
mod renderer;
11+
12+
pub fn main() -> IoResult<()> {
13+
let matches = Command::new("nb")
14+
.arg_required_else_help(true)
15+
.subcommand_required(true)
16+
.styles(Styles::plain())
17+
.subcommand(
18+
Command::new("check")
19+
.about("Check a changelog")
20+
.arg(Arg::new("FILE").value_parser(value_parser!(PathBuf)))
21+
.arg(
22+
Arg::new("output_format")
23+
.long("output-format")
24+
.value_parser(["full", "json", "jsonl", "short"])
25+
.default_value("short"),
26+
),
27+
)
28+
.subcommand(
29+
Command::new("rule")
30+
.about("Explain a rule")
31+
.group(ArgGroup::new("rule").required(true).args(["RULE", "all"]))
32+
.arg(
33+
Arg::new("RULE")
34+
.help("The rule code (e.g., E100)")
35+
.value_parser(ValueParser::new(parse_rule_code))
36+
.conflicts_with("all"),
37+
)
38+
.arg(
39+
Arg::new("all")
40+
.short('a')
41+
.long("all")
42+
.help("Explain all rules")
43+
.action(clap::ArgAction::SetTrue),
44+
),
45+
)
46+
.get_matches();
47+
match matches.subcommand() {
48+
Some(("check", submatches)) => commands::check(submatches),
49+
Some(("rule", submatches)) => commands::rule(submatches),
50+
_ => unreachable!(),
51+
}
52+
}
53+
54+
fn parse_rule_code(code: &str) -> Result<String, String> {
55+
let code = code.to_uppercase();
56+
if Rule::ALL.iter().any(|rule| rule.code() == code) {
57+
Ok(code)
58+
} else {
59+
Err(format!(
60+
"{}",
61+
Rule::ALL
62+
.iter()
63+
.map(|rule| rule.code())
64+
.collect::<Vec<&str>>()
65+
.join(", ")
66+
))
67+
}
68+
}

src/cli/commands.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use std::collections::HashMap;
2+
use std::io::{self, Result, Write};
3+
use std::path::PathBuf;
4+
5+
use clap::ArgMatches;
6+
7+
use crate::parse_file;
8+
use crate::rule::Rule;
9+
use crate::span::Index;
10+
11+
use super::renderer::{OutputFormat, render};
12+
13+
pub fn check(matches: &ArgMatches) -> Result<()> {
14+
let path = matches
15+
.get_one::<PathBuf>("FILE")
16+
.unwrap_or(&PathBuf::from("CHANGELOG.md"))
17+
.clone();
18+
let format: &String = matches.get_one("output_format").expect("default");
19+
let output_format = match format.as_str() {
20+
"short" => OutputFormat::Short,
21+
"json" => OutputFormat::Json,
22+
"jsonl" => OutputFormat::JsonLines,
23+
"full" => OutputFormat::Full,
24+
_ => unreachable!(),
25+
};
26+
let (_, diagnostics) = parse_file(&path).unwrap();
27+
if diagnostics.is_empty() {
28+
Ok(())
29+
} else {
30+
// TODO: How to avoid reading again to create index?
31+
let Ok(content) = std::fs::read_to_string(&path) else {
32+
todo!();
33+
};
34+
let index = Index::new(&content);
35+
let mut output = io::stdout();
36+
render(
37+
&mut output,
38+
diagnostics.as_slice(),
39+
&content,
40+
Some(&path),
41+
&index,
42+
output_format,
43+
)
44+
}
45+
}
46+
47+
pub fn rule(matches: &ArgMatches) -> Result<()> {
48+
let mut rules_by_code = HashMap::new();
49+
for rule in Rule::ALL {
50+
rules_by_code.insert(rule.code().to_string(), rule);
51+
}
52+
let mut output = io::stdout();
53+
if matches.get_flag("all") {
54+
for (i, rule) in Rule::ALL.iter().enumerate() {
55+
if i > 0 {
56+
writeln!(output)?;
57+
}
58+
write!(
59+
output,
60+
"# {}
61+
62+
{}
63+
",
64+
rule.code(),
65+
rule.doc()
66+
)?;
67+
}
68+
} else if let Some(code) = matches.get_one::<String>("RULE") {
69+
let rule = rules_by_code.get(code).unwrap();
70+
write!(
71+
output,
72+
"# {}
73+
74+
{}
75+
",
76+
rule.code(),
77+
rule.doc()
78+
)?
79+
} else {
80+
unreachable!()
81+
}
82+
Ok(())
83+
}
File renamed without changes.

src/lib.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ pub(crate) mod ir;
66
pub(crate) mod linter;
77
pub(crate) mod parser;
88
pub(crate) mod profile;
9-
#[cfg(feature = "cli")]
10-
pub(crate) mod renderer;
119
pub(crate) mod rule;
1210

1311
pub mod changelog;
12+
#[cfg(feature = "cli")]
13+
pub mod cli;
1414
pub mod error;
1515
pub mod span;
1616
pub mod unist;
@@ -20,9 +20,6 @@ pub use diagnostic::Diagnostic;
2020
pub use error::{Error, ParseError};
2121
pub use rule::Rule;
2222

23-
#[cfg(feature = "cli")]
24-
pub use renderer::{OutputFormat, render};
25-
2623
/// Parse a changelog from a string.
2724
///
2825
/// Parsing a changelog will nearly always succeed. This function returns a [`Result`] to support

src/main.rs

Lines changed: 4 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -1,146 +1,12 @@
1-
use std::collections::HashMap;
2-
use std::io::{self, Write};
3-
use std::path::PathBuf;
41
use std::process::ExitCode;
52

63
#[cfg(feature = "cli")]
7-
use clap::builder::Styles;
8-
use clap::builder::ValueParser;
9-
#[cfg(feature = "cli")]
10-
use clap::{Arg, Command, builder::ArgGroup, value_parser};
11-
12-
use notabene::span::Index;
13-
#[cfg(feature = "cli")]
14-
use notabene::{OutputFormat, Rule, parse_file, render};
15-
16-
fn parse_rule_code(code: &str) -> Result<String, String> {
17-
let code = code.to_uppercase();
18-
if Rule::ALL.iter().any(|rule| rule.code() == code) {
19-
Ok(code)
20-
} else {
21-
Err(format!(
22-
"{}",
23-
Rule::ALL
24-
.iter()
25-
.map(|rule| rule.code())
26-
.collect::<Vec<&str>>()
27-
.join(", ")
28-
))
29-
}
30-
}
4+
use notabene::cli;
315

326
#[cfg(feature = "cli")]
337
fn main() -> std::process::ExitCode {
34-
let mut rules_by_code = HashMap::new();
35-
for rule in Rule::ALL {
36-
rules_by_code.insert(rule.code().to_string(), rule);
37-
}
38-
let matches = Command::new("nb")
39-
.arg_required_else_help(true)
40-
.subcommand_required(true)
41-
.styles(Styles::plain())
42-
.subcommand(
43-
Command::new("check")
44-
.about("Check a changelog")
45-
.arg(Arg::new("FILE").value_parser(value_parser!(PathBuf)))
46-
.arg(
47-
Arg::new("output_format")
48-
.long("output-format")
49-
.value_parser(["full", "json", "jsonl", "short"])
50-
.default_value("short"),
51-
),
52-
)
53-
.subcommand(
54-
Command::new("rule")
55-
.about("Explain a rule")
56-
.group(ArgGroup::new("rule").required(true).args(["RULE", "all"]))
57-
.arg(
58-
Arg::new("RULE")
59-
.help("The rule code (e.g., E100)")
60-
.value_parser(ValueParser::new(parse_rule_code))
61-
.conflicts_with("all"),
62-
)
63-
.arg(
64-
Arg::new("all")
65-
.short('a')
66-
.long("all")
67-
.help("Explain all rules")
68-
.action(clap::ArgAction::SetTrue),
69-
),
70-
)
71-
.get_matches();
72-
match matches.subcommand() {
73-
Some(("check", submatches)) => {
74-
let path = submatches
75-
.get_one::<PathBuf>("FILE")
76-
.unwrap_or(&PathBuf::from("CHANGELOG.md"))
77-
.clone();
78-
let format: &String = submatches.get_one("output_format").expect("default");
79-
let output_format = match format.as_str() {
80-
"short" => OutputFormat::Short,
81-
"json" => OutputFormat::Json,
82-
"jsonl" => OutputFormat::JsonLines,
83-
"full" => OutputFormat::Full,
84-
_ => unreachable!(),
85-
};
86-
let (_, diagnostics) = parse_file(&path).unwrap();
87-
if diagnostics.is_empty() {
88-
ExitCode::SUCCESS
89-
} else {
90-
// TODO: How to avoid reading again to create index?
91-
let Ok(content) = std::fs::read_to_string(&path) else {
92-
todo!();
93-
};
94-
let index = Index::new(&content);
95-
let mut output = io::stdout();
96-
render(
97-
&mut output,
98-
diagnostics.as_slice(),
99-
&content,
100-
Some(&path),
101-
&index,
102-
output_format,
103-
)
104-
.unwrap();
105-
ExitCode::FAILURE
106-
}
107-
}
108-
Some(("rule", submatches)) => {
109-
let mut output = io::stdout();
110-
if submatches.get_flag("all") {
111-
for (i, rule) in Rule::ALL.iter().enumerate() {
112-
if i > 0 {
113-
writeln!(output).unwrap();
114-
}
115-
write!(
116-
output,
117-
"# {}
118-
119-
{}
120-
",
121-
rule.code(),
122-
rule.doc()
123-
)
124-
.unwrap();
125-
}
126-
ExitCode::SUCCESS
127-
} else if let Some(code) = submatches.get_one::<String>("RULE") {
128-
let rule = rules_by_code.get(code).unwrap();
129-
write!(
130-
output,
131-
"# {}
132-
133-
{}
134-
",
135-
rule.code(),
136-
rule.doc()
137-
)
138-
.unwrap();
139-
ExitCode::SUCCESS
140-
} else {
141-
unreachable!()
142-
}
143-
}
144-
_ => unreachable!(),
8+
match cli::main() {
9+
Ok(_) => ExitCode::SUCCESS,
10+
Err(_) => ExitCode::FAILURE,
14511
}
14612
}

0 commit comments

Comments
 (0)