mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-11 13:02:25 +00:00
CP1.2: build.rs keys the data tests on a whole data folder, handles non-UTF-8 paths; CI pins craft-i18n
Review of craft-libs#482: - craft_proof_data is set only when every Language data file is embedded, and the new craft_proof_no_data only when none is, so a partial folder (en-us but no pl) runs neither the pattern tests nor the no-data tests. - A CRAFT_I18N_DIR without language-data/ is watched (rerun-if-changed), so the build notices when the checkout gains it. - A CRAFT_I18N_DIR that isn't valid UTF-8 is reported and embeds nothing, instead of a lossy path in include_str!. - proof.yml checks out craft-i18n at 062a6375c322c88b96691a17f7b5fed71c51f7a8. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2
This commit is contained in:
1 parent
c80b6225f1
commit
48ed6f8cbd
3 files changed
+26
-16
No files matched your search
@@ -25,10 +25,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
# The Language data (craft-i18n `language-data/`), embedded by crates/proof/build.rs.
|
# The Language data (craft-i18n `language-data/`), embedded by crates/proof/build.rs.
|
||||||
# TODO(CP1.2): pin `ref` to the craft-i18n commit that adds language-data/ once it merges.
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: storytold/craft-i18n
|
repository: storytold/craft-i18n
|
||||||
|
ref: 062a6375c322c88b96691a17f7b5fed71c51f7a8
|
||||||
path: craft-i18n
|
path: craft-i18n
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
|
- uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
|
||||||
|
|||||||
+23
-12
@@ -4,12 +4,16 @@
|
|||||||
//! one constant per file in [`FILES`], each an `include_str!`/`include_bytes!` of
|
//! one constant per file in [`FILES`], each an `include_str!`/`include_bytes!` of
|
||||||
//! `$CRAFT_I18N_DIR/language-data/<path>`. Unset, or not a checkout, or a file missing (a warning):
|
//! `$CRAFT_I18N_DIR/language-data/<path>`. Unset, or not a checkout, or a file missing (a warning):
|
||||||
//! that constant is empty, and the crate has no embedded Language data for it (English spelling
|
//! that constant is empty, and the crate has no embedded Language data for it (English spelling
|
||||||
//! falls back to an installed Hunspell dictionary or the crate's own core word list, and
|
//! falls back to an installed Hunspell dictionary or the crate's own copy of the Moby word list, and
|
||||||
//! hyphenation that needs the data is off). With `CRAFT_I18N_REQUIRED=1` (releases) any of those
|
//! hyphenation that needs the data is off). With `CRAFT_I18N_REQUIRED=1` (releases) any of those
|
||||||
//! is an error instead.
|
//! is an error instead.
|
||||||
//!
|
//!
|
||||||
//! When the English word list is embedded, the `craft_proof_data` cfg is set (the tests that pin
|
//! When every file in [`FILES`] is embedded, the `craft_proof_data` cfg is set (the tests that pin
|
||||||
//! results of the data use it).
|
//! results of the data use it); when none is, `craft_proof_no_data` (the tests of a build without
|
||||||
|
//! the data). A partial data folder sets neither, so neither set of tests runs against it.
|
||||||
|
//!
|
||||||
|
//! `include_str!` takes a string, so a `CRAFT_I18N_DIR` that isn't valid UTF-8 can't be embedded:
|
||||||
|
//! it is treated like a folder without the data.
|
||||||
|
|
||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -32,6 +36,7 @@ fn main() {
|
|||||||
println!("cargo::rerun-if-env-changed=CRAFT_I18N_DIR");
|
println!("cargo::rerun-if-env-changed=CRAFT_I18N_DIR");
|
||||||
println!("cargo::rerun-if-env-changed=CRAFT_I18N_REQUIRED");
|
println!("cargo::rerun-if-env-changed=CRAFT_I18N_REQUIRED");
|
||||||
println!("cargo::rustc-check-cfg=cfg(craft_proof_data)");
|
println!("cargo::rustc-check-cfg=cfg(craft_proof_data)");
|
||||||
|
println!("cargo::rustc-check-cfg=cfg(craft_proof_no_data)");
|
||||||
let required = std::env::var("CRAFT_I18N_REQUIRED").is_ok_and(|v| v.trim() == "1");
|
let required = std::env::var("CRAFT_I18N_REQUIRED").is_ok_and(|v| v.trim() == "1");
|
||||||
let level = if required { "error" } else { "warning" };
|
let level = if required { "error" } else { "warning" };
|
||||||
let dir = match std::env::var_os("CRAFT_I18N_DIR").filter(|d| !d.is_empty()) {
|
let dir = match std::env::var_os("CRAFT_I18N_DIR").filter(|d| !d.is_empty()) {
|
||||||
@@ -47,35 +52,36 @@ fn main() {
|
|||||||
println!("cargo::error=CRAFT_I18N_REQUIRED=1 but CRAFT_I18N_DIR is unset: a release must embed the Language data");
|
println!("cargo::error=CRAFT_I18N_REQUIRED=1 but CRAFT_I18N_DIR is unset: a release must embed the Language data");
|
||||||
} else {
|
} else {
|
||||||
println!(
|
println!(
|
||||||
"cargo::warning=CRAFT_I18N_DIR is unset: craft-proof embeds no Language data (English spelling falls back to system Hunspell or the core word list; no hyphenation)"
|
"cargo::warning=CRAFT_I18N_DIR is unset: craft-proof embeds no Language data (English spelling falls back to system Hunspell or the built-in Moby word list; no hyphenation)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut o = String::from("// Generated by craft-proof's build.rs from CRAFT_I18N_DIR/language-data. Do not edit.\n");
|
let mut o = String::from("// Generated by craft-proof's build.rs from CRAFT_I18N_DIR/language-data. Do not edit.\n");
|
||||||
let mut english = false;
|
let mut embedded = 0;
|
||||||
for &(name, rel, bytes) in FILES {
|
for &(name, rel, bytes) in FILES {
|
||||||
let file = dir.as_ref().map(|d| d.join(rel)).filter(|f| f.is_file());
|
let file = dir.as_ref().map(|d| d.join(rel)).filter(|f| f.is_file());
|
||||||
if let (Some(d), None) = (&dir, &file) {
|
if let (Some(d), None) = (&dir, &file) {
|
||||||
println!("cargo::{level}=CRAFT_I18N_DIR: {} is missing; craft-proof embeds none", d.join(rel).display());
|
println!("cargo::{level}=CRAFT_I18N_DIR: {} is missing; craft-proof embeds none", d.join(rel).display());
|
||||||
}
|
}
|
||||||
let (ty, empty, mac) = if bytes { ("&[u8]", "b\"\"", "include_bytes") } else { ("&str", "\"\"", "include_str") };
|
let (ty, empty, mac) = if bytes { ("&[u8]", "b\"\"", "include_bytes") } else { ("&str", "\"\"", "include_str") };
|
||||||
let value = match &file {
|
// `language_data` only hands back a UTF-8 folder, so this is the path as it is.
|
||||||
|
let value = match file.as_ref().and_then(|f| f.to_str()) {
|
||||||
Some(f) => {
|
Some(f) => {
|
||||||
println!("cargo::rerun-if-changed={}", f.display());
|
println!("cargo::rerun-if-changed={f}");
|
||||||
if name == "EN_US_DIC" {
|
embedded += 1;
|
||||||
english = true;
|
|
||||||
}
|
|
||||||
// `{:?}` escapes the path (Windows backslashes) as a Rust string literal.
|
// `{:?}` escapes the path (Windows backslashes) as a Rust string literal.
|
||||||
format!("{mac}!({:?})", f.display().to_string())
|
format!("{mac}!({f:?})")
|
||||||
}
|
}
|
||||||
None => empty.to_string(),
|
None => empty.to_string(),
|
||||||
};
|
};
|
||||||
let _ = writeln!(o, "/// `language-data/{rel}` (empty when it isn't embedded).\npub(crate) static {name}: {ty} = {value};");
|
let _ = writeln!(o, "/// `language-data/{rel}` (empty when it isn't embedded).\npub(crate) static {name}: {ty} = {value};");
|
||||||
}
|
}
|
||||||
if english {
|
if embedded == FILES.len() {
|
||||||
println!("cargo::rustc-cfg=craft_proof_data");
|
println!("cargo::rustc-cfg=craft_proof_data");
|
||||||
|
} else if embedded == 0 {
|
||||||
|
println!("cargo::rustc-cfg=craft_proof_no_data");
|
||||||
}
|
}
|
||||||
let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap_or_default()).join("language_data.rs");
|
let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap_or_default()).join("language_data.rs");
|
||||||
if let Err(e) = std::fs::write(&out, o) {
|
if let Err(e) = std::fs::write(&out, o) {
|
||||||
@@ -91,8 +97,13 @@ fn language_data(dir: &Path) -> Result<PathBuf, String> {
|
|||||||
if !dir.is_absolute() {
|
if !dir.is_absolute() {
|
||||||
return Err(format!("{} is not an absolute path", dir.display()));
|
return Err(format!("{} is not an absolute path", dir.display()));
|
||||||
}
|
}
|
||||||
|
if dir.to_str().is_none() {
|
||||||
|
return Err(format!("{} is not valid UTF-8, which include_str! needs", dir.display()));
|
||||||
|
}
|
||||||
let data = dir.join("language-data");
|
let data = dir.join("language-data");
|
||||||
if !data.is_dir() {
|
if !data.is_dir() {
|
||||||
|
// Notice when the checkout gains its Language data.
|
||||||
|
println!("cargo::rerun-if-changed={}", dir.display());
|
||||||
return Err(format!("{} has no language-data/ (a craft-i18n checkout older than its Language data?)", dir.display()));
|
return Err(format!("{} has no language-data/ (a craft-i18n checkout older than its Language data?)", dir.display()));
|
||||||
}
|
}
|
||||||
println!("cargo::rerun-if-changed={}", data.display());
|
println!("cargo::rerun-if-changed={}", data.display());
|
||||||
|
|||||||
@@ -1115,9 +1115,8 @@ fn starts_consonant_sound(w: &str) -> bool {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) static DICTIONARY_TESTS: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
pub(crate) static DICTIONARY_TESTS: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||||
|
|
||||||
/// The no-Language-data build (`build.rs` sets `craft_proof_data` when the English word list is
|
/// The no-Language-data build (`build.rs` sets `craft_proof_no_data` when nothing is embedded).
|
||||||
/// embedded).
|
#[cfg(all(test, craft_proof_no_data))]
|
||||||
#[cfg(all(test, not(craft_proof_data)))]
|
|
||||||
mod no_data;
|
mod no_data;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
Reference in new issue
Block a user