Skip to content

Commit

Permalink
Implement graph option
Browse files Browse the repository at this point in the history
Fix #12
  • Loading branch information
himanoa committed Aug 21, 2021
1 parent 439a80c commit e35f9b5
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 0 deletions.
104 changes: 104 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,94 @@ pub fn parse_file(path: &PathBuf) -> Result<(), pest::error::Error<Rule>> {
}
}

pub fn parse_graph_stages(path: &PathBuf) -> Result<(), PestError<Rule>> {
use std::fs::File;
use std::io::Read;


match File::open(path) {
Ok(mut file) => {
let mut contents = String::new();

if let Err(e) = file.read_to_string(&mut contents) {
return Err(PestError::new_from_pos(
ErrorVariant::CustomError {
message: format!("{}", e),
},
pest::Position::from_start(""),
));
} else {
println!("{}", build_dot_file(stage_graphs(&contents)?));
Ok(())
}
}
Err(e) => {
return Err(PestError::new_from_pos(
ErrorVariant::CustomError {
message: format!("{}", e),
},
pest::Position::from_start(""),
));
}
}
}

fn build_dot_file<'a>(stages: Vec<&str>) -> String {
let nodes = stages.windows(2).fold("".to_string(), |acc, stage| {
let stage1 = stage[0];
let stage2 = stage[1];

if acc == "".to_string() {
return format!("\"{}\" -> \"{}\";", stage1, stage2);
}
format!("{}\"{}\" -> \"{}\";", acc, stage1, stage2)
});
format!(r#"digraph {{ {} }}"#, nodes)
}

fn stage_graphs(buffer: &str) -> Result<Vec<&str>, PestError<Rule>> {
fn get_stage_names(pairs: Pairs<Rule>) -> impl Iterator<Item = &str> + '_ {
pairs.flat_map(|stage| {
if let Rule::stage = stage.as_rule() {
if let Some(stage_name_span) = stage.into_inner().next() {
let stage_name = stage_name_span.as_str();
return Some(&stage_name[1..stage_name.len()-1]);
}
return None;
}
return None
})
}

if !is_declarative(buffer) {
return Err(PestError::new_from_pos(
ErrorVariant::CustomError {
message: "The buffer does not appear to be a Declarative Pipeline, I couldn't find pipeline { }".to_string(),
},
pest::Position::from_start(buffer),
));
}

let parser = PipelineParser::parse(Rule::pipeline, buffer)?;
if let Some(a) = parser.flat_map(|parsed| {
match parsed.as_rule() {
Rule::stagesDecl => {
Some(get_stage_names(parsed.into_inner()))
}
_ => None
}
}).next() {
return Ok(a.collect::<Vec<_>>());
} else {
return Err(PestError::new_from_pos(
ErrorVariant::CustomError {
message: "I couldn't find stages { }".to_string(),
},
pest::Position::from_start(buffer),
));
}
}

pub fn parse_pipeline_string(buffer: &str) -> Result<(), PestError<Rule>> {
if !is_declarative(buffer) {
return Err(PestError::new_from_pos(
Expand Down Expand Up @@ -525,4 +613,20 @@ pipeline {
.next()
.unwrap();
}

#[test]
fn test_stage_graphs() {
assert_eq!(
stage_graphs("pipeline{ agent any stages { stage('Build') { steps { sh 'printenv' }} stage('Test') { steps { sh 'cargo test' } } }}").unwrap(),
vec!["Build", "Test"]
)
}

#[test]
fn test_build_dot_file() {
assert_eq!(
build_dot_file(stage_graphs("pipeline{ agent any stages { stage('Build') { steps { sh 'printenv' }} stage('Test') { steps { sh 'cargo test' } } stage('Publish') { steps { sh 'cargo publish' } } }}").unwrap()),
r#"digraph { "Build" -> "Test";"Test" -> "Publish"; }"#
)
}
}
16 changes: 16 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ struct JdpOptions {
enum Command {
#[options(help = "Validate the syntax of a Jenkinsfile")]
Check(CheckOpts),
#[options(help = "Print the stages graph of a Jenkinsfile")]
Graph(GraphOpts)
}

// Options accepted for the `make` command
Expand All @@ -28,6 +30,15 @@ struct CheckOpts {
file: std::path::PathBuf,
}

#[derive(Debug, Options)]
struct GraphOpts {
#[options(help = "print help message")]
help: bool,
#[options(free, required, help = "Path to a Jenkinsfile")]
file: std::path::PathBuf,
}


/// The number of lines of context to show for errors
const LINES_OF_CONTEXT: usize = 4;

Expand Down Expand Up @@ -110,5 +121,10 @@ fn main() {
println!("Looks valid! Great work!");
}
}
Command::Graph(graphopts) => {
if let Err(_) = parse_graph_stages(&graphopts.file) {
std::process::exit(1);
}
}
}
}

0 comments on commit e35f9b5

Please sign in to comment.