rew 0.3.0

A text processing CLI tool that rewrites FS paths according to a pattern.
Documentation
use crate::color::spec_color;
use crate::utils::{into_static_str, str_from_utf8};
use lazy_static::lazy_static;
use std::io::{Result, Write};
use termcolor::{Buffer, Color, WriteColor};

const HEADING_PREFIX: &str = "# ";
const PADDED_BLOCK_PREFIX: &str = "    ";
const SHELL_PREFIX: &str = "$>";

const CODE_CHAR: char = '`';
const COMMENT_CHAR: char = '#';

const PRIMARY_COLOR: Color = Color::Yellow;
const SECONDARY_COLOR: Color = Color::Cyan;
const CODE_COLOR: Color = Color::Green;

lazy_static! {
    static ref COLORED_HELP_ENABLED: bool = atty::is(atty::Stream::Stdout)
        && std::env::args().any(|arg| arg == "-h" || arg == "--help");
}

pub fn highlight_static(text: &'static str) -> &'static str {
    if *COLORED_HELP_ENABLED {
        highlight_to_string(text).map_or(text, into_static_str)
    } else {
        text
    }
}

fn highlight_to_string(text: &str) -> Result<String> {
    let mut buffer = Buffer::ansi();
    highlight(&mut buffer, text)?;
    str_from_utf8(buffer.as_slice()).map(String::from)
}

pub fn highlight<O: Write + WriteColor>(output: &mut O, text: &str) -> Result<()> {
    for line in text.lines() {
        if let Some(header) = line.strip_prefix(HEADING_PREFIX) {
            output.set_color(&spec_color(PRIMARY_COLOR))?;
            write!(output, "{}", header)?;
        } else if let Some(block) = line.strip_prefix(PADDED_BLOCK_PREFIX) {
            write!(output, "{}", PADDED_BLOCK_PREFIX)?;

            if let Some(command) = block.strip_prefix(SHELL_PREFIX) {
                output.set_color(&spec_color(SECONDARY_COLOR))?;
                write!(output, "{}", SHELL_PREFIX)?;
                output.set_color(&spec_color(CODE_COLOR))?;

                if let Some(comment_index) = command.rfind(COMMENT_CHAR) {
                    write!(output, "{}", &command[..comment_index])?;
                    output.set_color(&spec_color(SECONDARY_COLOR))?;
                    write!(output, "{}", &command[comment_index..])?;
                } else {
                    write!(output, "{}", command)?;
                }
            } else {
                output.set_color(&spec_color(SECONDARY_COLOR))?;
                write!(output, "{}", block)?;
            }
        } else {
            highlight_code(output, line)?;
        }

        output.reset()?;
        writeln!(output)?;
    }

    Ok(())
}

fn highlight_code<O: Write + WriteColor>(output: &mut O, line: &str) -> Result<()> {
    let mut in_code = false;
    let mut last_index = 0;

    for (index, char) in line.char_indices() {
        if char == CODE_CHAR {
            write!(output, "{}", &line[last_index..index])?;
            if in_code {
                output.reset()?;
            } else {
                output.set_color(&spec_color(CODE_COLOR))?;
            }
            last_index = index + 1;
            in_code = !in_code;
        }
    }

    write!(output, "{}", &line[last_index..])
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::testing::{ColoredOuput, OutputChunk};
    use claim::*;
    use indoc::indoc;

    const SAMPLE_HELP: &str = indoc! {"
        # Heading

        Text.
        Text with `code`.
        Text with `code` and `more code`.

            Padded block.
            Padded block with `code`.

        Text.

            $> ls -la
            $> ls -la # Shell comment
    "};

    #[test]
    fn highlight_to_string() {
        assert_gt!(super::highlight_to_string(SAMPLE_HELP).unwrap().len(), 0);
    }

    #[test]
    fn highlight() {
        let mut ouput = ColoredOuput::new();
        super::highlight(&mut ouput, SAMPLE_HELP).unwrap();
        assert_eq!(
            ouput.chunks(),
            &[
                OutputChunk::color(Color::Yellow, "Heading"),
                OutputChunk::plain("\n\nText.\nText with "),
                OutputChunk::color(Color::Green, "code"),
                OutputChunk::plain(".\nText with "),
                OutputChunk::color(Color::Green, "code"),
                OutputChunk::plain(" and "),
                OutputChunk::color(Color::Green, "more code"),
                OutputChunk::plain(".\n\n    "),
                OutputChunk::color(Color::Cyan, "Padded block."),
                OutputChunk::plain("\n    "),
                OutputChunk::color(Color::Cyan, "Padded block with `code`."),
                OutputChunk::plain("\n\nText.\n\n    "),
                OutputChunk::color(Color::Cyan, "$>"),
                OutputChunk::color(Color::Green, " ls -la"),
                OutputChunk::plain("\n    "),
                OutputChunk::color(Color::Cyan, "$>"),
                OutputChunk::color(Color::Green, " ls -la "),
                OutputChunk::color(Color::Cyan, "# Shell comment"),
                OutputChunk::plain("\n")
            ]
        );
    }
}