Move pipeline load into Pipeline::load associated function
Load is the only way to construct a Pipeline, so it reads more
naturally as an associated function on the type it produces.
Also collapses a nested if-let to satisfy clippy::collapsible_if.
Assisted-by: GLM-5.1 via pi
diff --git a/src/ci/lua.rs b/src/ci/lua.rs
index 3440e1d..9081a7f 100644
--- a/src/ci/lua.rs
+++ b/src/ci/lua.rs
@@ -273,14 +273,14 @@ fn run_sh(lua: &Lua, (cmd, opts): (Cmd, Option<ShOpts>)) -> mlua::Result<mlua::V
.run(opts.unwrap_or_default())
.map_err(mlua::Error::external)?;
- if let Some(rt) = lua.app_data_ref::<Rc<RuntimeState>>() {
- if let Some(job) = rt.current_job.borrow().as_ref() {
- rt.outputs
- .borrow_mut()
- .entry(job.clone())
- .or_default()
- .push(output.clone());
- }
+ if let Some(rt) = lua.app_data_ref::<Rc<RuntimeState>>()
+ && let Some(job) = rt.current_job.borrow().as_ref()
+ {
+ rt.outputs
+ .borrow_mut()
+ .entry(job.clone())
+ .or_default()
+ .push(output.clone());
}
lua.to_value(&output)
@@ -288,7 +288,7 @@ fn run_sh(lua: &Lua, (cmd, opts): (Cmd, Option<ShOpts>)) -> mlua::Result<mlua::V
#[cfg(test)]
mod tests {
- use super::super::pipeline::load;
+ use super::super::pipeline::Pipeline;
use super::*;
#[test]
@@ -300,7 +300,8 @@ mod tests {
);
let source = r#"(local ci (require :quire.ci))
(ci.job :grab [:quire/push] (fn [_] (ci.secret :github_token)))"#;
- let pipeline = load(source, "ci.fnl", "ci.fnl", secrets).expect("load should succeed");
+ let pipeline =
+ Pipeline::load(source, "ci.fnl", "ci.fnl", secrets).expect("load should succeed");
let token: String = pipeline.jobs()[0]
.run_fn
.call(())
@@ -312,8 +313,8 @@ mod tests {
fn ci_secret_errors_for_unknown_name() {
let source = r#"(local ci (require :quire.ci))
(ci.job :grab [:quire/push] (fn [_] (ci.secret :missing)))"#;
- let pipeline =
- load(source, "ci.fnl", "ci.fnl", HashMap::new()).expect("load should succeed");
+ let pipeline = Pipeline::load(source, "ci.fnl", "ci.fnl", HashMap::new())
+ .expect("load should succeed");
let err = pipeline.jobs()[0]
.run_fn
.call::<mlua::Value>(())
@@ -329,8 +330,8 @@ mod tests {
/// invoke it, and decode the resulting Lua table as ShOutput through
/// the pipeline's VM via `lua.from_value`.
fn run_sh_via_job(source: &str) -> ShOutput {
- let pipeline =
- load(source, "ci.fnl", "ci.fnl", HashMap::new()).expect("load should succeed");
+ let pipeline = Pipeline::load(source, "ci.fnl", "ci.fnl", HashMap::new())
+ .expect("load should succeed");
let value: mlua::Value = pipeline.jobs()[0]
.run_fn
.call(())
@@ -406,7 +407,7 @@ mod tests {
#[test]
fn ci_sh_rejects_unknown_opt_key() {
- let pipeline = load(
+ let pipeline = Pipeline::load(
r#"(local ci (require :quire.ci))
(ci.job :go [:quire/push] (fn [_] (ci.sh "echo hi" {:cwdir "/tmp"})))"#,
"ci.fnl",
@@ -427,7 +428,7 @@ mod tests {
#[test]
fn ci_sh_rejects_non_sequence_table_as_cmd() {
- let pipeline = load(
+ let pipeline = Pipeline::load(
r#"(local ci (require :quire.ci))
(ci.job :go [:quire/push] (fn [_] (ci.sh {:env {:FOO "bar"}})))"#,
"ci.fnl",
@@ -448,7 +449,7 @@ mod tests {
#[test]
fn ci_sh_rejects_empty_argv() {
- let pipeline = load(
+ let pipeline = Pipeline::load(
r#"(local ci (require :quire.ci))
(ci.job :go [:quire/push] (fn [_] (ci.sh [])))"#,
"ci.fnl",
diff --git a/src/ci/mod.rs b/src/ci/mod.rs
index f735b69..6c8658b 100644
--- a/src/ci/mod.rs
+++ b/src/ci/mod.rs
@@ -67,7 +67,7 @@ impl Ci {
};
let name = CI_FNL.to_string();
let lua_name = format!("{}:{CI_FNL}", commit.sha);
- let pipeline = pipeline::load(&source, &lua_name, &name, secrets)?;
+ let pipeline = Pipeline::load(&source, &lua_name, &name, secrets)?;
Ok(Some(pipeline))
}
@@ -175,7 +175,7 @@ fn trigger_ref(
let name = CI_FNL.to_string();
let lua_name = format!("{}:{CI_FNL}", push_ref.new_sha);
- match pipeline::load(&source, &lua_name, &name, secrets) {
+ match Pipeline::load(&source, &lua_name, &name, secrets) {
Ok(_pipeline) => run.transition(RunState::Complete)?,
Err(e) => {
run.transition(RunState::Failed)?;
diff --git a/src/ci/pipeline.rs b/src/ci/pipeline.rs
index b4373d6..aa8ddeb 100644
--- a/src/ci/pipeline.rs
+++ b/src/ci/pipeline.rs
@@ -75,7 +75,7 @@ impl Job {
/// A validated CI pipeline — a job graph that has passed all
/// structural rules.
///
-/// Obtain via `pipeline::load`, which parses the Fennel source and
+/// Obtain via [`Pipeline::load`], which parses the Fennel source and
/// validates the result. Holding a `Pipeline` is proof that the graph
/// is sound.
///
@@ -117,6 +117,52 @@ impl Pipeline {
.map(|idx| self.jobs[self.graph[idx]].id.as_str())
.collect()
}
+
+ /// Parse and validate a ci.fnl source string into a `Pipeline`.
+ ///
+ /// Delegates evaluation to [`lua::parse`] for the Fennel-side work,
+ /// then runs the post-graph rules over the surviving jobs. Any errors
+ /// found are gathered into a single `LoadError` carrying the source
+ /// for miette to render with inline labels.
+ pub(crate) fn load(
+ source: &str,
+ filename: &str,
+ display: &str,
+ secrets: HashMap<String, SecretString>,
+ ) -> Result<Pipeline> {
+ let fennel = Fennel::new()?;
+ let results = lua::parse(&fennel, source, filename, display, secrets)?;
+
+ let mut errors = Vec::new();
+ let mut jobs = Vec::new();
+ for r in results {
+ match r {
+ Ok(j) => jobs.push(j),
+ Err(e) => errors.push(e),
+ }
+ }
+
+ let (graph, node_index) = build_graph(&jobs);
+
+ if let Err(post) = validate_post_graph(&jobs, &graph) {
+ errors.extend(post);
+ }
+
+ if errors.is_empty() {
+ Ok(Self {
+ jobs,
+ graph,
+ node_index,
+ fennel,
+ })
+ } else {
+ Err(LoadError {
+ src: NamedSource::new(display, source.to_string()),
+ errors,
+ }
+ .into())
+ }
+ }
}
/// Build the dependency graph for `jobs`. Inputs that don't match a
@@ -140,52 +186,6 @@ fn build_graph(jobs: &[Job]) -> (JobGraph, HashMap<String, NodeIndex>) {
(graph, node_index)
}
-/// Parse and validate a ci.fnl source string into a `Pipeline`.
-///
-/// Delegates evaluation to [`lua::parse`] for the Fennel-side work,
-/// then runs the post-graph rules over the surviving jobs. Any errors
-/// found are gathered into a single `LoadError` carrying the source
-/// for miette to render with inline labels.
-pub(crate) fn load(
- source: &str,
- filename: &str,
- display: &str,
- secrets: HashMap<String, SecretString>,
-) -> Result<Pipeline> {
- let fennel = Fennel::new()?;
- let results = lua::parse(&fennel, source, filename, display, secrets)?;
-
- let mut errors = Vec::new();
- let mut jobs = Vec::new();
- for r in results {
- match r {
- Ok(j) => jobs.push(j),
- Err(e) => errors.push(e),
- }
- }
-
- let (graph, node_index) = build_graph(&jobs);
-
- if let Err(post) = validate_post_graph(&jobs, &graph) {
- errors.extend(post);
- }
-
- if errors.is_empty() {
- Ok(Pipeline {
- jobs,
- graph,
- node_index,
- fennel,
- })
- } else {
- Err(LoadError {
- src: NamedSource::new(display, source.to_string()),
- errors,
- }
- .into())
- }
-}
-
/// Compute a span covering the given 1-indexed line in `source`.
/// Returns an empty span at offset 0 when the line is unknown.
fn span_for_line(source: &str, line: u32) -> SourceSpan {
@@ -344,8 +344,8 @@ mod tests {
fn load_registers_a_job() {
let source = r#"(local ci (require :quire.ci))
(ci.job :test [:quire/push] (fn [_] nil))"#;
- let pipeline =
- load(source, "ci.fnl", "ci.fnl", HashMap::new()).expect("load should succeed");
+ let pipeline = Pipeline::load(source, "ci.fnl", "ci.fnl", HashMap::new())
+ .expect("load should succeed");
let jobs = pipeline.jobs();
assert_eq!(jobs.len(), 1);
assert_eq!(jobs[0].id, "test");
@@ -359,8 +359,8 @@ mod tests {
(ci.job :build [:quire/push] (fn [_] nil))
(ci.job :test [:build] (fn [_] nil))
"#;
- let pipeline =
- load(source, "ci.fnl", "ci.fnl", HashMap::new()).expect("load should succeed");
+ let pipeline = Pipeline::load(source, "ci.fnl", "ci.fnl", HashMap::new())
+ .expect("load should succeed");
let jobs = pipeline.jobs();
assert_eq!(jobs.len(), 2);
assert_eq!(jobs[0].id, "build");
@@ -377,8 +377,8 @@ mod tests {
(ci.job :sixth [:quire/push] (fn [_] nil))";
- let pipeline =
- load(source, "ci.fnl", "ci.fnl", HashMap::new()).expect("load should succeed");
+ let pipeline = Pipeline::load(source, "ci.fnl", "ci.fnl", HashMap::new())
+ .expect("load should succeed");
let lines: Vec<usize> = pipeline
.jobs()
.iter()
@@ -389,7 +389,7 @@ mod tests {
#[test]
fn load_errors_on_bad_fennel() {
- let result = load("{:bad {:}", "ci.fnl", "ci.fnl", HashMap::new());
+ let result = Pipeline::load("{:bad {:}", "ci.fnl", "ci.fnl", HashMap::new());
assert!(result.is_err(), "malformed Fennel should fail");
}
diff --git a/src/ci/run.rs b/src/ci/run.rs
index c9f941e..ea3840d 100644
--- a/src/ci/run.rs
+++ b/src/ci/run.rs
@@ -641,8 +641,13 @@ mod tests {
}
fn load(source: &str) -> Pipeline {
- super::super::pipeline::load(source, "ci.fnl", "ci.fnl", std::collections::HashMap::new())
- .expect("load should succeed")
+ super::super::pipeline::Pipeline::load(
+ source,
+ "ci.fnl",
+ "ci.fnl",
+ std::collections::HashMap::new(),
+ )
+ .expect("load should succeed")
}
#[test]