#![cfg(feature = "build-binary")]
use colored::*;
use std::fs::File;
use std::io::{self, BufRead, BufReader, Lines, Stdin};
use std::path::{Path, PathBuf};
use textwrap::*;
use quick_error::ResultExt;
use clap::{AppSettings, FromArgMatches, IntoApp, Parser};
use untree::*;
fn main() {
let app = Args::into_app()
.global_setting(AppSettings::DeriveDisplayOrder)
.global_setting(AppSettings::NextLineHelp)
.setting(AppSettings::DisableHelpSubcommand)
.max_term_width(100);
let args = Args::from_arg_matches(&app.get_matches()).unwrap();
use Error::*;
if let Err(err) = run(args) {
match err {
MissingContext(err) => {
print_error("An error occured with unknown context.", err);
}
OnStdin(err) => {
print_error("An error occured while attempting to read from standard input.", err);
}
OnPath(path, action, err) => {
let path = path.to_str().unwrap_or("<unspeakable>").bold();
let action = action.describe(&path);
print_error(
format!("An error occured while attempting to {action}."),
err,
);
}
}
std::process::exit(1);
}
}
fn print_error(msg: impl std::fmt::Display, base_err: io::Error) {
let msg = format!("{msg}\n\nCause: {base_err}");
let width = termwidth();
let msg = fill(msg.as_str(), width - 4);
let msg = indent(msg.as_str(), " ");
let header = "ERROR:".bold().red();
eprintln!();
eprintln!("{header}");
eprintln!("{}", msg);
eprintln!();
}
fn run(args: Args) -> Result<()> {
let directory = args.dir.unwrap_or_else(|| "".into());
let options = UntreeOptions::new()
.dry_run(args.dry_run)
.verbose(args.verbose);
let tree_files = &args.tree_files;
if tree_files.is_empty() {
eprintln!("{}", "Reading tree from standard input".bold());
create_tree(&directory, read_stdin(), options).more_context(ReadStdin)
} else {
for path in tree_files {
let filename = path.to_str().unwrap_or("<unspeakable>");
if filename == "-" {
eprintln!("{}", "Reading tree from standard input".bold());
create_tree(&directory, read_stdin(), options)
.more_context(ReadStdin)?;
} else {
let path = path.strip_prefix("\\").unwrap_or(path);
let lines = read_lines(path)?;
eprintln!(
"{}",
format!("Reading tree from file '{filename}'").bold()
);
create_tree(&directory, lines, options)
.more_context(ReadFile.on(path))?;
}
}
Ok(())
}
}
fn read_stdin() -> Lines<BufReader<Stdin>> {
io::BufReader::new(io::stdin()).lines()
}
fn read_lines(path: &'_ Path) -> Result<Lines<BufReader<File>>> {
Ok(File::open(path)
.map(|file| io::BufReader::new(file).lines())
.context(OpenFileForReading.on(path))?)
}
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
pub struct Args {
#[clap(short, long("--dir"), parse(from_os_str))]
pub dir: Option<PathBuf>,
#[clap(parse(from_os_str))]
pub tree_files: Vec<PathBuf>,
#[clap(long)]
pub dry_run: bool,
#[clap(short, long)]
pub verbose: bool,
}