use anyhow::Result;
use std::path::Path;
use crate::config::Config;
pub fn write_desktop_file(config: &Config, dest: &Path) -> Result<()> {
let content = build_desktop(config);
write_text(dest, &content)
}
fn build_desktop(config: &Config) -> String {
let app = &config.app;
let mut s = String::new();
line(&mut s, "[Desktop Entry]");
kv(&mut s, "Type", "Application");
kv(&mut s, "Version", "1.0");
kv(&mut s, "Name", &de_escape(&app.name));
opt_kv(&mut s, "GenericName", app.generic_name.as_deref().map(de_escape).as_deref());
opt_kv(&mut s, "Comment", app.comment.as_deref().map(de_escape).as_deref());
kv(&mut s, "Exec", &de_escape(&app.exec));
kv(&mut s, "Icon", &app.id);
kv(&mut s, "Terminal", "false");
if !app.categories.is_empty() {
kv(&mut s, "Categories", &format!("{};", app.categories.join(";")));
}
if !app.keywords.is_empty() {
kv(&mut s, "Keywords", &format!("{};", app.keywords.join(";")));
}
kv(&mut s, "StartupNotify", "true");
opt_kv(&mut s, "StartupWMClass", app.startup_wm_class.as_deref());
if !app.mime_types.is_empty() {
kv(&mut s, "MimeType", &format!("{};", app.mime_types.join(";")));
}
s
}
pub fn write_metainfo(config: &Config, dest: &Path) -> Result<()> {
let content = build_metainfo(config);
write_text(dest, &content)
}
fn build_metainfo(config: &Config) -> String {
let app = &config.app;
let mut s = String::new();
line(&mut s, r#"<?xml version="1.0" encoding="UTF-8"?>"#);
line(&mut s, r#"<component type="desktop-application">"#);
xml_elem(&mut s, 1, "id", &app.id);
xml_elem(&mut s, 1, "name", &app.name);
if let Some(summary) = &app.comment {
xml_elem(&mut s, 1, "summary", summary);
}
if let Some(ml) = &app.metadata_license {
xml_elem(&mut s, 1, "metadata_license", ml);
}
if let Some(pl) = &app.license {
xml_elem(&mut s, 1, "project_license", pl);
}
s.push_str(&format!(
" <launchable type=\"desktop-id\">{}.desktop</launchable>\n",
xml_esc(&app.id)
));
s.push_str(&format!(
" <icon type=\"stock\">{}</icon>\n",
xml_esc(&app.id)
));
if let Some(dev) = &app.developer_name {
xml_elem(&mut s, 1, "developer_name", dev);
}
if let Some(url) = &app.homepage_url {
s.push_str(&format!(
" <url type=\"homepage\">{}</url>\n",
xml_esc(url)
));
}
if let Some(desc) = &app.description {
line(&mut s, " <description>");
s.push_str(&format!(" <p>{}</p>\n", xml_esc(desc)));
line(&mut s, " </description>");
}
if !app.mime_types.is_empty() {
line(&mut s, " <provides>");
for mt in &app.mime_types {
xml_elem(&mut s, 2, "mediatype", mt);
}
line(&mut s, " </provides>");
}
if !app.categories.is_empty() {
line(&mut s, " <categories>");
for cat in &app.categories {
xml_elem(&mut s, 2, "category", cat);
}
line(&mut s, " </categories>");
}
if !app.keywords.is_empty() {
line(&mut s, " <keywords>");
for kw in &app.keywords {
xml_elem(&mut s, 2, "keyword", kw);
}
line(&mut s, " </keywords>");
}
line(&mut s, r#" <content_rating type="oars-1.1" />"#);
line(&mut s, "</component>");
s
}
fn write_text(dest: &Path, content: &str) -> Result<()> {
if let Some(parent) = dest.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(dest, content)
.map_err(|e| anyhow::anyhow!("cannot write '{}': {}", dest.display(), e))
}
fn line(s: &mut String, text: &str) {
s.push_str(text);
s.push('\n');
}
fn kv(s: &mut String, key: &str, value: &str) {
s.push_str(key);
s.push('=');
s.push_str(value);
s.push('\n');
}
fn opt_kv(s: &mut String, key: &str, value: Option<&str>) {
if let Some(v) = value {
kv(s, key, v);
}
}
fn xml_elem(s: &mut String, depth: usize, tag: &str, content: &str) {
let indent = " ".repeat(depth);
s.push_str(&format!("{}<{}>{}</{}>\n", indent, tag, xml_esc(content), tag));
}
fn de_escape(s: &str) -> String {
s.replace('\\', "\\\\")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t")
}
fn xml_esc(s: &str) -> String {
s.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}