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