Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add util: tabs #21

Merged
merged 3 commits into from
Mar 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ https://github.com/jgarzik/posixutils
- [ ] strings
- [ ] strip (Development)
- [x] stty
- [ ] tabs
- [x] tabs
- [ ] tail
- [ ] talk
- [x] tee
Expand Down
1 change: 1 addition & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Translations

* Standard OS error texts must be translated
* Clap error messages must be translated

## OS errors

Expand Down
9 changes: 3 additions & 6 deletions file/src/cat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut exit_code = 0;

for filename in &args.files {
match cat_file(filename) {
Ok(()) => {}
Err(e) => {
exit_code = 1;
eprintln!("{}: {}", filename, e);
}
if let Err(e) = cat_file(filename) {
exit_code = 1;
eprintln!("{}: {}", filename, e);
}
}

Expand Down
16 changes: 16 additions & 0 deletions plib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ pub const PROJECT_NAME: &'static str = "posixutils-rs";

pub const BUFSZ: usize = 8 * 1024;

pub const TERM_VAR: &'static str = "TERM";
pub const DEFAULT_TERM: &'static str = "vt100";

pub struct TestPlan {
pub cmd: String,
pub args: Vec<String>,
Expand Down Expand Up @@ -50,3 +53,16 @@ pub fn run_test(plan: TestPlan) {
assert!(output.status.success());
assert_eq!(output.status.code(), Some(0));
}

pub fn get_terminal() -> String {
let term: String = match std::env::var(TERM_VAR) {
Ok(val) => val,
Err(_) => String::new(),
};

if term.is_empty() {
String::from(DEFAULT_TERM)
} else {
term
}
}
4 changes: 4 additions & 0 deletions screen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ libc = "0.2"
name = "stty"
path = "src/stty.rs"

[[bin]]
name = "tabs"
path = "src/tabs.rs"

[[bin]]
name = "tput"
path = "src/tput.rs"
Expand Down
229 changes: 229 additions & 0 deletions screen/src/tabs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
//
// Copyright (c) 2024 Jeff Garzik
//
// This file is part of the posixutils-rs project covered under
// the MIT License. For the full license text, please see the LICENSE
// file in the root directory of this project.
// SPDX-License-Identifier: MIT
//
// TODO:
// - (efficiency): collect all output in a buffer, then write to stdout
// - Research if 100 is a POSIX-compliant limit for MAX_STOPS
//

extern crate clap;
extern crate plib;

use clap::Parser;
use gettextrs::{bind_textdomain_codeset, gettext, textdomain};
use plib::PROJECT_NAME;
use std::io::{self, Error, ErrorKind, Write};
use terminfo::{capability as cap, Database};

// arbitrarily chosen. todo: search if POSIX-ly correct.
const MAX_STOPS: usize = 100;

/// tabs - set terminal tabs
#[derive(Parser, Debug)]
#[command(author, version, about, long_about)]
struct Args {
/// Indicate the type of terminal.
#[arg(short = 'T', long)]
term: Option<String>,

/// Specify repetitive tab stops separated by (1) columns
#[arg(short = '1', long)]
rep_1: bool,

/// Specify repetitive tab stops separated by (2) columns
#[arg(short = '2', long)]
rep_2: bool,

/// Specify repetitive tab stops separated by (3) columns
#[arg(short = '3', long)]
rep_3: bool,

/// Specify repetitive tab stops separated by (4) columns
#[arg(short = '4', long)]
rep_4: bool,

/// Specify repetitive tab stops separated by (5) columns
#[arg(short = '5', long)]
rep_5: bool,

/// Specify repetitive tab stops separated by (6) columns
#[arg(short = '6', long)]
rep_6: bool,

/// Specify repetitive tab stops separated by (7) columns
#[arg(short = '7', long)]
rep_7: bool,

/// Specify repetitive tab stops separated by (8) columns
#[arg(short = '8', long)]
rep_8: bool,

/// Specify repetitive tab stops separated by (9) columns
#[arg(short = '9', long)]
rep_9: bool,

/// Assembler, applicable to some mainframes.
/// 1=[1,10,16,36,72]
/// 2=[1,10,16,40,72]
#[arg(short, long, default_missing_value="1", value_parser = clap::value_parser!(u8).range(1..=2))]
assembler: Option<u8>,

/// COBOL, normal and compact formats.
/// 1=[1,8,12,16,20,55]
/// 2=[1,6,10,14,49]
/// 3=[1,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,67]
#[arg(short, long, default_missing_value="1", value_parser = clap::value_parser!(u8).range(1..=3))]
cobol: Option<u8>,

/// FORTAN: [1,7,11,15,19,23]
#[arg(short, long)]
fortran: bool,

/// SNOBOL: [1,10,55]
#[arg(short, long)]
snobol: bool,

/// Assembler, applicable to some mainframes. [1,12,20,44]
#[arg(short = 'u')]
assembler_u: bool,

/// PL/1: [1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61]
#[arg(short, long)]
pl1: bool,

/// Optional: A single command line argument that consists of one or more tab-stop values (n) separated by a separator character
tabstops: Option<String>,
}

fn parse_cmd_line(args: &Args) -> Result<Vec<u16>, &'static str> {
let mut tabstops: Vec<u16> = Vec::new();
let mut repeating_stop: Option<u16> = None;

if args.rep_9 {
repeating_stop = Some(9);
} else if args.rep_8 {
repeating_stop = Some(8);
} else if args.rep_7 {
repeating_stop = Some(7);
} else if args.rep_6 {
repeating_stop = Some(6);
} else if args.rep_5 {
repeating_stop = Some(5);
} else if args.rep_4 {
repeating_stop = Some(4);
} else if args.rep_3 {
repeating_stop = Some(3);
} else if args.rep_2 {
repeating_stop = Some(2);
} else if args.rep_1 {
repeating_stop = Some(1);
} else if let Some(variant) = args.assembler {
match variant {
1 => tabstops = vec![1, 10, 16, 36, 72],
2 => tabstops = vec![1, 10, 16, 40, 72],
_ => unreachable!(),
}
} else if let Some(variant) = args.cobol {
match variant {
1 => tabstops = vec![1, 8, 12, 16, 20, 55],
2 => tabstops = vec![1, 6, 10, 14, 49],
3 => {
tabstops = vec![
1, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, 54, 58, 62, 67,
]
}
_ => unreachable!(),
}
} else if args.fortran {
tabstops = vec![1, 7, 11, 15, 19, 23];
} else if args.pl1 {
tabstops = vec![1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61];
} else if args.snobol {
tabstops = vec![1, 10, 55];
} else if args.assembler_u {
tabstops = vec![1, 12, 20, 44];
} else if let Some(ref tabstops_str) = args.tabstops {
for stop in tabstops_str.split(',') {
tabstops.push(
stop.parse()
.expect(gettext("Invalid tabstop value.").as_str()),
);
}
}

// handle repetitive tab stops
if let Some(stop_n) = repeating_stop {
for _i in 0..MAX_STOPS {
tabstops.push(stop_n);
}
}

// validate that stops are in strictly ascending order
for i in 1..tabstops.len() {
if tabstops[i] <= tabstops[i - 1] {
return Err("Tabstops must be in strictly ascending order.");
}
}

Ok(tabstops)
}

// set hardware tabs.
fn set_hw_tabs(info: &Database, tabstops: &Vec<u16>) -> io::Result<()> {
let clear_cap = info.get::<cap::ClearAllTabs>();
let set_cap = info.get::<cap::SetTab>();

if clear_cap.is_none() || set_cap.is_none() {
let msg = gettext("Terminal does not support hardware tabs.");
return Err(Error::new(ErrorKind::Other, msg));
}
let clear_cap = clear_cap.unwrap();
let set_cap = set_cap.unwrap();

// clear existing tabs
if let Err(e) = clear_cap.expand().to(io::stdout()) {
let msg = format!("{}: {}", gettext("Failed to clear tabs"), e);
return Err(Error::new(ErrorKind::Other, msg));
}

// set new tabs
let mut col = 0;
for stop in tabstops {
let stop = *stop as usize;

while col < stop {
io::stdout().write_all(b" ")?;
col += 1;
}

if let Err(e) = set_cap.expand().to(io::stdout()) {
let msg = format!("{}: {}", gettext("Failed to set tab"), e);
return Err(Error::new(ErrorKind::Other, msg));
}
}

Ok(())
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
// parse command line arguments
let args = Args::parse();

textdomain(PROJECT_NAME)?;
bind_textdomain_codeset(PROJECT_NAME, "UTF-8")?;

let info = match args.term {
None => Database::from_env().unwrap(),
Some(ref termtype) => Database::from_name(termtype).unwrap(),
};

let tabstops = parse_cmd_line(&args)?;
set_hw_tabs(&info, &tabstops)?;

Ok(())
}
9 changes: 3 additions & 6 deletions sys/src/ipcrm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

let mut exit_code = 0;

match remove_ipcs(&args) {
Ok(()) => {}
Err(e) => {
exit_code = 1;
eprintln!("{}", e);
}
if let Err(e) = remove_ipcs(&args) {
exit_code = 1;
eprintln!("{}", e);
}

std::process::exit(exit_code)
Expand Down
9 changes: 3 additions & 6 deletions text/src/asa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut exit_code = 0;

for filename in &args.files {
match asa_file(filename) {
Ok(()) => {}
Err(e) => {
exit_code = 1;
eprintln!("{}: {}", filename, e);
}
if let Err(e) = asa_file(filename) {
exit_code = 1;
eprintln!("{}: {}", filename, e);
}
}

Expand Down
9 changes: 3 additions & 6 deletions text/src/comm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,12 +169,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

let mut exit_code = 0;

match comm_file(mask, lead_dup, &args.file1, &args.file2) {
Ok(()) => {}
Err(e) => {
exit_code = 1;
eprintln!("{}", e);
}
if let Err(e) = comm_file(mask, lead_dup, &args.file1, &args.file2) {
exit_code = 1;
eprintln!("{}", e);
}

std::process::exit(exit_code)
Expand Down
9 changes: 3 additions & 6 deletions text/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,12 +166,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut exit_code = 0;

for filename in &args.files {
match expand_file(&tablist, filename) {
Ok(()) => {}
Err(e) => {
exit_code = 1;
eprintln!("{}: {}", filename, e);
}
if let Err(e) = expand_file(&tablist, filename) {
exit_code = 1;
eprintln!("{}: {}", filename, e);
}
}

Expand Down
9 changes: 3 additions & 6 deletions text/src/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut exit_code = 0;

for filename in &args.files {
match fold_file(&args, filename) {
Ok(()) => {}
Err(e) => {
exit_code = 1;
eprintln!("{}: {}", filename, e);
}
if let Err(e) = fold_file(&args, filename) {
exit_code = 1;
eprintln!("{}: {}", filename, e);
}
}

Expand Down
9 changes: 3 additions & 6 deletions text/src/head.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut first = true;

for filename in &args.files {
match head_file(&args, filename, first, want_header) {
Ok(()) => {}
Err(e) => {
exit_code = 1;
eprintln!("{}: {}", filename, e);
}
if let Err(e) = head_file(&args, filename, first, want_header) {
exit_code = 1;
eprintln!("{}: {}", filename, e);
}

first = false;
Expand Down
Loading