lux_lib/lua_version/
mod.rs1use 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)]
19#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
20pub enum LuaVersion {
21 #[serde(rename = "5.1")]
22 Lua51,
23 #[serde(rename = "5.2")]
24 Lua52,
25 #[serde(rename = "5.3")]
26 Lua53,
27 #[serde(rename = "5.4")]
28 Lua54,
29 #[serde(rename = "5.5")]
30 Lua55,
31 #[serde(rename = "jit")]
32 LuaJIT,
33 #[serde(rename = "jit5.2")]
34 LuaJIT52,
35 #[serde(rename = "luau")]
36 Luau,
37}
38
39#[derive(Debug, Error, Diagnostic)]
40pub enum LuaVersionError {
41 #[error("unsupported Lua version: '{0}'")]
42 UnsupportedLuaVersion(PackageVersion),
43}
44
45impl LuaVersion {
46 pub fn as_version(&self) -> PackageVersion {
47 unsafe {
48 match self {
49 LuaVersion::Lua51 => "5.1.0".parse().unwrap_unchecked(),
50 LuaVersion::Lua52 => "5.2.0".parse().unwrap_unchecked(),
51 LuaVersion::Lua53 => "5.3.0".parse().unwrap_unchecked(),
52 LuaVersion::Lua54 => "5.4.0".parse().unwrap_unchecked(),
53 LuaVersion::Lua55 => "5.5.0".parse().unwrap_unchecked(),
54 LuaVersion::LuaJIT => "5.1.0".parse().unwrap_unchecked(),
55 LuaVersion::LuaJIT52 => "5.2.0".parse().unwrap_unchecked(),
56 LuaVersion::Luau => "5.1.0".parse().unwrap_unchecked(),
57 }
58 }
59 }
60 pub fn version_compatibility_str(&self) -> String {
61 match self {
62 LuaVersion::Lua51 | LuaVersion::LuaJIT | LuaVersion::Luau => "5.1".into(),
63 LuaVersion::Lua52 | LuaVersion::LuaJIT52 => "5.2".into(),
64 LuaVersion::Lua53 => "5.3".into(),
65 LuaVersion::Lua54 => "5.4".into(),
66 LuaVersion::Lua55 => "5.5".into(),
67 }
68 }
69 pub fn as_version_req(&self) -> PackageVersionReq {
70 unsafe {
71 format!("~> {}", self.version_compatibility_str())
72 .parse()
73 .unwrap_unchecked()
74 }
75 }
76
77 pub fn from_version(version: PackageVersion) -> Result<LuaVersion, LuaVersionError> {
79 let luajit_version_req: PackageVersionReq = unsafe { "~> 2".parse().unwrap_unchecked() };
81 if luajit_version_req.matches(&version) {
82 Ok(LuaVersion::LuaJIT)
83 } else if LuaVersion::Lua51.as_version_req().matches(&version) {
84 Ok(LuaVersion::Lua51)
85 } else if LuaVersion::Lua52.as_version_req().matches(&version) {
86 Ok(LuaVersion::Lua52)
87 } else if LuaVersion::Lua53.as_version_req().matches(&version) {
88 Ok(LuaVersion::Lua53)
89 } else if LuaVersion::Lua54.as_version_req().matches(&version) {
90 Ok(LuaVersion::Lua54)
91 } else if LuaVersion::Lua55.as_version_req().matches(&version) {
92 Ok(LuaVersion::Lua55)
93 } else {
94 Err(LuaVersionError::UnsupportedLuaVersion(version))
95 }
96 }
97
98 pub(crate) fn is_luajit(&self) -> bool {
99 matches!(self, Self::LuaJIT | Self::LuaJIT52)
100 }
101
102 pub(crate) fn is_luau(&self) -> bool {
103 matches!(self, Self::Luau)
104 }
105
106 pub fn lux_lib_dir(&self) -> Option<PathBuf> {
108 option_env!("LUX_LIB_DIR")
109 .map(PathBuf::from)
110 .map(|path| path.join(self.to_string()))
111 .or_else(|| self.probe_lux_lib_dir())
112 .or_else(|| {
113 match self {
114 LuaVersion::LuaJIT => Some(LuaVersion::Lua51),
115 LuaVersion::LuaJIT52 => Some(LuaVersion::Lua52),
116 _ => None,
117 }
118 .and_then(|compat_version| compat_version.probe_lux_lib_dir())
119 })
120 .or_else(|| lux_lib_resource_dir().map(|path| path.join(self.to_string())))
121 }
122
123 fn probe_lux_lib_dir(&self) -> Option<PathBuf> {
124 let lib_name = format!("lux-lua{self}");
125 pkg_config::Config::new()
126 .print_system_libs(false)
127 .cargo_metadata(false)
128 .env_metadata(false)
129 .probe(&lib_name)
130 .ok()
131 .and_then(|library| library.link_paths.first().cloned())
132 }
133}
134
135#[derive(Error, Debug, Diagnostic)]
136#[non_exhaustive]
137#[error("lua version not set")]
138pub struct LuaVersionUnset {
139 #[help]
140 help: String,
141}
142
143impl LuaVersion {
144 pub fn from(config: &Config) -> Result<&Self, LuaVersionUnset> {
145 config.lua_version().ok_or(LuaVersionUnset {
146 help: format!(
147 r#"provide a version through `lx --lua-version <ver> <cmd>`
148or make sure Lux can discover your Lua installation on your PATH.
149valid versions are: {}
150"#,
151 LuaVersion::iter().join(",")
152 ),
153 })
154 }
155}
156
157impl FromStr for LuaVersion {
158 type Err = String;
159
160 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
161 match s {
162 "5.1" | "51" => Ok(LuaVersion::Lua51),
163 "5.2" | "52" => Ok(LuaVersion::Lua52),
164 "5.3" | "53" => Ok(LuaVersion::Lua53),
165 "5.4" | "54" => Ok(LuaVersion::Lua54),
166 "5.5" | "55" => Ok(LuaVersion::Lua55),
167 "jit" | "luajit" => Ok(LuaVersion::LuaJIT),
168 "jit52" | "luajit52" => Ok(LuaVersion::LuaJIT52),
169 "luau" => Ok(LuaVersion::Luau),
170 _ => Err(format!(
171 r#"unrecognized Lua version.
172supported versions: {}
173 "#,
174 LuaVersion::iter().join(",")
175 )),
176 }
177 }
178}
179
180impl Display for LuaVersion {
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182 f.write_str(match self {
183 LuaVersion::Lua51 => "5.1",
184 LuaVersion::Lua52 => "5.2",
185 LuaVersion::Lua53 => "5.3",
186 LuaVersion::Lua54 => "5.4",
187 LuaVersion::Lua55 => "5.5",
188 LuaVersion::LuaJIT => "jit",
189 LuaVersion::LuaJIT52 => "jit52",
190 LuaVersion::Luau => "luau",
191 })
192 }
193}
194
195fn lux_lib_resource_dir() -> Option<PathBuf> {
197 if cfg!(target_env = "msvc") {
198 std::env::current_exe()
200 .ok()
201 .and_then(|exe_path| exe_path.parent().map(Path::to_path_buf))
202 .and_then(|exe_dir| {
203 let lib_dir = exe_dir.join("lux-lua");
204 if lib_dir.is_dir() {
205 Some(lib_dir)
206 } else {
207 None
208 }
209 })
210 } else if cfg!(target_os = "macos") {
211 std::env::current_exe()
213 .ok()
214 .and_then(|exe_path| exe_path.parent().map(Path::to_path_buf))
215 .and_then(|macos_dir| macos_dir.parent().map(Path::to_path_buf))
216 .and_then(|contents_dir| {
217 let lib_dir = contents_dir.join("Resources").join("lux-lua");
218 if lib_dir.is_dir() {
219 Some(lib_dir)
220 } else {
221 None
222 }
223 })
224 } else {
225 if let Some(appdir) = std::env::var_os("APPDIR") {
227 let lib_dir = Path::new(&appdir).join("usr/share/lux-lua");
228 if lib_dir.is_dir() {
229 return Some(lib_dir);
230 }
231 }
232 let lib_dir = PathBuf::from("/usr/share/lux-lua");
233 if lib_dir.is_dir() {
234 Some(lib_dir)
235 } else {
236 None
237 }
238 }
239}