syntax-html 0.2.0

A simple cli application for generating syntax-highlighted HTML of code files
use std::cell::LazyCell;
use std::cmp::PartialEq;
use std::collections::HashSet;
use std::path::Path;
use syntect::html::{ClassStyle, ClassedHTMLGenerator};
use syntect::parsing::SyntaxSet;
use syntect::util::LinesWithEndings;

#[derive(PartialEq)]
enum HelpType {
    Abbreviated,
    Full,
}

/// Build a vector of LazyCell from a collection of getters.
/// Useful because it gives full ownership of the resulting values,
/// which is difficult when iterating over the getters directly.
fn lazy_vec<T, F: FnOnce() -> T, C>(getters: C) -> Vec<LazyCell<T, F>> where C: IntoIterator<Item = F>{
    getters.into_iter().map(|getter| LazyCell::new(getter)).collect()
}

fn syntax_sets() -> Vec<LazyCell<SyntaxSet>> {
    lazy_vec([
        || SyntaxSet::load_defaults_newlines(),
        || two_face::syntax::extra_newlines(),
    ])
}

fn print_help(arg0: &str, help_type: HelpType) {
    print!(" Usage: {} <filename> [extension]

 If the syntax cannot be readily (or correctly) determined by the file's extension,
 an explicit one should be provided as the second argument.

  -h, --help    Display this help text
  --help-all    Also lists all supported extensions
", arg0);

    if help_type == HelpType::Abbreviated {
        return;
    }

    let syntax_sets = syntax_sets();

    let all_extensions = syntax_sets.iter()
        .flat_map(|set| set.syntaxes())
        .flat_map(|syntax| &syntax.file_extensions);

    let unique_extensions: HashSet<&String> = HashSet::from_iter(all_extensions);
    let mut unique_extensions: Vec<&String> = unique_extensions.into_iter().collect();
    unique_extensions.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase()));

    let mut line_length = 0;
    let joiner = ", ";
    let indent = "  ";

    print!("\n Supported extensions:\n\n{}", indent);

    let mut iter = unique_extensions.iter().peekable();
    while let Some(ext) = iter.next() {
        line_length += ext.len() + joiner.len();
        if line_length > 80 {
            print!("\n{}", indent);
            line_length = 0;
        }
        print!("{}", ext);
        if iter.peek().is_some() {
            print!("{}", joiner);
        }
    }
}

fn main() {
    let program_name = std::env::args().next().unwrap_or("syntax-html".into());
    let mut args = std::env::args().skip(1);

    let file = args.next().expect("filename argument should be provided");
    if file == "-h" || file == "--help" {
        print_help(&program_name, HelpType::Abbreviated);
        return;
    }
    if file == "--help-all" {
        print_help(&program_name, HelpType::Full);
        return;
    }

    let ext = args.next().or_else(|| {
        Path::new(&file).extension()
            .and_then(|os_str| os_str.to_str())
            .map(|str| str.to_owned())
    }).expect("a valid extension should be on the file or an explicit extension argument should be provided");

    let syntax_sets = syntax_sets();
    let (syntax_set, syntax) = syntax_sets.iter().filter_map(|set| {
        match set.find_syntax_by_extension(&ext) {
            Some(syn) => Some((set, syn)),
            None => None,
        }
    }).next().expect(&format!("no syntax found in syntect or two_face or extension: '{}'", ext));

    let mut html_generator = ClassedHTMLGenerator::new_with_class_style(syntax, syntax_set, ClassStyle::Spaced);

    let code = std::fs::read_to_string(file).expect("reading file to string");
    for line in LinesWithEndings::from(&code) {
        html_generator.parse_html_for_line_which_includes_newline(line).expect("generate html line");
    }

    println!("{}", html_generator.finalize());
}