use crate::fsutil;
use crate::{LovelyError, Result};
use std::collections::BTreeMap;
use std::path::Path;
pub const LOCK_FILE: &str = "lovely.lock";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LockFile {
pub schema: u32,
pub runtime_channel: String,
pub love: LockedComponent,
pub emscripten: LockedComponent,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LockedComponent {
pub source: String,
pub revision: String,
pub sha256: String,
}
impl LockFile {
pub fn preview_default() -> Self {
Self {
schema: 1,
runtime_channel: "love-11-plus".to_string(),
love: LockedComponent {
source: "https://github.com/love2d/love".to_string(),
revision: "main".to_string(),
sha256: "unresolved".to_string(),
},
emscripten: LockedComponent {
source: "emsdk".to_string(),
revision: "pinned-by-runtime-manifest".to_string(),
sha256: "unresolved".to_string(),
},
}
}
pub fn load_from(path: &Path) -> Result<Self> {
let text = fsutil::read_to_string(path)?;
Self::parse(&text)
}
pub fn parse(text: &str) -> Result<Self> {
let mut values = BTreeMap::<String, String>::new();
for (index, line) in text.lines().enumerate() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let Some((key, value)) = line.split_once('=') else {
return Err(LovelyError::Lock(format!(
"line {} is not a key/value pair",
index + 1
)));
};
values.insert(key.trim().to_string(), unquote(value.trim()));
}
let schema = values
.get("schema")
.and_then(|value| value.parse::<u32>().ok())
.unwrap_or(1);
Ok(Self {
schema,
runtime_channel: take(&values, "runtime_channel")?,
love: LockedComponent {
source: take(&values, "love.source")?,
revision: take(&values, "love.revision")?,
sha256: take(&values, "love.sha256")?,
},
emscripten: LockedComponent {
source: take(&values, "emscripten.source")?,
revision: take(&values, "emscripten.revision")?,
sha256: take(&values, "emscripten.sha256")?,
},
})
}
pub fn to_text(&self) -> String {
format!(
r#"# Generated by Lovely. Commit this file for reproducible builds.
schema = {schema}
runtime_channel = "{runtime_channel}"
love.source = "{love_source}"
love.revision = "{love_revision}"
love.sha256 = "{love_sha}"
emscripten.source = "{emscripten_source}"
emscripten.revision = "{emscripten_revision}"
emscripten.sha256 = "{emscripten_sha}"
"#,
schema = self.schema,
runtime_channel = escape(&self.runtime_channel),
love_source = escape(&self.love.source),
love_revision = escape(&self.love.revision),
love_sha = escape(&self.love.sha256),
emscripten_source = escape(&self.emscripten.source),
emscripten_revision = escape(&self.emscripten.revision),
emscripten_sha = escape(&self.emscripten.sha256),
)
}
pub fn has_unresolved_checksums(&self) -> bool {
[&self.love, &self.emscripten]
.iter()
.any(|component| component.sha256 == "unresolved")
}
}
fn take(values: &BTreeMap<String, String>, key: &str) -> Result<String> {
values
.get(key)
.cloned()
.ok_or_else(|| LovelyError::Lock(format!("missing {key}")))
}
fn unquote(value: &str) -> String {
if value.starts_with('"') && value.ends_with('"') && value.len() >= 2 {
value[1..value.len() - 1].replace("\\\"", "\"")
} else {
value.to_string()
}
}
fn escape(input: &str) -> String {
input.replace('\\', "\\\\").replace('"', "\\\"")
}