Skip to content

Commit 762e7d2

Browse files
committed
Template for multiple Rust components in a single workspace
Signed-off-by: itowlson <[email protected]>
1 parent c1011ac commit 762e7d2

File tree

11 files changed

+162
-9
lines changed

11 files changed

+162
-9
lines changed

crates/templates/src/reader.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,20 @@ pub(crate) struct RawTemplateManifestV1 {
3030
#[serde(deny_unknown_fields, rename_all = "snake_case")]
3131
pub(crate) struct RawTemplateVariant {
3232
pub supported: Option<bool>,
33+
pub copy_into: Option<RawCopyInto>,
3334
pub skip_files: Option<Vec<String>>,
3435
pub skip_parameters: Option<Vec<String>>,
3536
pub snippets: Option<HashMap<String, String>>,
3637
pub conditions: Option<HashMap<String, RawConditional>>,
3738
}
3839

40+
#[derive(Debug, Deserialize)]
41+
#[serde(deny_unknown_fields, rename_all = "snake_case")]
42+
pub(crate) enum RawCopyInto {
43+
Root,
44+
Own,
45+
}
46+
3947
#[derive(Debug, Deserialize)]
4048
#[serde(deny_unknown_fields, rename_all = "snake_case")]
4149
pub(crate) struct RawConditional {

crates/templates/src/renderer.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,24 @@ pub(crate) enum TemplateContent {
1919
pub(crate) enum RenderOperation {
2020
AppendToml(PathBuf, TemplateContent),
2121
MergeToml(PathBuf, MergeTarget, TemplateContent), // file to merge into, table to merge into, content to merge
22-
WriteFile(PathBuf, TemplateContent),
22+
WriteFile(TemplateablePath, TemplateContent),
2323
CreateDirectory(PathBuf, std::sync::Arc<liquid::Template>),
2424
}
2525

26+
pub(crate) enum TemplateablePath {
27+
Plain(PathBuf),
28+
Template(liquid::Template),
29+
}
30+
31+
impl TemplateablePath {
32+
fn render(self, globals: &liquid::Object) -> anyhow::Result<PathBuf> {
33+
Ok(match self {
34+
TemplateablePath::Plain(path) => path,
35+
TemplateablePath::Template(template) => PathBuf::from(template.render(globals)?),
36+
})
37+
}
38+
}
39+
2640
pub(crate) enum MergeTarget {
2741
Application(&'static str),
2842
}
@@ -63,6 +77,7 @@ impl RenderOperation {
6377
match self {
6478
Self::WriteFile(path, content) => {
6579
let rendered = content.render(globals)?;
80+
let path = path.render(globals)?;
6681
Ok(TemplateOutput::WriteFile(path, rendered))
6782
}
6883
Self::AppendToml(path, content) => {

crates/templates/src/run.rs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ impl Run {
8484
self.build_renderer_raw(interaction).await.into()
8585
}
8686

87+
fn allow_overwrite(&self) -> bool {
88+
// If the template variant asks to be generated into the app root,
89+
// we assume that it knows what it's doing and intends to avoid
90+
// overwriting. This is true for the one use case we have, although
91+
// we need to track if it's a flawed assumption in general cases.
92+
self.options.allow_overwrite || self.template.use_root(&self.options.variant)
93+
}
94+
8795
// The 'raw' in this refers to the output type, which is an ugly representation
8896
// of cancellation: Ok(Some(...)) means a result, Ok(None) means cancelled, Err
8997
// means error. Why have this ugly representation? Because it makes it terser to
@@ -99,7 +107,7 @@ impl Run {
99107
// TODO: rationalise `path` and `dir`
100108
let to = self.generation_target_dir();
101109

102-
if !self.options.allow_overwrite {
110+
if !self.allow_overwrite() {
103111
match interaction.allow_generate_into(&to) {
104112
Cancellable::Cancelled => return Ok(None),
105113
Cancellable::Ok(_) => (),
@@ -179,11 +187,28 @@ impl Run {
179187
let outputs = Self::to_output_paths(from, to, template_contents);
180188
let file_ops = outputs
181189
.into_iter()
182-
.map(|(path, content)| RenderOperation::WriteFile(path, content))
190+
.map(|(path, content)| {
191+
RenderOperation::WriteFile(Self::templateable_path(path, parser), content)
192+
})
183193
.collect();
184194
Ok(file_ops)
185195
}
186196

197+
fn templateable_path(
198+
path: PathBuf,
199+
parser: &liquid::Parser,
200+
) -> crate::renderer::TemplateablePath {
201+
let path_str = path.display().to_string();
202+
if !path_str.contains("{{") {
203+
return crate::renderer::TemplateablePath::Plain(path);
204+
}
205+
206+
match parser.parse(&path_str) {
207+
Ok(t) => crate::renderer::TemplateablePath::Template(t),
208+
Err(_) => crate::renderer::TemplateablePath::Plain(path),
209+
}
210+
}
211+
187212
async fn special_values(&self) -> HashMap<String, String> {
188213
let mut values = HashMap::new();
189214

@@ -206,10 +231,15 @@ impl Run {
206231
fn generation_target_dir(&self) -> PathBuf {
207232
match &self.options.variant {
208233
TemplateVariantInfo::NewApplication => self.options.output_path.clone(),
209-
TemplateVariantInfo::AddComponent { manifest_path } => manifest_path
210-
.parent()
211-
.unwrap()
212-
.join(&self.options.output_path),
234+
TemplateVariantInfo::AddComponent { manifest_path } => {
235+
let root = manifest_path.parent().unwrap();
236+
237+
if self.template.use_root(&self.options.variant) {
238+
root.to_owned()
239+
} else {
240+
root.join(&self.options.output_path)
241+
}
242+
}
213243
}
214244
}
215245

crates/templates/src/template.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use regex::Regex;
1111
use crate::{
1212
constraints::StringConstraints,
1313
reader::{
14-
RawCondition, RawConditional, RawExtraOutput, RawParameter, RawTemplateManifest,
15-
RawTemplateManifestV1, RawTemplateVariant,
14+
RawCondition, RawConditional, RawCopyInto, RawExtraOutput, RawParameter,
15+
RawTemplateManifest, RawTemplateManifestV1, RawTemplateVariant,
1616
},
1717
run::{Run, RunOptions},
1818
store::TemplateLayout,
@@ -94,8 +94,16 @@ impl TemplateVariantInfo {
9494
}
9595
}
9696

97+
#[derive(Clone, Debug, Default)]
98+
enum CopyInto {
99+
#[default]
100+
OwnDirectory,
101+
Root,
102+
}
103+
97104
#[derive(Clone, Debug, Default)]
98105
pub(crate) struct TemplateVariant {
106+
copy_into: CopyInto,
99107
skip_files: Vec<String>,
100108
skip_parameters: Vec<String>,
101109
snippets: HashMap<String, String>,
@@ -385,7 +393,9 @@ impl Template {
385393
}
386394

387395
fn parse_template_variant(raw: RawTemplateVariant) -> TemplateVariant {
396+
let copy_into = raw.copy_into.map(Self::parse_copy_into).unwrap_or_default();
388397
TemplateVariant {
398+
copy_into,
389399
skip_files: raw.skip_files.unwrap_or_default(),
390400
skip_parameters: raw.skip_parameters.unwrap_or_default(),
391401
snippets: raw.snippets.unwrap_or_default(),
@@ -398,6 +408,13 @@ impl Template {
398408
}
399409
}
400410

411+
fn parse_copy_into(raw: RawCopyInto) -> CopyInto {
412+
match raw {
413+
RawCopyInto::Root => CopyInto::Root,
414+
RawCopyInto::Own => CopyInto::OwnDirectory,
415+
}
416+
}
417+
401418
fn parse_conditional(conditional: RawConditional) -> Conditional {
402419
Conditional {
403420
condition: Self::parse_condition(conditional.condition),
@@ -452,6 +469,11 @@ impl Template {
452469
.collect()
453470
}
454471

472+
pub(crate) fn use_root(&self, variant_info: &TemplateVariantInfo) -> bool {
473+
let variant = self.variant(variant_info).unwrap(); // TODO: for now
474+
matches!(variant.copy_into, CopyInto::Root)
475+
}
476+
455477
pub(crate) fn check_compatible_trigger(&self, app_trigger: Option<&str>) -> anyhow::Result<()> {
456478
// The application we are merging into might not have a trigger yet, in which case
457479
// we're good to go.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
target/
2+
.spin/
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[workspace]
2+
resolver = "2"
3+
members = ["crates/*"]
4+
5+
[workspace.dependencies]
6+
anyhow = "1"
7+
spin-sdk = "3.1.0"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "{{project-name | kebab_case}}"
3+
authors = ["{{authors}}"]
4+
description = "{{project-description}}"
5+
version = "0.1.0"
6+
rust-version = "1.78"
7+
edition = "2021"
8+
9+
[lib]
10+
crate-type = ["cdylib"]
11+
12+
[dependencies]
13+
anyhow = { workspace = true }
14+
spin-sdk = { workspace = true }
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use spin_sdk::http::{IntoResponse, Request, Response};
2+
use spin_sdk::http_component;
3+
4+
/// A simple Spin HTTP component.
5+
#[http_component]
6+
fn handle_{{project-name | snake_case}}(req: Request) -> anyhow::Result<impl IntoResponse> {
7+
println!("Handling request to {:?}", req.header("spin-full-url"));
8+
Ok(Response::builder()
9+
.status(200)
10+
.header("content-type", "text/plain")
11+
.body("Hello World!")
12+
.build())
13+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
spin_manifest_version = 2
2+
3+
[application]
4+
name = "{{project-name | kebab_case}}"
5+
version = "0.1.0"
6+
authors = ["{{authors}}"]
7+
description = "{{project-description}}"
8+
9+
[[trigger.http]]
10+
route = "{{http-path}}"
11+
component = "{{project-name | kebab_case}}"
12+
13+
[component.{{project-name | kebab_case}}]
14+
source = "target/wasm32-wasip1/release/{{project-name | snake_case}}.wasm"
15+
allowed_outbound_hosts = []
16+
[component.{{project-name | kebab_case}}.build]
17+
command = "cargo build -p {{project-name | kebab_case}} --target wasm32-wasip1 --release"
18+
watch = ["crates/{{project-name | kebab_case}}/src/**/*.rs", "crates/{{project-name | kebab_case}}/Cargo.toml"]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[[trigger.http]]
2+
route = "{{http-path}}"
3+
component = "{{project-name | kebab_case}}"
4+
5+
[component.{{project-name | kebab_case}}]
6+
source = "target/wasm32-wasip1/release/{{project-name | snake_case}}.wasm"
7+
allowed_outbound_hosts = []
8+
[component.{{project-name | kebab_case}}.build]
9+
command = "cargo build -p {{project-name | kebab_case}} --target wasm32-wasip1 --release"
10+
watch = ["crates/{{project-name | kebab_case}}/src/**/*.rs", "crates/{{project-name | kebab_case}}/Cargo.toml"]

0 commit comments

Comments
 (0)