Skip to content

Commit 47ca624

Browse files
stty: add combination settings
1 parent 31bb953 commit 47ca624

File tree

3 files changed

+182
-6
lines changed

3 files changed

+182
-6
lines changed

src/uu/stty/src/flags.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
// spell-checker:ignore isig icanon iexten echoe crterase echok echonl noflsh xcase tostop echoprt prterase echoctl ctlecho echoke crtkill flusho extproc
1010
// spell-checker:ignore lnext rprnt susp swtch vdiscard veof veol verase vintr vkill vlnext vquit vreprint vstart vstop vsusp vswtc vwerase werase
1111
// spell-checker:ignore sigquit sigtstp
12+
// spell-checker:ignore cbreak decctlq evenp litout oddp
1213

1314
use crate::Flag;
1415

@@ -365,3 +366,23 @@ pub const CONTROL_CHARS: &[(&str, S)] = &[
365366
// Discards the current line.
366367
("discard", S::VDISCARD),
367368
];
369+
370+
/// This constant lists all possible combination settings, using a bool to represent if the setting is negatable
371+
pub const COMBINATION_SETTINGS: &[(&str, bool)] = &[
372+
("LCASE", true),
373+
("lcase", true),
374+
("cbreak", true),
375+
("cooked", true),
376+
("crt", false),
377+
("dec", false),
378+
("decctlq", true),
379+
("ek", false),
380+
("evenp", true),
381+
("litout", true),
382+
("nl", true),
383+
("oddp", true),
384+
("parity", true),
385+
("pass8", true),
386+
("raw", true),
387+
("sane", false),
388+
];

src/uu/stty/src/stty.rs

Lines changed: 145 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,22 @@
44
// file that was distributed with this source code.
55

66
// spell-checker:ignore clocal erange tcgetattr tcsetattr tcsanow tiocgwinsz tiocswinsz cfgetospeed cfsetospeed ushort vmin vtime cflag lflag ispeed ospeed
7+
// spell-checker:ignore parenb parodd cmspar hupcl cstopb cread clocal crtscts CSIZE
8+
// spell-checker:ignore ignbrk brkint ignpar parmrk inpck istrip inlcr igncr icrnl ixoff ixon iuclc ixany imaxbel iutf
9+
// spell-checker:ignore opost olcuc ocrnl onlcr onocr onlret ofdel nldly crdly tabdly bsdly vtdly ffdly ofill
10+
// spell-checker:ignore isig icanon iexten echoe crterase echok echonl noflsh xcase tostop echoprt prterase echoctl ctlecho echoke crtkill flusho extproc
11+
// spell-checker:ignore lnext rprnt susp swtch vdiscard veof veol verase vintr vkill vlnext vquit vreprint vstart vstop vsusp vswtc vwerase werase
12+
// spell-checker:ignore sigquit sigtstp
13+
// spell-checker:ignore cbreak decctlq evenp litout oddp
714

815
mod flags;
916

1017
use crate::flags::AllFlags;
18+
use crate::flags::COMBINATION_SETTINGS;
1119
use clap::{Arg, ArgAction, ArgMatches, Command};
1220
use nix::libc::{O_NONBLOCK, TIOCGWINSZ, TIOCSWINSZ, c_ushort};
1321
use nix::sys::termios::{
14-
ControlFlags, InputFlags, LocalFlags, OutputFlags, SpecialCharacterIndices, Termios,
22+
ControlFlags, InputFlags, LocalFlags, OutputFlags, SpecialCharacterIndices as S, Termios,
1523
cfgetospeed, cfsetospeed, tcgetattr, tcsetattr,
1624
};
1725
use nix::{ioctl_read_bad, ioctl_write_ptr_bad};
@@ -104,6 +112,7 @@ enum Device {
104112
Stdout(Stdout),
105113
}
106114

