starting work on stty

This commit is contained in:
Terts Diepraam 2022-06-24 20:01:02 +02:00 committed by Sylvestre Ledru
parent 87e3899477
commit 600cab0bd8
7 changed files with 610 additions and 0 deletions

10
Cargo.lock generated
View file

@ -445,6 +445,7 @@ dependencies = [
"uu_split",
"uu_stat",
"uu_stdbuf",
"uu_stty",
"uu_sum",
"uu_sync",
"uu_tac",
@ -2908,6 +2909,15 @@ dependencies = [
"uucore",
]
[[package]]
name = "uu_stty"
version = "0.0.14"
dependencies = [
"clap 3.1.18",
"nix",
"uucore",
]
[[package]]
name = "uu_sum"
version = "0.0.14"

View file

@ -190,6 +190,7 @@ feat_require_unix = [
"nohup",
"pathchk",
"stat",
"stty",
"timeout",
"tty",
"uname",
@ -348,6 +349,7 @@ sort = { optional=true, version="0.0.14", package="uu_sort", path="src/uu/so
split = { optional=true, version="0.0.14", package="uu_split", path="src/uu/split" }
stat = { optional=true, version="0.0.14", package="uu_stat", path="src/uu/stat" }
stdbuf = { optional=true, version="0.0.14", package="uu_stdbuf", path="src/uu/stdbuf" }
stty = { optional=true, version="0.0.14", package="uu_stty", path="src/uu/stty" }
sum = { optional=true, version="0.0.14", package="uu_sum", path="src/uu/sum" }
sync = { optional=true, version="0.0.14", package="uu_sync", path="src/uu/sync" }
tac = { optional=true, version="0.0.14", package="uu_tac", path="src/uu/tac" }

24
src/uu/stty/Cargo.toml Normal file
View file

@ -0,0 +1,24 @@
[package]
name = "uu_stty"
version = "0.0.14"
authors = ["uutils developers"]
license = "MIT"
description = "stty ~ (uutils) print or change terminal characteristics"
homepage = "https://github.com/uutils/coreutils"
repository = "https://github.com/uutils/coreutils/tree/main/src/uu/stty"
keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"]
categories = ["command-line-utilities"]
edition = "2021"
[lib]
path = "src/stty.rs"
[dependencies]
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }
nix = { version="0.24.1", features = ["term"] }
[[bin]]
name = "stty"
path = "src/main.rs"

1
src/uu/stty/LICENSE Symbolic link
View file

@ -0,0 +1 @@
../../../LICENSE

332
src/uu/stty/src/flags.rs Normal file
View file

@ -0,0 +1,332 @@
// * This file is part of the uutils coreutils package.
// *
// * For the full copyright and license information, please view the LICENSE file
// * that was distributed with this source code.
// spell-checker:ignore parenb parodd cmspar hupcl cstopb cread clocal crtscts
// spell-checker:ignore ignbrk brkint ignpar parmrk inpck istrip inlcr igncr icrnl ixoff ixon iuclc ixany imaxbel iutf
// spell-checker:ignore opost olcuc ocrnl onlcr onocr onlret ofill ofdel
// spell-checker:ignore isig icanon iexten echoe crterase echok echonl noflsh xcase tostop echoprt prterase echoctl ctlecho echoke crtkill flusho extproc
use crate::Flag;
use nix::sys::termios::{ControlFlags, InputFlags, LocalFlags, OutputFlags};
pub const CONTROL_FLAGS: [Flag<ControlFlags>; 8] = [
Flag {
name: "parenb",
flag: ControlFlags::PARENB,
show: true,
sane: false,
},
Flag {
name: "parodd",
flag: ControlFlags::PARODD,
show: true,
sane: false,
},
Flag {
name: "cmspar",
flag: ControlFlags::CMSPAR,
show: true,
sane: false,
},
Flag {
name: "hupcl",
flag: ControlFlags::HUPCL,
show: true,
sane: true,
},
Flag {
name: "cstopb",
flag: ControlFlags::CSTOPB,
show: true,
sane: false,
},
Flag {
name: "cread",
flag: ControlFlags::CREAD,
show: true,
sane: true,
},
Flag {
name: "clocal",
flag: ControlFlags::CLOCAL,
show: true,
sane: false,
},
Flag {
name: "crtscts",
flag: ControlFlags::CRTSCTS,
show: true,
sane: false,
},
];
pub const INPUT_FLAGS: [Flag<InputFlags>; 15] = [
Flag {
name: "ignbrk",
flag: InputFlags::IGNBRK,
show: true,
sane: false,
},
Flag {
name: "brkint",
flag: InputFlags::BRKINT,
show: true,
sane: true,
},
Flag {
name: "ignpar",
flag: InputFlags::IGNPAR,
show: true,
sane: false,
},
Flag {
name: "parmrk",
flag: InputFlags::PARMRK,
show: true,
sane: false,
},
Flag {
name: "inpck",
flag: InputFlags::INPCK,
show: true,
sane: false,
},
Flag {
name: "istrip",
flag: InputFlags::ISTRIP,
show: true,
sane: false,
},
Flag {
name: "inlcr",
flag: InputFlags::INLCR,
show: true,
sane: false,
},
Flag {
name: "igncr",
flag: InputFlags::IGNCR,
show: true,
sane: false,
},
Flag {
name: "icrnl",
flag: InputFlags::ICRNL,
show: true,
sane: true,
},
Flag {
name: "ixoff",
flag: InputFlags::IXOFF,
show: true,
sane: false,
},
Flag {
name: "tandem",
flag: InputFlags::IXOFF,
show: false,
sane: false,
},
Flag {
name: "ixon",
flag: InputFlags::IXON,
show: true,
sane: false,
},
// not supported by nix
// Flag {
// name: "iuclc",
// flag: InputFlags::IUCLC,
// show: true,
// default: false,
// },
Flag {
name: "ixany",
flag: InputFlags::IXANY,
show: true,
sane: false,
},
Flag {
name: "imaxbel",
flag: InputFlags::IMAXBEL,
show: true,
sane: true,
},
Flag {
name: "iutf8",
flag: InputFlags::IUTF8,
show: true,
sane: false,
},
];
pub const OUTPUT_FLAGS: [Flag<OutputFlags>; 8] = [
Flag {
name: "opost",
flag: OutputFlags::OPOST,
show: true,
sane: true,
},
Flag {
name: "olcuc",
flag: OutputFlags::OLCUC,
show: true,
sane: false,
},
Flag {
name: "ocrnl",
flag: OutputFlags::OCRNL,
show: true,
sane: false,
},
Flag {
name: "onlcr",
flag: OutputFlags::ONLCR,
show: true,
sane: true,
},
Flag {
name: "onocr",
flag: OutputFlags::ONOCR,
show: true,
sane: false,
},
Flag {
name: "onlret",
flag: OutputFlags::ONLRET,
show: true,
sane: false,
},
Flag {
name: "ofill",
flag: OutputFlags::OFILL,
show: true,
sane: false,
},
Flag {
name: "ofdel",
flag: OutputFlags::OFDEL,
show: true,
sane: false,
},
];
pub const LOCAL_FLAGS: [Flag<LocalFlags>; 18] = [
Flag {
name: "isig",
flag: LocalFlags::ISIG,
show: true,
sane: true,
},
Flag {
name: "icanon",
flag: LocalFlags::ICANON,
show: true,
sane: true,
},
Flag {
name: "iexten",
flag: LocalFlags::IEXTEN,
show: true,
sane: true,
},
Flag {
name: "echo",
flag: LocalFlags::ECHO,
show: true,
sane: true,
},
Flag {
name: "echoe",
flag: LocalFlags::ECHOE,
show: true,
sane: true,
},
Flag {
name: "crterase",
flag: LocalFlags::ECHOE,
show: false,
sane: true,
},
Flag {
name: "echok",
flag: LocalFlags::ECHOK,
show: true,
sane: true,
},
Flag {
name: "echonl",
flag: LocalFlags::ECHONL,
show: true,
sane: false,
},
Flag {
name: "noflsh",
flag: LocalFlags::NOFLSH,
show: true,
sane: false,
},
// Not supported by nix
// Flag {
// name: "xcase",
// flag: LocalFlags::XCASE,
// show: true,
// sane: false,
// },
Flag {
name: "tostop",
flag: LocalFlags::TOSTOP,
show: true,
sane: false,
},
Flag {
name: "echoprt",
flag: LocalFlags::ECHOPRT,
show: true,
sane: false,
},
Flag {
name: "prterase",
flag: LocalFlags::ECHOPRT,
show: false,
sane: false,
},
Flag {
name: "echoctl",
flag: LocalFlags::ECHOCTL,
show: true,
sane: true,
},
Flag {
name: "ctlecho",
flag: LocalFlags::ECHOCTL,
show: false,
sane: true,
},
Flag {
name: "echoke",
flag: LocalFlags::ECHOKE,
show: true,
sane: true,
},
Flag {
name: "crtkill",
flag: LocalFlags::ECHOKE,
show: false,
sane: true,
},
Flag {
name: "flusho",
flag: LocalFlags::FLUSHO,
show: true,
sane: false,
},
Flag {
name: "extproc",
flag: LocalFlags::EXTPROC,
show: true,
sane: false,
},
];

1
src/uu/stty/src/main.rs Normal file
View file

@ -0,0 +1 @@
uucore::bin!(uu_stty);

240
src/uu/stty/src/stty.rs Normal file
View file

@ -0,0 +1,240 @@
// * This file is part of the uutils coreutils package.
// *
// * For the full copyright and license information, please view the LICENSE file
// * that was distributed with this source code.
// spell-checker:ignore tcgetattr tcsetattr tcsanow
mod flags;
use clap::{crate_version, Arg, ArgMatches, Command};
use nix::sys::termios::{
tcgetattr, tcsetattr, ControlFlags, InputFlags, LocalFlags, OutputFlags, Termios,
};
use std::io::{self, stdout};
use std::os::unix::io::{AsRawFd, RawFd};
use uucore::error::UResult;
use uucore::{format_usage, InvalidEncodingHandling};
use flags::{CONTROL_FLAGS, INPUT_FLAGS, LOCAL_FLAGS, OUTPUT_FLAGS};
const NAME: &str = "stty";
const USAGE: &str = "\
{} [-F DEVICE | --file=DEVICE] [SETTING]...
{} [-F DEVICE | --file=DEVICE] [-a|--all]
{} [-F DEVICE | --file=DEVICE] [-g|--save]";
const SUMMARY: &str = "Print or change terminal characteristics.";
pub struct Flag<T> {
name: &'static str,
flag: T,
show: bool,
sane: bool,
}
trait TermiosFlag {
fn is_in(&self, termios: &Termios) -> bool;
fn apply(&self, termios: &mut Termios, val: bool);
}
mod options {
pub const ALL: &str = "all";
pub const SAVE: &str = "save";
pub const FILE: &str = "file";
pub const SETTINGS: &str = "settings";
}
struct Options<'a> {
all: bool,
_save: bool,
file: RawFd,
settings: Option<Vec<&'a str>>,
}
impl<'a> Options<'a> {
fn from(matches: &'a ArgMatches) -> io::Result<Self> {
Ok(Self {
all: matches.is_present(options::ALL),
_save: matches.is_present(options::SAVE),
file: match matches.value_of(options::FILE) {
Some(_f) => todo!(),
None => stdout().as_raw_fd(),
},
settings: matches.values_of(options::SETTINGS).map(|v| v.collect()),
})
}
}
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let matches = uu_app().get_matches_from(args);
let opts = Options::from(&matches)?;
stty(&opts)
}
fn stty(opts: &Options) -> UResult<()> {
// TODO: Figure out the right error message
let mut termios = tcgetattr(opts.file).expect("Could not get terminal attributes");
if let Some(settings) = &opts.settings {
for setting in settings {
apply_setting(&mut termios, setting);
}
tcsetattr(opts.file, nix::sys::termios::SetArg::TCSANOW, &termios)
.expect("Could not write terminal attributes");
} else {
print_settings(&termios, opts);
}
Ok(())
}
fn print_settings(termios: &Termios, opts: &Options) {
if print_flags(termios, opts, &CONTROL_FLAGS) {
println!();
}
if print_flags(termios, opts, &INPUT_FLAGS) {
println!();
}
if print_flags(termios, opts, &OUTPUT_FLAGS) {
println!();
}
if print_flags(termios, opts, &LOCAL_FLAGS) {
println!();
}
}
fn print_flags<T>(termios: &Termios, opts: &Options, flags: &[Flag<T>]) -> bool
where
Flag<T>: TermiosFlag,
{
let mut printed = false;
for flag in flags {
if !flag.show {
continue;
}
let val = flag.is_in(termios);
if opts.all || val != flag.sane {
if !val {
print!("-");
}
print!("{} ", flag.name);
printed = true;
}
}
printed
}
fn apply_setting(termios: &mut Termios, s: &str) -> Option<()> {
if let Some(()) = apply_flag(termios, &CONTROL_FLAGS, s) {
return Some(());
}
if let Some(()) = apply_flag(termios, &INPUT_FLAGS, s) {
return Some(());
}
if let Some(()) = apply_flag(termios, &OUTPUT_FLAGS, s) {
return Some(());
}
if let Some(()) = apply_flag(termios, &LOCAL_FLAGS, s) {
return Some(());
}
None
}
fn apply_flag<T>(termios: &mut Termios, flags: &[Flag<T>], name: &str) -> Option<()>
where
T: Copy,
Flag<T>: TermiosFlag,
{
let (remove, name) = strip_hyphen(name);
find(flags, name)?.apply(termios, !remove);
Some(())
}
fn strip_hyphen(s: &str) -> (bool, &str) {
match s.strip_prefix('-') {
Some(s) => (true, s),
None => (false, s),
}
}
pub fn uu_app<'a>() -> Command<'a> {
Command::new(uucore::util_name())
.name(NAME)
.version(crate_version!())
.override_usage(format_usage(USAGE))
.about(SUMMARY)
.infer_long_args(true)
.arg(Arg::new(options::ALL).short('a').long(options::ALL))
.arg(Arg::new(options::SAVE).short('g').long(options::SAVE))
.arg(
Arg::new(options::FILE)
.short('F')
.long(options::FILE)
.takes_value(true)
.value_hint(clap::ValueHint::FilePath),
)
.arg(
Arg::new(options::SETTINGS)
.takes_value(true)
.multiple_values(true),
)
}
impl TermiosFlag for Flag<ControlFlags> {
fn is_in(&self, termios: &Termios) -> bool {
termios.control_flags.contains(self.flag)
}
fn apply(&self, termios: &mut Termios, val: bool) {
termios.control_flags.set(self.flag, val)
}
}
impl TermiosFlag for Flag<InputFlags> {
fn is_in(&self, termios: &Termios) -> bool {
termios.input_flags.contains(self.flag)
}
fn apply(&self, termios: &mut Termios, val: bool) {
termios.input_flags.set(self.flag, val)
}
}
impl TermiosFlag for Flag<OutputFlags> {
fn is_in(&self, termios: &Termios) -> bool {
termios.output_flags.contains(self.flag)
}
fn apply(&self, termios: &mut Termios, val: bool) {
termios.output_flags.set(self.flag, val)
}
}
impl TermiosFlag for Flag<LocalFlags> {
fn is_in(&self, termios: &Termios) -> bool {
termios.local_flags.contains(self.flag)
}
fn apply(&self, termios: &mut Termios, val: bool) {
termios.local_flags.set(self.flag, val)
}
}
fn find<'a, T>(flags: &'a [Flag<T>], flag_name: &str) -> Option<&'a Flag<T>>
where
T: Copy,
{
flags.iter().find_map(|flag| {
if flag.name == flag_name {
Some(flag)
} else {
None
}
})
}