Skip to content

Commit d380d32

Browse files
authored
feat: improve converter (#107)
1 parent ef373ce commit d380d32

File tree

19 files changed

+1468
-979
lines changed

19 files changed

+1468
-979
lines changed

.dev/23-schema-first-zerv-conversion.md

Lines changed: 101 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -706,16 +706,112 @@ All operations return `Result<T, ZervError>`:
706706
- No duplicate components allowed
707707
- Comprehensive error messages for all validation failures
708708

709-
### 🔄 In Progress
709+
### ✅ Step 2: Update PEP440 from_zerv Implementation - COMPLETED
710710

711-
- **Step 2**: `src/version/pep440/from_zerv.rs` - Plan 20 integration
712-
- **Step 3**: `src/version/semver/from_zerv.rs` - Plan 20 integration
713-
- **Step 4**: `src/version/pep440/mod.rs` - Two-tier API
714-
- **Step 5**: `src/version/semver/mod.rs` - Two-tier API
711+
**File**: `src/version/pep440/from_zerv.rs`
712+
713+
**✅ All requirements implemented:**
714+
715+
1. **✅ Schema-driven approach** - Replaced manual resolution with schema structure processing
716+
2. **✅ Plan 20 integration** - Uses `resolve_value()` and `resolve_expanded_values()` methods exclusively
717+
3. **✅ Component categorization** - Uses `is_secondary_component()` for proper placement logic
718+
4. **✅ Sanitization strategy** - Uses `Sanitizer::uint()` for integers and `Sanitizer::pep440_local_str()` for local strings
719+
5. **✅ Code organization** - Extracted processing logic into separate methods for better maintainability
720+
721+
**✅ Implementation details:**
722+
723+
- **Core processing**: `process_core()` - Appends integers to release vector, overflows to local
724+
- **Extra core processing**: `process_extra_core()` - Handles secondary components (Epoch, PreRelease, Post, Dev) with specific logic, non-secondary components go to local
725+
- **Build processing**: `process_build()` - All components go to local segments
726+
- **Helper method**: `add_flattened_to_local()` - Splits dot-separated values and adds to local segments
727+
- **Import optimization**: Added proper imports for PostLabel and DevLabel
728+
729+
**✅ Test verification**: All 47 PEP440 from_zerv tests pass, confirming functionality is preserved
730+
731+
### ✅ Step 3: Update SemVer from_zerv Implementation - COMPLETED
732+
733+
**File**: `src/version/semver/from_zerv.rs`
734+
735+
**✅ All requirements implemented:**
736+
737+
1. **✅ Schema-driven approach** - Replaced manual resolution with schema structure processing
738+
2. **✅ Plan 20 integration** - Uses `resolve_value()` and `resolve_expanded_values()` methods exclusively
739+
3. **✅ Component categorization** - Uses `is_secondary_component()` for proper placement logic
740+
4. **✅ Sanitization strategy** - Uses `Sanitizer::uint()` for integers and `Sanitizer::semver_str()` for strings
741+
5. **✅ Code organization** - Extracted processing logic into separate methods for better maintainability
742+
6. **✅ Custom field handling** - Fixed `Var::Custom` sanitization to apply sanitizer even when no custom data exists
743+
744+
**✅ Implementation details:**
745+
746+
- **Core processing**: `process_core()` - First 3 parsable ints go to major/minor/patch, rest to pre-release
747+
- **Extra core processing**: `process_extra_core()` - Secondary components get labeled with `resolve_expanded_values()`, others go to pre-release
748+
- **Build processing**: `process_build()` - All components go to build metadata
749+
- **Helper methods**: `add_flattened_to_prerelease()` and `add_flattened_to_build()` for dot-separated values
750+
- **Bug fix**: Fixed `Var::Custom` to apply sanitization even when no custom data exists
751+
752+
**✅ Test verification**: All 72 SemVer from_zerv tests pass, confirming functionality is preserved
753+
754+
### ✅ Step 4: Two-Tier API for PEP440 to_zerv - COMPLETED
755+
756+
**File**: `src/version/pep440/to_zerv.rs`
757+
758+
**✅ All requirements implemented:**
759+
760+
1. **✅ Two-tier API structure** - `From<PEP440>` trait uses default schema, `to_zerv_with_schema()` accepts custom schemas
761+
2. **✅ Default schema factory** - `ZervSchema::pep440_default()` method implemented in schema/core.rs
762+
3. **✅ Schema validation** - Only supports default PEP440 schema for now, returns proper error for custom schemas
763+
4. **✅ Field mapping** - Maps PEP440 fields to ZervVars with proper type conversions (u32 → u64)
764+
5. **✅ Pre-release handling** - Uses `PreReleaseVar` struct with label and optional number
765+
6. **✅ Excess release parts** - Modifies schema to add excess parts beyond major.minor.patch to core
766+
7. **✅ Local segments** - Adds local segments to build section of schema
767+
8. **✅ Plan compliance** - Follows plan specification exactly, respects provided schema and modifies for excess parts
768+
769+
**✅ Implementation details:**
770+
771+
- **Simple API**: `From<PEP440>` uses `pep440_default()` schema and expects conversion to work
772+
- **Advanced API**: `to_zerv_with_schema()` validates schema compatibility and handles custom schemas (future)
773+
- **Schema modification**: Properly modifies provided schema for excess release parts and local segments
774+
- **Error handling**: Returns `ZervError::NotImplemented` for unsupported custom schemas
775+
- **Type safety**: All numeric conversions properly handle u32 → u64 casting
776+
- **Lint compliance**: Fixed clippy warnings by using struct initialization instead of field reassignment
777+
778+
**✅ Test verification**: All 45 PEP440 to_zerv tests pass, including round-trip conversions
779+
780+
### ✅ Step 5: Two-Tier API for SemVer to_zerv - COMPLETED
781+
782+
**File**: `src/version/semver/to_zerv.rs`
783+
784+
**✅ All requirements implemented:**
785+
786+
1. **✅ Two-tier API structure** - `From<SemVer>` trait uses default schema, `to_zerv_with_schema()` accepts custom schemas
787+
2. **✅ Default schema factory** - `ZervSchema::semver_default()` method implemented in schema/core.rs
788+
3. **✅ Schema validation** - Only supports default SemVer schema for now, returns proper error for custom schemas
789+
4. **✅ Field mapping** - Maps SemVer fields to ZervVars with proper type handling (u64 → u64)
790+
5. **✅ Pre-release processing** - Uses existing `PreReleaseProcessor` to handle complex pre-release patterns
791+
6. **✅ Build metadata handling** - Adds build metadata components to schema build section
792+
7. **✅ Schema modification** - Properly modifies provided schema for pre-release and build components
793+
8. **✅ Plan compliance** - Follows plan specification exactly, respects provided schema and modifies for additional components
794+
795+
**✅ Implementation details:**
796+
797+
- **Simple API**: `From<SemVer>` uses `semver_default()` schema and expects conversion to work
798+
- **Advanced API**: `to_zerv_with_schema()` validates schema compatibility and handles custom schemas (future)
799+
- **Schema modification**: Uses validated setters to add extra_core and build components
800+
- **Error handling**: Returns `ZervError::NotImplemented` for unsupported custom schemas
801+
- **Type safety**: No unnecessary casts since SemVer fields are already u64
802+
- **Lint compliance**: Fixed clippy warnings by removing unnecessary type casts
803+
804+
**✅ Test verification**: All 69 SemVer to_zerv tests pass, including round-trip conversions
805+
806+
### 🔄 Next Steps
807+
808+
- **Step 6**: Update remaining test files and modules for new validated API
715809

716810
### ⚠️ Breaking Changes Expected
717811

718812
- ✅ Test files - Updated for getter access and new validation rules
813+
-**Schema factory methods** - Both PEP440 and SemVer default schemas implemented
814+
-**Two-tier APIs** - Both PEP440 and SemVer conversion APIs completed
719815
- ⚠️ **Remaining modules** - CLI and other modules still need updates for private fields
720816
- Modules with complex dependencies may be temporarily commented out
721817

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
# SemVer to Zerv Conversion - Clean Implementation
2+
3+
```rust
4+
use super::{
5+
BuildMetadata,
6+
PreReleaseIdentifier,
7+
SemVer,
8+
};
9+
use crate::error::ZervError;
10+
use crate::version::zerv::core::PreReleaseLabel;
11+
use crate::version::zerv::{
12+
Component,
13+
PreReleaseVar,
14+
Var,
15+
Zerv,
16+
ZervSchema,
17+
ZervVars,
18+
};
19+
20+
struct PreReleaseProcessor<'a> {
21+
vars: &'a mut ZervVars,
22+
schema: &'a mut ZervSchema,
23+
pending_var: Option<Var>,
24+
}
25+
26+
impl<'a> PreReleaseProcessor<'a> {
27+
fn new(vars: &'a mut ZervVars, schema: &'a mut ZervSchema) -> Self {
28+
Self {
29+
vars,
30+
schema,
31+
pending_var: None,
32+
}
33+
}
34+
35+
fn is_var_set(&self, var: &Var) -> bool {
36+
match var {
37+
Var::PreRelease => self.vars.pre_release.is_some() || self.pending_var == Some(Var::PreRelease),
38+
Var::Epoch => self.vars.epoch.is_some() || self.pending_var == Some(Var::Epoch),
39+
Var::Post => self.vars.post.is_some() || self.pending_var == Some(Var::Post),
40+
Var::Dev => self.vars.dev.is_some() || self.pending_var == Some(Var::Dev),
41+
_ => false,
42+
}
43+
}
44+
45+
fn handle_pending_prerelease(&mut self, identifier: &PreReleaseIdentifier) -> Result<bool, ZervError> {
46+
if let Some(Var::PreRelease) = self.pending_var {
47+
if let PreReleaseIdentifier::String(_) = identifier {
48+
self.schema.push_extra_core(Component::Var(Var::PreRelease))?;
49+
self.pending_var = None;
50+
return Ok(true);
51+
}
52+
}
53+
Ok(false)
54+
}
55+
56+
fn handle_duplicate(&mut self, var: Var, s: &str) -> Result<bool, ZervError> {
57+
if let Some(pending) = self.pending_var {
58+
if let Some(current_var) = Var::try_from_secondary_label(s) {
59+
if current_var == pending || self.is_var_set(&current_var) {
60+
self.schema.push_extra_core(Component::Var(pending))?;
61+
self.pending_var = None;
62+
self.schema.push_extra_core(Component::Str(s.to_string()))?;
63+
return Ok(true);
64+
}
65+
}
66+
}
67+
Ok(false)
68+
}
69+
70+
fn set_var_value(&mut self, var: Var, value: Option<u64>) -> Result<(), ZervError> {
71+
match var {
72+
Var::Epoch => self.vars.epoch = value,
73+
Var::Post => self.vars.post = value,
74+
Var::Dev => self.vars.dev = value,
75+
Var::PreRelease => {
76+
if let Some(ref mut pr) = self.vars.pre_release {
77+
pr.number = value;
78+
}
79+
}
80+
_ => {}
81+
}
82+
self.schema.push_extra_core(Component::Var(var))?;
83+
self.pending_var = None;
84+
Ok(())
85+
}
86+
87+
fn process_string(&mut self, s: &str) -> Result<(), ZervError> {
88+
if let Some(var) = Var::try_from_secondary_label(s) {
89+
if self.is_var_set(&var) {
90+
self.schema.push_extra_core(Component::Str(s.to_string()))?;
91+
} else if var == Var::PreRelease {
92+
if let Some(label) = PreReleaseLabel::try_from_str(s) {
93+
self.vars.pre_release = Some(PreReleaseVar { label, number: None });
94+
self.pending_var = Some(var);
95+
} else {
96+
self.schema.push_extra_core(Component::Str(s.to_string()))?;
97+
}
98+
} else {
99+
self.pending_var = Some(var);
100+
}
101+
} else {
102+
self.schema.push_extra_core(Component::Str(s.to_string()))?;
103+
}
104+
Ok(())
105+
}
106+
107+
fn process_uint(&mut self, n: u64) -> Result<(), ZervError> {
108+
if let Some(var) = self.pending_var {
109+
self.set_var_value(var, Some(n))
110+
} else {
111+
self.schema.push_extra_core(Component::Int(n))
112+
}
113+
}
114+
115+
fn finalize(&mut self) -> Result<(), ZervError> {
116+
if let Some(var) = self.pending_var {
117+
self.schema.push_extra_core(Component::Var(var))?;
118+
}
119+
Ok(())
120+
}
121+
}
122+
123+
impl From<SemVer> for Zerv {
124+
fn from(semver: SemVer) -> Self {
125+
let schema = ZervSchema::semver_default().expect("SemVer default schema should be valid");
126+
semver
127+
.to_zerv_with_schema(&schema)
128+
.expect("SemVer default conversion should work")
129+
}
130+
}
131+
132+
impl SemVer {
133+
pub fn to_zerv_with_schema(&self, schema: &ZervSchema) -> Result<Zerv, ZervError> {
134+
if *schema != ZervSchema::semver_default()? {
135+
return Err(ZervError::NotImplemented(
136+
"Custom schemas not yet implemented for SemVer conversion".to_string(),
137+
));
138+
}
139+
140+
let mut vars = ZervVars {
141+
major: Some(self.major),
142+
minor: Some(self.minor),
143+
patch: Some(self.patch),
144+
..Default::default()
145+
};
146+
147+
let mut schema = schema.clone();
148+
let mut processor = PreReleaseProcessor::new(&mut vars, &mut schema);
149+
150+
// Process pre-release identifiers
151+
if let Some(pre_release) = &self.pre_release {
152+
for identifier in pre_release {
153+
// Handle pending PreRelease var
154+
if processor.handle_pending_prerelease(identifier)? {
155+
continue;
156+
}
157+
158+
// Handle pending var with potential duplicates
159+
if let PreReleaseIdentifier::String(s) = identifier {
160+
if processor.handle_duplicate(processor.pending_var.unwrap_or(Var::Major), s)? {
161+
continue;
162+
}
163+
}
164+
165+
// Process pending var value
166+
if processor.pending_var.is_some() {
167+
let value = match identifier {
168+
PreReleaseIdentifier::UInt(n) => Some(*n),
169+
_ => None,
170+
};
171+
processor.set_var_value(processor.pending_var.unwrap(), value)?;
172+
continue;
173+
}
174+
175+
// Process new identifier
176+
match identifier {
177+
PreReleaseIdentifier::String(s) => processor.process_string(s)?,
178+
PreReleaseIdentifier::UInt(n) => processor.process_uint(*n)?,
179+
}
180+
}
181+
}
182+
183+
processor.finalize()?;
184+
185+
// Handle build metadata
186+
if let Some(build_metadata) = &self.build_metadata {
187+
for metadata in build_metadata {
188+
let component = match metadata {
189+
BuildMetadata::String(s) => Component::Str(s.clone()),
190+
BuildMetadata::UInt(n) => Component::Int(*n),
191+
};
192+
schema.push_build(component)?;
193+
}
194+
}
195+
196+
Ok(Zerv { vars, schema })
197+
}
198+
}
199+
```

.github/workflows/ci-pre-commit.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
python-version: "3.x"
1616

1717
- name: Setup Node.js
18-
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
18+
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
1919
with:
2020
node-version: "latest"
2121

0 commit comments

Comments
 (0)