lune_utils/
version_string.rs

1use std::sync::{Arc, LazyLock};
2
3use mlua::prelude::*;
4use semver::Version;
5
6static LUAU_VERSION: LazyLock<Arc<String>> = LazyLock::new(create_luau_version_string);
7
8/**
9    Returns a Lune version string, in the format `Lune x.y.z+luau`.
10
11    The version string passed should be the version of the Lune runtime,
12    obtained from `env!("CARGO_PKG_VERSION")` or a similar mechanism.
13
14    # Panics
15
16    Panics if the version string is empty or contains invalid characters.
17*/
18#[must_use]
19pub fn get_version_string(lune_version: impl AsRef<str>) -> String {
20    let lune_version = lune_version.as_ref();
21
22    assert!(!lune_version.is_empty(), "Lune version string is empty");
23    match Version::parse(lune_version) {
24        Ok(semver) => format!("Lune {semver}+{}", *LUAU_VERSION),
25        Err(e) => panic!("Lune version string is not valid semver: {e}"),
26    }
27}
28
29fn create_luau_version_string() -> Arc<String> {
30    // Extract the current Luau version from a fresh Lua state / VM that can't be accessed externally.
31    let luau_version_full = {
32        let temp_lua = Lua::new();
33
34        let luau_version_full = temp_lua
35            .globals()
36            .get::<LuaString>("_VERSION")
37            .expect("Missing _VERSION global");
38
39        luau_version_full
40            .to_str()
41            .context("Invalid utf8 found in _VERSION global")
42            .expect("Expected _VERSION global to be a string")
43            .to_string()
44    };
45
46    // Luau version is expected to be in the format "Luau 0.x" and sometimes "Luau 0.x.y"
47    assert!(
48        luau_version_full.starts_with("Luau 0."),
49        "_VERSION global is formatted incorrectly\
50        \nFound string '{luau_version_full}'"
51    );
52    let luau_version_noprefix = luau_version_full.strip_prefix("Luau 0.").unwrap().trim();
53
54    // We make some guarantees about the format of the _VERSION global,
55    // so make sure that the luau version also follows those rules.
56    if luau_version_noprefix.is_empty() {
57        panic!(
58            "_VERSION global is missing version number\
59            \nFound string '{luau_version_full}'"
60        )
61    } else if !luau_version_noprefix.chars().all(is_valid_version_char) {
62        panic!(
63            "_VERSION global contains invalid characters\
64            \nFound string '{luau_version_full}'"
65        )
66    }
67
68    luau_version_noprefix.to_string().into()
69}
70
71fn is_valid_version_char(c: char) -> bool {
72    matches!(c, '0'..='9' | '.')
73}