use std::path::PathBuf;
use std::io::{self, Read};
use std::fs::{self, File};
use structopt::StructOpt;
use terminal_size::{Width, terminal_size};
use console::strip_ansi_codes;
use pulldown_cmark::{Parser, Options};
use syncat_stylesheet::Stylesheet;
mod dirs;
mod printer;
mod table;
mod words;
mod termpix;
use printer::Printer;
use words::Words;
#[derive(StructOpt, Debug)]
#[structopt(name = "paper")]
#[structopt(rename_all = "kebab-case")]
#[structopt(raw(setting = "structopt::clap::AppSettings::ColoredHelp"))]
pub struct Opts {
#[structopt(short, long, default_value="6")]
pub margin: usize,
#[structopt(short, long)]
pub h_margin: Option<usize>,
#[structopt(short, long)]
pub v_margin: Option<usize>,
#[structopt(short, long, default_value="92")]
pub width: usize,
#[structopt(short, long)]
pub plain: bool,
#[structopt(short, long, default_value="4")]
pub tab_length: usize,
#[structopt(short="u", long)]
pub hide_urls: bool,
#[structopt(short="i", long)]
pub no_images: bool,
#[structopt(short, long)]
pub syncat: bool,
#[structopt(long)]
pub dev: bool,
#[structopt(name="FILE", parse(from_os_str))]
pub files: Vec<PathBuf>,
}
fn normalize(tab_len: usize, source: &str) -> String {
source
.lines()
.map(|line| {
let mut len = 0;
let line = strip_ansi_codes(line);
if line.contains('\t') {
line.chars()
.flat_map(|ch| {
if ch == '\t' {
let missing = tab_len - (len % tab_len);
len += missing;
vec![' '; missing]
} else {
len += 1;
vec![ch]
}
})
.collect::<String>()
.into()
} else {
line
}
})
.map(|line| format!("{}\n", line))
.collect::<String>()
}
fn print<I>(opts: Opts, sources: I) where I: Iterator<Item=Result<String, std::io::Error>> {
let h_margin = opts.h_margin.unwrap_or(opts.margin);
let v_margin = opts.v_margin.unwrap_or(opts.margin);
let terminal_width = terminal_size().map(|(Width(width), _)| width).unwrap_or(opts.width as u16) as usize;
let width = usize::min(opts.width, terminal_width - 1);
if width < h_margin * 2 + 40 {
eprintln!("The width is too short!");
return;
}
let centering = " ".repeat((terminal_width - width) / 2);
let stylesheet = File::open(dirs::syncat_config().join("style/active/md.syncat"))
.map_err(Into::into)
.and_then(|mut file| Stylesheet::from_reader(&mut file))
.unwrap_or_else(|_| include_str!("default.syncat").parse::<Stylesheet>().unwrap());
let paper_style = stylesheet.resolve_basic(&["paper"], None).build();
let shadow_style = stylesheet.resolve_basic(&["shadow"], None).build();
let blank_line = format!("{}", paper_style.paint(" ".repeat(width)));
let end_shadow = format!("{}", shadow_style.paint(" "));
let margin = format!("{}", paper_style.paint(" ".repeat(h_margin)));
let available_width = width - 2 * h_margin;
for source in sources {
let source = match source {
Ok(source) => normalize(opts.tab_length, &source),
Err(error) => {
println!("{}", error);
continue;
}
};
if opts.plain {
println!("{}{}", centering, blank_line);
for _ in 0..v_margin {
println!("{}{}{}", centering, blank_line, end_shadow);
}
for line in source.lines() {
let mut buffer = String::new();
let mut indent = None;
for word in Words::preserving_whitespace(line) {
if buffer.chars().count() + word.chars().count() > available_width {
println!(
"{}{}{}{}{}{}",
centering,
margin,
paper_style.paint(&buffer),
paper_style.paint(" ".repeat(available_width - buffer.chars().count())),
margin,
shadow_style.paint(" "),
);
buffer.clear();
}
if buffer.is_empty() {
if indent.is_none() {
let indent_len = word.chars().take_while(|ch| ch.is_whitespace()).count();
indent = Some(word[0..indent_len].to_string());
}
buffer.push_str(indent.as_ref().unwrap());
buffer.push_str(word.trim());
} else {
buffer.push_str(&word);
}
}
println!(
"{}{}{}{}{}{}",
centering,
margin,
paper_style.paint(&buffer),
paper_style.paint(" ".repeat(available_width - buffer.chars().count())),
margin,
shadow_style.paint(" "),
);
}
for _ in 0..v_margin {
println!("{}{}{}", centering, blank_line, end_shadow);
}
println!("{} {}", centering, shadow_style.paint(" ".repeat(width)));
} else if opts.dev {
let parser = Parser::new_ext(&source, Options::all());
for event in parser {
println!("{:?}", event);
}
} else {
let parser = Parser::new_ext(&source, Options::all());
println!("{}{}", centering, blank_line);
for _ in 0..v_margin {
println!("{}{}{}", centering, blank_line, end_shadow);
}
let mut printer = Printer::new(¢ering, &margin, available_width, &stylesheet, &opts);
for event in parser {
printer.handle(event);
}
for _ in 0..v_margin {
println!("{}{}{}", centering, blank_line, end_shadow);
}
println!("{} {}", centering, shadow_style.paint(" ".repeat(width)));
}
}
}
fn main() {
let opts = Opts::from_args();
if opts.files.is_empty() {
let mut string = String::new();
io::stdin().read_to_string(&mut string).unwrap();
print(opts, vec![Ok(string)].into_iter());
} else {
let sources = opts.files.clone()
.into_iter()
.map(|path| fs::read_to_string(&path),
);
print(opts, sources);
}
}