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(", ")
}
}