Skip to content

Commit 8445a78

Browse files
committed
feat: implement default prerelease
1 parent cf7260e commit 8445a78

File tree

4 files changed

+113
-28
lines changed

4 files changed

+113
-28
lines changed

src/cli/flow/pipeline.rs

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1+
use std::str::FromStr;
2+
13
use ron::from_str;
24

35
use crate::cli::common::args::OutputConfig;
46
use crate::cli::flow::args::FlowArgs;
57
use crate::cli::utils::output_formatter::OutputFormatter;
6-
use crate::cli::version::args::VersionArgs;
8+
use crate::cli::utils::template::Template;
9+
use crate::cli::version::args::{
10+
BumpsConfig,
11+
VersionArgs,
12+
};
713
use crate::cli::version::pipeline::run_version_pipeline;
814
use crate::error::ZervError;
15+
use crate::utils::constants::pre_release_labels::ALPHA;
916

1017
/// Main flow pipeline handler
1118
///
@@ -21,8 +28,7 @@ pub fn run_flow_pipeline(args: FlowArgs) -> Result<String, ZervError> {
2128
let mut args = args;
2229
args.validate()?;
2330

24-
// For now, just call version command with zerv output format
25-
// TODO: Phase 2+ - Translate flow arguments to version arguments
31+
// Create version args with flow-specific defaults
2632
let version_args = VersionArgs {
2733
input: args.input.clone(),
2834
output: OutputConfig {
@@ -32,7 +38,11 @@ pub fn run_flow_pipeline(args: FlowArgs) -> Result<String, ZervError> {
3238
},
3339
main: Default::default(),
3440
overrides: Default::default(),
35-
bumps: Default::default(),
41+
bumps: BumpsConfig {
42+
bump_pre_release_label: Some(ALPHA.to_string()),
43+
bump_pre_release_num: Some(Some(Template::from_str("{{hash_int bumped_branch 5}}")?)),
44+
..Default::default()
45+
},
3646
};
3747

3848
// Call version pipeline to get RON output
@@ -101,32 +111,35 @@ mod tests {
101111
#[test]
102112
fn test_trunk_based_development_flow() {
103113
test_info!("Starting trunk-based development flow test");
104-
105114
if !should_run_docker_tests() {
106115
return; // Skip when `ZERV_TEST_DOCKER` are disabled
107116
}
108117

109-
// Step 1: Start with a clean tag
110118
let fixture =
111119
GitRepoFixture::tagged("v1.0.0").expect("Failed to create git fixture with tag");
112120
let fixture_path = fixture.path().to_string_lossy();
121+
let main_hash = Template::render("{{hash_int 'main' 5}}");
113122

114-
test_flow_pipeline_with_fixture(&fixture_path, r"1.0.0", r"1.0.0");
123+
test_flow_pipeline_with_fixture(
124+
&fixture_path,
125+
&format!("1.0.0-alpha.{}", main_hash),
126+
&format!("1.0.0a{}", main_hash),
127+
);
115128

116-
// Step 2: Checkout feature branch
117129
fixture
118130
.checkout_branch("feature-1")
119131
.expect("Failed to checkout feature-1 branch");
120132

121-
test_flow_pipeline_with_fixture(&fixture_path, r"1.0.0", r"1.0.0");
122-
123-
// Step 3: Make dirty working directory
124-
fixture.make_dirty().expect("Failed to make fixture dirty");
133+
let feature_1_hash = Template::render("{{hash_int 'feature-1' 5}}");
125134

126135
test_flow_pipeline_with_fixture(
127136
&fixture_path,
128-
r"1.0.0+feature.1.0.{{commit_hash_7}}",
129-
r"1.0.0+feature.1.0.{{commit_hash_7}}",
137+
&format!("1.0.0-alpha.{}", feature_1_hash),
138+
&format!("1.0.0a{}", feature_1_hash),
130139
);
140+
141+
let dirty_hash = Template::render("{{hash_int 'dirty-working-dir' 5}}");
142+
assert!(!dirty_hash.is_empty());
143+
assert_eq!(dirty_hash.len(), 5);
131144
}
132145
}

src/cli/utils/output_formatter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ impl OutputFormatter {
1818
) -> Result<String, ZervError> {
1919
// 1. Resolve template if provided, otherwise use standard format
2020
let mut output = if let Some(template) = output_template {
21-
template.resolve(zerv_object)?
21+
template.resolve(Some(zerv_object))?
2222
} else {
2323
Self::format_base_output(zerv_object, output_format)?
2424
};

src/cli/utils/template/types.rs

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ where
1818
T: FromStr + Clone,
1919
T::Err: Display,
2020
{
21-
/// Resolve template using Zerv object context, return final value
22-
pub fn resolve(&self, zerv: &Zerv) -> Result<T, ZervError> {
21+
/// Resolve template using optional Zerv object context, return final value
22+
pub fn resolve(&self, zerv: Option<&Zerv>) -> Result<T, ZervError> {
2323
match self {
2424
Template::Value(v) => Ok(v.clone()),
2525
Template::Template(template) => {
@@ -32,25 +32,40 @@ where
3232
}
3333
}
3434

35-
/// Render Handlebars template using Zerv object as context
36-
fn render_template(template: &str, zerv: &Zerv) -> Result<String, ZervError> {
35+
/// Render Handlebars template using optional Zerv object as context
36+
fn render_template(template: &str, zerv: Option<&Zerv>) -> Result<String, ZervError> {
3737
let mut handlebars = handlebars::Handlebars::new();
3838
handlebars.set_strict_mode(false); // Allow missing variables
3939

4040
// Register custom Zerv helpers
4141
register_helpers(&mut handlebars)?;
4242

43-
// Create template context from Zerv object
44-
let template_context = TemplateContext::from_zerv(zerv);
45-
let context = serde_json::to_value(template_context)
46-
.map_err(|e| ZervError::TemplateError(format!("Serialization error: {e}")))?;
43+
// Create template context from Zerv object or empty context
44+
let context = if let Some(z) = zerv {
45+
let template_context = TemplateContext::from_zerv(z);
46+
serde_json::to_value(template_context)
47+
.map_err(|e| ZervError::TemplateError(format!("Serialization error: {e}")))?
48+
} else {
49+
serde_json::Value::Object(serde_json::Map::new())
50+
};
4751

4852
handlebars
4953
.render_template(template, &context)
5054
.map_err(|e| ZervError::TemplateError(format!("Template render error: {e}")))
5155
}
5256
}
5357

58+
impl Template<String> {
59+
/// Parse a template string and render it to final string result.
60+
/// Uses empty context (no Zerv object) and handles errors internally.
61+
pub fn render(template_str: &str) -> String {
62+
let template = Self::from(template_str.to_string());
63+
template
64+
.resolve(None)
65+
.unwrap_or_else(|e| format!("template_error: {}", e))
66+
}
67+
}
68+
5469
impl From<String> for Template<String> {
5570
fn from(value: String) -> Self {
5671
if value.contains("{{") && value.contains("}}") {
@@ -128,7 +143,64 @@ mod tests {
128143
fn test_template_resolve(#[case] input: &str, #[case] expected: &str) {
129144
let template: Template<String> = Template::from_str(input).unwrap();
130145
let zerv = ZervFixture::new().with_version(1, 2, 0).build();
131-
let result = template.resolve(&zerv).unwrap();
146+
let result = template.resolve(Some(&zerv)).unwrap();
132147
assert_eq!(result, expected);
133148
}
149+
150+
#[rstest]
151+
// Test basic string without template syntax
152+
#[case("static_text", "static_text")]
153+
// Test template with helpers (without Zerv context)
154+
#[case("{{hash_int 'test' 5}}", "16668")]
155+
#[case("{{hash 'test'}}", "c7dedb4")]
156+
#[case("{{sanitize 'Feature-Branch'}}", "feature.branch")]
157+
#[case("{{prefix 'abcdefghij' 5}}", "abcde")]
158+
// Test math helpers
159+
#[case("{{add 5 3}}", "8")]
160+
#[case("{{multiply 7 6}}", "42")]
161+
// Test complex template with multiple helpers
162+
#[case("hash_{{hash_int 'branch' 3}}_{{prefix 'commit' 2}}", "hash_380_co")]
163+
fn test_template_render(#[case] input: &str, #[case] expected: &str) {
164+
let result = Template::render(input);
165+
assert_eq!(result, expected);
166+
}
167+
168+
#[test]
169+
fn test_template_render_error_handling() {
170+
// Test with invalid helper that should produce error
171+
let result = Template::render("{{unknown_helper 'test'}}");
172+
173+
// Should not panic and should return error message
174+
assert!(result.starts_with("template_error:"));
175+
assert!(result.contains("Template render error"));
176+
}
177+
178+
#[test]
179+
fn test_template_render_empty_string() {
180+
let result = Template::render("");
181+
assert_eq!(result, "");
182+
}
183+
184+
#[test]
185+
fn test_template_render_with_context_variables() {
186+
// When using Template::render(), context variables should not be available
187+
// and should be treated as empty/missing
188+
let result = Template::render("{{missing_var}}");
189+
assert_eq!(result, ""); // Missing variables become empty strings
190+
}
191+
192+
#[rstest]
193+
#[case("{{hash_int 'feature-1' 5}}")]
194+
#[case("{{sanitize 'Test-Branch-Name'}}")]
195+
#[case("{{add 10 20}}")]
196+
fn test_template_render_consistency(#[case] template_str: &str) {
197+
// Template::render() should give same result as the verbose chain
198+
let render_result = Template::render(template_str);
199+
200+
let verbose_result = Template::from(template_str.to_string())
201+
.resolve(None)
202+
.unwrap_or_else(|e| format!("template_error: {}", e));
203+
204+
assert_eq!(render_result, verbose_result);
205+
}
134206
}

src/cli/version/args/resolved.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ impl ResolvedOverrides {
131131
T::Err: Display,
132132
{
133133
match template {
134-
Some(t) => Ok(Some(t.resolve(zerv)?)),
134+
Some(t) => Ok(Some(t.resolve(Some(zerv))?)),
135135
None => Ok(None),
136136
}
137137
}
@@ -142,7 +142,7 @@ impl ResolvedOverrides {
142142
) -> Result<Vec<String>, ZervError> {
143143
templates
144144
.iter()
145-
.map(|template| template.resolve(zerv))
145+
.map(|template| template.resolve(Some(zerv)))
146146
.collect()
147147
}
148148

@@ -187,7 +187,7 @@ impl ResolvedBumps {
187187
zerv: &Zerv,
188188
) -> Result<Option<Option<u32>>, ZervError> {
189189
match bump {
190-
Some(Some(template)) => Ok(Some(Some(template.resolve(zerv)?))),
190+
Some(Some(template)) => Ok(Some(Some(template.resolve(Some(zerv))?))),
191191
Some(None) => Ok(Some(None)),
192192
None => Ok(None),
193193
}
@@ -199,7 +199,7 @@ impl ResolvedBumps {
199199
) -> Result<Vec<String>, ZervError> {
200200
templates
201201
.iter()
202-
.map(|template| template.resolve(zerv))
202+
.map(|template| template.resolve(Some(zerv)))
203203
.collect()
204204
}
205205
}

0 commit comments

Comments
 (0)