cronista 0.1.0

cli tool for parsing and rendering cron expressions
Documentation
use clap::Parser;
use cronista::{self, Types};
use nom::{
    branch::alt,
    bytes::complete::{take_until, take_while},
    character::complete::not_line_ending,
    combinator::map,
    error::Error,
    multi::many_m_n,
    sequence::tuple,
    IResult,
};

#[macro_use]
extern crate prettytable;
use prettytable::Table;

#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
    #[arg(short, long)]
    crontab: String,
}

fn main() {
    let args = Args::parse();

    let output = CronTab::parser(&args.crontab).unwrap();

    let mut table = Table::new();

    table.add_row(row!["minute", output.1.time[0].as_str()]);
    table.add_row(row!["hour", output.1.time[1].as_str()]);
    table.add_row(row!["day", output.1.time[2].as_str()]);
    table.add_row(row!["Month", output.1.time[3].as_str()]);
    table.add_row(row!["day of week", output.1.time[4].as_str()]);
    table.add_row(row!["Script", output.1.shell_command.as_str()]);

    table.printstd();
}

#[derive(Debug, Clone)]
enum Time {
    Minute,
    Stunde,
    Tag,
    Monat,
    Wochentag,
}

impl Time {
    fn pos_to_time(size: usize) -> Self {
        match size {
            0 => Self::Minute,
            1 => Self::Stunde,
            2 => Self::Tag,
            3 => Self::Monat,
            4 => Self::Wochentag,
            5.. => panic!(),
        }
    }

    fn element_to_begin_and_end(input: Self) -> (u8, u8) {
        match input {
            Self::Minute => (0, 59),
            Self::Stunde => (0, 23),
            Self::Tag => (0, 31),
            Self::Monat => (1, 12),
            Self::Wochentag => (0, 6),
        }
    }
}

#[derive(Debug, Clone)]
struct TimeElement {
    types: Types,
    time: Time,
}

impl TimeElement {
    fn constructor(pos: usize, input: &str) -> IResult<&str, Self> {
        let (rest, elements) = Types::parser(input)?;

        Ok((
            rest,
            TimeElement {
                types: elements,
                time: Time::pos_to_time(pos),
            },
        ))
    }
}

#[derive(Debug)]
struct CronTab {
    time: Vec<String>,
    shell_command: String,
}

impl CronTab {
    fn parser(input: &str) -> IResult<&str, Self> {
        let (mut shell_command, time) = many_m_n(
            5,
            5,
            map(
                tuple((
                    take_while(|c| c == ' '),
                    alt((take_until::<&str, &str, Error<&str>>(" "), not_line_ending)),
                )),
                |parsed| parsed.1,
            ),
        )(input)?;
        shell_command = shell_command.trim();
        let time = time
            .iter()
            .enumerate()
            .map(|(pos, input)| TimeElement::constructor(pos, input))
            .collect::<Result<Vec<_>, _>>()?;
        let time = time
            .iter()
            .map(|inp| {
                let new_inp = inp.1.clone();
                let (begin, end) = Time::element_to_begin_and_end(new_inp.time);
                Types::render(new_inp.types, begin, end)
            })
            .collect::<Vec<String>>();

        Ok((
            "",
            CronTab {
                time,
                shell_command: shell_command.to_owned(),
            },
        ))
    }
}

trait Render {
    fn render(input: Self, begin: u8, limit: u8) -> String;

    fn render_all_x_steps(inp: u8, limit: u8, begin: u8) -> String;

    fn render_asterik() -> String;

    fn render_digit(input: u8) -> String;

    fn render_list(input: Vec<Self>, limit: u8, begin: u8) -> String
    where
        Self: Sized;

    fn render_range(inp_min: u8, inp_max: u8) -> String;
}

impl Render for Types {
    fn render(input: Self, begin: u8, limit: u8) -> String {
        match input {
            Self::Asterik => Self::render_asterik(),
            Self::Digit(inp) => Self::render_digit(inp),
            Self::List(inp) => Self::render_list(inp, limit, begin),
            Self::AllXSteps(inp) => Self::render_all_x_steps(inp, limit, begin),
            Self::Range {
                min: inp_min,
                max: inp_max,
            } => Self::render_range(inp_min, inp_max),
        }
    }

    fn render_all_x_steps(inp: u8, limit: u8, begin: u8) -> String {
        (begin..=limit)
            .step_by(inp as usize)
            .map(|value| value.to_string())
            .collect::<Vec<String>>()
            .join(", ")
    }

    fn render_asterik() -> String {
        String::from("all times(*)")
    }

    fn render_digit(input: u8) -> String {
        input.to_string()
    }

    fn render_list(input: Vec<Self>, limit: u8, begin: u8) -> String {
        input
            .iter()
            .map(|parsed| Self::render(parsed.to_owned(), limit, begin))
            .collect::<Vec<String>>()
            .join(",")
    }

    fn render_range(inp_min: u8, inp_max: u8) -> String {
        (inp_min..=inp_max)
            .map(|value| value.to_string())
            .collect::<Vec<String>>()
            .join(", ")
    }
}