use std::collections::HashMap;
use std::fs::File;
use std::io::{Error, Read, Write};
use seahorse::{App, Command, Context};
use tailcall::tailcall;
use crate::bytes_iter::BytesIter;
use crate::constants::*;
use crate::error::*;
use crate::util::write_u8;
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum TabConf {
TabConstant(usize),
TabMap(usize, HashMap<usize, usize>),
}
pub fn detab_app() -> App {
App::new("detab")
.author("Brandon Elam Barker")
.action(run_detab_seahorse_action)
.command(run_detab_seahorse_cmd())
}
const DETAB_USAGE: &str = "detab SOURCE_FILE DEST_FILE";
pub fn run_detab_seahorse_cmd() -> Command {
Command::new("detab")
.description(
"detab: remove tabs from a file\
; output to STDOUT is the default",
)
.usage(DETAB_USAGE)
.action(run_detab_seahorse_action)
}
pub fn run_detab_seahorse_action(ctxt: &Context) {
let args = &mut ctxt.args.iter();
let src = args.next().user_err("detab: missing source");
let f_out: Box<dyn Write> = match args.next() {
Some(dst) => Box::new(
File::create(&dst)
.user_err(&format!("Couldn't open destination: {}", &dst)),
),
None => Box::new(std::io::stdout()),
};
run_detab(&src, f_out);
}
pub fn run_detab(src: &str, dst: Box<dyn Write>) {
detab(src, dst).user_err("Error in detab");
}
pub fn detab<W: Write>(src: &str, mut f_out: W) -> Result<(), Error> {
let f_in = File::open(&src).sfw_err("Couldn't open source")?;
let f_in_iter = BytesIter::new(f_in, DEFAULT_BUF_SIZE);
detab_go(
&TabConf::TabConstant(2),
&mut f_out,
f_in_iter,
vec![].into_iter(),
0,
)
}
pub fn tab_pos_to_space(tab_config: &TabConf, pos: usize) -> usize {
match tab_config {
TabConf::TabConstant(spcs) => *spcs,
TabConf::TabMap(tab_def, tmap) => *tmap.get(&pos).unwrap_or(tab_def),
}
}
const SPACE_ARRAY: [u8; 256] = [b' '; 256];
#[tailcall]
fn detab_go<'a, R, W>(
tab_cnf: &TabConf,
f_out: &mut W,
mut bytes_iter: BytesIter<R>,
mut buf_iter: std::vec::IntoIter<u8>,
tab_pos: usize,
) -> Result<(), Error>
where
R: Read,
W: Write,
{
match buf_iter.next() {
Some(byte) => {
let tab_pos_new = match byte {
b'\t' => {
let spc_count = tab_pos_to_space(tab_cnf, tab_pos);
f_out.write_all(&SPACE_ARRAY[0..spc_count])?;
tab_pos + 1
}
b'\n' => {
write_u8(f_out, byte)?;
0
}
_ => {
write_u8(f_out, byte)?;
tab_pos
}
};
detab_go(tab_cnf, f_out, bytes_iter, buf_iter, tab_pos_new)
}
None => {
match bytes_iter.next() {
Some(buf_new) => {
let buf_iter = buf_new?.into_iter();
detab_go(tab_cnf, f_out, bytes_iter, buf_iter, tab_pos)
}
None => Ok(()),
}
}
}
}
pub fn entab_app() -> App {
App::new("entab")
.author("Brandon Elam Barker")
.action(run_entab_seahorse_action)
.command(run_entab_seahorse_cmd())
}
const ENTAB_USAGE: &str = "entab SOURCE_FILE DEST_FILE";
pub fn run_entab_seahorse_cmd() -> Command {
Command::new("entab")
.description(
"entab: replace spaces with tabs in a file\
; output to STDOUT is the default",
)
.usage(ENTAB_USAGE)
.action(run_entab_seahorse_action)
}
pub fn run_entab_seahorse_action(ctxt: &Context) {
let args = &mut ctxt.args.iter();
let src = args.next().user_err("entab: missing source");
let f_out: Box<dyn Write> = match args.next() {
Some(dst) => Box::new(
File::create(&dst)
.user_err(&format!("Couldn't open destination: {}", &dst)),
),
None => Box::new(std::io::stdout()),
};
run_entab(&src, f_out);
}
pub fn run_entab(src: &str, dst: Box<dyn Write>) {
entab(src, dst).user_err("Error in entab");
}
pub fn entab<W: Write>(src: &str, mut f_out: W) -> Result<(), Error> {
let f_in = File::open(&src).sfw_err("Couldn't open source")?;
let f_in_iter = BytesIter::new(f_in, DEFAULT_BUF_SIZE);
entab_go(
&TabConf::TabConstant(2),
&mut f_out,
f_in_iter,
vec![].into_iter(),
0,
0,
)
}
#[tailcall]
fn entab_go<'a, R, W>(
tab_cnf: &TabConf,
f_out: &mut W,
mut bytes_iter: BytesIter<R>,
mut buf_iter: std::vec::IntoIter<u8>,
tab_pos: usize,
spc_count: usize,
) -> Result<(), Error>
where
R: Read,
W: Write,
{
match buf_iter.next() {
Some(byte) => {
let (tab_pos, spc_count) = match byte {
b' ' => (tab_pos + 1, spc_count + 1),
b'\n' => (0, 0),
b'\t' => (tab_pos + 1, spc_count),
_ => (tab_pos, spc_count),
};
let spaces_for_tab = tab_pos_to_space(tab_cnf, tab_pos);
let (tab_pos, spc_count) = if spc_count == spaces_for_tab {
write_u8(f_out, b'\t')?;
(tab_pos + 1, 0)
} else {
match byte {
b' ' => (tab_pos, spc_count),
_ => {
f_out.write_all(
&(0..spc_count).map(|_| b' ').collect::<Vec<u8>>(),
)?;
write_u8(f_out, byte)?;
(tab_pos, 0)
}
}
};
entab_go(tab_cnf, f_out, bytes_iter, buf_iter, tab_pos, spc_count)
}
None => {
match bytes_iter.next() {
Some(buf_new) => {
let buf_iter = buf_new?.into_iter();
entab_go(
tab_cnf, f_out, bytes_iter, buf_iter, tab_pos,
spc_count,
)
}
None => Ok(()),
}
}
}
}