Skip to content

Commit

Permalink
ref: more clippy lints
Browse files Browse the repository at this point in the history
  • Loading branch information
YC committed Dec 3, 2023
1 parent b9092bc commit c161d8e
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 42 deletions.
14 changes: 11 additions & 3 deletions safe_ascii/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#![warn(clippy::pedantic)]
#![crate_name = "safe_ascii"]

/// Type for storing precomputed mapping between u8 to String.
/// (Subject to change)
pub struct AsciiMapping {
/// Mapping for each of the 256 possible u8 values.
mapping: [String; 256],
}

Expand All @@ -14,9 +16,10 @@ impl AsciiMapping {
/// let mut exclude: [bool; 256] = [false; 256];
/// let _ = AsciiMapping::new(&safe_ascii::map_to_mnemonic, exclude);
/// ```
#[must_use]
pub fn new(map_fn: &dyn Fn(u8) -> String, exclusion_list: [bool; 256]) -> Self {
// https://stackoverflow.com/questions/28656387
let mut result: [String; 256] = [(); 256].map(|_| String::default());
let mut result: [String; 256] = [(); 256].map(|()| String::default());

for i in 0u8..=255 {
if exclusion_list[i as usize] {
Expand All @@ -26,7 +29,7 @@ impl AsciiMapping {
}
}

AsciiMapping { mapping: result }
Self { mapping: result }
}

/// Convert a u8 according to the mapping.
Expand All @@ -37,6 +40,7 @@ impl AsciiMapping {
/// let mapping = AsciiMapping::new(&safe_ascii::map_to_mnemonic, exclude);
/// assert_eq!(mapping.convert_u8(0), "(NUL)");
/// ```
#[must_use]
pub fn convert_u8(&self, input: u8) -> &str {
&self.mapping[input as usize]
}
Expand All @@ -49,6 +53,7 @@ impl AsciiMapping {
/// let mapping = AsciiMapping::new(&safe_ascii::map_to_mnemonic, exclude);
/// assert_eq!(mapping.convert_u8_slice(&['h' as u8, ' ' as u8, 'i' as u8], 3), "h(SP)i");
/// ```
#[must_use]
pub fn convert_u8_slice(&self, input: &[u8], size: usize) -> String {
input[..size]
.iter()
Expand All @@ -73,6 +78,7 @@ impl AsciiMapping {
/// assert_eq!(safe_ascii::map_to_mnemonic('a' as u8), "a");
/// assert_eq!(safe_ascii::map_to_mnemonic('~' as u8), "~");
/// ```
#[must_use]
pub fn map_to_mnemonic(c: u8) -> String {
match c {
0 => "(NUL)".to_owned(),
Expand Down Expand Up @@ -128,6 +134,7 @@ pub fn map_to_mnemonic(c: u8) -> String {
/// assert_eq!(safe_ascii::map_to_escape('0' as u8), "\\x30");
/// assert_eq!(safe_ascii::map_to_escape('~' as u8), "\\x7e");
/// ```
#[must_use]
pub fn map_to_escape(c: u8) -> String {
format!("\\x{c:02x}")
}
Expand All @@ -148,10 +155,11 @@ pub fn map_to_escape(c: u8) -> String {
/// assert_eq!(safe_ascii::map_suppress('~' as u8), "~");
/// ```
// Map to escape sequence form
#[must_use]
pub fn map_suppress(c: u8) -> String {
match c {
33..=126 => (c as char).to_string(), // Printable
_ => "".to_owned(),
_ => String::new(),
}
}

Expand Down
92 changes: 53 additions & 39 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
#![warn(clippy::pedantic)]

use clap::Parser;
use core::cmp::min;
use safe_ascii::{map_suppress, map_to_escape, map_to_mnemonic, AsciiMapping};
use std::{
env,
env, error,
fs::File,
io::{self, BufReader, Read, Write},
process,
};

/// Mode of conversion/suppression.
#[derive(clap::ValueEnum, Clone)]
enum Mode {
/// Mnemonic representation, e.g. (NUL) for '\0'
Mnemonic,
/// Hex escape sequence, e.g. \x00 for '\0'
Escape,
/// Suppress non-printable
Suppress,
}

/// CLI Definition for clap
#[derive(Parser)]
#[command(author, version, about)]
struct Args {
Expand Down Expand Up @@ -66,47 +73,48 @@ Use '-' for stdin"
files: Vec<String>,
}

#[test]
fn verify_cli() {
use clap::CommandFactory;
Args::command().debug_assert()
}

fn main() -> Result<(), std::io::Error> {
fn main() -> Result<(), io::Error> {
let args = Args::parse();
let exclude = match parse_exclude(args.exclude) {
Ok(exclude) => exclude,
Err(e) => {
eprintln!("{}", e);
std::process::exit(1);
eprintln!("{e}");
process::exit(1);
}
};

let map_fn = match args.mode {
Mode::Mnemonic => map_to_mnemonic,
Mode::Escape => map_to_escape,
_ => map_suppress,
Mode::Suppress => map_suppress,
};
let mapping = AsciiMapping::new(&map_fn, exclude);

let mut truncate = args.truncate;

// If files are given, then use files; otherwise, use stdin
if !args.files.is_empty() {
if args.files.is_empty() {
// Early return if no more chars should be printed
if truncate == 0 {
return Ok(());
}

try_process(&mut io::stdin(), &mapping, &mut truncate)?;
} else {
for filename in &args.files {
// Early return if no more chars should be printed
if truncate == 0 {
return Ok(());
}

if filename == "-" {
try_process_file(&mut io::stdin(), &mapping, &mut truncate)?;
try_process(&mut io::stdin(), &mapping, &mut truncate)?;
continue;
}

let file = File::open(filename);
match file {
Ok(file) => try_process_file(&mut BufReader::new(file), &mapping, &mut truncate)?,
Ok(file) => try_process(&mut BufReader::new(file), &mapping, &mut truncate)?,
Err(err) => {
eprintln!(
"{}: {}: {}",
Expand All @@ -117,38 +125,32 @@ fn main() -> Result<(), std::io::Error> {
}
}
}
} else {
// Early return if no more chars should be printed
if truncate == 0 {
return Ok(());
}

try_process_file(&mut std::io::stdin(), &mapping, &mut truncate)?
}

Ok(())
}

fn try_process_file<R: Read>(
/// Wrapper for process function, to handle SIGPIPE.
fn try_process<R: Read>(
reader: &mut R,
mapping: &AsciiMapping,
truncate: &mut i128,
) -> Result<(), std::io::Error> {
if let Err(e) = process_file(reader, mapping, truncate) {
) -> Result<(), io::Error> {
if let Err(e) = process(reader, mapping, truncate) {
if e.kind() == io::ErrorKind::BrokenPipe {
std::process::exit(141);
}
Err(e)?
Err(e)?;
};
Ok(())
}

// Process files with Read trait
fn process_file<R: Read>(
/// Read from input reader, perform conversion, and write to stdout.
fn process<R: Read>(
reader: &mut R,
mapping: &AsciiMapping,
truncate: &mut i128,
) -> Result<(), std::io::Error> {
) -> Result<(), io::Error> {
let stdout = io::stdout();
let mut handle = stdout.lock();

Expand All @@ -161,39 +163,51 @@ fn process_file<R: Read>(
}

if *truncate < 0 {
// No truncate limit
handle.write_all(mapping.convert_u8_slice(&buf, n).as_bytes())?;
continue;
} else if *truncate >= n as i128 {
// Won't reach limit in this block
handle.write_all(mapping.convert_u8_slice(&buf, n).as_bytes())?;
} else if *truncate <= n as i128 {
*truncate -= n as i128;
} else {
// Will reach limit within this block
#[allow(clippy::cast_sign_loss)]
handle.write_all(
mapping
.convert_u8_slice(&buf, min(*truncate as usize, n))
.convert_u8_slice(&buf, *truncate as usize)
.as_bytes(),
)?;
*truncate = 0;
} else {
handle.write_all(mapping.convert_u8_slice(&buf, n).as_bytes())?;
*truncate -= n as i128;
}
}
Ok(())
}

// Parses exclude string
fn parse_exclude(s: Vec<String>) -> Result<[bool; 256], Box<dyn std::error::Error>> {
/// Parses exclude string
fn parse_exclude(exclusions: Vec<String>) -> Result<[bool; 256], Box<dyn error::Error>> {
// Initialize to false
let mut exclude: [bool; 256] = [false; 256];

// Split by comma, parse into int, set index of exclude array
for i in s {
if let Ok(i) = str::parse::<u8>(&i) {
for exclusion in exclusions {
if let Ok(i) = str::parse::<u8>(&exclusion) {
exclude[i as usize] = true;
} else {
Err(format!(
"Error: Encountered unparsable value \"{i}\" in exclusion list"
"Error: Encountered unparsable value \"{exclusion}\" in exclusion list"
))?;
}
}
Ok(exclude)
}

#[test]
fn verify_cli() {
use clap::CommandFactory;
Args::command().debug_assert()
}

#[cfg(test)]
mod cli {
use std::io::Write;
Expand Down

0 comments on commit c161d8e

Please sign in to comment.