alba_core/model.rs
1//! The typed project model that `alba-core` evaluates a Beamfile's AST
2//! into: [`Project`], its [`Beam`]s, and the small value types they carry.
3//!
4//! Scope boundary: a [`Beam`]'s `needs` list is converted straight from
5//! the AST's `BeamRef`s (namespaced by [`crate::loader::load_project`]
6//! when it came from an `import`) without validating that the referenced
7//! beam actually exists — that is [`crate::validate_graph`]'s job, not
8//! this module's.
9
10use std::path::PathBuf;
11
12use alba_syntax::{Span, Spanned, StringTemplate};
13
14use crate::eval::Scope;
15
16/// Identifies which source file a [`Beam`] or [`crate::CoreError`] came
17/// from, for pointing diagnostics at the right source text. Assigned by
18/// [`crate::loader::load_project`] (the root file is always `SourceId(0)`,
19/// each import gets the next one in load order) and resolvable back to a
20/// path and source text via [`crate::loader::SourceMap::get`].
21/// `load_str`'s single-file loading always produces `SourceId(0)`.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
23pub struct SourceId(pub usize);
24
25/// A fully namespaced beam identifier, e.g. `"api:build"` for a beam
26/// imported under the alias `api`, `"api:db:migrate"` for one imported
27/// transitively through a chain of aliases, or `"build"` for a local one.
28#[derive(Debug, Clone, PartialEq, Eq, Hash)]
29pub struct BeamId(pub String);
30
31/// How a beam's commands run.
32#[derive(Debug, Clone, PartialEq)]
33pub enum ExecutorKind {
34 /// The default, when a beam declares no `executor` field at all.
35 /// Which shell implementation actually runs a beam is the engine's
36 /// concern, not this model's.
37 Shell,
38 /// `executor system_shell`: the host shell, the per-beam opt-out from
39 /// the embedded shell.
40 SystemShell,
41 /// `executor docker { image "..." volumes [...] workdir "..." }`: the
42 /// engine runs this beam's commands inside a container built from
43 /// `image`, with `volumes` bind-mounted (`host:container` each) and
44 /// `workdir` as the container's working directory when set.
45 Docker {
46 image: String,
47 volumes: Vec<String>,
48 workdir: Option<String>,
49 },
50 /// `executor <name> { ... }` for any name that is not `shell`,
51 /// `system_shell`, or `docker`. Not an error: `name` is resolved at
52 /// plan time to an external `alba-executor-<name>` binary on the
53 /// `PATH`, and `options` is handed to it verbatim.
54 Plugin {
55 name: String,
56 options: Vec<(String, OptionValue)>,
57 },
58}
59
60/// The value half of an executor option, once rendered (its `{expr}`
61/// interpolation resolved, for the `Str`/`List` cases) at load time. Mirrors
62/// [`alba_syntax::ExecutorOptionValue`], minus the interpolation itself.
63#[derive(Debug, Clone, PartialEq)]
64pub enum OptionValue {
65 Str(String),
66 Bool(bool),
67 List(Vec<String>),
68}
69
70/// A value produced by evaluating an [`alba_syntax::Expr`]: either of the
71/// DSL's two runtime types.
72#[derive(Debug, Clone, PartialEq)]
73pub enum Value {
74 Str(String),
75 Bool(bool),
76}
77
78impl Value {
79 /// A short, human-readable name for this value's type, for error
80 /// messages ("expected a boolean, found a string").
81 pub(crate) fn type_name(&self) -> &'static str {
82 match self {
83 Value::Str(_) => "a string",
84 Value::Bool(_) => "a boolean",
85 }
86 }
87
88 /// This value as it appears when interpolated into a template:
89 /// strings verbatim, booleans as `true`/`false`.
90 pub(crate) fn as_display(&self) -> &str {
91 match self {
92 Value::Str(s) => s.as_str(),
93 Value::Bool(true) => "true",
94 Value::Bool(false) => "false",
95 }
96 }
97}
98
99/// A single beam: the unit of work produced by evaluating a `beam { ... }`
100/// declaration.
101///
102/// Load-time vs. schedule-time rendering: `description`, `inputs`,
103/// `outputs`, `cwd`, and the executor's options are rendered once, at load
104/// time, into plain values — referencing a beam parameter there is a load
105/// error. `run` and `env` are kept as [`StringTemplate`]s and rendered at
106/// schedule time (via [`crate::render_template`]), once the beam's
107/// parameters are bound into a [`Scope`] built from [`Beam::scope`] via
108/// [`Scope::with_params`].
109#[derive(Debug, Clone)]
110pub struct Beam {
111 pub id: BeamId,
112 pub description: Option<String>,
113 /// Each entry keeps the span of the reference that produced it, so a
114 /// `needs` entry that does not resolve is underlined exactly, rather
115 /// than the whole beam declaration containing it.
116 pub needs: Vec<Spanned<BeamId>>,
117 pub params: Vec<String>,
118 /// Evaluated but inert in this crate: the cache (out of scope here) is
119 /// the only intended consumer.
120 pub inputs: Vec<String>,
121 pub outputs: Vec<String>,
122 pub run: Vec<StringTemplate>,
123 pub env: Vec<(String, StringTemplate)>,
124 pub cwd: Option<String>,
125 pub executor: ExecutorKind,
126 pub allow_failure: bool,
127 /// The directory of the defining Beamfile, used as the `cwd` base.
128 /// Always absolute for a project built by `crate::loader::load_project`
129 /// (it derives this via `std::path::absolute`, not the file's
130 /// possibly-relative path, so it stays correct even if the process's
131 /// current directory changes between loading and running) — except for
132 /// `load_str`'s single-file front door, which has no real file on disk
133 /// to resolve against and uses `"."` (see its doc comment).
134 pub dir: PathBuf,
135 pub span: Span,
136 /// Which source file this beam was defined in, for diagnostics.
137 pub source: SourceId,
138 /// The defining file's evaluated file-level `let` bindings. The
139 /// engine composes this with the beam's own parameter values at
140 /// schedule time — `beam.scope.with_params(&beam.params, &args)` —
141 /// before rendering `run`/`env`.
142 pub scope: Scope,
143}
144
145/// A git hook bound to a beam: `hook pre-commit { beam check }`. Only the
146/// root Beamfile's hooks make it here; an import's are ignored, as its
147/// `default` is.
148#[derive(Debug, Clone, PartialEq)]
149pub struct Hook {
150 /// The git hook name (`pre-commit`), already checked against
151 /// [`crate::git::KNOWN_HOOKS`].
152 pub name: String,
153 /// The beam the hook runs, with the span of the reference that named
154 /// it, for pointing at an unresolved one.
155 pub beam: Spanned<BeamId>,
156 /// How many arguments git passes to this hook; the beam may declare at
157 /// most that many parameters.
158 pub arity: usize,
159 pub span: Span,
160 pub source: SourceId,
161}
162
163/// A fully evaluated Beamfile (single-file loading only; see the module
164/// doc comment): every beam it declares, and which one runs by default.
165#[derive(Debug, Clone)]
166pub struct Project {
167 pub beams: Vec<Beam>,
168 /// The beam a bare `alba` runs, with the span of the `default`
169 /// declaration that named it — [`crate::validate_graph`] checks it
170 /// resolves and points at that span when it does not.
171 pub default: Option<Spanned<BeamId>>,
172 /// The root Beamfile's hook declarations, in source order.
173 pub hooks: Vec<Hook>,
174}