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)]
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 }
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 pub fn from_version(version: PackageVersion) -> Result<LuaVersion, LuaVersionError> {
77 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 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(|| self.probe_lux_lib_dir())
106 .or_else(|| {
107 match self {
108 LuaVersion::LuaJIT => Some(LuaVersion::Lua51),
109 LuaVersion::LuaJIT52 => Some(LuaVersion::Lua52),
110 _ => None,
111 }
112 .and_then(|compat_version| compat_version.probe_lux_lib_dir())
113 })
114 .or_else(|| lux_lib_resource_dir().map(|path| path.join(self.to_string())))
115 }
116
117 fn probe_lux_lib_dir(&self) -> Option<PathBuf> {
118 let lib_name = format!("lux-lua{self}");
119 pkg_config::Config::new()
120 .print_system_libs(false)
121 .cargo_metadata(false)
122 .env_metadata(false)
123 .probe(&lib_name)
124 .ok()
125 .and_then(|library| library.link_paths.first().cloned())
126 }
127}
128
129#[derive(Error, Debug, Diagnostic)]
130#[non_exhaustive]
131#[error("lua version not set")]
132pub struct LuaVersionUnset {
133 #[help]
134 help: String,
135}
136
137impl LuaVersion {
138 pub fn from(config: &Config) -> Result<&Self, LuaVersionUnset> {
139 config.lua_version().ok_or(LuaVersionUnset {
140 help: format!(
141 r#"provide a version through `lx --lua-version <ver> <cmd>`
142or make sure Lux can discover your Lua installation on your PATH.
143valid versions are: {}
144"#,
145 LuaVersion::iter().join(",")
146 ),
147 })
148 }
149}
150
151impl FromStr for LuaVersion {
152 type Err = String;
153
154 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
155 match s {
156 "5.1" | "51" => Ok(LuaVersion::Lua51),
157 "5.2" | "52" => Ok(LuaVersion::Lua52),
158 "5.3" | "53" => Ok(LuaVersion::Lua53),
159 "5.4" | "54" => Ok(LuaVersion::Lua54),
160 "5.5" | "55" => Ok(LuaVersion::Lua55),
161 "jit" | "luajit" => Ok(LuaVersion::LuaJIT),
162 "jit52" | "luajit52" => Ok(LuaVersion::LuaJIT52),
163 _ => Err(format!(
164 r#"unrecognized Lua version.
165supported versions: {}
166 "#,
167 LuaVersion::iter().join(",")
168 )),
169 }
170 }
171}
172
173impl Display for LuaVersion {
174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175 f.write_str(match self {
176 LuaVersion::Lua51 => "5.1",
177 LuaVersion::Lua52 => "5.2",
178 LuaVersion::Lua53 => "5.3",
179 LuaVersion::Lua54 => "5.4",
180 LuaVersion::Lua55 => "5.5",
181 LuaVersion::LuaJIT => "jit",
182 LuaVersion::LuaJIT52 => "jit52",
183 })
184 }
185}
186
187fn lux_lib_resource_dir() -> Option<PathBuf> {
189 if cfg!(target_env = "msvc") {
190 std::env::current_exe()
192 .ok()
193 .and_then(|exe_path| exe_path.parent().map(Path::to_path_buf))
194 .and_then(|exe_dir| {
195 let lib_dir = exe_dir.join("lux-lua");
196 if lib_dir.is_dir() {
197 Some(lib_dir)
198 } else {
199 None
200 }
201 })
202 } else if cfg!(target_os = "macos") {
203 std::env::current_exe()
205 .ok()
206 .and_then(|exe_path| exe_path.parent().map(Path::to_path_buf))
207 .and_then(|macos_dir| macos_dir.parent().map(Path::to_path_buf))
208 .and_then(|contents_dir| {
209 let lib_dir = contents_dir.join("Resources").join("lux-lua");
210 if lib_dir.is_dir() {
211 Some(lib_dir)
212 } else {
213 None
214 }
215 })
216 } else {
217 if let Some(appdir) = std::env::var_os("APPDIR") {
219 let lib_dir = Path::new(&appdir).join("usr/share/lux-lua");
220 if lib_dir.is_dir() {
221 return Some(lib_dir);
222 }
223 }
224 let lib_dir = PathBuf::from("/usr/share/lux-lua");
225 if lib_dir.is_dir() {
226 Some(lib_dir)
227 } else {
228 None
229 }
230 }
231}