Skip to content

Commit 9c68e0e

Browse files
authored
feat: implement assert_commands (#127)
1 parent 1c7cd80 commit 9c68e0e

File tree

25 files changed

+2325
-325
lines changed

25 files changed

+2325
-325
lines changed
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
# Plan 47: Enhanced Tera Template Context with Version Parsing
2+
3+
**Status**: Completed
4+
**Priority**: Medium
5+
**Context**: Users need more granular access to version components in Tera templates, particularly for SemVer and PEP440 format parsing
6+
**Goals**: Provide structured access to version parts (base, pre-release, build) and add PEP440 label parsing
7+
8+
## Implementation Plan
9+
10+
### 1. Enhance Template Context Structures
11+
12+
**File**: `src/cli/utils/template/context.rs`
13+
14+
**Add import at top of file:**
15+
16+
```rust
17+
use crate::version::pep440::PEP440;
18+
use crate::version::pep440::utils::pre_release_label_to_pep440_string;
19+
use crate::version::semver::SemVer;
20+
use crate::version::zerv::Zerv;
21+
```
22+
23+
**Update PreReleaseContext struct** - add PEP440 label using existing function:
24+
25+
```rust
26+
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
27+
pub struct PreReleaseContext {
28+
pub label: String, // Current: "alpha", "beta", etc.
29+
pub number: Option<u64>,
30+
pub pep440_label: Option<String>, // PEP440 format: "rc", "a", "b", etc.
31+
}
32+
```
33+
34+
**Add new fields to ZervTemplateContext struct**:
35+
36+
```rust
37+
// SemVer parsed components
38+
pub semver_base_part: String,
39+
pub semver_pre_release_part: Option<String>, // None if no pre-release
40+
pub semver_build_part: Option<String>, // None if no build metadata
41+
pub semver_docker: String,
42+
43+
// PEP440 parsed components
44+
pub pep440_base_part: String,
45+
pub pep440_pre_release_part: Option<String>, // None if no pre-release
46+
pub pep440_build_part: Option<String>, // None if no build metadata
47+
```
48+
49+
### 2. Add Conditional Prefix Function to Tera
50+
51+
**Add new function to template functions** - File: `src/cli/utils/template/functions.rs`
52+
53+
```rust
54+
/// Add conditional prefix to string (only if string is not empty)
55+
/// Usage: {{ prefix_if(value, prefix="+") }}
56+
fn prefix_if_function(args: &std::collections::HashMap<String, Value>) -> Result<Value, tera::Error> {
57+
let value = args
58+
.get("value")
59+
.and_then(|v| v.as_str())
60+
.ok_or_else(|| tera::Error::msg("prefix_if function requires a 'value' parameter"))?;
61+
62+
let prefix = args
63+
.get("prefix")
64+
.and_then(|v| v.as_str())
65+
.ok_or_else(|| tera::Error::msg("prefix_if function requires a 'prefix' parameter"))?;
66+
67+
if value.is_empty() {
68+
Ok(Value::String("".to_string()))
69+
} else {
70+
Ok(Value::String(format!("{}{}", prefix, value)))
71+
}
72+
}
73+
```
74+
75+
**Update register_functions() to include the new function:**
76+
77+
```rust
78+
pub fn register_functions(tera: &mut Tera) -> Result<(), ZervError> {
79+
tera.register_function("sanitize", Box::new(sanitize_function));
80+
tera.register_function("hash", Box::new(hash_function));
81+
tera.register_function("hash_int", Box::new(hash_int_function));
82+
tera.register_function("prefix", Box::new(prefix_function));
83+
tera.register_function("prefix_if", Box::new(prefix_if_function)); // NEW
84+
tera.register_function("format_timestamp", Box::new(format_timestamp_function));
85+
Ok(())
86+
}
87+
```
88+
89+
### 3. Add Methods to Version Classes
90+
91+
#### Add methods to SemVer class
92+
93+
**File**: `src/version/semver.rs`
94+
95+
```rust
96+
impl SemVer {
97+
pub fn to_base_part(&self) -> String {
98+
format!("{}.{}.{}", self.major, self.minor, self.patch)
99+
}
100+
101+
pub fn to_pre_release_part(&self) -> Option<String> {
102+
self.pre_release.as_ref().map(|pr| pr.to_string())
103+
}
104+
105+
pub fn to_build_part(&self) -> Option<String> {
106+
self.build.as_ref().map(|b| b.to_string())
107+
}
108+
109+
pub fn to_docker_format(&self) -> String {
110+
let mut parts = vec![self.to_base_part()];
111+
if let Some(pre) = self.to_pre_release_part() {
112+
parts.push(pre);
113+
}
114+
if let Some(build) = self.to_build_part() {
115+
parts.push(build);
116+
}
117+
parts.join("-")
118+
}
119+
}
120+
```
121+
122+
#### Add methods to PEP440 class
123+
124+
**File**: `src/version/pep440/mod.rs` or appropriate file
125+
126+
```rust
127+
impl PEP440 {
128+
pub fn to_base_part(&self) -> String {
129+
// Return base version including epoch if present
130+
// Implementation depends on PEP440 structure
131+
}
132+
133+
pub fn to_pre_release_part(&self) -> Option<String> {
134+
// Return pre-release components
135+
// Implementation depends on PEP440 structure
136+
}
137+
138+
pub fn to_build_part(&self) -> Option<String> {
139+
// Return build metadata
140+
// Implementation depends on PEP440 structure
141+
}
142+
}
143+
```
144+
145+
### 4. Update ZervTemplateContext::from_zerv()
146+
147+
Modify the `from_zerv()` method to use the new methods:
148+
149+
```rust
150+
impl ZervTemplateContext {
151+
pub fn from_zerv(zerv: &Zerv) -> Self {
152+
let vars = &zerv.vars;
153+
154+
let semver = SemVer::from(zerv.clone());
155+
let pep440 = PEP440::from(zerv.clone());
156+
157+
Self {
158+
// ... existing fields ...
159+
pre_release: vars.pre_release.as_ref().map(|pr| PreReleaseContext {
160+
label: pr.label.label_str().to_string(),
161+
number: pr.number,
162+
pep440_label: Some(pre_release_label_to_pep440_string(&pr.label).to_string()),
163+
}),
164+
semver_base_part: semver.to_base_part(),
165+
semver_pre_release_part: semver.to_pre_release_part(),
166+
semver_build_part: semver.to_build_part(),
167+
semver_docker: semver.to_docker_format(),
168+
pep440_base_part: pep440.to_base_part(),
169+
pep440_pre_release_part: pep440.to_pre_release_part(),
170+
pep440_build_part: pep440.to_build_part(),
171+
}
172+
}
173+
}
174+
```
175+
176+
### 5. Add Comprehensive Tests
177+
178+
Add test cases covering:
179+
180+
- Basic SemVer parsing: `1.2.3`
181+
- SemVer with pre-release: `1.2.3-alpha.1`
182+
- SemVer with build: `1.2.3+build.456`
183+
- SemVer with both: `1.2.3-alpha.1+build.456`
184+
- Basic PEP440: `1.2.3`
185+
- PEP440 with rc: `1.2.3rc1`
186+
- PEP440 with dev: `1.2.3dev1`
187+
- PEP440 with post: `1.2.3.post1`
188+
- Complex PEP440: `1.2.3rc1.post2.dev3+build.456`
189+
190+
### 5. Integration with Existing Version Classes
191+
192+
Ensure compatibility with:
193+
194+
- `SemVer` class in `src/version/semver.rs`
195+
- `PEP440` class in `src/version/pep440.rs`
196+
- `Zerv` class in `src/version/zerv.rs`
197+
198+
## Testing Strategy
199+
200+
### Unit Tests
201+
202+
- Test each parsing function with various version formats
203+
- Test edge cases (empty strings, malformed versions)
204+
- Test integration with existing version classes
205+
206+
### Integration Tests
207+
208+
- Test template rendering with new context fields
209+
- Verify Docker tag format generation
210+
- Test with real Zerv versions from fixtures
211+
212+
## Success Criteria
213+
214+
1. ✅ All new template context fields are properly populated
215+
2. ✅ SemVer parsing correctly separates base, pre-release, and build parts
216+
3. ✅ PEP440 parsing correctly extracts labels (rc, a, b) and components
217+
4. ✅ Docker format follows `base-pre-release-build` pattern
218+
5. ✅ All existing tests pass without modification
219+
6. ✅ New comprehensive test coverage for parsing functions
220+
7. ✅ Template examples work correctly with new fields
221+
222+
## Documentation Updates
223+
224+
- Update template documentation with new field examples
225+
- Add usage examples for Docker tag generation
226+
- Document PEP440 label mapping
227+
228+
## Example Template Usage
229+
230+
After implementation, templates will be able to use:
231+
232+
```tera
233+
# SemVer components
234+
semver: {{ semver }}
235+
base: {{ semver_base_part }}
236+
pre: {{ semver_pre_release_part }}
237+
build: {{ semver_build_part }}
238+
docker: {{ semver_docker }}
239+
240+
# PEP440 components
241+
pep440: {{ pep440 }}
242+
base: {{ pep440_base_part }}
243+
pre: {{ pep440_pre_release_part }}
244+
build: {{ pep440_build_part }}
245+
246+
# Pre-release context (access both label formats)
247+
{% if pre_release %}
248+
label: {{ pre_release.label }} # "alpha", "beta", etc.
249+
pep440_label: {{ pre_release.pep440_label }} # "a", "b", "rc", etc.
250+
number: {{ pre_release.number }}
251+
{% endif %}
252+
253+
# Conditional prefix usage (NEW)
254+
# semver_build_part might be "build.456" or ""
255+
build_with_prefix: {{ prefix_if(semver_build_part, prefix="+") }} # "+build.456" or ""
256+
pre_with_prefix: {{ prefix_if(semver_pre_release_part, prefix="-") }} # "-alpha.1" or ""
257+
```
258+
259+
## Implementation Notes
260+
261+
- **COMPLETED**: All features implemented successfully
262+
- **COMPLETED**: All tests passing (2,334 tests)
263+
- **COMPLETED**: Template context enhanced with new fields
264+
- **COMPLETED**: prefix_if function added and tested
265+
- **COMPLETED**: SemVer and PEP440 parsing methods implemented
266+
- **COMPLETED**: Comprehensive test coverage added
267+
- **COMPLETED**: Backward compatibility maintained

0 commit comments

Comments
 (0)