use crate::{DeclaredDep, DepKind, ManifestError, ManifestParser, ParsedManifest};
pub struct RockspecParser;
impl ManifestParser for RockspecParser {
fn filename(&self) -> &'static str {
"*.rockspec"
}
fn parse(&self, content: &str) -> Result<ParsedManifest, ManifestError> {
let mut name = None;
let mut version = None;
let mut deps = Vec::new();
let mut in_deps = false;
for line in content.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with("--") {
continue;
}
if trimmed.starts_with("package")
&& trimmed.contains('=')
&& name.is_none()
&& let Some(v) = extract_lua_string(trimmed)
{
name = Some(v);
continue;
}
if trimmed.starts_with("version")
&& trimmed.contains('=')
&& version.is_none()
&& let Some(v) = extract_lua_string(trimmed)
{
version = Some(v);
continue;
}
if trimmed.starts_with("dependencies") && trimmed.contains('{') {
in_deps = true;
extract_dep_strings(trimmed, &mut deps);
if trimmed.contains('}') {
in_deps = false;
}
continue;
}
if in_deps {
extract_dep_strings(trimmed, &mut deps);
if trimmed.contains('}') {
in_deps = false;
}
}
}
Ok(ParsedManifest {
ecosystem: "luarocks",
name,
version,
dependencies: deps,
})
}
}
fn extract_lua_string(line: &str) -> Option<String> {
let start = line.find('"')? + 1;
let end = line[start..].find('"')?;
Some(line[start..start + end].to_string())
}
fn extract_dep_strings(line: &str, out: &mut Vec<DeclaredDep>) {
let mut s = line;
while let Some(q_start) = s.find('"') {
s = &s[q_start + 1..];
if let Some(q_end) = s.find('"') {
let spec = s[..q_end].trim();
if let Some(dep) = parse_rockspec_spec(spec) {
out.push(dep);
}
s = &s[q_end + 1..];
} else {
break;
}
}
}
fn parse_rockspec_spec(spec: &str) -> Option<DeclaredDep> {
let spec = spec.trim();
if spec.is_empty() {
return None;
}
const OPS: &[&str] = &[">=", "<=", "!=", ">", "<", "==", "~>"];
for op in OPS {
if let Some(idx) = spec.find(op) {
let name = spec[..idx].trim().to_string();
if name.is_empty() || name == "lua" {
return None; }
let version_req = spec[idx..].trim().to_string();
return Some(DeclaredDep {
name,
version_req: Some(version_req),
kind: DepKind::Normal,
});
}
}
if spec == "lua" {
return None;
}
Some(DeclaredDep {
name: spec.to_string(),
version_req: None,
kind: DepKind::Normal,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ManifestParser;
#[test]
fn test_parse_rockspec() {
let content = r#"package = "mypkg"
version = "1.0-1"
source = { url = "https://example.com/mypkg-1.0.tar.gz" }
description = { summary = "My package" }
dependencies = {
"lua >= 5.1",
"luasocket >= 3.0",
"dkjson ~> 2.5",
"argparse"
}
build = { type = "builtin" }
"#;
let m = RockspecParser.parse(content).unwrap();
assert_eq!(m.ecosystem, "luarocks");
assert_eq!(m.name.as_deref(), Some("mypkg"));
assert_eq!(m.version.as_deref(), Some("1.0-1"));
assert!(!m.dependencies.iter().any(|d| d.name == "lua"));
let socket = m
.dependencies
.iter()
.find(|d| d.name == "luasocket")
.unwrap();
assert_eq!(socket.version_req.as_deref(), Some(">= 3.0"));
let argparse = m
.dependencies
.iter()
.find(|d| d.name == "argparse")
.unwrap();
assert!(argparse.version_req.is_none());
}
}