1mod operation;
7mod simd;
8mod unicode_table;
9
10use clap::{Arg, ArgAction, Command, value_parser};
11use operation::{
12 DeleteOperation, Sequence, SqueezeOperation, SymbolTranslator, TranslateOperation,
13 flush_output, translate_input,
14};
15use simd::process_input;
16use std::ffi::OsString;
17use std::io::{stdin, stdout};
18use uucore::display::Quotable;
19use uucore::error::{UResult, USimpleError, UUsageError};
20use uucore::fs::is_stdin_directory;
21#[cfg(not(target_os = "windows"))]
22use uucore::libc;
23use uucore::translate;
24use uucore::{format_usage, os_str_as_bytes, show};
25
26mod options {
27 pub const COMPLEMENT: &str = "complement";
28 pub const DELETE: &str = "delete";
29 pub const SQUEEZE: &str = "squeeze-repeats";
30 pub const TRUNCATE_SET1: &str = "truncate-set1";
31 pub const SETS: &str = "sets";
32}
33
34#[uucore::main]
35pub fn uumain(args: impl uucore::Args) -> UResult<()> {
36 #[cfg(not(target_os = "windows"))]
41 unsafe {
42 libc::signal(libc::SIGPIPE, libc::SIG_DFL);
43 }
44
45 let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
46
47 let delete_flag = matches.get_flag(options::DELETE);
48 let complement_flag = matches.get_flag(options::COMPLEMENT);
49 let squeeze_flag = matches.get_flag(options::SQUEEZE);
50 let truncate_set1_flag = matches.get_flag(options::TRUNCATE_SET1);
51
52 let sets: Vec<_> = matches
55 .get_many::<OsString>(options::SETS)
56 .into_iter()
57 .flatten()
58 .map(ToOwned::to_owned)
59 .collect();
60
61 if sets.is_empty() {
62 return Err(UUsageError::new(1, translate!("tr-error-missing-operand")));
63 }
64
65 let sets_len = sets.len();
66
67 if !(delete_flag || squeeze_flag) && sets_len == 1 {
68 return Err(UUsageError::new(
69 1,
70 translate!("tr-error-missing-operand-translating", "set" => sets[0].quote()),
71 ));
72 }
73
74 if delete_flag && squeeze_flag && sets_len == 1 {
75 return Err(UUsageError::new(
76 1,
77 translate!("tr-error-missing-operand-deleting-squeezing", "set" => sets[0].quote()),
78 ));
79 }
80
81 if sets_len > 1 {
82 if delete_flag && !squeeze_flag {
83 let op = sets[1].quote();
84 let msg = if sets_len == 2 {
85 translate!("tr-error-extra-operand-deleting-without-squeezing", "operand" => op)
86 } else {
87 translate!("tr-error-extra-operand-simple", "operand" => op)
88 };
89 return Err(UUsageError::new(1, msg));
90 }
91 if sets_len > 2 {
92 let op = sets[2].quote();
93 let msg = translate!("tr-error-extra-operand-simple", "operand" => op);
94 return Err(UUsageError::new(1, msg));
95 }
96 }
97
98 if let Some(first) = sets.first() {
99 let slice = os_str_as_bytes(first)?;
100 let trailing_backslashes = slice.iter().rev().take_while(|&&c| c == b'\\').count();
101 if trailing_backslashes % 2 == 1 {
102 show!(USimpleError::new(
104 0,
105 translate!("tr-warning-unescaped-backslash")
106 ));
107 }
108 }
109
110 let stdin = stdin();
111 let mut locked_stdin = stdin.lock();
112 let mut locked_stdout = stdout().lock();
113
114 let translating = !delete_flag && sets.len() > 1;
116 let mut sets_iter = sets.iter().map(|c| c.as_os_str());
117 let (set1, set2) = Sequence::solve_set_characters(
118 os_str_as_bytes(sets_iter.next().unwrap_or_default())?,
119 os_str_as_bytes(sets_iter.next().unwrap_or_default())?,
120 complement_flag,
121 truncate_set1_flag && translating,
123 translating,
124 )?;
125
126 if is_stdin_directory(&stdin) {
127 return Err(USimpleError::new(1, translate!("tr-error-read-directory")));
128 }
129
130 if delete_flag {
132 if squeeze_flag {
133 let delete_op = DeleteOperation::new(set1);
134 let squeeze_op = SqueezeOperation::new(set2);
135 let op = delete_op.chain(squeeze_op);
136 translate_input(&mut locked_stdin, &mut locked_stdout, op)?;
137 } else {
138 let op = DeleteOperation::new(set1);
139 process_input(&mut locked_stdin, &mut locked_stdout, &op)?;
140 }
141 } else if squeeze_flag {
142 if sets_len == 1 {
143 let op = SqueezeOperation::new(set1);
144 translate_input(&mut locked_stdin, &mut locked_stdout, op)?;
145 } else {
146 let translate_op = TranslateOperation::new(set1, set2.clone())?;
147 let squeeze_op = SqueezeOperation::new(set2);
148 let op = translate_op.chain(squeeze_op);
149 translate_input(&mut locked_stdin, &mut locked_stdout, op)?;
150 }
151 } else {
152 let op = TranslateOperation::new(set1, set2)?;
153 process_input(&mut locked_stdin, &mut locked_stdout, &op)?;
154 }
155
156 flush_output(&mut locked_stdout)?;
157
158 Ok(())
159}
160
161pub fn uu_app() -> Command {
162 Command::new(uucore::util_name())
163 .version(uucore::crate_version!())
164 .help_template(uucore::localized_help_template(uucore::util_name()))
165 .about(translate!("tr-about"))
166 .override_usage(format_usage(&translate!("tr-usage")))
167 .after_help(translate!("tr-after-help"))
168 .infer_long_args(true)
169 .trailing_var_arg(true)
170 .arg(
171 Arg::new(options::COMPLEMENT)
172 .visible_short_alias('C')
173 .short('c')
174 .long(options::COMPLEMENT)
175 .help(translate!("tr-help-complement"))
176 .action(ArgAction::SetTrue)
177 .overrides_with(options::COMPLEMENT),
178 )
179 .arg(
180 Arg::new(options::DELETE)
181 .short('d')
182 .long(options::DELETE)
183 .help(translate!("tr-help-delete"))
184 .action(ArgAction::SetTrue)
185 .overrides_with(options::DELETE),
186 )
187 .arg(
188 Arg::new(options::SQUEEZE)
189 .long(options::SQUEEZE)
190 .short('s')
191 .help(translate!("tr-help-squeeze"))
192 .action(ArgAction::SetTrue)
193 .overrides_with(options::SQUEEZE),
194 )
195 .arg(
196 Arg::new(options::TRUNCATE_SET1)
197 .long(options::TRUNCATE_SET1)
198 .short('t')
199 .help(translate!("tr-help-truncate-set1"))
200 .action(ArgAction::SetTrue)
201 .overrides_with(options::TRUNCATE_SET1),
202 )
203 .arg(
204 Arg::new(options::SETS)
205 .num_args(1..)
206 .value_parser(value_parser!(OsString)),
207 )
208}