mirror of
https://github.com/uutils/coreutils
synced 2024-12-12 22:32:53 +00:00
starting work on stty
This commit is contained in:
parent
87e3899477
commit
600cab0bd8
7 changed files with 610 additions and 0 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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
24
src/uu/stty/Cargo.toml
Normal 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
1
src/uu/stty/LICENSE
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../../LICENSE
|
332
src/uu/stty/src/flags.rs
Normal file
332
src/uu/stty/src/flags.rs
Normal 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
1
src/uu/stty/src/main.rs
Normal file
|
@ -0,0 +1 @@
|
|||
uucore::bin!(uu_stty);
|
240
src/uu/stty/src/stty.rs
Normal file
240
src/uu/stty/src/stty.rs
Normal 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
|
||||
}
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue