Skip to main content

codegen/
helpers.rs

1use anyhow::anyhow;
2use wowlab_fs::path::Path;
3use wowlab_types::game::SpecId;
4
5/// Lowercase a manifest key into its `snake_case` module form.
6pub(crate) fn to_snake(name: &str) -> String {
7    name.to_lowercase()
8}
9
10/// Converts a manifest identifier to its rotation token.
11pub(crate) fn to_rotation_key(name: &str) -> String {
12    to_snake(name).replace("_s_", "s_")
13}
14
15fn title_case(s: &str, lowercase_tail: bool) -> String {
16    s.split('_')
17        .map(|w| {
18            let mut chars = w.chars();
19
20            match chars.next() {
21                None => String::new(),
22                Some(c) => {
23                    let mut out = c.to_uppercase().to_string();
24
25                    if lowercase_tail {
26                        out.extend(chars.map(|ch| ch.to_ascii_lowercase()));
27                    } else {
28                        out.extend(chars);
29                    }
30
31                    out
32                }
33            }
34        })
35        .collect::<Vec<_>>()
36        .join(" ")
37}
38
39/// Title-case a manifest key into a human-readable display name.
40pub(crate) fn to_display_name(name: &str) -> String {
41    title_case(name, true)
42}
43
44/// `WoW` API spec ID -> `SpecId` enum variant.
45pub(crate) fn spec_from_wow_id(wow_id: u32) -> anyhow::Result<SpecId> {
46    SpecId::from_wow_spec_id(wow_id).ok_or_else(|| anyhow!("unknown WoW spec ID: {wow_id}"))
47}
48
49/// Title-case display name derived from a manifest filename stem.
50pub(crate) fn display_name_from_filename(path: &Path) -> anyhow::Result<String> {
51    let stem = path
52        .file_stem()
53        .ok_or_else(|| anyhow!("manifest path has no filename: {}", path.display()))?
54        .to_string_lossy();
55
56    Ok(title_case(&stem, false))
57}
58
59/// Module name derived from a manifest filename stem.
60pub(crate) fn module_name_from_filename(path: &Path) -> anyhow::Result<String> {
61    Ok(path
62        .file_stem()
63        .ok_or_else(|| anyhow!("manifest path has no filename: {}", path.display()))?
64        .to_string_lossy()
65        .into_owned())
66}
67
68/// Format an f64 as a Rust literal, with `.0` suffix on whole numbers.
69pub(crate) fn fmt_f64(v: f64) -> String {
70    if v.fract().abs() < f64::EPSILON && v.abs() < 1e15 {
71        format!("{v:.1}")
72    } else {
73        format!("{v}")
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use googletest::prelude::*;
80
81    use super::*;
82
83    #[gtest]
84    fn rotation_key_matches_simc_possessive_token() -> Result<()> {
85        verify_that!(to_rotation_key("GOREMAW_S_BITE"), eq("goremaws_bite"))?;
86
87        verify_that!(to_rotation_key("REAVERS_GLAIVE"), eq("reavers_glaive"))
88    }
89
90    #[gtest]
91    fn manifest_name_helpers_reject_paths_without_filenames() -> Result<()> {
92        let path = Path::new("/");
93
94        let display_error = display_name_from_filename(path).err().or_fail()?;
95        let module_error = module_name_from_filename(path).err().or_fail()?;
96
97        verify_that!(
98            display_error.to_string(),
99            eq("manifest path has no filename: /")
100        )?;
101
102        verify_that!(
103            module_error.to_string(),
104            eq("manifest path has no filename: /")
105        )
106    }
107}