115+
#[derive(Debug)]
107116
enum ControlCharMappingError {
108117
IntOutOfRange(String),
109118
MultipleChars(String),
@@ -120,7 +129,7 @@ enum PrintSetting {
120129

121130
enum ArgOptions<'a> {
122131
Flags(AllFlags<'a>),
123-
Mapping((SpecialCharacterIndices, u8)),
132+
Mapping((S, u8)),
124133
Special(SpecialSetting),
125134
Print(PrintSetting),
126135
}
@@ -321,6 +330,9 @@ fn stty(opts: &Options) -> UResult<()> {
321330
));
322331
}
323332
valid_args.push(flag.into());
333+
// combination setting
334+
} else if let Some(combo) = string_to_combo(arg) {
335+
valid_args.append(&mut combo_to_flags(combo));
324336
} else if *arg == "rows" {
325337
if let Some(rows) = args_iter.next() {
326338
if let Some(n) = parse_rows_cols(rows) {
@@ -510,7 +522,7 @@ fn print_terminal_size(termios: &Termios, opts: &Options) -> nix::Result<()> {
510522
Ok(())
511523
}
512524

513-
fn cc_to_index(option: &str) -> Option<SpecialCharacterIndices> {
525+
fn cc_to_index(option: &str) -> Option<S> {
514526
for cc in CONTROL_CHARS {
515527
if option == cc.0 {
516528
return Some(cc.1);
@@ -519,6 +531,15 @@ fn cc_to_index(option: &str) -> Option<SpecialCharacterIndices> {
519531
None
520532
}
521533

534+
fn string_to_combo(arg: &str) -> Option<&str> {
535+
let is_negated = arg.starts_with('-');
536+
let name = arg.trim_start_matches('-');
537+
COMBINATION_SETTINGS
538+
.iter()
539+
.find(|&&(combo_name, is_negatable)| name == combo_name && (!is_negated || is_negatable))
540+
.map(|_| arg)
541+
}
542+
522543
fn string_to_baud(arg: &str) -> Option<AllFlags> {
523544
// BSDs use a u32 for the baud rate, so any decimal number applies.
524545
#[cfg(any(
@@ -622,11 +643,11 @@ fn print_control_chars(termios: &Termios, opts: &Options) -> nix::Result<()> {
622643
HashMap::from([
623644
(
624645
"min".to_string(),
625-
termios.control_chars[SpecialCharacterIndices::VMIN as usize].to_string()
646+
termios.control_chars[S::VMIN as usize].to_string()
626647
),
627648
(
628649
"time".to_string(),
629-
termios.control_chars[SpecialCharacterIndices::VTIME as usize].to_string()
650+
termios.control_chars[S::VTIME as usize].to_string()
630651
)
631652
])
632653
)
@@ -741,7 +762,7 @@ fn apply_baud_rate_flag(termios: &mut Termios, input: &AllFlags) {
741762
}
742763
}
743764

744-
fn apply_char_mapping(termios: &mut Termios, mapping: &(SpecialCharacterIndices, u8)) {
765+
fn apply_char_mapping(termios: &mut Termios, mapping: &(S, u8)) {
745766
termios.control_chars[mapping.0 as usize] = mapping.1;
746767
}
747768

@@ -807,6 +828,124 @@ fn string_to_control_char(s: &str) -> Result<u8, ControlCharMappingError> {
807828
}
808829
}
809830

831+
// decomposes a combination argument into a vec of corresponding flags
832+
fn combo_to_flags(combo: &str) -> Vec<ArgOptions> {
833+
let mut flags = Vec::new();
834+
let mut ccs = Vec::new();
835+
match combo {
836+
"lcase" | "LCASE" => {
837+
flags = vec!["xcase", "iuclc", "olcuc"];
838+
}
839+
"-lcase" | "-LCASE" => {
840+
flags = vec!["-xcase", "-iuclc", "-olcuc"];
841+
}
842+
"cbreak" => {
843+
flags = vec!["-icanon"];
844+
}
845+
"-cbreak" => {
846+
flags = vec!["icanon"];
847+
}
848+
"cooked" | "-raw" => {
849+
flags = vec![
850+
"brkint", "ignpar", "istrip", "icrnl", "ixon", "opost", "isig", "icanon",
851+
];
852+
ccs = vec![(S::VEOF, "^D"), (S::VEOL, "")];
853+
}
854+
"crt" => {
855+
flags = vec!["echoe", "echoctl", "echoke"];
856+
}
857+
"dec" => {
858+
flags = vec!["echoe", "echoctl", "echoke", "-ixany"];
859+
ccs = vec![(S::VINTR, "^C"), (S::VERASE, "^?"), (S::VKILL, "^U")];
860+
}
861+
"decctlq" => {
862+
flags = vec!["ixany"];
863+
}
864+
"-decctlq" => {
865+
flags = vec!["-ixany"];
866+
}
867+
"ek" => {
868+
ccs = vec![(S::VERASE, "^?"), (S::VKILL, "^U")];
869+
}
870+
"evenp" | "parity" => {
871+
flags = vec!["parenb", "-parodd", "cs7"];
872+
}
873+
"-evenp" | "-parity" => {
874+
flags = vec!["-parenb", "cs8"];
875+
}
876+
"litout" => {
877+
flags = vec!["-parenb", "-istrip", "-opost", "-cs8"];
878+
}
879+
"-litout" => {
880+
flags = vec!["parenb", "istrip", "opost", "cs7"];
881+
}
882+
"nl" => {
883+
flags = vec!["-icrnl", "-onlcr"];
884+
}
885+
"-nl" => {
886+
flags = vec!["icrnl", "-inlcr", "-igncr", "onlcr", "-ocrnl", "-onlret"];
887+
}
888+
"oddp" => {
889+
flags = vec!["parenb", "parodd", "cs7"];
890+
}
891+
"-oddp" => {
892+
flags = vec!["-parenb", "cs8"];
893+
}
894+
"pass8" => {
895+
flags = vec!["-parenb", "-istrip", "cs8"];
896+
}
897+
"-pass8" => {
898+
flags = vec!["parenb", "istrip", "cs7"];
899+
}
900+
"raw" | "-cooked" => {
901+
flags = vec![
902+
"-ignbrk", "-brkint", "-ignpar", "-parmrk", "-inpck", "-istrip", "-inlcr",
903+
"-igncr", "-icrnl", "-ixon", "-ixoff", "-icanon", "-opost", "-isig", "-iuclc",
904+
"-xcase", "-ixany", "-imaxbel",
905+
];
906+
// TODO: add 'min 1' and 'time 0' settings here after they have been implemented
907+
}
908+
"sane" => {
909+
flags = vec![
910+
"cread", "-ignbrk", "brkint", "-inlcr", "-igncr", "icrnl", "icanon", "iexten",
911+
"echo", "echoe", "echok", "-echonl", "-noflsh", "-ixoff", "-iutf8", "-iuclc",
912+
"-xcase", "-ixany", "imaxbel", "-olcuc", "-ocrnl", "opost", "-ofill", "onlcr",
913+
"-onocr", "-onlret", "n10", "cr0", "tab0", "bs0", "vt0", "ff0", "isig", "-tostop",
914+
"-ofdel", "-echoprt", "echoctl", "echoke", "-extproc", "-flush0",
915+
];
916+
ccs = vec![
917+
(S::VINTR, "^C"),
918+
(S::VQUIT, "^\\"),
919+
(S::VERASE, "^?"),
920+
(S::VKILL, "^U"),
921+
(S::VEOF, "^D"),
922+
(S::VEOL, ""),
923+
(S::VEOL2, ""),
924+
#[cfg(target_os = "linux")]
925+
(S::VSWTC, ""),
926+
(S::VSTART, "^Q"),
927+
(S::VSTOP, "^S"),
928+
(S::VSUSP, "^Z"),
929+
(S::VREPRINT, "^R"),
930+
(S::VWERASE, "^W"),
931+
(S::VLNEXT, "^V"),
932+
(S::VDISCARD, "^O"),
933+
];
934+
}
935+
_ => unreachable!("invalid combination setting: must have been caught earlier"),
936+
}
937+
let mut flags = flags
938+
.iter()
939+
.filter_map(|f| string_to_flag(f).map(ArgOptions::Flags))
940+
.collect::<Vec<ArgOptions>>();
941+
let mut ccs = ccs
942+
.iter()
943+
.map(|cc| ArgOptions::Mapping((cc.0, string_to_control_char(cc.1).unwrap())))
944+
.collect::<Vec<ArgOptions>>();
945+
flags.append(&mut ccs);
946+
flags
947+
}
948+
810949
pub fn uu_app() -> Command {
811950
Command::new(uucore::util_name())
812951
.version(uucore::crate_version!())

tests/by-util/test_stty.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,19 @@ fn row_column_sizes() {
243243
.fails()
244244
.stderr_contains("missing argument to 'rows'");
245245
}
246+
247+
#[test]
248+
fn non_negatable_combo() {
249+
new_ucmd!()
250+
.args(&["-dec"])
251+
.fails()
252+
.stderr_contains("invalid argument '-dec'");
253+
new_ucmd!()
254+
.args(&["-crt"])
255+
.fails()
256+
.stderr_contains("invalid argument '-crt'");
257+
new_ucmd!()
258+
.args(&["-ek"])
259+
.fails()
260+
.stderr_contains("invalid argument '-ek'");
261+
}

0 commit comments

Comments
 (0)