Skip to content

Commit 000e12f

Browse files
authored
Add Windows 7 release target (#11)
* docs: mark doctests as ignored - we are not exposing any public functions, hence doctests will fail otherwise * docs: improve coverage across helpers and models * docs: remove commented example code * docs: remove example code snippets * chore(ci): add win7 msvc release target * ci: simplify release matrix and add win7 i686 build-std handling * ci: avoid rust-std download for win7 build-std targets
1 parent 55bc00f commit 000e12f

11 files changed

Lines changed: 221 additions & 157 deletions

File tree

.github/workflows/release.yml

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ jobs:
3030
~/.cargo/registry
3131
~/.cargo/git
3232
target
33-
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
33+
key: ${{ runner.os }}-cargo-stable-${{ hashFiles('**/Cargo.lock') }}
3434
restore-keys: |
3535
${{ runner.os }}-cargo-
3636
37-
- name: Install stable toolchain
37+
- name: Install toolchain
3838
uses: dtolnay/rust-toolchain@0b1efabc08b657293548b77fb76cc02d26091c7e
3939
with:
4040
toolchain: stable
@@ -63,27 +63,59 @@ jobs:
6363
linux: true
6464
pkg_config_libdir: ""
6565
multilib: false
66+
toolchain: stable
67+
build_std_flag: ""
68+
install_target: true
6669
- runner: windows-latest
6770
target: x86_64-pc-windows-msvc
6871
archive: zip
6972
rustflags: ""
7073
linux: false
7174
pkg_config_libdir: ""
7275
multilib: false
76+
toolchain: stable
77+
build_std_flag: ""
78+
install_target: true
79+
- runner: windows-latest
80+
target: x86_64-win7-windows-msvc
81+
archive: zip
82+
rustflags: ""
83+
linux: false
84+
pkg_config_libdir: ""
85+
multilib: false
86+
toolchain: nightly
87+
build_std_flag: "-Z build-std"
88+
install_target: false
7389
- runner: ubuntu-latest
7490
target: i686-unknown-linux-gnu
7591
archive: tar.gz
7692
rustflags: ""
7793
linux: true
7894
pkg_config_libdir: "/usr/lib/i386-linux-gnu/pkgconfig"
7995
multilib: true
96+
toolchain: stable
97+
build_std_flag: ""
98+
install_target: true
8099
- runner: windows-latest
81100
target: i686-pc-windows-msvc
82101
archive: zip
83102
rustflags: ""
84103
linux: false
85104
pkg_config_libdir: ""
86105
multilib: false
106+
toolchain: stable
107+
build_std_flag: ""
108+
install_target: true
109+
- runner: windows-latest
110+
target: i686-win7-windows-msvc
111+
archive: zip
112+
rustflags: ""
113+
linux: false
114+
pkg_config_libdir: ""
115+
multilib: false
116+
toolchain: nightly
117+
build_std_flag: "-Z build-std"
118+
install_target: false
87119
runs-on: ${{ matrix.runner }}
88120
env:
89121
TARGET: ${{ matrix.target }}
@@ -93,15 +125,30 @@ jobs:
93125
- name: Checkout
94126
uses: actions/checkout@v6
95127

96-
- name: Set safe ref name
128+
- name: Set safe ref name (Windows)
129+
if: runner.os == 'Windows'
130+
shell: pwsh
131+
run: |
132+
$safe = $env:GITHUB_REF_NAME -replace '/', '-'
133+
Add-Content -Path $env:GITHUB_ENV -Value "SAFE_REF=$safe"
134+
135+
- name: Set safe ref name (Unix)
136+
if: runner.os != 'Windows'
97137
shell: bash
98138
run: echo "SAFE_REF=${GITHUB_REF_NAME//\//-}" >> "$GITHUB_ENV"
99139

100-
- name: Install stable toolchain
101-
uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a
140+
- name: Install toolchain
141+
uses: dtolnay/rust-toolchain@0b1efabc08b657293548b77fb76cc02d26091c7e
102142
with:
103-
toolchain: stable
104-
target: ${{ matrix.target }}
143+
toolchain: ${{ matrix.toolchain }}
144+
145+
- name: Add target
146+
if: matrix.install_target
147+
run: rustup target add ${{ matrix.target }} --toolchain ${{ matrix.toolchain }}
148+
149+
- name: Add rust-src component
150+
if: matrix.build_std_flag != ''
151+
run: rustup component add rust-src --toolchain ${{ matrix.toolchain }}
105152

106153
- name: Install Linux build deps
107154
if: matrix.linux
@@ -131,18 +178,18 @@ jobs:
131178
~/.cargo/registry
132179
~/.cargo/git
133180
target
134-
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
181+
key: ${{ runner.os }}-cargo-${{ matrix.toolchain }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
135182
restore-keys: |
136183
${{ runner.os }}-cargo-
137184
138185
- name: Build release
139-
run: cargo build --release --target ${{ matrix.target }}
186+
run: cargo +${{ matrix.toolchain }} build ${{ matrix.build_std_flag }} --release --target ${{ matrix.target }}
140187

141188
- name: Package artifact (Linux)
142189
if: matrix.archive == 'tar.gz'
143190
run: |
144191
bin_path=target/$TARGET/release/elnpack
145-
pkg_dir=package-linux
192+
pkg_dir=package-${{ runner.os }}
146193
mkdir -p "$pkg_dir"
147194
cp "$bin_path" "$pkg_dir/"
148195
cp README.md LICENSE "$pkg_dir/"
@@ -153,7 +200,7 @@ jobs:
153200
shell: pwsh
154201
run: |
155202
$binPath = "target/${env:TARGET}/release/elnpack.exe"
156-
$pkgDir = "package-windows"
203+
$pkgDir = "package-${env:RUNNER_OS}"
157204
New-Item -ItemType Directory -Force -Path $pkgDir | Out-Null
158205
Copy-Item $binPath "$pkgDir/"
159206
Copy-Item "README.md","LICENSE" "$pkgDir/"

src/app/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ use eframe::egui;
88
use egui_phosphor::Variant;
99

1010
/// Bootstrap the desktop application and run the main egui event loop.
11+
///
12+
/// # Errors
13+
///
14+
/// Propagates any failure from `eframe::run_native`, such as window creation errors.
15+
///
16+
/// # Examples
17+
///
18+
/// ```rust,ignore
19+
/// fn main() -> eframe::Result<()> {
20+
/// elnpack::app::run()
21+
/// }
22+
/// ```
1123
pub fn run() -> eframe::Result<()> {
1224
// Register Phosphor icon font.
1325
let mut fonts = egui::FontDefinitions::default();

src/logic/eln.rs

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,8 @@ pub fn ensure_extension(mut path: PathBuf, extension: &str) -> PathBuf {
9494
///
9595
/// # Examples
9696
///
97-
/// ```no_run
97+
/// ```rust,ignore
9898
/// use time::OffsetDateTime;
99-
/// // Construct attachments and extra fields according to your application's types,
100-
/// // then call `build_and_write_archive`.
10199
/// let output = std::path::Path::new("example.eln");
102100
/// let title = "My Experiment";
103101
/// let body = "# Notes\n\nExperiment body";
@@ -317,7 +315,7 @@ pub fn build_and_write_archive(
317315
///
318316
/// # Examples
319317
///
320-
/// ```
318+
/// ```rust,ignore
321319
/// let export = build_extra_fields_export(&[], &[]).unwrap();
322320
/// assert!(export.property_values.is_empty());
323321
/// assert!(export.variable_measured_ids.len() >= 1); // metadata property id is always present
@@ -395,8 +393,7 @@ fn build_extra_fields_export(
395393
///
396394
/// # Examples
397395
///
398-
/// ```
399-
/// // Produce minimal eLabFTW metadata with no fields or groups.
396+
/// ```rust,ignore
400397
/// let json = reconstruct_elabftw_metadata(&[], &[]).unwrap();
401398
/// assert!(json.contains(r#""elabftw""#));
402399
/// assert!(json.contains(r#""extra_fields""#));
@@ -525,10 +522,9 @@ fn reconstruct_elabftw_metadata(
525522
///
526523
/// # Examples
527524
///
528-
/// ```
525+
/// ```rust,ignore
529526
/// # use crate::models::extra_fields::{ExtraField, ExtraFieldKind};
530527
/// # use serde_json::Value;
531-
/// // multi-value field
532528
/// let f_multi = ExtraField {
533529
/// value: "".to_string(),
534530
/// value_multi: vec!["a".into(), "b".into()],
@@ -539,7 +535,6 @@ fn reconstruct_elabftw_metadata(
539535
/// let v = crate::logic::eln::value_to_json(&f_multi);
540536
/// assert_eq!(v, Value::Array(vec![Value::String("a".into()), Value::String("b".into())]));
541537
///
542-
/// // numeric kind exported as string
543538
/// let f_num = ExtraField {
544539
/// value: "3.14".to_string(),
545540
/// value_multi: Vec::new(),
@@ -686,13 +681,6 @@ mod tests {
686681
/// - the experiment node's `variableMeasured` contains both a per-field `PropertyValue` and the metadata `PropertyValue`,
687682
/// - the per-field `PropertyValue` for the "Detector" field has the expected `@type`, `valueReference`, `value`, and `unitText`,
688683
/// - the `elabftw_metadata` blob is present and includes the "Detector" field with the expected `type` and `value`.
689-
///
690-
/// # Examples
691-
///
692-
/// ```
693-
/// // Creates an archive with one select extra field and verifies the ro-crate metadata
694-
/// // contains both the PropertyValue node and the elabftw_metadata blob.
695-
/// ```
696684
#[test]
697685
fn build_and_write_archive_writes_elabftw_style_extra_fields() {
698686
use tempfile::TempDir;

src/models/attachment.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,25 @@ pub struct Attachment {
2525

2626
impl Attachment {
2727
/// Construct a new attachment with pre-sanitized metadata.
28+
///
29+
/// The caller must provide a name that is already filesystem-safe and
30+
/// unique within the archive.
31+
///
32+
/// # Examples
33+
///
34+
/// ```rust,ignore
35+
/// use std::path::PathBuf;
36+
/// use elnpack::models::attachment::Attachment;
37+
///
38+
/// let att = Attachment::new(
39+
/// PathBuf::from("/tmp/note.txt"),
40+
/// "note.txt".into(),
41+
/// "text/plain".into(),
42+
/// "unavailable".into(),
43+
/// 42,
44+
/// );
45+
/// assert_eq!(att.sanitized_name, "note.txt");
46+
/// ```
2847
pub fn new(
2948
path: PathBuf,
3049
sanitized_name: String,
@@ -43,6 +62,23 @@ impl Attachment {
4362
}
4463

4564
/// Ensure there are no duplicate archive paths produced by sanitized names.
65+
///
66+
/// # Errors
67+
///
68+
/// Returns an error when two attachments share the same `sanitized_name`.
69+
///
70+
/// # Examples
71+
///
72+
/// ```rust,ignore
73+
/// use std::path::PathBuf;
74+
/// use elnpack::models::attachment::{Attachment, assert_unique_sanitized_names};
75+
///
76+
/// let attachments = vec![
77+
/// Attachment::new(PathBuf::from("a.txt"), "a.txt".into(), "text/plain".into(), "unavailable".into(), 1),
78+
/// Attachment::new(PathBuf::from("b.txt"), "a.txt".into(), "text/plain".into(), "unavailable".into(), 1),
79+
/// ];
80+
/// assert!(assert_unique_sanitized_names(&attachments).is_err());
81+
/// ```
4682
pub fn assert_unique_sanitized_names(attachments: &[Attachment]) -> Result<()> {
4783
let mut seen = HashSet::new();
4884
for att in attachments {

src/models/extra_fields.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ impl ExtraFieldKind {
3838
///
3939
/// # Examples
4040
///
41-
/// ```
41+
/// ```rust,ignore
4242
/// use crate::models::extra_fields::ExtraFieldKind;
4343
///
4444
/// assert_eq!(ExtraFieldKind::from_str("text"), ExtraFieldKind::Text);
@@ -128,7 +128,7 @@ impl ExtraField {
128128
///
129129
/// # Examples
130130
///
131-
/// ```
131+
/// ```rust,ignore
132132
/// use crate::models::extra_fields::{ExtraField, ExtraFieldKind, validate_field};
133133
///
134134
/// let valid_number = ExtraField {
@@ -280,7 +280,7 @@ pub struct ExtraFieldsImport {
280280
///
281281
/// # Examples
282282
///
283-
/// ```
283+
/// ```rust,ignore
284284
/// let json = r#"
285285
/// {
286286
/// "extra_fields": {
@@ -386,7 +386,7 @@ pub fn parse_elabftw_extra_fields(json: &str) -> Result<ExtraFieldsImport> {
386386
///
387387
/// # Examples
388388
///
389-
/// ```
389+
/// ```rust,ignore
390390
/// use serde_json::Value;
391391
///
392392
/// assert_eq!(super::value_to_string(None), None);

src/models/keywords.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@ pub struct Keywords {
1111

1212
impl Keywords {
1313
/// Create a keyword collection, normalizing duplicates case-insensitively.
14+
///
15+
/// Leading/trailing whitespace is preserved; only duplicate tokens
16+
/// (case-insensitive) are removed, keeping the first occurrence's casing.
17+
///
18+
/// # Examples
19+
///
20+
/// ```rust,ignore
21+
/// use elnpack::models::keywords::Keywords;
22+
///
23+
/// let kw = Keywords::new(vec!["DNA".into(), "dna".into(), "RNA".into()]);
24+
/// assert_eq!(kw.items(), &["DNA", "RNA"]);
25+
/// ```
1426
pub fn new(items: Vec<String>) -> Self {
1527
let mut kw = Self { items };
1628
kw.normalize();
@@ -19,11 +31,26 @@ impl Keywords {
1931

2032
#[allow(dead_code)]
2133
/// Borrow the normalized keyword slice.
34+
///
35+
/// # Examples
36+
///
37+
/// ```rust,ignore
38+
/// let kw = elnpack::models::keywords::Keywords::new(vec!["A".into()]);
39+
/// assert_eq!(kw.items(), &["A"]);
40+
/// ```
2241
pub fn items(&self) -> &[String] {
2342
&self.items
2443
}
2544

2645
/// Consume the wrapper and return the owned vector.
46+
///
47+
/// # Examples
48+
///
49+
/// ```rust,ignore
50+
/// let kw = elnpack::models::keywords::Keywords::new(vec!["X".into()]);
51+
/// let owned = kw.into_vec();
52+
/// assert_eq!(owned, vec!["X".to_string()]);
53+
/// ```
2754
pub fn into_vec(self) -> Vec<String> {
2855
self.items
2956
}

src/mvu/mod.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ pub struct SavePayload {
116116
///
117117
/// # Examples
118118
///
119-
/// ```
119+
/// ```rust,ignore
120120
/// let mut model = AppModel::default();
121121
/// let mut cmds = Vec::new();
122122
/// update(&mut model, Msg::EntryTitleChanged("New title".into()), &mut cmds);
@@ -220,10 +220,8 @@ pub fn update(model: &mut AppModel, msg: Msg, cmds: &mut Vec<Command>) {
220220
///
221221
/// # Examples
222222
///
223-
/// ```
223+
/// ```rust,ignore
224224
/// use std::path::PathBuf;
225-
/// // Construct a command for a non-existent file to exercise the hash path that falls back
226-
/// // to `"unavailable"` for the sha256 and `0` for size.
227225
/// let cmd = crate::mvu::Command::HashFile { path: PathBuf::from("nonexistent"), _retry: 0 };
228226
/// match crate::mvu::run_command(cmd) {
229227
/// crate::mvu::Msg::Attachments(crate::mvu::AttachmentsMsg::HashComputed { sha256, size, .. }) => {
@@ -608,8 +606,7 @@ mod tests {
608606
///
609607
/// # Examples
610608
///
611-
/// ```
612-
/// // inside a test:
609+
/// ```rust,ignore
613610
/// let mut model = AppModel::default();
614611
/// add_typed_field(&mut model, ExtraFieldKind::Number, "42");
615612
/// assert_eq!(model.extra_fields.fields.len(), 1);

0 commit comments

Comments
 (0)