Skip to main content

lux_lib/lua_version/
mod.rs

1use std::{
2    fmt::Display,
3    path::{Path, PathBuf},
4    str::FromStr,
5};
6
7use crate::{
8    config::Config,
9    package::{PackageVersion, PackageVersionReq},
10};
11use miette::Diagnostic;
12use serde::{Deserialize, Serialize};
13use strum_macros::EnumIter;
14use thiserror::Error;
15
16#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, EnumIter)]
17pub enum LuaVersion {
18    #[serde(rename = "5.1")]
19    Lua51,
20    #[serde(rename = "5.2")]
21    Lua52,
22    #[serde(rename = "5.3")]
23    Lua53,
24    #[serde(rename = "5.4")]
25    Lua54,
26    #[serde(rename = "5.5")]
27    Lua55,
28    #[serde(rename = "jit")]
29    LuaJIT,
30    #[serde(rename = "jit5.2")]
31    LuaJIT52,
32    // TODO(vhyrro): Support luau?
33    // LuaU,
34}
35
36#[derive(Debug, Error, Diagnostic)]
37pub enum LuaVersionError {
38    #[error("unsupported Lua version: {0}")]
39    UnsupportedLuaVersion(PackageVersion),
40}
41
42impl LuaVersion {
43    pub fn as_version(&self) -> PackageVersion {
44        unsafe {
45            match self {
46                LuaVersion::Lua51 => "5.1.0".parse().unwrap_unchecked(),
47                LuaVersion::Lua52 => "5.2.0".parse().unwrap_unchecked(),
48                LuaVersion::Lua53 => "5.3.0".parse().unwrap_unchecked(),
49                LuaVersion::Lua54 => "5.4.0".parse().unwrap_unchecked(),
50                LuaVersion::Lua55 => "5.5.0".parse().unwrap_unchecked(),
51                LuaVersion::LuaJIT => "5.1.0".parse().unwrap_unchecked(),
52                LuaVersion::LuaJIT52 => "5.2.0".parse().unwrap_unchecked(),
53            }
54        }
55    }
56    pub fn version_compatibility_str(&self) -> String {
57        match self {
58            LuaVersion::Lua51 | LuaVersion::LuaJIT => "5.1".into(),
59            LuaVersion::Lua52 | LuaVersion::LuaJIT52 => "5.2".into(),
60            LuaVersion::Lua53 => "5.3".into(),
61            LuaVersion::Lua54 => "5.4".into(),
62            LuaVersion::Lua55 => "5.5".into(),
63        }
64    }
65    pub fn as_version_req(&self) -> PackageVersionReq {
66        unsafe {
67            format!("~> {}", self.version_compatibility_str())
68                .parse()
69                .unwrap_unchecked()
70        }
71    }
72
73    /// Get the LuaVersion from a version that has been parsed from the `lua -v` output
74    pub fn from_version(version: PackageVersion) -> Result<LuaVersion, LuaVersionError> {
75        // NOTE: Special case. luajit -v outputs 2.x.y as a version
76        let luajit_version_req: PackageVersionReq = unsafe { "~> 2".parse().unwrap_unchecked() };
77        if luajit_version_req.matches(&version) {
78            Ok(LuaVersion::LuaJIT)
79        } else if LuaVersion::Lua51.as_version_req().matches(&version) {
80            Ok(LuaVersion::Lua51)
81        } else if LuaVersion::Lua52.as_version_req().matches(&version) {
82            Ok(LuaVersion::Lua52)
83        } else if LuaVersion::Lua53.as_version_req().matches(&version) {
84            Ok(LuaVersion::Lua53)
85        } else if LuaVersion::Lua54.as_version_req().matches(&version) {
86            Ok(LuaVersion::Lua54)
87        } else if LuaVersion::Lua55.as_version_req().matches(&version) {
88            Ok(LuaVersion::Lua55)
89        } else {
90            Err(LuaVersionError::UnsupportedLuaVersion(version))
91        }
92    }
93
94    pub(crate) fn is_luajit(&self) -> bool {
95        matches!(self, Self::LuaJIT | Self::LuaJIT52)
96    }
97
98    /// Searches for the path to the lux-lua library for this version
99    pub fn lux_lib_dir(&self) -> Option<PathBuf> {
100        option_env!("LUX_LIB_DIR")
101            .map(PathBuf::from)
102            .map(|path| path.join(self.to_string()))
103            .or_else(|| {
104                let lib_name = format!("lux-lua{self}");
105                pkg_config::Config::new()
106                    .print_system_libs(false)
107                    .cargo_metadata(false)
108                    .env_metadata(false)
109                    .probe(&lib_name)
110                    .ok()
111                    .and_then(|library| library.link_paths.first().cloned())
112            })
113            .or_else(|| lux_lib_resource_dir().map(|path| path.join(self.to_string())))
114    }
115}
116
117#[derive(Error, Debug, Diagnostic)]
118#[error(
119    r#"lua version not set.
120Please provide a version through `lx --lua-version <ver> <cmd>`
121Valid versions are: '5.1', '5.2', '5.3', '5.4', '5.5', 'jit' and 'jit52'.
122"#
123)]
124pub struct LuaVersionUnset;
125
126impl LuaVersion {
127    pub fn from(config: &Config) -> Result<&Self, LuaVersionUnset> {
128        config.lua_version().ok_or(LuaVersionUnset)
129    }
130}
131
132impl FromStr for LuaVersion {
133    type Err = String;
134
135    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
136        match s {
137            "5.1" | "51" => Ok(LuaVersion::Lua51),
138            "5.2" | "52" => Ok(LuaVersion::Lua52),
139            "5.3" | "53" => Ok(LuaVersion::Lua53),
140            "5.4" | "54" => Ok(LuaVersion::Lua54),
141            "5.5" | "55" => Ok(LuaVersion::Lua55),
142            "jit" | "luajit" => Ok(LuaVersion::LuaJIT),
143            "jit52" | "luajit52" => Ok(LuaVersion::LuaJIT52),
144            _ => Err(r#"unrecognized Lua version.
145                 Supported versions: '5.1', '5.2', '5.3', '5.4', '5.5', 'jit', 'jit52'.
146                 "#
147            .into()),
148        }
149    }
150}
151
152impl Display for LuaVersion {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        f.write_str(match self {
155            LuaVersion::Lua51 => "5.1",
156            LuaVersion::Lua52 => "5.2",
157            LuaVersion::Lua53 => "5.3",
158            LuaVersion::Lua54 => "5.4",
159            LuaVersion::Lua55 => "5.5",
160            LuaVersion::LuaJIT => "jit",
161            LuaVersion::LuaJIT52 => "jit52",
162        })
163    }
164}
165
166/// Searches for the lux-lib directory in a binary distribution's resources
167fn lux_lib_resource_dir() -> Option<PathBuf> {
168    if cfg!(target_env = "msvc") {
169        // The msvc .exe and .msi binary installers install lux-lua to the executable's directory.
170        std::env::current_exe()
171            .ok()
172            .and_then(|exe_path| exe_path.parent().map(Path::to_path_buf))
173            .and_then(|exe_dir| {
174                let lib_dir = exe_dir.join("lux-lua");
175                if lib_dir.is_dir() {
176                    Some(lib_dir)
177                } else {
178                    None
179                }
180            })
181    } else if cfg!(target_os = "macos") {
182        // Currently, we only bundle resources with an .app ApplicationBundle
183        std::env::current_exe()
184            .ok()
185            .and_then(|exe_path| exe_path.parent().map(Path::to_path_buf))
186            .and_then(|macos_dir| macos_dir.parent().map(Path::to_path_buf))
187            .and_then(|contents_dir| {
188                let lib_dir = contents_dir.join("Resources").join("lux-lua");
189                if lib_dir.is_dir() {
190                    Some(lib_dir)
191                } else {
192                    None
193                }
194            })
195    } else {
196        // .deb and AppImage packages
197        let lib_dir = PathBuf::from("/usr/share/lux-lua");
198        if lib_dir.is_dir() {
199            Some(lib_dir)
200        } else {
201            None
202        }
203    }
204}