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