1#![deny(dead_code)]
2#![deny(missing_docs)]
3#![allow(clippy::non_ascii_literal)]
5#![allow(clippy::identity_op)]
7#![allow(clippy::wildcard_imports)]
9#![allow(clippy::from_iter_instead_of_collect)]
11#![allow(clippy::new_without_default)]
12#![allow(clippy::items_after_statements)]
13#![cfg_attr(not(test), deny(clippy::dbg_macro))]
15#![cfg_attr(test, allow(clippy::dbg_macro))]
16
17pub use doc_chunks as documentation;
22#[cfg(test)]
23pub(crate) use doc_chunks::{chyrp_up, fluff_up};
24
25pub mod action;
26mod checker;
27mod config;
28pub mod errors;
29mod reflow;
30mod suggestion;
31mod tinhat;
32mod traverse;
33
34pub use self::action::*;
35pub use self::config::args::*;
36pub use self::config::{Config, HunspellConfig, LanguageToolConfig};
37pub use self::documentation::span::*;
38pub use self::documentation::util::*;
39pub use self::documentation::{
40 util, CheckableChunk, Clusters, CommentVariant, CommentVariantCategory, ContentOrigin,
41 Documentation, PlainOverlay, Range,
42};
43pub use self::suggestion::*;
44pub use self::tinhat::*;
45
46use self::errors::{bail, Result};
47
48use std::io::Write;
49
50#[cfg(target_os = "windows")]
51use signal_hook as _;
52
53use checker::Checker;
54
55#[derive(Debug, Clone, Copy, Eq, PartialEq)]
60pub enum ExitCode {
61 Success,
64 Signal,
66 Custom(u8),
68 }
70
71impl ExitCode {
72 pub fn as_u8(&self) -> u8 {
74 match *self {
75 Self::Success => 0u8,
76 Self::Signal => 130u8,
77 Self::Custom(code) => code,
78 }
79 }
80}
81
82pub fn run(args: Args) -> Result<ExitCode> {
84 let _ = ::rayon::ThreadPoolBuilder::new()
85 .num_threads(args.job_count())
86 .build_global();
87
88 env_logger::Builder::from_env(env_logger::Env::new().filter_or("CARGO_SPELLCHECK", "warn"))
89 .filter_level(args.verbosity())
90 .filter_module("nlprule", log::LevelFilter::Error)
91 .filter_module("mio", log::LevelFilter::Error)
92 .init();
93
94 #[cfg(not(target_os = "windows"))]
95 signal_handler(move || {
96 if let Err(e) = action::interactive::ScopedRaw::restore_terminal() {
97 log::warn!("Failed to restore terminal: {e}");
98 }
99 });
100
101 let (unified, config) = match &args.command {
102 Some(Sub::Completions { shell }) => {
103 let sink = &mut std::io::stdout();
104 generate_completions(*shell, sink);
105 let _ = sink.flush();
106 return Ok(ExitCode::Success);
107 }
108 _ => args.unified()?,
109 };
110
111 match unified {
112 UnifiedArgs::Config {
114 dest_config,
115 checker_filter_set,
116 } => {
117 log::trace!("Configuration chore");
118 let mut config = Config::full();
119 Args::checker_selection_override(
120 checker_filter_set.as_ref().map(AsRef::as_ref),
121 &mut config,
122 )?;
123
124 match dest_config {
125 ConfigWriteDestination::Stdout => {
126 println!("{}", config.to_toml()?);
127 return Ok(ExitCode::Success);
128 }
129 ConfigWriteDestination::File { overwrite, path } => {
130 if path.exists() && !overwrite {
131 bail!(
132 "Attempting to overwrite {} requires `--force`.",
133 path.display()
134 );
135 }
136
137 log::info!("Writing configuration file to {}", path.display());
138 config.write_values_to_path(path)?;
139 }
140 }
141 Ok(ExitCode::Success)
142 }
143 UnifiedArgs::Operate {
144 action,
145 paths,
146 recursive,
147 skip_readme,
148 config_path,
149 dev_comments,
150 exit_code_override,
151 } => {
152 log::debug!("Executing: {action:?} with {config:?} from {config_path:?}");
153
154 let documents =
155 traverse::extract(paths, recursive, skip_readme, dev_comments, &config)?;
156
157 let rt = tokio::runtime::Runtime::new()?;
158 let finish = rt.block_on(async move { action.run(documents, config).await })?;
159
160 match finish {
161 Finish::Success | Finish::MistakeCount(0) => Ok(ExitCode::Success),
162 Finish::MistakeCount(_n) => Ok(ExitCode::Custom(exit_code_override)),
163 Finish::Abort => Ok(ExitCode::Signal),
164 }
165 }
166 }
167}
168
169#[cfg(test)]
170mod tests;