feat: add rust actions
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
19
crates/cuddle-please-actions/Cargo.toml
Normal file
19
crates/cuddle-please-actions/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "cuddle-please-actions"
|
||||
description = "A release-please inspired release manager tool, built on top of cuddle, but also useful standalone, cuddle-please supports, your ci of choice, as well as gitea, github"
|
||||
repository = "https://git.front.kjuulh.io/kjuulh/cuddle-please"
|
||||
readme = "../../README.md"
|
||||
license-file = "../../LICENSE"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publishable = true
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
tracing.workspace = true
|
||||
semver.workspace = true
|
||||
toml_edit.workspace = true
|
||||
yaml-rust2.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions.workspace = true
|
9
crates/cuddle-please-actions/src/actions.rs
Normal file
9
crates/cuddle-please-actions/src/actions.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use semver::Version;
|
||||
|
||||
use crate::ActionConfig;
|
||||
|
||||
pub trait Action {
|
||||
fn enabled(&self, config: &ActionConfig) -> anyhow::Result<bool>;
|
||||
fn name(&self) -> String;
|
||||
fn execute(&self, version: &Version) -> anyhow::Result<()>;
|
||||
}
|
49
crates/cuddle-please-actions/src/config.rs
Normal file
49
crates/cuddle-please-actions/src/config.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
pub enum ActionConfig {
|
||||
Actual { doc: yaml_rust2::Yaml },
|
||||
None,
|
||||
}
|
||||
|
||||
impl Deref for ActionConfig {
|
||||
type Target = yaml_rust2::Yaml;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match &self {
|
||||
ActionConfig::Actual { doc } => doc,
|
||||
ActionConfig::None => &yaml_rust2::Yaml::BadValue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for ActionConfig {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
let mut cuddle = yaml_rust2::YamlLoader::load_from_str(value)?;
|
||||
|
||||
if cuddle.len() != 1 {
|
||||
anyhow::bail!("cuddle.yaml can only be 1 document wide");
|
||||
}
|
||||
|
||||
let doc = cuddle.pop().unwrap();
|
||||
let doc = doc["please"]["actions"].clone();
|
||||
|
||||
if doc.is_badvalue() {
|
||||
return Ok(Self::None);
|
||||
}
|
||||
|
||||
Ok(Self::Actual { doc })
|
||||
}
|
||||
}
|
||||
|
||||
impl ActionConfig {
|
||||
pub fn parse() -> anyhow::Result<Self> {
|
||||
let cuddle_yaml =
|
||||
std::fs::read_to_string("cuddle.yaml").context("failed to read cuddle.yaml")?;
|
||||
|
||||
Self::try_from(cuddle_yaml.as_str())
|
||||
}
|
||||
}
|
45
crates/cuddle-please-actions/src/lib.rs
Normal file
45
crates/cuddle-please-actions/src/lib.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
pub(crate) mod actions;
|
||||
mod config;
|
||||
mod rust_action;
|
||||
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use catalog::RustAction;
|
||||
pub use config::ActionConfig;
|
||||
use semver::Version;
|
||||
pub mod catalog {
|
||||
pub use crate::rust_action::*;
|
||||
}
|
||||
|
||||
pub struct Action(Arc<dyn actions::Action>);
|
||||
|
||||
impl Deref for Action {
|
||||
type Target = Arc<dyn actions::Action>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Actions(Vec<Action>);
|
||||
|
||||
impl Actions {
|
||||
pub fn from_cuddle() -> anyhow::Result<Self> {
|
||||
let config = ActionConfig::parse()?;
|
||||
|
||||
Ok(Self(
|
||||
vec![Action(Arc::new(RustAction::new()))]
|
||||
.into_iter()
|
||||
.filter(|a| a.enabled(&config).unwrap_or_default())
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn execute(&self, version: &Version) -> anyhow::Result<()> {
|
||||
for action in &self.0 {
|
||||
action.execute(version)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
237
crates/cuddle-please-actions/src/rust_action.rs
Normal file
237
crates/cuddle-please-actions/src/rust_action.rs
Normal file
@@ -0,0 +1,237 @@
|
||||
use std::io::Write;
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::{actions::Action, ActionConfig};
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct RustAction {}
|
||||
|
||||
impl RustAction {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
fn execute_content(
|
||||
&self,
|
||||
version: &semver::Version,
|
||||
cargo_content: &str,
|
||||
) -> anyhow::Result<String> {
|
||||
tracing::trace!("parsing Cargo.toml file as tolm");
|
||||
let mut cargo_doc = cargo_content.parse::<toml_edit::DocumentMut>()?;
|
||||
|
||||
tracing::debug!(
|
||||
"updating cargo workspace package version to {}",
|
||||
version.to_string()
|
||||
);
|
||||
|
||||
let workspace = if cargo_doc.contains_table("workspace") {
|
||||
cargo_doc["workspace"].as_table_mut().unwrap()
|
||||
} else {
|
||||
let mut t = toml_edit::Table::new();
|
||||
t.set_implicit(true);
|
||||
cargo_doc["workspace"] = toml_edit::Item::Table(t);
|
||||
cargo_doc["workspace"].as_table_mut().unwrap()
|
||||
};
|
||||
let package = workspace["package"].or_insert(toml_edit::table());
|
||||
package["version"] = toml_edit::value(version.to_string());
|
||||
|
||||
Ok(cargo_doc.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Action for RustAction {
|
||||
fn enabled(&self, config: &ActionConfig) -> anyhow::Result<bool> {
|
||||
if let Ok(v) = std::env::var("CUDDLE_PLEASE_RUST_ACTION") {
|
||||
if let Ok(true) = v.parse::<bool>() {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
let val = &config[self.name().as_str()];
|
||||
|
||||
if val.is_badvalue() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Ok(val.as_bool().unwrap_or(true))
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
"rust".into()
|
||||
}
|
||||
|
||||
fn execute(&self, version: &semver::Version) -> anyhow::Result<()> {
|
||||
tracing::info!(
|
||||
"running rust action for version: {} and file: Cargo.toml",
|
||||
version.to_string()
|
||||
);
|
||||
|
||||
let path = std::path::PathBuf::from("Cargo.toml");
|
||||
|
||||
tracing::trace!("reading Cargo.toml");
|
||||
|
||||
let file = match std::fs::read_to_string(&path) {
|
||||
Ok(file) => file,
|
||||
Err(e) => match e.kind() {
|
||||
std::io::ErrorKind::NotFound => {
|
||||
anyhow::bail!("err: Cargo.toml was not found in dir")
|
||||
}
|
||||
_ => Err(e)?,
|
||||
},
|
||||
};
|
||||
|
||||
let cargo_doc = self.execute_content(version, &file)?;
|
||||
|
||||
let mut cargo_file = std::fs::File::create(&path)?;
|
||||
cargo_file.write_all(cargo_doc.as_bytes())?;
|
||||
cargo_file.sync_all()?;
|
||||
|
||||
tracing::debug!("finished writing cargo file");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use semver::{BuildMetadata, Prerelease};
|
||||
|
||||
use crate::{actions::Action, ActionConfig};
|
||||
|
||||
use super::RustAction;
|
||||
|
||||
#[test]
|
||||
fn test_is_enabled() {
|
||||
let config = ActionConfig::try_from(
|
||||
r#"
|
||||
please:
|
||||
actions:
|
||||
rust: true
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let enabled = RustAction::new().enabled(&config).unwrap();
|
||||
|
||||
assert!(enabled)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_disabled_by_default() {
|
||||
let config = ActionConfig::try_from(
|
||||
r#"
|
||||
please:
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let enabled = RustAction::new().enabled(&config).unwrap();
|
||||
|
||||
assert!(!enabled)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_disabled() {
|
||||
let config = ActionConfig::try_from(
|
||||
r#"
|
||||
please:
|
||||
actions:
|
||||
rust: false
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let enabled = RustAction::new().enabled(&config).unwrap();
|
||||
|
||||
assert!(!enabled)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_value_is_enabled() {
|
||||
let config = ActionConfig::try_from(
|
||||
r#"
|
||||
please:
|
||||
actions:
|
||||
rust:
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let enabled = RustAction::new().enabled(&config).unwrap();
|
||||
|
||||
assert!(enabled)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_edit_empty_file() {
|
||||
let output = RustAction::default()
|
||||
.execute_content(
|
||||
&semver::Version {
|
||||
major: 0,
|
||||
minor: 1,
|
||||
patch: 0,
|
||||
pre: Prerelease::default(),
|
||||
build: BuildMetadata::default(),
|
||||
},
|
||||
"",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
pretty_assertions::assert_eq!(
|
||||
r#"[workspace.package]
|
||||
version = "0.1.0"
|
||||
"#,
|
||||
&output,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_only_edits_stuff() {
|
||||
let input = r#"
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
|
||||
[package]
|
||||
something = {some = "something"}
|
||||
|
||||
# Some comment
|
||||
|
||||
[workspace.package]
|
||||
version = "0.0.0" # some comment
|
||||
readme = "../../"
|
||||
"#;
|
||||
|
||||
let output = RustAction::default()
|
||||
.execute_content(
|
||||
&semver::Version {
|
||||
major: 0,
|
||||
minor: 1,
|
||||
patch: 0,
|
||||
pre: Prerelease::default(),
|
||||
build: BuildMetadata::default(),
|
||||
},
|
||||
input,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
pretty_assertions::assert_eq!(
|
||||
r#"
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
|
||||
[package]
|
||||
something = {some = "something"}
|
||||
|
||||
# Some comment
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
readme = "../../"
|
||||
"#,
|
||||
&output,
|
||||
)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user