diff --git a/src/action_doc.rs b/src/action_doc.rs index 85c8a0a..eee0164 100644 --- a/src/action_doc.rs +++ b/src/action_doc.rs @@ -1,44 +1,22 @@ use std::collections::HashMap; use std::fmt::{Formatter, Write}; -use std::fs::File; -use serde::{Deserialize}; - -#[derive(Debug, Deserialize, PartialEq)] -pub struct GithubActionInput { - description: String, - default: Option, - #[serde(default)] - required: bool -} - -#[derive(Debug, Deserialize, PartialEq)] -pub struct GithubActionOutput { - description: String -} +use crate::github::action::{GithubAction, GithubActionInput, GithubActionOutput}; +use crate::markdown::{Markdown, MarkdownDocumented}; -#[derive(Debug, Deserialize, PartialEq)] -pub struct GithubAction { - name: String, - description: String, - author: Option, - inputs: Option>, - outputs: Option> +impl MarkdownDocumented for GithubActionOutput { + fn to_markdown(&self) -> Markdown { + todo!() + } } impl GithubAction { - pub fn parse(path: &String) -> Result> { - let f = File::open(path).unwrap(); - let action: GithubAction = serde_yaml::from_reader(f)?; - Ok(action) - } - - fn clean(s: String) -> String { + fn clean(s: &String) -> String { return s.trim() .replace("-", "‑") .replace(" ", " ") } - fn inputs_to_markdown(inputs: Option>) -> String { + fn inputs_to_markdown(inputs: &Option>) -> String { match inputs { Some(inputs) => { let mut mdown = String::new(); @@ -47,7 +25,7 @@ impl GithubAction { mdown.push_str("| :------------------- | :---------- | :------- |:--------------|\n"); for (name, input) in inputs { - let input_default = match input.default { + let input_default = match &input.default { Some(x) => format!("`{}`", x), None => " ".to_string() }; @@ -55,7 +33,7 @@ impl GithubAction { Self::clean(name), input.description.replace("\n", "
"), match input.required { true => "yes", false => "no" }, - Self::clean(input_default) + Self::clean(&input_default) ).unwrap(); } @@ -65,7 +43,7 @@ impl GithubAction { } } - fn outputs_to_markdown(outputs: Option>) -> String { + fn outputs_to_markdown(outputs: &Option>) -> String { match outputs { Some(inputs) => { let mut mdown = String::new(); @@ -83,19 +61,23 @@ impl GithubAction { } } - pub fn to_markdown(self) -> String { - let mut mdown = String::new(); +} + +impl MarkdownDocumented for GithubAction { + fn to_markdown(&self) -> Markdown { + let mut doc = Markdown::new(); + + doc.append_heading(&self.name); - mdown += &format!("# {}\n\n", self.name); - mdown += &self.description; + doc += &self.description; - mdown += "\n\n## Inputs\n"; - mdown += &Self::inputs_to_markdown(self.inputs); + doc += "\n\n## Inputs\n"; + doc += &Self::inputs_to_markdown(&self.inputs); - mdown += "\n## Outputs\n\n"; - mdown += &Self::outputs_to_markdown(self.outputs); + doc += "\n## Outputs\n\n"; + doc += &Self::outputs_to_markdown(&self.outputs); - return mdown + return doc } } diff --git a/src/github.rs b/src/github.rs new file mode 100644 index 0000000..0d20c0c --- /dev/null +++ b/src/github.rs @@ -0,0 +1,6 @@ +//! GitHub actions and workflow model. +//! +//! Provides serde based structs for GitHub's actions and workflow YAML. +//! +pub mod action; +pub mod workflow; diff --git a/src/github/action.rs b/src/github/action.rs new file mode 100644 index 0000000..4c3773c --- /dev/null +++ b/src/github/action.rs @@ -0,0 +1,34 @@ +use std::collections::HashMap; +use std::fs::File; +use serde::{Deserialize}; + +#[derive(Debug, Deserialize, PartialEq)] +pub struct GithubActionInput { + pub description: String, + pub default: Option, + #[serde(default)] + pub required: bool +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct GithubActionOutput { + pub description: String +} + +/// Represents a GitHub action. +#[derive(Debug, Deserialize, PartialEq)] +pub struct GithubAction { + pub name: String, + pub description: String, + pub author: Option, + pub inputs: Option>, + pub outputs: Option> +} + +impl GithubAction { + pub fn parse(path: &String) -> Result> { + let f = File::open(path).unwrap(); + let action: GithubAction = serde_yaml::from_reader(f)?; + Ok(action) + } +} diff --git a/src/github/workflow.rs b/src/github/workflow.rs new file mode 100644 index 0000000..9d4bc29 --- /dev/null +++ b/src/github/workflow.rs @@ -0,0 +1,84 @@ +use std::collections::HashMap; +use std::fmt::Formatter; +use std::fs::File; +use serde::{Deserialize}; + +#[derive(Debug, Deserialize, PartialEq)] +pub struct GitHubWorkflow { + pub name: String, + pub on: HashMap, + pub jobs: HashMap +} + +impl GitHubWorkflow { + pub fn parse(path: &String) -> Result> { + let f = File::open(path).unwrap(); + let action: GitHubWorkflow = serde_yaml::from_reader(f)?; + Ok(action) + } +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct GithubWorkflowInput { + pub description: String, + pub default: Option, + #[serde(default)] + pub required: bool, + #[serde(alias = "type")] + pub input_type: String +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct GithubWorkflowSecret { + pub description: String, + #[serde(default)] + pub required: bool +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct GithubWorkflowTriggerPayload { + #[serde(default)] + pub branches: Vec, + #[serde(default)] + pub paths: Vec, + #[serde(default)] + pub inputs: HashMap, + #[serde(default)] + pub secrets: HashMap +} + +#[derive(Debug, Deserialize, Eq, PartialEq, Hash)] +pub enum GithubWorkflowTrigger { + #[serde(alias = "pull_request")] + PullRequest, + #[serde(alias = "push")] + Push, + #[serde(alias = "workflow_call")] + WorkflowCall, + #[serde(alias = "workflow_dispatch")] + WorkflowDispatch +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct WorkflowJob { + pub name: String, + pub uses: Option, + #[serde(default)] + pub needs: Vec, + #[serde(default)] + pub steps: Vec +} + +#[derive(Debug, Deserialize, PartialEq)] +pub struct WorkflowJobStep { + pub id: Option, + pub name: Option, + pub run: Option, + pub uses: Option +} + +impl std::fmt::Display for GithubWorkflowTrigger { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} diff --git a/src/main.rs b/src/main.rs index 5d1969c..6e653e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,13 +2,15 @@ extern crate core; mod cli; mod action_doc; +mod github; mod markdown; mod workflow_docs; use std::fs; use std::path::Path; -use action_doc::GithubAction; -use crate::workflow_docs::GitHubWorkflow; +use github::action::GithubAction; +use github::workflow::GitHubWorkflow; +use crate::markdown::MarkdownDocumented; fn main() -> Result<(), Box> { let args = cli::parse_args(); @@ -18,7 +20,7 @@ fn main() -> Result<(), Box> { let gha = GithubAction::parse(&action_file) .expect("Unable to parse Github action"); let readme_path = Path::new(&action_file).to_path_buf().parent().unwrap().join("README.md"); - fs::write(readme_path.to_str().unwrap(), gha.to_markdown()).expect("Unable to write readme"); + fs::write(readme_path.to_str().unwrap(), gha.to_markdown().to_string()).expect("Unable to write readme"); } cli::Commands::Workflow { workflow_file} => { let workflow = GitHubWorkflow::parse(&workflow_file) @@ -30,7 +32,7 @@ fn main() -> Result<(), Box> { .join(&format!("{}.md", wf_path.file_stem().unwrap().to_str().unwrap())); println!("Writing workflow readme {:?}", &readme_path); - fs::write(readme_path.to_str().unwrap(), workflow.to_markdown()).expect("Unable to write readme"); + fs::write(readme_path.to_str().unwrap(), workflow.to_markdown().to_string()).expect("Unable to write readme"); } } diff --git a/src/markdown.rs b/src/markdown.rs index 1ea93f4..488746a 100644 --- a/src/markdown.rs +++ b/src/markdown.rs @@ -1,11 +1,15 @@ #![allow(dead_code)] use std::fmt::{Display, Formatter}; +use std::ops; +// Simple markdown document container. pub struct Markdown { doc: String } impl Markdown { + + // Returns a new [`Markdown`]. pub fn new() -> Markdown { Markdown { doc: String::new() } } @@ -52,6 +56,25 @@ impl Display for Markdown { } } +impl ops::Add<&str> for Markdown { + type Output = Markdown; + + fn add(mut self, rhs: &str) -> Self::Output { + self.append_text(rhs); + self + } +} + +impl ops::AddAssign<&str> for Markdown { + fn add_assign(&mut self, rhs: &str) { + self.append_text(rhs) + } +} + +pub trait MarkdownDocumented { + fn to_markdown(&self) -> Markdown; +} + pub fn backtick(s: &String) -> String { format!("`{}`", s) } diff --git a/src/workflow_docs.rs b/src/workflow_docs.rs index a07c090..57bb3c1 100644 --- a/src/workflow_docs.rs +++ b/src/workflow_docs.rs @@ -1,30 +1,13 @@ -mod inputs; mod jobs; mod triggers; -use std::collections::HashMap; -use std::fs::File; use heck::ToSnakeCase; -use serde::{Deserialize}; -use triggers::{GithubWorkflowTrigger, GithubWorkflowTriggerPayload }; -use jobs::WorkflowJob; +use crate::github::workflow::{GitHubWorkflow }; use crate::markdown::Markdown; +use crate::MarkdownDocumented; -#[derive(Debug, Deserialize, PartialEq)] -pub struct GitHubWorkflow { - pub name: String, - pub on: HashMap, - pub jobs: HashMap -} - -impl GitHubWorkflow { - pub fn parse(path: &String) -> Result> { - let f = File::open(path).unwrap(); - let action: GitHubWorkflow = serde_yaml::from_reader(f)?; - Ok(action) - } - - pub fn to_markdown(self) -> String { +impl MarkdownDocumented for GitHubWorkflow { + fn to_markdown(&self) -> Markdown { let mut doc = Markdown::new(); doc.append_heading(&self.name); @@ -35,16 +18,16 @@ impl GitHubWorkflow { doc.append_list(trigger_items); doc.append_new_lines(1); - for (trigger, payload) in &self.on { - doc.append_text(&payload.to_markdown(trigger)); + for trigger in &self.on { + doc.append_text(&trigger.to_markdown().to_string()); } // jobs doc.append_line("## Jobs\n"); for (_name, job) in &self.jobs { - doc.append_text(&job.to_markdown()); + doc.append_text(&job.to_markdown().to_string()); } - return doc.to_string(); + return doc } } diff --git a/src/workflow_docs/inputs.rs b/src/workflow_docs/inputs.rs deleted file mode 100644 index fe0f5ec..0000000 --- a/src/workflow_docs/inputs.rs +++ /dev/null @@ -1,18 +0,0 @@ -use serde::{Deserialize}; - -#[derive(Debug, Deserialize, PartialEq)] -pub struct GithubWorkflowInput { - pub description: String, - pub default: Option, - #[serde(default)] - pub required: bool, - #[serde(alias = "type")] - pub input_type: String -} - -#[derive(Debug, Deserialize, PartialEq)] -pub struct GithubWorkflowSecret { - pub description: String, - #[serde(default)] - pub required: bool -} diff --git a/src/workflow_docs/jobs.rs b/src/workflow_docs/jobs.rs index 3510ae0..6364178 100644 --- a/src/workflow_docs/jobs.rs +++ b/src/workflow_docs/jobs.rs @@ -1,26 +1,9 @@ -use serde::{Deserialize}; +use crate::github::workflow::WorkflowJob; use crate::markdown::Markdown; +use crate::MarkdownDocumented; -#[derive(Debug, Deserialize, PartialEq)] -pub struct WorkflowJob { - pub name: String, - pub uses: Option, - #[serde(default)] - pub needs: Vec, - #[serde(default)] - pub steps: Vec -} - -#[derive(Debug, Deserialize, PartialEq)] -pub struct WorkflowJobStep { - pub id: Option, - pub name: Option, - pub run: Option, - pub uses: Option -} - -impl WorkflowJob { - pub fn to_markdown(&self) -> String { +impl MarkdownDocumented for WorkflowJob { + fn to_markdown(&self) -> Markdown { let mut d = Markdown::new(); d.append_text(&format!("### {} ", &self.name)); @@ -57,6 +40,6 @@ impl WorkflowJob { d.append_new_lines(1); } - return d.to_string() + return d } } diff --git a/src/workflow_docs/triggers.rs b/src/workflow_docs/triggers.rs index 3fff6ed..9efa502 100644 --- a/src/workflow_docs/triggers.rs +++ b/src/workflow_docs/triggers.rs @@ -1,43 +1,11 @@ -use std::collections::HashMap; -use std::fmt::{Formatter}; use indoc::indoc; -use serde::{Deserialize}; use crate::markdown::{backtick, Markdown}; -use super::inputs::{GithubWorkflowInput, GithubWorkflowSecret}; - -#[derive(Debug, Deserialize, PartialEq)] -pub struct GithubWorkflowTriggerPayload { - #[serde(default)] - pub branches: Vec, - #[serde(default)] - pub paths: Vec, - #[serde(default)] - pub inputs: HashMap, - #[serde(default)] - pub secrets: HashMap -} - -#[derive(Debug, Deserialize, Eq, PartialEq, Hash)] -pub enum GithubWorkflowTrigger { - #[serde(alias = "pull_request")] - PullRequest, - #[serde(alias = "push")] - Push, - #[serde(alias = "workflow_call")] - WorkflowCall, - #[serde(alias = "workflow_dispatch")] - WorkflowDispatch -} - -impl std::fmt::Display for GithubWorkflowTrigger { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} +use crate::github::workflow::{GithubWorkflowTrigger, GithubWorkflowTriggerPayload}; +use crate::MarkdownDocumented; impl GithubWorkflowTriggerPayload { - fn doc_pull_request(&self) -> String { + fn doc_pull_request(&self) -> Markdown { let mut doc = Markdown::new(); doc.append_line("### Pull Request"); @@ -54,10 +22,10 @@ impl GithubWorkflowTriggerPayload { doc.append_list(paths); } - return doc.to_string(); + return doc } - fn doc_workflow_call(&self) -> String { + fn doc_workflow_call(&self) -> Markdown { let mut mdown = String::new(); mdown.push_str(indoc!(" ### Workflow Call @@ -110,18 +78,25 @@ impl GithubWorkflowTriggerPayload { mdown.push_str("No secrets.") } - return mdown + let mut doc = Markdown::new(); + + doc.append_text(&mdown); + + return doc } +} - pub fn to_markdown(&self, trigger: &GithubWorkflowTrigger) -> String { +impl MarkdownDocumented for (&GithubWorkflowTrigger, &GithubWorkflowTriggerPayload){ + fn to_markdown(&self) -> Markdown { + let (trigger, payload) = self; return match trigger { GithubWorkflowTrigger::PullRequest => { - self.doc_pull_request() + payload.doc_pull_request() }, GithubWorkflowTrigger::WorkflowCall => { - self.doc_workflow_call() + payload.doc_workflow_call() } - _ => { format!("") } + _ => { Markdown::new() } } } }