Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 24 additions & 42 deletions src/action_doc.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
#[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<String>,
inputs: Option<HashMap<String, GithubActionInput>>,
outputs: Option<HashMap<String, GithubActionOutput>>
impl MarkdownDocumented for GithubActionOutput {
fn to_markdown(&self) -> Markdown {
todo!()
}
}

impl GithubAction {
pub fn parse(path: &String) -> Result<GithubAction, Box<dyn std::error::Error>> {
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<HashMap<String, GithubActionInput>>) -> String {
fn inputs_to_markdown(inputs: &Option<HashMap<String, GithubActionInput>>) -> String {
match inputs {
Some(inputs) => {
let mut mdown = String::new();
Expand All @@ -47,15 +25,15 @@ 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()
};
writeln!(mdown, "| {:18} | {} | {} | {} |",
Self::clean(name),
input.description.replace("\n", "<br>"),
match input.required { true => "yes", false => "no" },
Self::clean(input_default)
Self::clean(&input_default)
).unwrap();
}

Expand All @@ -65,7 +43,7 @@ impl GithubAction {
}
}

fn outputs_to_markdown(outputs: Option<HashMap<String, GithubActionOutput>>) -> String {
fn outputs_to_markdown(outputs: &Option<HashMap<String, GithubActionOutput>>) -> String {
match outputs {
Some(inputs) => {
let mut mdown = String::new();
Expand All @@ -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
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/github.rs
Original file line number Diff line number Diff line change
@@ -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;
34 changes: 34 additions & 0 deletions src/github/action.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
#[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<String>,
pub inputs: Option<HashMap<String, GithubActionInput>>,
pub outputs: Option<HashMap<String, GithubActionOutput>>
}

impl GithubAction {
pub fn parse(path: &String) -> Result<GithubAction, Box<dyn std::error::Error>> {
let f = File::open(path).unwrap();
let action: GithubAction = serde_yaml::from_reader(f)?;
Ok(action)
}
}
84 changes: 84 additions & 0 deletions src/github/workflow.rs
Original file line number Diff line number Diff line change
@@ -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<GithubWorkflowTrigger, GithubWorkflowTriggerPayload>,
pub jobs: HashMap<String, WorkflowJob>
}

impl GitHubWorkflow {
pub fn parse(path: &String) -> Result<GitHubWorkflow, Box<dyn std::error::Error>> {
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<String>,
#[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<String>,
#[serde(default)]
pub paths: Vec<String>,
#[serde(default)]
pub inputs: HashMap<String, GithubWorkflowInput>,
#[serde(default)]
pub secrets: HashMap<String, GithubWorkflowSecret>
}

#[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<String>,
#[serde(default)]
pub needs: Vec<String>,
#[serde(default)]
pub steps: Vec<WorkflowJobStep>
}

#[derive(Debug, Deserialize, PartialEq)]
pub struct WorkflowJobStep {
pub id: Option<String>,
pub name: Option<String>,
pub run: Option<String>,
pub uses: Option<String>
}

impl std::fmt::Display for GithubWorkflowTrigger {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
10 changes: 6 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn std::error::Error>> {
let args = cli::parse_args();
Expand All @@ -18,7 +20,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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)
Expand All @@ -30,7 +32,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.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");
}
}

Expand Down
23 changes: 23 additions & 0 deletions src/markdown.rs
Original file line number Diff line number Diff line change
@@ -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() }
}
Expand Down Expand Up @@ -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)
}
33 changes: 8 additions & 25 deletions src/workflow_docs.rs
Original file line number Diff line number Diff line change
@@ -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<GithubWorkflowTrigger, GithubWorkflowTriggerPayload>,
pub jobs: HashMap<String, WorkflowJob>
}

impl GitHubWorkflow {
pub fn parse(path: &String) -> Result<GitHubWorkflow, Box<dyn std::error::Error>> {
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);
Expand All @@ -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
}
}
18 changes: 0 additions & 18 deletions src/workflow_docs/inputs.rs

This file was deleted.

Loading