catalist 0.8.0

Cat, but for lists
Documentation
//! # Catalist
//!
//! The catalist utility reads files sequentially, writing list items to stdout.
//! If a file is a single dash (`-`) or absent, catalist reads from stdin.
//!
//! Blank lines and comments are ignored. A comment is anything after a
//! hash (`#`). Inline comments must also be preceeded by whitespace. For example,
//! `this#line` would be printed, while for `this #line`, only `this` would be printed.
//!
//! By default, lines containing whitespace are surrounded by quotes,
//! though this can be configured by specifying an option for `--quotes`, as
//! shown below.
//!
//! # Arguments
//!
//! `-q/--quotes <logic>`
//!
//! Configure the quotation logic. `logic` can be any of the following:
//!
//! * `auto` - Only quote items that contain whitepsace
//! * `always` - Quote every item, regardless of content
//! * `never` - Print each item as-is
//!
//! If omitted, `auto` is assumed. If present but not one of those options,
//! the program will exit with code 1.

use catalist::{content_of, file_reader, QuoteStyle};
use std::io::BufRead;
use std::io::{self, Write};
use structopt::{clap::crate_version, StructOpt};

#[derive(StructOpt, Debug)]
#[structopt(
    name = "catalist",
    version = crate_version!(),
    author = "Robert C. <me@rob.ac>"
)]
struct Args {
    #[structopt(
        help = "Read input from files (stdin if omitted or - )",
        allow_hyphen_values = true,
        default_value = "-",
    )]
    files: Vec<String>,

    #[structopt(
        help = "Set naive quotation logic",
        short, long,
        parse(from_str),
        case_insensitive = true,
        possible_values = &QuoteStyle::variants(),
        default_value = "auto",
    )]
    quotes: QuoteStyle,
}

#[paw::main]
fn main(args: Args) {
    let mut stdout = io::stdout();
    let mut exit_code = 0;

    for file in args.files {
        // If we can't get a valid reader, output an error but don't
        // stop the program - just set the exit code.
        let reader = match file_reader(&file) {
            Ok(rdr) => rdr,
            Err(_) => {
                eprintln!("'{}': unable to read file", file);
                exit_code = 1;
                continue;
            }
        };

        for line in reader.lines().filter_map(Result::ok) {
            let content = content_of(&line);
            if let Some(text) = content {
                let _ = writeln!(stdout, "{}", args.quotes.naive_surround(text));
            }
        }
    }

    std::process::exit(exit_code);
}