/* * Default behavior: * * aft < file.aft * pretty-prints as a table to stdout * * ls | aft | aft-sort * detects non-aft stdin and converts to aft on stdout */ use std::str::FromStr; use std::io::{self, Read, Write, BufRead}; use std::error::Error; use colored::*; #[derive(Debug)] enum AftFieldType { Str, Int, Float, Bool, Array, Null, Other(String), } impl FromStr for AftFieldType { type Err = Box; fn from_str(raw: &str) -> Result> { match raw { "" => Ok(AftFieldType::Null), "str" => Ok(AftFieldType::Str), "int" | "integer" => Ok(AftFieldType::Int), "float" => Ok(AftFieldType::Float), "bool" | "boolean" => Ok(AftFieldType::Bool), "array" => Ok(AftFieldType::Array), other => Ok(AftFieldType::Other(other.to_string())), } } } #[derive(Debug)] struct AftHeader { col_names: Vec, col_types: Option>, } fn main() -> Result<(), Box> { let mut stdout = io::stdout(); let stdin = io::stdin(); let mut stdin = stdin.lock(); // if we aren't connected to terminal, just pass through if !atty::is(atty::Stream::Stdout) { io::copy(&mut stdin, &mut stdout)?; return Ok(()) } // read first byte of input to check if we are getting AFT; if not just pass through let mut first_byte = [0; 1]; let got = stdin.read(&mut first_byte)?; if got != 1 { panic!("couldn't read a byte from stdin"); }; if first_byte[0] != 0x01 { stdout.write(&first_byte)?; io::copy(&mut stdin, &mut stdout)?; return Ok(()) } let mut stdin = io::BufReader::new(stdin); let mut header_bytes = vec![]; // TODO: sanity limit on length stdin.read_until(0x02, &mut header_bytes)?; if header_bytes[header_bytes.len()-1] != 0x02 { panic!("expected an AFT header"); }; let header_string = String::from_utf8_lossy(&header_bytes); let header_rows: Vec<&str> = header_string.splitn(2, "\n").collect(); let header = match header_rows.len() { 0 => panic!("expected a header"), 1 => AftHeader { col_names: header_rows[0].split("\x1E").map(|v| v.to_string()).collect(), col_types: None, }, _ => AftHeader { col_names: header_rows[0].split("\x1E").map(|v| v.to_string()).collect(), col_types: Some(header_rows[1].split("\x1E").map(|v| AftFieldType::from_str(v)).collect::, Box>>()?), }, }; let mut tw = tabwriter::TabWriter::new(stdout); writeln!(tw, "{}", header.col_names.join("\t").bold())?; for line in stdin.lines() { let line = line?; writeln!(tw, "{}", line.replace("\x1E", "\t").replace("\x1D", ""))?; } tw.flush()?; Ok(()) }