aboutsummaryrefslogtreecommitdiffstats
path: root/src/bin
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin')
-rw-r--r--src/bin/aft-demo.rs35
-rw-r--r--src/bin/aft-head.rs8
-rw-r--r--src/bin/aft.rs105
3 files changed, 148 insertions, 0 deletions
diff --git a/src/bin/aft-demo.rs b/src/bin/aft-demo.rs
new file mode 100644
index 0000000..9a680c1
--- /dev/null
+++ b/src/bin/aft-demo.rs
@@ -0,0 +1,35 @@
+
+/*
+ * Writes example aft to stdout
+ */
+
+fn main() {
+
+ /*
+ * Sepal length Sepal width Petal length Petal width Species
+ * 5.1 3.5 1.4 0.2 I. setosa
+ * 4.9 3.0 1.4 0.2 I. setosa
+ * 4.7 3.2 1.3 0.2 I. setosa
+ * 4.6 3.1 1.5 0.2 I. setosa
+ * 5.0 3.6 1.4 0.2 I. setosa
+ */
+
+ // start header
+ print!("\x01");
+
+ // colum names
+ println!("{}", ["Sepal length", "Speal width", "Petal length", "Petal width", "Species"].join("\x1E"));
+
+ // colum types
+ println!("{}", ["float", "float", "float", "float", "str"].join("\x1E"));
+
+ // end header / start table
+ print!("\x02");
+
+ // print rows
+ println!("{}", ["5.1", "3.5", "1.4", "0.2", "I. setosa"].join("\x1E"));
+ println!("{}", ["4.9", "3.0", "1.4", "0.2", "I. setosa"].join("\x1E"));
+ println!("{}", ["4.7", "3.2", "1.3", "0.2", "I. setosa"].join("\x1E"));
+ println!("{}", ["4.6", "3.1", "1.5", "0.2", "I. setosa"].join("\x1E"));
+ println!("{}", ["5.0", "3.6", "1.4", "0.2", "I. setosa"].join("\x1E"));
+}
diff --git a/src/bin/aft-head.rs b/src/bin/aft-head.rs
new file mode 100644
index 0000000..b8c848c
--- /dev/null
+++ b/src/bin/aft-head.rs
@@ -0,0 +1,8 @@
+
+/*
+ * Takes the first n lines of input, passes to stdout.
+ */
+
+fn main() {
+ println!("Hello, world2!");
+}
diff --git a/src/bin/aft.rs b/src/bin/aft.rs
new file mode 100644
index 0000000..0ada921
--- /dev/null
+++ b/src/bin/aft.rs
@@ -0,0 +1,105 @@
+
+/*
+ * 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<dyn Error>;
+
+ fn from_str(raw: &str) -> Result<AftFieldType, Box<dyn Error>> {
+ 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<String>,
+ col_types: Option<Vec<AftFieldType>>,
+}
+
+fn main() -> Result<(), Box<dyn Error>> {
+
+ 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::<Result<Vec<AftFieldType>, Box<dyn Error>>>()?),
+ },
+ };
+
+ 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"))?;
+ }
+ tw.flush()?;
+
+ Ok(())
+}