lux_lib/lua_rockspec/
mod.rs

1mod build;
2mod dependency;
3mod deploy;
4mod partial;
5mod platform;
6mod rock_source;
7mod serde_util;
8mod test_spec;
9
10use std::{
11    collections::HashMap, convert::Infallible, fmt::Display, io, path::PathBuf, str::FromStr,
12};
13
14use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, UserData, Value};
15use serde::{de::DeserializeOwned, Deserialize, Serialize};
16
17pub use build::*;
18pub use dependency::*;
19pub use deploy::*;
20pub use partial::*;
21pub use platform::*;
22pub use rock_source::*;
23pub use serde_util::*;
24use ssri::Integrity;
25pub use test_spec::*;
26use thiserror::Error;
27use url::Url;
28
29use crate::{
30    config::{LuaVersion, LuaVersionUnset},
31    hash::HasIntegrity,
32    package::{PackageName, PackageSpec, PackageVersion, PackageVersionReq},
33    project::project_toml::ProjectTomlError,
34    project::ProjectRoot,
35    rockspec::{lua_dependency::LuaDependencySpec, Rockspec},
36};
37
38#[derive(Error, Debug)]
39pub enum LuaRockspecError {
40    #[error("could not parse rockspec: {0}")]
41    MLua(#[from] mlua::Error),
42    #[error("{}copy_directories cannot contain the rockspec name", ._0.as_ref().map(|p| format!("{p}: ")).unwrap_or_default())]
43    CopyDirectoriesContainRockspecName(Option<String>),
44    #[error("could not parse rockspec: {0}")]
45    LuaTable(#[from] LuaTableError),
46    #[error("cannot create Lua rockspec with off-spec dependency: {0}")]
47    OffSpecDependency(PackageName),
48    #[error("cannot create Lua rockspec with off-spec build dependency: {0}")]
49    OffSpecBuildDependency(PackageName),
50    #[error("cannot create Lua rockspec with off-spec test dependency: {0}")]
51    OffSpecTestDependency(PackageName),
52    #[error(transparent)]
53    ProjectToml(#[from] ProjectTomlError),
54}
55
56#[derive(Clone, Debug)]
57#[cfg_attr(test, derive(PartialEq))]
58pub struct LocalLuaRockspec {
59    /// The file format version. Example: "1.0"
60    rockspec_format: Option<RockspecFormat>,
61    /// The name of the package. Example: "luasocket"
62    package: PackageName,
63    /// The version of the package, plus a suffix indicating the revision of the rockspec. Example: "2.0.1-1"
64    version: PackageVersion,
65    description: RockDescription,
66    supported_platforms: PlatformSupport,
67    /// The Lua version requirement for this rock
68    lua: PackageVersionReq,
69    dependencies: PerPlatform<Vec<LuaDependencySpec>>,
70    build_dependencies: PerPlatform<Vec<LuaDependencySpec>>,
71    external_dependencies: PerPlatform<HashMap<String, ExternalDependencySpec>>,
72    test_dependencies: PerPlatform<Vec<LuaDependencySpec>>,
73    build: PerPlatform<BuildSpec>,
74    source: PerPlatform<RemoteRockSource>,
75    test: PerPlatform<TestSpec>,
76    deploy: PerPlatform<DeploySpec>,
77    /// The original content of this rockspec, needed by luarocks
78    raw_content: String,
79}
80
81impl UserData for LocalLuaRockspec {
82    fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
83        methods.add_method("package", |_, this, _: ()| Ok(this.package.clone()));
84        methods.add_method("version", |_, this, _: ()| Ok(this.version.clone()));
85        methods.add_method("description", |_, this, _: ()| Ok(this.description.clone()));
86        methods.add_method("supported_platforms", |_, this, _: ()| {
87            Ok(this.supported_platforms.clone())
88        });
89        methods.add_method("lua", |_, this, _: ()| Ok(this.lua.clone()));
90        methods.add_method("dependencies", |_, this, _: ()| {
91            Ok(this.dependencies.clone())
92        });
93        methods.add_method("build_dependencies", |_, this, _: ()| {
94            Ok(this.build_dependencies.clone())
95        });
96        methods.add_method("external_dependencies", |_, this, _: ()| {
97            Ok(this.external_dependencies.clone())
98        });
99        methods.add_method("test_dependencies", |_, this, _: ()| {
100            Ok(this.test_dependencies.clone())
101        });
102        methods.add_method("build", |_, this, _: ()| Ok(this.build.clone()));
103        methods.add_method("source", |_, this, _: ()| Ok(this.source.clone()));
104        methods.add_method("test", |_, this, _: ()| Ok(this.test.clone()));
105        methods.add_method("format", |_, this, _: ()| Ok(this.rockspec_format.clone()));
106
107        methods.add_method("to_lua_rockspec_string", |_, this, _: ()| {
108            this.to_lua_remote_rockspec_string()
109                .map_err(|err| mlua::Error::RuntimeError(err.to_string()))
110        });
111    }
112}
113
114impl LocalLuaRockspec {
115    pub fn new(
116        rockspec_content: &str,
117        project_root: ProjectRoot,
118    ) -> Result<Self, LuaRockspecError> {
119        let lua = Lua::new();
120        lua.load(rockspec_content).exec()?;
121
122        let globals = lua.globals();
123
124        let dependencies: PerPlatform<Vec<LuaDependencySpec>> = globals.get("dependencies")?;
125
126        let lua_version_req = dependencies
127            .current_platform()
128            .iter()
129            .find(|dep| dep.name().to_string() == "lua")
130            .cloned()
131            .map(|dep| dep.version_req().clone())
132            .unwrap_or(PackageVersionReq::Any);
133
134        fn strip_lua(
135            dependencies: PerPlatform<Vec<LuaDependencySpec>>,
136        ) -> PerPlatform<Vec<LuaDependencySpec>> {
137            dependencies.map(|deps| {
138                deps.iter()
139                    .filter(|dep| dep.name().to_string() != "lua")
140                    .cloned()
141                    .collect()
142            })
143        }
144
145        let build_dependencies: PerPlatform<Vec<LuaDependencySpec>> =
146            globals.get("build_dependencies")?;
147
148        let test_dependencies: PerPlatform<Vec<LuaDependencySpec>> =
149            globals.get("test_dependencies")?;
150
151        let rockspec = LocalLuaRockspec {
152            rockspec_format: globals.get("rockspec_format")?,
153            package: globals.get("package")?,
154            version: globals.get("version")?,
155            description: parse_lua_tbl_or_default(&lua, "description")?,
156            supported_platforms: parse_lua_tbl_or_default(&lua, "supported_platforms")?,
157            lua: lua_version_req,
158            dependencies: strip_lua(dependencies),
159            build_dependencies: strip_lua(build_dependencies),
160            test_dependencies: strip_lua(test_dependencies),
161            external_dependencies: globals.get("external_dependencies")?,
162            build: globals.get("build")?,
163            test: globals.get("test")?,
164            deploy: globals.get("deploy")?,
165            raw_content: rockspec_content.into(),
166
167            source: globals
168                .get::<Option<PerPlatform<RemoteRockSource>>>("source")?
169                .unwrap_or_else(|| {
170                    PerPlatform::new(RockSourceSpec::File(project_root.to_path_buf()).into())
171                }),
172        };
173
174        let rockspec_file_name = format!("{}-{}.rockspec", rockspec.package(), rockspec.version());
175        if rockspec
176            .build()
177            .default
178            .copy_directories
179            .contains(&PathBuf::from(&rockspec_file_name))
180        {
181            return Err(LuaRockspecError::CopyDirectoriesContainRockspecName(None));
182        }
183
184        for (platform, build_override) in &rockspec.build().per_platform {
185            if build_override
186                .copy_directories
187                .contains(&PathBuf::from(&rockspec_file_name))
188            {
189                return Err(LuaRockspecError::CopyDirectoriesContainRockspecName(Some(
190                    platform.to_string(),
191                )));
192            }
193        }
194        Ok(rockspec)
195    }
196}
197
198impl Rockspec for LocalLuaRockspec {
199    type Error = Infallible;
200
201    fn package(&self) -> &PackageName {
202        &self.package
203    }
204
205    fn version(&self) -> &PackageVersion {
206        &self.version
207    }
208
209    fn description(&self) -> &RockDescription {
210        &self.description
211    }
212
213    fn supported_platforms(&self) -> &PlatformSupport {
214        &self.supported_platforms
215    }
216
217    fn lua(&self) -> &PackageVersionReq {
218        &self.lua
219    }
220
221    fn dependencies(&self) -> &PerPlatform<Vec<LuaDependencySpec>> {
222        &self.dependencies
223    }
224
225    fn build_dependencies(&self) -> &PerPlatform<Vec<LuaDependencySpec>> {
226        &self.build_dependencies
227    }
228
229    fn external_dependencies(&self) -> &PerPlatform<HashMap<String, ExternalDependencySpec>> {
230        &self.external_dependencies
231    }
232
233    fn test_dependencies(&self) -> &PerPlatform<Vec<LuaDependencySpec>> {
234        &self.test_dependencies
235    }
236
237    fn build(&self) -> &PerPlatform<BuildSpec> {
238        &self.build
239    }
240
241    fn test(&self) -> &PerPlatform<TestSpec> {
242        &self.test
243    }
244
245    fn source(&self) -> &PerPlatform<RemoteRockSource> {
246        &self.source
247    }
248
249    fn deploy(&self) -> &PerPlatform<DeploySpec> {
250        &self.deploy
251    }
252
253    fn build_mut(&mut self) -> &mut PerPlatform<BuildSpec> {
254        &mut self.build
255    }
256
257    fn test_mut(&mut self) -> &mut PerPlatform<TestSpec> {
258        &mut self.test
259    }
260
261    fn source_mut(&mut self) -> &mut PerPlatform<RemoteRockSource> {
262        &mut self.source
263    }
264
265    fn deploy_mut(&mut self) -> &mut PerPlatform<DeploySpec> {
266        &mut self.deploy
267    }
268
269    fn format(&self) -> &Option<RockspecFormat> {
270        &self.rockspec_format
271    }
272
273    fn to_lua_remote_rockspec_string(&self) -> Result<String, Self::Error> {
274        Ok(self.raw_content.clone())
275    }
276}
277
278impl HasIntegrity for LocalLuaRockspec {
279    fn hash(&self) -> io::Result<Integrity> {
280        Ok(Integrity::from(&self.raw_content))
281    }
282}
283
284#[derive(Clone, Debug)]
285#[cfg_attr(test, derive(PartialEq))]
286pub struct RemoteLuaRockspec {
287    local: LocalLuaRockspec,
288    source: PerPlatform<RemoteRockSource>,
289}
290
291impl UserData for RemoteLuaRockspec {
292    fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
293        methods.add_method("package", |_, this, _: ()| Ok(this.local.package.clone()));
294        methods.add_method("version", |_, this, _: ()| Ok(this.local.version.clone()));
295        methods.add_method("description", |_, this, _: ()| {
296            Ok(this.local.description.clone())
297        });
298        methods.add_method("supported_platforms", |_, this, _: ()| {
299            Ok(this.local.supported_platforms.clone())
300        });
301        methods.add_method("lua", |_, this, _: ()| Ok(this.local.lua.clone()));
302        methods.add_method("dependencies", |_, this, _: ()| {
303            Ok(this.local.dependencies.clone())
304        });
305        methods.add_method("build_dependencies", |_, this, _: ()| {
306            Ok(this.local.build_dependencies.clone())
307        });
308        methods.add_method("external_dependencies", |_, this, _: ()| {
309            Ok(this.local.external_dependencies.clone())
310        });
311        methods.add_method("test_dependencies", |_, this, _: ()| {
312            Ok(this.local.test_dependencies.clone())
313        });
314        methods.add_method("build", |_, this, _: ()| Ok(this.local.build.clone()));
315        methods.add_method("source", |_, this, _: ()| Ok(this.source.clone()));
316        methods.add_method("test", |_, this, _: ()| Ok(this.local.test.clone()));
317        methods.add_method("format", |_, this, _: ()| {
318            Ok(this.local.rockspec_format.clone())
319        });
320
321        methods.add_method("to_lua_rockspec_string", |_, this, _: ()| {
322            this.to_lua_remote_rockspec_string()
323                .map_err(|err| mlua::Error::RuntimeError(err.to_string()))
324        });
325    }
326}
327
328impl RemoteLuaRockspec {
329    pub fn new(rockspec_content: &str) -> Result<Self, LuaRockspecError> {
330        let lua = Lua::new();
331        lua.load(rockspec_content).exec()?;
332
333        let globals = lua.globals();
334        let source = globals.get("source")?;
335
336        let rockspec = RemoteLuaRockspec {
337            local: LocalLuaRockspec::new(rockspec_content, ProjectRoot::new())?,
338            source,
339        };
340
341        Ok(rockspec)
342    }
343
344    pub fn from_package_and_source_spec(
345        package_spec: PackageSpec,
346        source_spec: RockSourceSpec,
347    ) -> Self {
348        let version = package_spec.version().clone();
349        let rockspec_format: RockspecFormat = "3.0".into();
350        let raw_content = format!(
351            r#"
352rockspec_format = "{}"
353package = "{}"
354version = "{}"
355{}
356build = {{
357  type = "source"
358}}"#,
359            &rockspec_format,
360            package_spec.name(),
361            &version,
362            &source_spec.display_lua(),
363        );
364
365        let source: RemoteRockSource = source_spec.into();
366
367        let local = LocalLuaRockspec {
368            rockspec_format: Some(rockspec_format),
369            package: package_spec.name().clone(),
370            version,
371            description: RockDescription::default(),
372            supported_platforms: PlatformSupport::default(),
373            lua: PackageVersionReq::Any,
374            dependencies: PerPlatform::default(),
375            build_dependencies: PerPlatform::default(),
376            external_dependencies: PerPlatform::default(),
377            test_dependencies: PerPlatform::default(),
378            build: PerPlatform::new(BuildSpec {
379                build_backend: Some(BuildBackendSpec::Source),
380                install: InstallSpec::default(),
381                copy_directories: Vec::new(),
382                patches: HashMap::new(),
383            }),
384            source: PerPlatform::new(source.clone()),
385            test: PerPlatform::default(),
386            deploy: PerPlatform::default(),
387            raw_content,
388        };
389        Self {
390            local,
391            source: PerPlatform::new(source),
392        }
393    }
394}
395
396impl Rockspec for RemoteLuaRockspec {
397    type Error = Infallible;
398
399    fn package(&self) -> &PackageName {
400        self.local.package()
401    }
402
403    fn version(&self) -> &PackageVersion {
404        self.local.version()
405    }
406
407    fn description(&self) -> &RockDescription {
408        self.local.description()
409    }
410
411    fn supported_platforms(&self) -> &PlatformSupport {
412        self.local.supported_platforms()
413    }
414
415    fn lua(&self) -> &PackageVersionReq {
416        self.local.lua()
417    }
418
419    fn dependencies(&self) -> &PerPlatform<Vec<LuaDependencySpec>> {
420        self.local.dependencies()
421    }
422
423    fn build_dependencies(&self) -> &PerPlatform<Vec<LuaDependencySpec>> {
424        match self.format() {
425            // Rockspec formats < 3.0 don't support `build_dependencies`,
426            // so we have to return regular dependencies if the build backend might need to use them.
427            Some(RockspecFormat::_1_0 | RockspecFormat::_2_0)
428                if self
429                    .build()
430                    .current_platform()
431                    .build_backend
432                    .as_ref()
433                    .is_some_and(|build_backend| build_backend.can_use_build_dependencies()) =>
434            {
435                self.local.dependencies()
436            }
437            _ => self.local.build_dependencies(),
438        }
439    }
440
441    fn external_dependencies(&self) -> &PerPlatform<HashMap<String, ExternalDependencySpec>> {
442        self.local.external_dependencies()
443    }
444
445    fn test_dependencies(&self) -> &PerPlatform<Vec<LuaDependencySpec>> {
446        self.local.test_dependencies()
447    }
448
449    fn build(&self) -> &PerPlatform<BuildSpec> {
450        self.local.build()
451    }
452
453    fn test(&self) -> &PerPlatform<TestSpec> {
454        self.local.test()
455    }
456
457    fn source(&self) -> &PerPlatform<RemoteRockSource> {
458        &self.source
459    }
460
461    fn deploy(&self) -> &PerPlatform<DeploySpec> {
462        self.local.deploy()
463    }
464
465    fn build_mut(&mut self) -> &mut PerPlatform<BuildSpec> {
466        self.local.build_mut()
467    }
468
469    fn test_mut(&mut self) -> &mut PerPlatform<TestSpec> {
470        self.local.test_mut()
471    }
472
473    fn source_mut(&mut self) -> &mut PerPlatform<RemoteRockSource> {
474        &mut self.source
475    }
476
477    fn deploy_mut(&mut self) -> &mut PerPlatform<DeploySpec> {
478        self.local.deploy_mut()
479    }
480
481    fn format(&self) -> &Option<RockspecFormat> {
482        self.local.format()
483    }
484
485    fn to_lua_remote_rockspec_string(&self) -> Result<String, Self::Error> {
486        Ok(self.local.raw_content.clone())
487    }
488}
489
490#[derive(Error, Debug)]
491pub enum LuaVersionError {
492    #[error("The lua version {0} is not supported by {1} version {1}!")]
493    LuaVersionUnsupported(LuaVersion, PackageName, PackageVersion),
494    #[error(transparent)]
495    LuaVersionUnset(#[from] LuaVersionUnset),
496}
497
498impl HasIntegrity for RemoteLuaRockspec {
499    fn hash(&self) -> io::Result<Integrity> {
500        Ok(Integrity::from(&self.local.raw_content))
501    }
502}
503
504#[derive(Clone, Deserialize, Debug, PartialEq, Default)]
505pub struct RockDescription {
506    /// A one-line description of the package.
507    pub summary: Option<String>,
508    /// A longer description of the package.
509    pub detailed: Option<String>,
510    /// The license used by the package.
511    pub license: Option<String>,
512    /// An URL for the project. This is not the URL for the tarball, but the address of a website.
513    #[serde(default, deserialize_with = "deserialize_url")]
514    pub homepage: Option<Url>,
515    /// An URL for the project's issue tracker.
516    pub issues_url: Option<String>,
517    /// Contact information for the rockspec maintainer.
518    pub maintainer: Option<String>,
519    /// A list of short strings that specify labels for categorization of this rock.
520    #[serde(default)]
521    pub labels: Vec<String>,
522}
523
524fn deserialize_url<'de, D>(deserializer: D) -> Result<Option<Url>, D::Error>
525where
526    D: serde::Deserializer<'de>,
527{
528    let s = Option::<String>::deserialize(deserializer)?;
529    s.map(|s| Url::parse(&s).map_err(serde::de::Error::custom))
530        .transpose()
531}
532
533impl DisplayAsLuaKV for RockDescription {
534    fn display_lua(&self) -> DisplayLuaKV {
535        let mut description = Vec::new();
536
537        if let Some(summary) = &self.summary {
538            description.push(DisplayLuaKV {
539                key: "summary".to_string(),
540                value: DisplayLuaValue::String(summary.clone()),
541            })
542        }
543        if let Some(detailed) = &self.detailed {
544            description.push(DisplayLuaKV {
545                key: "detailed".to_string(),
546                value: DisplayLuaValue::String(detailed.clone()),
547            })
548        }
549        if let Some(license) = &self.license {
550            description.push(DisplayLuaKV {
551                key: "license".to_string(),
552                value: DisplayLuaValue::String(license.clone()),
553            })
554        }
555        if let Some(homepage) = &self.homepage {
556            description.push(DisplayLuaKV {
557                key: "homepage".to_string(),
558                value: DisplayLuaValue::String(homepage.to_string()),
559            })
560        }
561        if let Some(issues_url) = &self.issues_url {
562            description.push(DisplayLuaKV {
563                key: "issues_url".to_string(),
564                value: DisplayLuaValue::String(issues_url.clone()),
565            })
566        }
567        if let Some(maintainer) = &self.maintainer {
568            description.push(DisplayLuaKV {
569                key: "maintainer".to_string(),
570                value: DisplayLuaValue::String(maintainer.clone()),
571            })
572        }
573        if !self.labels.is_empty() {
574            description.push(DisplayLuaKV {
575                key: "labels".to_string(),
576                value: DisplayLuaValue::List(
577                    self.labels
578                        .iter()
579                        .cloned()
580                        .map(DisplayLuaValue::String)
581                        .collect(),
582                ),
583            })
584        }
585
586        DisplayLuaKV {
587            key: "description".to_string(),
588            value: DisplayLuaValue::Table(description),
589        }
590    }
591}
592
593impl UserData for RockDescription {
594    fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
595        methods.add_method("summary", |_, this, _: ()| Ok(this.summary.clone()));
596        methods.add_method("detailed", |_, this, _: ()| Ok(this.detailed.clone()));
597        methods.add_method("license", |_, this, _: ()| Ok(this.license.clone()));
598        methods.add_method("homepage", |_, this, _: ()| {
599            Ok(this.homepage.clone().map(|url| url.to_string()))
600        });
601        methods.add_method("issues_url", |_, this, _: ()| Ok(this.issues_url.clone()));
602        methods.add_method("maintainer", |_, this, _: ()| Ok(this.maintainer.clone()));
603        methods.add_method("labels", |_, this, _: ()| Ok(this.labels.clone()));
604    }
605}
606
607#[derive(Error, Debug)]
608#[error("invalid rockspec format: {0}")]
609pub struct InvalidRockspecFormat(String);
610
611#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
612pub enum RockspecFormat {
613    #[serde(rename = "1.0")]
614    _1_0,
615    #[serde(rename = "2.0")]
616    _2_0,
617    #[serde(rename = "3.0")]
618    _3_0,
619}
620
621impl FromStr for RockspecFormat {
622    type Err = InvalidRockspecFormat;
623
624    fn from_str(s: &str) -> Result<Self, Self::Err> {
625        match s {
626            "1.0" => Ok(Self::_1_0),
627            "2.0" => Ok(Self::_2_0),
628            "3.0" => Ok(Self::_3_0),
629            txt => Err(InvalidRockspecFormat(txt.to_string())),
630        }
631    }
632}
633
634impl From<&str> for RockspecFormat {
635    fn from(s: &str) -> Self {
636        Self::from_str(s).unwrap()
637    }
638}
639
640impl FromLua for RockspecFormat {
641    fn from_lua(
642        value: mlua::prelude::LuaValue,
643        lua: &mlua::prelude::Lua,
644    ) -> mlua::prelude::LuaResult<Self> {
645        let s = String::from_lua(value, lua)?;
646        Self::from_str(&s).map_err(|err| mlua::Error::DeserializeError(err.to_string()))
647    }
648}
649
650impl IntoLua for RockspecFormat {
651    fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
652        self.to_string().into_lua(lua)
653    }
654}
655
656impl Display for RockspecFormat {
657    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
658        match self {
659            Self::_1_0 => write!(f, "1.0"),
660            Self::_2_0 => write!(f, "2.0"),
661            Self::_3_0 => write!(f, "3.0"),
662        }
663    }
664}
665
666#[derive(Error, Debug)]
667pub enum LuaTableError {
668    #[error("could not parse {variable}. Expected list, but got {invalid_type}")]
669    ParseError {
670        variable: String,
671        invalid_type: String,
672    },
673    #[error(transparent)]
674    MLua(#[from] mlua::Error),
675}
676
677fn parse_lua_tbl_or_default<T>(lua: &Lua, lua_var_name: &str) -> Result<T, LuaTableError>
678where
679    T: Default,
680    T: DeserializeOwned,
681{
682    let ret = match lua.globals().get(lua_var_name)? {
683        Value::Nil => T::default(),
684        value @ Value::Table(_) => lua.from_value(value)?,
685        value => Err(LuaTableError::ParseError {
686            variable: lua_var_name.to_string(),
687            invalid_type: value.type_name().to_string(),
688        })?,
689    };
690    Ok(ret)
691}
692
693#[cfg(test)]
694mod tests {
695
696    use std::path::PathBuf;
697
698    use crate::git::GitSource;
699    use crate::lua_rockspec::PlatformIdentifier;
700    use crate::package::PackageSpec;
701
702    use super::*;
703
704    #[tokio::test]
705    pub async fn parse_rockspec() {
706        let rockspec_content = "
707        rockspec_format = '1.0'\n
708        package = 'foo'\n
709        version = '1.0.0-1'\n
710        source = {\n
711            url = 'https://github.com/nvim-neorocks/rocks.nvim/archive/1.0.0/rocks.nvim.zip',\n
712        }\n
713        "
714        .to_string();
715        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
716        assert_eq!(rockspec.local.rockspec_format, Some("1.0".into()));
717        assert_eq!(rockspec.local.package, "foo".into());
718        assert_eq!(rockspec.local.version, "1.0.0-1".parse().unwrap());
719        assert_eq!(rockspec.local.description, RockDescription::default());
720
721        let rockspec_content = "
722        package = 'bar'\n
723        version = '2.0.0-1'\n
724        description = {}\n
725        source = {\n
726            url = 'https://github.com/nvim-neorocks/rocks.nvim/archive/1.0.0/rocks.nvim.zip',\n
727        }\n
728        "
729        .to_string();
730        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
731        assert_eq!(rockspec.local.rockspec_format, None);
732        assert_eq!(rockspec.local.package, "bar".into());
733        assert_eq!(rockspec.local.version, "2.0.0-1".parse().unwrap());
734        assert_eq!(rockspec.local.description, RockDescription::default());
735
736        let rockspec_content = "
737        package = 'rocks.nvim'\n
738        version = '3.0.0-1'\n
739        description = {\n
740            summary = 'some summary',
741            detailed = 'some detailed description',
742            license = 'MIT',
743            homepage = 'https://github.com/nvim-neorocks/rocks.nvim',
744            issues_url = 'https://github.com/nvim-neorocks/rocks.nvim/issues',
745            maintainer = 'neorocks',
746        }\n
747        source = {\n
748            url = 'https://github.com/nvim-neorocks/rocks.nvim/archive/1.0.0/rocks.nvim.zip',\n
749        }\n
750        "
751        .to_string();
752        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
753        assert_eq!(rockspec.local.rockspec_format, None);
754        assert_eq!(rockspec.local.package, "rocks.nvim".into());
755        assert_eq!(rockspec.local.version, "3.0.0-1".parse().unwrap());
756        let expected_description = RockDescription {
757            summary: Some("some summary".into()),
758            detailed: Some("some detailed description".into()),
759            license: Some("MIT".into()),
760            homepage: Some(Url::parse("https://github.com/nvim-neorocks/rocks.nvim").unwrap()),
761            issues_url: Some("https://github.com/nvim-neorocks/rocks.nvim/issues".into()),
762            maintainer: Some("neorocks".into()),
763            labels: Vec::new(),
764        };
765        assert_eq!(rockspec.local.description, expected_description);
766
767        let rockspec_content = "
768        package = 'rocks.nvim'\n
769        version = '3.0.0-1'\n
770        description = {\n
771            summary = 'some summary',
772            detailed = 'some detailed description',
773            license = 'MIT',
774            homepage = 'https://github.com/nvim-neorocks/rocks.nvim',
775            issues_url = 'https://github.com/nvim-neorocks/rocks.nvim/issues',
776            maintainer = 'neorocks',
777            labels = {},
778        }\n
779        external_dependencies = { FOO = { library = 'foo' } }\n
780        source = {\n
781            url = 'https://github.com/nvim-neorocks/rocks.nvim/archive/1.0.0/rocks.nvim.zip',\n
782        }\n
783        "
784        .to_string();
785        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
786        assert_eq!(rockspec.local.rockspec_format, None);
787        assert_eq!(rockspec.local.package, "rocks.nvim".into());
788        assert_eq!(rockspec.local.version, "3.0.0-1".parse().unwrap());
789        let expected_description = RockDescription {
790            summary: Some("some summary".into()),
791            detailed: Some("some detailed description".into()),
792            license: Some("MIT".into()),
793            homepage: Some(Url::parse("https://github.com/nvim-neorocks/rocks.nvim").unwrap()),
794            issues_url: Some("https://github.com/nvim-neorocks/rocks.nvim/issues".into()),
795            maintainer: Some("neorocks".into()),
796            labels: Vec::new(),
797        };
798        assert_eq!(rockspec.local.description, expected_description);
799        assert_eq!(
800            *rockspec
801                .local
802                .external_dependencies
803                .default
804                .get("FOO")
805                .unwrap(),
806            ExternalDependencySpec {
807                library: Some("foo".into()),
808                header: None
809            }
810        );
811
812        let rockspec_content = "
813        package = 'rocks.nvim'\n
814        version = '3.0.0-1'\n
815        description = {\n
816            summary = 'some summary',
817            detailed = 'some detailed description',
818            license = 'MIT',
819            homepage = 'https://github.com/nvim-neorocks/rocks.nvim',
820            issues_url = 'https://github.com/nvim-neorocks/rocks.nvim/issues',
821            maintainer = 'neorocks',
822            labels = { 'package management', },
823        }\n
824        supported_platforms = { 'unix', '!windows' }\n
825        dependencies = { 'neorg ~> 6' }\n
826        build_dependencies = { 'foo' }\n
827        external_dependencies = { FOO = { header = 'foo.h' } }\n
828        test_dependencies = { 'busted >= 2.0.0' }\n
829        source = {\n
830            url = 'git+https://github.com/nvim-neorocks/rocks.nvim',\n
831            hash = 'sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=',\n
832        }\n
833        "
834        .to_string();
835        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
836        assert_eq!(rockspec.local.rockspec_format, None);
837        assert_eq!(rockspec.local.package, "rocks.nvim".into());
838        assert_eq!(rockspec.local.version, "3.0.0-1".parse().unwrap());
839        let expected_description = RockDescription {
840            summary: Some("some summary".into()),
841            detailed: Some("some detailed description".into()),
842            license: Some("MIT".into()),
843            homepage: Some(Url::parse("https://github.com/nvim-neorocks/rocks.nvim").unwrap()),
844            issues_url: Some("https://github.com/nvim-neorocks/rocks.nvim/issues".into()),
845            maintainer: Some("neorocks".into()),
846            labels: vec!["package management".into()],
847        };
848        assert_eq!(rockspec.local.description, expected_description);
849        assert!(rockspec
850            .local
851            .supported_platforms
852            .is_supported(&PlatformIdentifier::Unix));
853        assert!(!rockspec
854            .local
855            .supported_platforms
856            .is_supported(&PlatformIdentifier::Windows));
857        let neorg = PackageSpec::parse("neorg".into(), "6.0.0".into()).unwrap();
858        assert!(rockspec
859            .local
860            .dependencies
861            .default
862            .into_iter()
863            .any(|dep| dep.matches(&neorg)));
864        let foo = PackageSpec::parse("foo".into(), "1.0.0".into()).unwrap();
865        assert!(rockspec
866            .local
867            .build_dependencies
868            .default
869            .into_iter()
870            .any(|dep| dep.matches(&foo)));
871        let busted = PackageSpec::parse("busted".into(), "2.2.0".into()).unwrap();
872        assert_eq!(
873            *rockspec
874                .local
875                .external_dependencies
876                .default
877                .get("FOO")
878                .unwrap(),
879            ExternalDependencySpec {
880                header: Some("foo.h".into()),
881                library: None
882            }
883        );
884        assert!(rockspec
885            .local
886            .test_dependencies
887            .default
888            .into_iter()
889            .any(|dep| dep.matches(&busted)));
890
891        let rockspec_content = "
892        rockspec_format = '1.0'\n
893        package = 'foo'\n
894        version = '1.0.0-1'\n
895        source = {\n
896            url = 'git+https://hub.com/example-project/',\n
897            branch = 'bar',\n
898        }\n
899        "
900        .to_string();
901        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
902        assert_eq!(
903            rockspec.local.source.default.source_spec,
904            RockSourceSpec::Git(GitSource {
905                url: "https://hub.com/example-project/".parse().unwrap(),
906                checkout_ref: Some("bar".into())
907            })
908        );
909        assert_eq!(rockspec.local.test, PerPlatform::default());
910        let rockspec_content = "
911        rockspec_format = '1.0'\n
912        package = 'foo'\n
913        version = '1.0.0-1'\n
914        source = {\n
915            url = 'git+https://hub.com/example-project/',\n
916            tag = 'bar',\n
917        }\n
918        "
919        .to_string();
920        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
921        assert_eq!(
922            rockspec.local.source.default.source_spec,
923            RockSourceSpec::Git(GitSource {
924                url: "https://hub.com/example-project/".parse().unwrap(),
925                checkout_ref: Some("bar".into())
926            })
927        );
928        let rockspec_content = "
929        rockspec_format = '1.0'\n
930        package = 'foo'\n
931        version = '1.0.0-1'\n
932        source = {\n
933            url = 'git+https://hub.com/example-project/',\n
934            branch = 'bar',\n
935            tag = 'baz',\n
936        }\n
937        "
938        .to_string();
939        let _rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap_err();
940        let rockspec_content = "
941        rockspec_format = '1.0'\n
942        package = 'foo'\n
943        version = '1.0.0-1'\n
944        source = {\n
945            url = 'git+https://hub.com/example-project/',\n
946            tag = 'bar',\n
947            file = 'foo.tar.gz',\n
948        }\n
949        build = {\n
950            install = {\n
951                conf = {['foo.bar'] = 'config/bar.toml'},\n
952            },\n
953        }\n
954        "
955        .to_string();
956        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
957        assert_eq!(
958            rockspec.local.source.default.archive_name,
959            Some("foo.tar.gz".into())
960        );
961        let foo_bar_path = rockspec
962            .local
963            .build
964            .default
965            .install
966            .conf
967            .get("foo.bar")
968            .unwrap();
969        assert_eq!(*foo_bar_path, PathBuf::from("config/bar.toml"));
970        let rockspec_content = "
971        rockspec_format = '1.0'\n
972        package = 'foo'\n
973        version = '1.0.0-1'\n
974        source = {\n
975            url = 'git+https://hub.com/example-project/foo.zip',\n
976        }\n
977        build = {\n
978            install = {\n
979                lua = {['foo.bar'] = 'src/bar.lua'},\n
980                bin = {['foo.bar'] = 'bin/bar'},\n
981            },\n
982        }\n
983        "
984        .to_string();
985        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
986        assert!(matches!(
987            rockspec.local.build.default.build_backend,
988            Some(BuildBackendSpec::Builtin { .. })
989        ));
990        let foo_bar_path = rockspec
991            .local
992            .build
993            .default
994            .install
995            .lua
996            .get(&LuaModule::from_str("foo.bar").unwrap())
997            .unwrap();
998        assert_eq!(*foo_bar_path, PathBuf::from("src/bar.lua"));
999        let foo_bar_path = rockspec
1000            .local
1001            .build
1002            .default
1003            .install
1004            .bin
1005            .get("foo.bar")
1006            .unwrap();
1007        assert_eq!(*foo_bar_path, PathBuf::from("bin/bar"));
1008        let rockspec_content = "
1009        rockspec_format = '1.0'\n
1010        package = 'foo'\n
1011        version = '1.0.0-1'\n
1012        source = {\n
1013            url = 'git+https://hub.com/example-project/',\n
1014        }\n
1015        build = {\n
1016            copy_directories = { 'lua' },\n
1017        }\n
1018        "
1019        .to_string();
1020        let _rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap_err();
1021        let rockspec_content = "
1022        rockspec_format = '1.0'\n
1023        package = 'foo'\n
1024        version = '1.0.0-1'\n
1025        source = {\n
1026            url = 'git+https://hub.com/example-project/',\n
1027        }\n
1028        build = {\n
1029            copy_directories = { 'lib' },\n
1030        }\n
1031        "
1032        .to_string();
1033        let _rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap_err();
1034        let rockspec_content = "
1035        rockspec_format = '1.0'\n
1036        package = 'foo'\n
1037        version = '1.0.0-1'\n
1038        source = {\n
1039            url = 'git+https://hub.com/example-project/',\n
1040        }\n
1041        build = {\n
1042            copy_directories = { 'rock_manifest' },\n
1043        }\n
1044        "
1045        .to_string();
1046        let _rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap_err();
1047        let rockspec_content = "
1048        rockspec_format = '1.0'\n
1049        package = 'foo'\n
1050        version = '1.0.0-1'\n
1051        source = {\n
1052            url = 'git+https://hub.com/example-project/foo.zip',\n
1053            dir = 'baz',\n
1054        }\n
1055        build = {\n
1056            type = 'make',\n
1057            install = {\n
1058                lib = {['foo.bar'] = 'lib/bar.so'},\n
1059            },\n
1060            copy_directories = {\n
1061                'plugin',\n
1062                'ftplugin',\n
1063            },\n
1064            patches = {\n
1065                ['lua51-support.diff'] = [[\n
1066                    --- before.c\n
1067                    +++ path/to/after.c\n
1068                ]],\n
1069            },\n
1070        }\n
1071        "
1072        .to_string();
1073        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1074        assert_eq!(rockspec.local.source.default.unpack_dir, Some("baz".into()));
1075        assert_eq!(
1076            rockspec.local.build.default.build_backend,
1077            Some(BuildBackendSpec::Make(MakeBuildSpec::default()))
1078        );
1079        let foo_bar_path = rockspec
1080            .local
1081            .build
1082            .default
1083            .install
1084            .lib
1085            .get(&LuaModule::from_str("foo.bar").unwrap())
1086            .unwrap();
1087        assert_eq!(*foo_bar_path, PathBuf::from("lib/bar.so"));
1088        let copy_directories = rockspec.local.build.default.copy_directories;
1089        assert_eq!(
1090            copy_directories,
1091            vec![PathBuf::from("plugin"), PathBuf::from("ftplugin")]
1092        );
1093        let patches = rockspec.local.build.default.patches;
1094        let _patch = patches.get(&PathBuf::from("lua51-support.diff")).unwrap();
1095        let rockspec_content = "
1096        rockspec_format = '1.0'\n
1097        package = 'foo'\n
1098        version = '1.0.0-1'\n
1099        source = {\n
1100            url = 'git+https://hub.com/example-project/foo.zip',\n
1101        }\n
1102        build = {\n
1103            type = 'cmake',\n
1104        }\n
1105        "
1106        .to_string();
1107        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1108        assert_eq!(
1109            rockspec.local.build.default.build_backend,
1110            Some(BuildBackendSpec::CMake(CMakeBuildSpec::default()))
1111        );
1112        let rockspec_content = "
1113        rockspec_format = '1.0'\n
1114        package = 'foo'\n
1115        version = '1.0.0-1'\n
1116        source = {\n
1117            url = 'git+https://hub.com/example-project/foo.zip',\n
1118        }\n
1119        build = {\n
1120            type = 'command',\n
1121            build_command = 'foo',\n
1122            install_command = 'bar',\n
1123        }\n
1124        "
1125        .to_string();
1126        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1127        assert!(matches!(
1128            rockspec.local.build.default.build_backend,
1129            Some(BuildBackendSpec::Command(CommandBuildSpec { .. }))
1130        ));
1131        let rockspec_content = "
1132        rockspec_format = '1.0'\n
1133        package = 'foo'\n
1134        version = '1.0.0-1'\n
1135        source = {\n
1136            url = 'git+https://hub.com/example-project/foo.zip',\n
1137        }\n
1138        build = {\n
1139            type = 'command',\n
1140            install_command = 'foo',\n
1141        }\n
1142        "
1143        .to_string();
1144        RemoteLuaRockspec::new(&rockspec_content).unwrap();
1145        let rockspec_content = "
1146        rockspec_format = '1.0'\n
1147        package = 'foo'\n
1148        version = '1.0.0-1'\n
1149        source = {\n
1150            url = 'git+https://hub.com/example-project/foo.zip',\n
1151        }\n
1152        build = {\n
1153            type = 'command',\n
1154            build_command = 'foo',\n
1155        }\n
1156        "
1157        .to_string();
1158        RemoteLuaRockspec::new(&rockspec_content).unwrap();
1159        // platform overrides
1160        let rockspec_content = "
1161        package = 'rocks'\n
1162        version = '3.0.0-1'\n
1163        dependencies = {\n
1164          'neorg ~> 6',\n
1165          'toml-edit ~> 1',\n
1166          platforms = {\n
1167            windows = {\n
1168              'neorg = 5.0.0',\n
1169              'toml = 1.0.0',\n
1170            },\n
1171            unix = {\n
1172              'neorg = 5.0.0',\n
1173            },\n
1174            linux = {\n
1175              'toml = 1.0.0',\n
1176            },\n
1177          },\n
1178        }\n
1179        source = {\n
1180            url = 'git+https://github.com/nvim-neorocks/rocks.nvim',\n
1181            hash = 'sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=',\n
1182        }\n
1183        "
1184        .to_string();
1185        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1186        let neorg_override = PackageSpec::parse("neorg".into(), "5.0.0".into()).unwrap();
1187        let toml_edit = PackageSpec::parse("toml-edit".into(), "1.0.0".into()).unwrap();
1188        let toml = PackageSpec::parse("toml".into(), "1.0.0".into()).unwrap();
1189        assert_eq!(rockspec.local.dependencies.default.len(), 2);
1190        let per_platform = &rockspec.local.dependencies.per_platform;
1191        assert_eq!(
1192            per_platform
1193                .get(&PlatformIdentifier::Windows)
1194                .unwrap()
1195                .iter()
1196                .filter(|dep| dep.matches(&neorg_override)
1197                    || dep.matches(&toml_edit)
1198                    || dep.matches(&toml))
1199                .count(),
1200            3
1201        );
1202        assert_eq!(
1203            per_platform
1204                .get(&PlatformIdentifier::Unix)
1205                .unwrap()
1206                .iter()
1207                .filter(|dep| dep.matches(&neorg_override)
1208                    || dep.matches(&toml_edit)
1209                    || dep.matches(&toml))
1210                .count(),
1211            2
1212        );
1213        assert_eq!(
1214            per_platform
1215                .get(&PlatformIdentifier::Linux)
1216                .unwrap()
1217                .iter()
1218                .filter(|dep| dep.matches(&neorg_override)
1219                    || dep.matches(&toml_edit)
1220                    || dep.matches(&toml))
1221                .count(),
1222            3
1223        );
1224        let rockspec_content = "
1225        package = 'rocks'\n
1226        version = '3.0.0-1'\n
1227        external_dependencies = {\n
1228            FOO = { library = 'foo' },\n
1229            platforms = {\n
1230              windows = {\n
1231                FOO = { library = 'foo.dll' },\n
1232              },\n
1233              unix = {\n
1234                BAR = { header = 'bar.h' },\n
1235              },\n
1236              linux = {\n
1237                FOO = { library = 'foo.so' },\n
1238              },\n
1239            },\n
1240        }\n
1241        source = {\n
1242            url = 'https://github.com/nvim-neorocks/rocks.nvim/archive/1.0.0/rocks.nvim.zip',\n
1243        }\n
1244        "
1245        .to_string();
1246        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1247        assert_eq!(
1248            *rockspec
1249                .local
1250                .external_dependencies
1251                .default
1252                .get("FOO")
1253                .unwrap(),
1254            ExternalDependencySpec {
1255                library: Some("foo".into()),
1256                header: None
1257            }
1258        );
1259        let per_platform = rockspec.local.external_dependencies.per_platform;
1260        assert_eq!(
1261            *per_platform
1262                .get(&PlatformIdentifier::Windows)
1263                .and_then(|it| it.get("FOO"))
1264                .unwrap(),
1265            ExternalDependencySpec {
1266                library: Some("foo.dll".into()),
1267                header: None
1268            }
1269        );
1270        assert_eq!(
1271            *per_platform
1272                .get(&PlatformIdentifier::Unix)
1273                .and_then(|it| it.get("FOO"))
1274                .unwrap(),
1275            ExternalDependencySpec {
1276                library: Some("foo".into()),
1277                header: None
1278            }
1279        );
1280        assert_eq!(
1281            *per_platform
1282                .get(&PlatformIdentifier::Unix)
1283                .and_then(|it| it.get("BAR"))
1284                .unwrap(),
1285            ExternalDependencySpec {
1286                header: Some("bar.h".into()),
1287                library: None
1288            }
1289        );
1290        assert_eq!(
1291            *per_platform
1292                .get(&PlatformIdentifier::Linux)
1293                .and_then(|it| it.get("BAR"))
1294                .unwrap(),
1295            ExternalDependencySpec {
1296                header: Some("bar.h".into()),
1297                library: None
1298            }
1299        );
1300        assert_eq!(
1301            *per_platform
1302                .get(&PlatformIdentifier::Linux)
1303                .and_then(|it| it.get("FOO"))
1304                .unwrap(),
1305            ExternalDependencySpec {
1306                library: Some("foo.so".into()),
1307                header: None
1308            }
1309        );
1310        let rockspec_content = "
1311        rockspec_format = '1.0'\n
1312        package = 'foo'\n
1313        version = '1.0.0-1'\n
1314        source = {\n
1315            url = 'git+https://hub.com/example-project/.git',\n
1316            branch = 'bar',\n
1317            platforms = {\n
1318                macosx = {\n
1319                    branch = 'mac',\n
1320                },\n
1321                windows = {\n
1322                    url = 'git+https://winhub.com/example-project/.git',\n
1323                    branch = 'win',\n
1324                },\n
1325            },\n
1326        }\n
1327        "
1328        .to_string();
1329        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1330        assert_eq!(
1331            rockspec.local.source.default.source_spec,
1332            RockSourceSpec::Git(GitSource {
1333                url: "https://hub.com/example-project/.git".parse().unwrap(),
1334                checkout_ref: Some("bar".into())
1335            })
1336        );
1337        assert_eq!(
1338            rockspec
1339                .source
1340                .per_platform
1341                .get(&PlatformIdentifier::MacOSX)
1342                .map(|it| it.source_spec.clone())
1343                .unwrap(),
1344            RockSourceSpec::Git(GitSource {
1345                url: "https://hub.com/example-project/.git".parse().unwrap(),
1346                checkout_ref: Some("mac".into())
1347            })
1348        );
1349        assert_eq!(
1350            rockspec
1351                .source
1352                .per_platform
1353                .get(&PlatformIdentifier::Windows)
1354                .map(|it| it.source_spec.clone())
1355                .unwrap(),
1356            RockSourceSpec::Git(GitSource {
1357                url: "https://winhub.com/example-project/.git".parse().unwrap(),
1358                checkout_ref: Some("win".into())
1359            })
1360        );
1361        let rockspec_content = "
1362        rockspec_format = '1.0'\n
1363        package = 'foo'\n
1364        version = '1.0.0-1'\n
1365        source = { url = 'git+https://hub.com/example-project/foo.zip' }\n
1366        build = {\n
1367            type = 'make',\n
1368            install = {\n
1369                lib = {['foo.bar'] = 'lib/bar.so'},\n
1370            },\n
1371            copy_directories = { 'plugin' },\n
1372            platforms = {\n
1373                unix = {\n
1374                    copy_directories = { 'ftplugin' },\n
1375                },\n
1376                linux = {\n
1377                    copy_directories = { 'foo' },\n
1378                },\n
1379            },\n
1380        }\n
1381        "
1382        .to_string();
1383        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1384        let per_platform = rockspec.local.build.per_platform;
1385        let unix = per_platform.get(&PlatformIdentifier::Unix).unwrap();
1386        assert_eq!(
1387            unix.copy_directories,
1388            vec![PathBuf::from("plugin"), PathBuf::from("ftplugin")]
1389        );
1390        let linux = per_platform.get(&PlatformIdentifier::Linux).unwrap();
1391        assert_eq!(
1392            linux.copy_directories,
1393            vec![
1394                PathBuf::from("plugin"),
1395                PathBuf::from("foo"),
1396                PathBuf::from("ftplugin")
1397            ]
1398        );
1399        let rockspec_content = "
1400        package = 'foo'\n
1401        version = '1.0.0-1'\n
1402        source = { url = 'git+https://hub.com/example-project/foo.zip' }\n
1403        build = {\n
1404            type = 'builtin',\n
1405            modules = {\n
1406                cjson = {\n
1407                    sources = { 'lua_cjson.c', 'strbuf.c', 'fpconv.c' },\n
1408                }\n
1409            },\n
1410            platforms = {\n
1411                win32 = { modules = { cjson = { defines = {\n
1412                    'DISABLE_INVALID_NUMBERS', 'USE_INTERNAL_ISINF'\n
1413                } } } }\n
1414            },\n
1415        }\n
1416        "
1417        .to_string();
1418        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1419        let per_platform = &rockspec.local.build.per_platform;
1420        let win32 = per_platform.get(&PlatformIdentifier::Windows).unwrap();
1421        assert_eq!(
1422            win32.build_backend,
1423            Some(BuildBackendSpec::Builtin(BuiltinBuildSpec {
1424                modules: vec![(
1425                    LuaModule::from_str("cjson").unwrap(),
1426                    ModuleSpec::ModulePaths(ModulePaths {
1427                        sources: vec!["lua_cjson.c".into(), "strbuf.c".into(), "fpconv.c".into()],
1428                        libraries: Vec::default(),
1429                        defines: vec![
1430                            ("DISABLE_INVALID_NUMBERS".into(), None),
1431                            ("USE_INTERNAL_ISINF".into(), None)
1432                        ],
1433                        incdirs: Vec::default(),
1434                        libdirs: Vec::default(),
1435                    })
1436                )]
1437                .into_iter()
1438                .collect()
1439            }))
1440        );
1441        let rockspec_content = "
1442        rockspec_format = '1.0'\n
1443        package = 'foo'\n
1444        version = '1.0.0-1'\n
1445        deploy = {\n
1446            wrap_bin_scripts = false,\n
1447        }\n
1448        source = { url = 'git+https://hub.com/example-project/foo.zip' }\n
1449        ";
1450        let rockspec = RemoteLuaRockspec::new(rockspec_content).unwrap();
1451        let deploy_spec = &rockspec.deploy().current_platform();
1452        assert!(!deploy_spec.wrap_bin_scripts);
1453    }
1454
1455    #[tokio::test]
1456    pub async fn parse_scm_rockspec() {
1457        let rockspec_content = "
1458        package = 'foo'\n
1459        version = 'scm-1'\n
1460        source = {\n
1461            url = 'https://github.com/nvim-neorocks/rocks.nvim/archive/1.0.0/rocks.nvim.zip',\n
1462        }\n
1463        "
1464        .to_string();
1465        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1466        assert_eq!(rockspec.local.package, "foo".into());
1467        assert_eq!(rockspec.local.version, "scm-1".parse().unwrap());
1468    }
1469
1470    #[tokio::test]
1471    pub async fn regression_luasystem() {
1472        let rockspec_content =
1473            String::from_utf8(std::fs::read("resources/test/luasystem-0.4.4-1.rockspec").unwrap())
1474                .unwrap();
1475        let rockspec = RemoteLuaRockspec::new(&rockspec_content).unwrap();
1476        let build_spec = rockspec.local.build.current_platform();
1477        assert!(matches!(
1478            build_spec.build_backend,
1479            Some(BuildBackendSpec::Builtin { .. })
1480        ));
1481        if let Some(BuildBackendSpec::Builtin(BuiltinBuildSpec { modules })) =
1482            &build_spec.build_backend
1483        {
1484            assert_eq!(
1485                modules.get(&LuaModule::from_str("system.init").unwrap()),
1486                Some(&ModuleSpec::SourcePath("system/init.lua".into()))
1487            );
1488            assert_eq!(
1489                modules.get(&LuaModule::from_str("system.core").unwrap()),
1490                Some(&ModuleSpec::ModulePaths(ModulePaths {
1491                    sources: vec![
1492                        "src/core.c".into(),
1493                        "src/compat.c".into(),
1494                        "src/time.c".into(),
1495                        "src/environment.c".into(),
1496                        "src/random.c".into(),
1497                        "src/term.c".into(),
1498                        "src/bitflags.c".into(),
1499                        "src/wcwidth.c".into(),
1500                    ],
1501                    defines: luasystem_expected_defines(),
1502                    libraries: luasystem_expected_libraries(),
1503                    incdirs: luasystem_expected_incdirs(),
1504                    libdirs: luasystem_expected_libdirs(),
1505                }))
1506            );
1507        }
1508        if let Some(BuildBackendSpec::Builtin(BuiltinBuildSpec { modules })) = &rockspec
1509            .local
1510            .build
1511            .get(&PlatformIdentifier::Windows)
1512            .build_backend
1513        {
1514            if let ModuleSpec::ModulePaths(paths) = modules
1515                .get(&LuaModule::from_str("system.core").unwrap())
1516                .unwrap()
1517            {
1518                assert_eq!(paths.libraries, luasystem_expected_windows_libraries());
1519            };
1520        }
1521        if let Some(BuildBackendSpec::Builtin(BuiltinBuildSpec { modules })) = &rockspec
1522            .local
1523            .build
1524            .get(&PlatformIdentifier::Win32)
1525            .build_backend
1526        {
1527            if let ModuleSpec::ModulePaths(paths) = modules
1528                .get(&LuaModule::from_str("system.core").unwrap())
1529                .unwrap()
1530            {
1531                assert_eq!(paths.libraries, luasystem_expected_windows_libraries());
1532            };
1533        }
1534    }
1535
1536    fn luasystem_expected_defines() -> Vec<(String, Option<String>)> {
1537        if cfg!(target_os = "windows") {
1538            vec![
1539                ("WINVER".into(), Some("0x0600".into())),
1540                ("_WIN32_WINNT".into(), Some("0x0600".into())),
1541            ]
1542        } else {
1543            Vec::default()
1544        }
1545    }
1546
1547    fn luasystem_expected_windows_libraries() -> Vec<PathBuf> {
1548        vec!["advapi32".into(), "winmm".into()]
1549    }
1550    fn luasystem_expected_libraries() -> Vec<PathBuf> {
1551        if cfg!(target_os = "linux") {
1552            vec!["rt".into()]
1553        } else if cfg!(target_os = "windows") {
1554            luasystem_expected_windows_libraries()
1555        } else {
1556            Vec::default()
1557        }
1558    }
1559
1560    fn luasystem_expected_incdirs() -> Vec<PathBuf> {
1561        Vec::default()
1562    }
1563
1564    fn luasystem_expected_libdirs() -> Vec<PathBuf> {
1565        Vec::default()
1566    }
1567
1568    #[tokio::test]
1569    pub async fn rust_mlua_rockspec() {
1570        let rockspec_content = "
1571    package = 'foo'\n
1572    version = 'scm-1'\n
1573    source = {\n
1574        url = 'https://github.com/nvim-neorocks/rocks.nvim/archive/1.0.0/rocks.nvim.zip',\n
1575    }\n
1576    build = {
1577        type = 'rust-mlua',
1578        modules = {
1579            'foo',
1580            bar = 'baz',
1581        },
1582        target_path = 'path/to/cargo/target/directory',
1583        default_features = false,
1584        include = {
1585            'file.lua',
1586            ['path/to/another/file.lua'] = 'another-file.lua',
1587        },
1588        features = {'extra', 'features'},
1589    }
1590            ";
1591        let rockspec = RemoteLuaRockspec::new(rockspec_content).unwrap();
1592        let build_spec = rockspec.local.build.current_platform();
1593        if let Some(BuildBackendSpec::RustMlua(build_spec)) = build_spec.build_backend.to_owned() {
1594            assert_eq!(
1595                build_spec.modules.get("foo").unwrap(),
1596                &PathBuf::from(format!("libfoo.{}", std::env::consts::DLL_EXTENSION))
1597            );
1598            assert_eq!(
1599                build_spec.modules.get("bar").unwrap(),
1600                &PathBuf::from(format!("libbaz.{}", std::env::consts::DLL_EXTENSION))
1601            );
1602            assert_eq!(
1603                build_spec.include.get(&PathBuf::from("file.lua")).unwrap(),
1604                &PathBuf::from("file.lua")
1605            );
1606            assert_eq!(
1607                build_spec
1608                    .include
1609                    .get(&PathBuf::from("path/to/another/file.lua"))
1610                    .unwrap(),
1611                &PathBuf::from("another-file.lua")
1612            );
1613        } else {
1614            panic!("Expected RustMlua build backend");
1615        }
1616    }
1617
1618    #[tokio::test]
1619    pub async fn regression_ltui() {
1620        let content =
1621            String::from_utf8(std::fs::read("resources/test/ltui-2.8-2.rockspec").unwrap())
1622                .unwrap();
1623        RemoteLuaRockspec::new(&content).unwrap();
1624    }
1625
1626    // Luarocks allows the `install.bin` field to be a list, even though it
1627    // should only allow a table.
1628    #[tokio::test]
1629    pub async fn regression_off_spec_install_binaries() {
1630        let rockspec_content = r#"
1631            package = "WSAPI"
1632            version = "1.7-1"
1633
1634            source = {
1635              url = "git://github.com/keplerproject/wsapi",
1636              tag = "v1.7",
1637            }
1638
1639            build = {
1640              type = "builtin",
1641              modules = {
1642                ["wsapi"] = "src/wsapi.lua",
1643              },
1644              -- Offending Line
1645              install = { bin = { "src/launcher/wsapi.cgi" } }
1646            }
1647        "#;
1648
1649        let rockspec = RemoteLuaRockspec::new(rockspec_content).unwrap();
1650
1651        assert_eq!(
1652            rockspec.build().current_platform().install.bin,
1653            HashMap::from([("wsapi".into(), PathBuf::from("src/launcher/wsapi.cgi"))])
1654        );
1655    }
1656
1657    #[tokio::test]
1658    pub async fn regression_external_dependencies() {
1659        let content =
1660            String::from_utf8(std::fs::read("resources/test/luaossl-20220711-0.rockspec").unwrap())
1661                .unwrap();
1662        let rockspec = RemoteLuaRockspec::new(&content).unwrap();
1663        if cfg!(target_family = "unix") {
1664            assert_eq!(
1665                rockspec
1666                    .local
1667                    .external_dependencies
1668                    .current_platform()
1669                    .get("OPENSSL")
1670                    .unwrap(),
1671                &ExternalDependencySpec {
1672                    library: Some("ssl".into()),
1673                    header: Some("openssl/ssl.h".into()),
1674                }
1675            );
1676        }
1677        let per_platform = rockspec.local.external_dependencies.per_platform;
1678        assert_eq!(
1679            *per_platform
1680                .get(&PlatformIdentifier::Windows)
1681                .and_then(|it| it.get("OPENSSL"))
1682                .unwrap(),
1683            ExternalDependencySpec {
1684                library: Some("libeay32".into()),
1685                header: Some("openssl/ssl.h".into()),
1686            }
1687        );
1688    }
1689
1690    #[tokio::test]
1691    pub async fn remote_lua_rockspec_from_package_and_source_spec() {
1692        let package_req = "foo@1.0.5".parse().unwrap();
1693        let source = GitSource {
1694            url: "https://hub.com/example-project.git".parse().unwrap(),
1695            checkout_ref: Some("1.0.5".into()),
1696        };
1697        let source_spec = RockSourceSpec::Git(source);
1698        let rockspec =
1699            RemoteLuaRockspec::from_package_and_source_spec(package_req, source_spec.clone());
1700        let generated_rockspec_str = rockspec.local.raw_content;
1701        let rockspec2 = RemoteLuaRockspec::new(&generated_rockspec_str).unwrap();
1702        assert_eq!(rockspec2.local.package, "foo".into());
1703        assert_eq!(rockspec2.local.version, "1.0.5".parse().unwrap());
1704        assert_eq!(rockspec2.local.source, PerPlatform::new(source_spec.into()));
1705    }
1706}