Skip to content

Commit 09a4dc1

Browse files
authored
feat: implement pep440/zerv conversion (#36)
- implement pep440/zerv conversion - update .releaserc.js
1 parent b104b6b commit 09a4dc1

File tree

7 files changed

+461
-25
lines changed

7 files changed

+461
-25
lines changed

.releaserc.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module.exports = {
2+
branches: ["main"],
3+
plugins: [
4+
[
5+
"@semantic-release/commit-analyzer",
6+
{
7+
preset: "angular",
8+
releaseRules: [
9+
// Pre-alpha configuration: all commit types trigger patch releases
10+
// TODO: Change feat to "minor" when project reaches stable state
11+
{ type: "feat", release: "patch" },
12+
{ type: "fix", release: "patch" },
13+
{ type: "chore", release: "patch" },
14+
{ type: "docs", release: "patch" },
15+
{ type: "style", release: "patch" },
16+
{ type: "refactor", release: "patch" },
17+
{ type: "perf", release: "patch" },
18+
{ type: "test", release: "patch" },
19+
{ type: "build", release: "patch" },
20+
{ type: "ci", release: "patch" },
21+
],
22+
},
23+
],
24+
"@semantic-release/release-notes-generator",
25+
"@semantic-release/github",
26+
],
27+
}

.releaserc.json

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/version/pep440/from_zerv.rs

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
use super::{LocalSegment, PEP440};
2+
use crate::version::pep440::core::{DevLabel, PostLabel};
3+
use crate::version::zerv::{Component, Zerv, resolve_timestamp};
4+
5+
impl From<Zerv> for PEP440 {
6+
fn from(zerv: Zerv) -> Self {
7+
// Extract values from core components
8+
let mut core_values = Vec::new();
9+
for comp in &zerv.format.core {
10+
let val = match comp {
11+
Component::VarField(field) => match field.as_str() {
12+
"major" => zerv.vars.major.unwrap_or(0),
13+
"minor" => zerv.vars.minor.unwrap_or(0),
14+
"patch" => zerv.vars.patch.unwrap_or(0),
15+
_ => 0,
16+
},
17+
Component::VarTimestamp(pattern) => {
18+
resolve_timestamp(pattern, zerv.vars.tag_timestamp).unwrap_or(0)
19+
}
20+
Component::Integer(n) => *n,
21+
_ => 0,
22+
};
23+
core_values.push(val as u32);
24+
}
25+
26+
// Extract release from core, filtering out non-numeric components that overflow to local
27+
let mut release = Vec::new();
28+
let mut local_overflow = Vec::new();
29+
30+
for val in core_values {
31+
release.push(val);
32+
}
33+
34+
// If release is empty, default to [0]
35+
if release.is_empty() {
36+
release.push(0);
37+
}
38+
39+
// Process extra_core for epoch, pre-release, post, dev, and other components
40+
let mut epoch = 0;
41+
let mut pre_label = None;
42+
let mut pre_number = None;
43+
let mut post_label = None;
44+
let mut post_number = None;
45+
let mut dev_label = None;
46+
let mut dev_number = None;
47+
48+
for comp in &zerv.format.extra_core {
49+
match comp {
50+
Component::VarField(field) => match field.as_str() {
51+
"pre_release" => {
52+
if let Some(pr) = &zerv.vars.pre_release {
53+
pre_label = Some(pr.label.clone());
54+
pre_number = pr.number.map(|n| n as u32);
55+
}
56+
}
57+
"epoch" => {
58+
epoch = zerv.vars.epoch.unwrap_or(0) as u32;
59+
}
60+
"post" => {
61+
post_label = Some(PostLabel::Post);
62+
post_number = zerv.vars.post.map(|n| n as u32);
63+
}
64+
"dev" => {
65+
dev_label = Some(DevLabel::Dev);
66+
dev_number = zerv.vars.dev.map(|n| n as u32);
67+
}
68+
_ => {
69+
// Other fields overflow to local
70+
local_overflow.push(LocalSegment::String(field.clone()));
71+
}
72+
},
73+
Component::String(s) => {
74+
local_overflow.push(LocalSegment::String(s.clone()));
75+
}
76+
Component::Integer(n) => {
77+
if *n <= u32::MAX as u64 {
78+
local_overflow.push(LocalSegment::Integer(*n as u32));
79+
} else {
80+
local_overflow.push(LocalSegment::String(n.to_string()));
81+
}
82+
}
83+
_ => {}
84+
}
85+
}
86+
87+
// Process build components - they go to local
88+
for comp in &zerv.format.build {
89+
match comp {
90+
Component::String(s) => {
91+
local_overflow.push(LocalSegment::String(s.clone()));
92+
}
93+
Component::Integer(n) => {
94+
if *n <= u32::MAX as u64 {
95+
local_overflow.push(LocalSegment::Integer(*n as u32));
96+
} else {
97+
local_overflow.push(LocalSegment::String(n.to_string()));
98+
}
99+
}
100+
Component::VarTimestamp(pattern) => {
101+
let val = resolve_timestamp(pattern, zerv.vars.tag_timestamp).unwrap_or(0);
102+
if val <= u32::MAX as u64 {
103+
local_overflow.push(LocalSegment::Integer(val as u32));
104+
} else {
105+
local_overflow.push(LocalSegment::String(val.to_string()));
106+
}
107+
}
108+
_ => {}
109+
}
110+
}
111+
112+
let local = if local_overflow.is_empty() {
113+
None
114+
} else {
115+
Some(local_overflow)
116+
};
117+
118+
PEP440 {
119+
epoch,
120+
release,
121+
pre_label,
122+
pre_number,
123+
post_label,
124+
post_number,
125+
dev_label,
126+
dev_number,
127+
local,
128+
}
129+
}
130+
}
131+
132+
#[cfg(test)]
133+
mod tests {
134+
use super::*;
135+
use crate::version::zerv::test_utils::*;
136+
use crate::version::zerv::{Component, PreReleaseLabel};
137+
use rstest::rstest;
138+
139+
#[rstest]
140+
// Basic version
141+
#[case(with_version(1, 2, 3), "1.2.3")]
142+
// With epoch
143+
#[case({
144+
let mut zerv = with_version(1, 2, 3);
145+
zerv.format.extra_core.push(Component::VarField("epoch".to_string()));
146+
zerv.vars.epoch = Some(2);
147+
zerv
148+
}, "2!1.2.3")]
149+
// With pre-release
150+
#[case({
151+
let mut zerv = with_version(1, 2, 3);
152+
zerv.format.extra_core.push(Component::VarField("pre_release".to_string()));
153+
zerv.vars.pre_release = Some(crate::version::zerv::PreReleaseVar {
154+
label: PreReleaseLabel::Alpha,
155+
number: Some(1),
156+
});
157+
zerv
158+
}, "1.2.3a1")]
159+
// With post
160+
#[case({
161+
let mut zerv = with_version(1, 2, 3);
162+
zerv.format.extra_core.push(Component::VarField("post".to_string()));
163+
zerv.vars.post = Some(1);
164+
zerv
165+
}, "1.2.3.post1")]
166+
// With dev
167+
#[case({
168+
let mut zerv = with_version(1, 2, 3);
169+
zerv.format.extra_core.push(Component::VarField("dev".to_string()));
170+
zerv.vars.dev = Some(1);
171+
zerv
172+
}, "1.2.3.dev1")]
173+
// With local from build
174+
#[case({
175+
let mut zerv = with_version(1, 2, 3);
176+
zerv.format.build = vec![
177+
Component::String("ubuntu".to_string()),
178+
Component::Integer(20),
179+
Component::Integer(4),
180+
];
181+
zerv
182+
}, "1.2.3+ubuntu.20.4")]
183+
// Complex version with all components
184+
#[case({
185+
let mut zerv = with_version(1, 2, 3);
186+
zerv.format.extra_core = vec![
187+
Component::VarField("epoch".to_string()),
188+
Component::VarField("pre_release".to_string()),
189+
Component::VarField("post".to_string()),
190+
Component::VarField("dev".to_string()),
191+
];
192+
zerv.format.build = vec![
193+
Component::String("local".to_string()),
194+
Component::Integer(1),
195+
];
196+
zerv.vars.epoch = Some(2);
197+
zerv.vars.pre_release = Some(crate::version::zerv::PreReleaseVar {
198+
label: PreReleaseLabel::Alpha,
199+
number: Some(1),
200+
});
201+
zerv.vars.post = Some(1);
202+
zerv.vars.dev = Some(1);
203+
zerv
204+
}, "2!1.2.3a1.post1.dev1+local.1")]
205+
fn test_zerv_to_pep440_conversion(#[case] zerv: Zerv, #[case] expected_pep440_str: &str) {
206+
let pep440: PEP440 = zerv.into();
207+
assert_eq!(pep440.to_string(), expected_pep440_str);
208+
}
209+
}

src/version/pep440/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
pub mod core;
22
mod display;
3+
mod from_zerv;
34
mod ordering;
45
mod parser;
6+
mod to_zerv;
57
pub mod utils;
68

79
pub use core::{LocalSegment, PEP440};

0 commit comments

Comments
 (0)