Skip to main content

tar_install/
recipe.rs

1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::fs;
4use std::path::Path;
5
6#[derive(Debug, Clone, Default, Serialize, Deserialize)]
7pub struct RecipeDesktop {
8    pub name: Option<String>,
9    pub generic_name: Option<String>,
10    pub categories: Option<Vec<String>>,
11    pub terminal: Option<bool>,
12    pub comment: Option<String>,
13}
14
15#[derive(Debug, Clone, Default, Serialize, Deserialize)]
16pub struct AppRecipe {
17    pub id: Option<String>,
18    pub name: Option<String>,
19    pub version: Option<String>,
20    pub probe_version: Option<bool>,
21    pub exec: Option<String>,
22    pub command: Option<String>,
23    pub icon: Option<String>,
24    pub desktop: Option<RecipeDesktop>,
25    pub args: Option<Vec<String>>,
26    pub env: Option<std::collections::BTreeMap<String, String>>,
27    pub working_dir: Option<String>,
28}
29
30#[derive(Debug, Clone, Default)]
31pub struct InstallInput {
32    pub id: Option<String>,
33    pub name: Option<String>,
34    pub version: Option<String>,
35    pub probe_version: Option<bool>,
36    pub exec: Option<String>,
37    pub command: Option<String>,
38    pub icon: Option<String>,
39    pub recipe: Option<AppRecipe>,
40    pub force: bool,
41    pub interactive_config: bool,
42}
43
44pub fn load_recipe(path: &Path) -> Result<AppRecipe> {
45    let text = fs::read_to_string(path).with_context(|| format!("failed to read recipe: {}", path.display()))?;
46    let recipe: AppRecipe = serde_yaml::from_str(&text)
47        .with_context(|| format!("failed to parse YAML recipe: {}", path.display()))?;
48    Ok(recipe)
49}
50
51pub fn sanitize_id(value: &str) -> String {
52    let lowered = value.trim().to_ascii_lowercase();
53    let mut out = String::new();
54    let mut last_dash = false;
55    for ch in lowered.chars() {
56        if ch.is_ascii_alphanumeric() || ch == '.' || ch == '_' {
57            out.push(ch);
58            last_dash = false;
59        } else if !last_dash {
60            out.push('-');
61            last_dash = true;
62        }
63    }
64    out.trim_matches('-').to_string()
65}
66
67pub fn sanitize_command(value: &str) -> String {
68    let lowered = value.trim().to_ascii_lowercase();
69    let mut out = String::new();
70    let mut last_dash = false;
71    for ch in lowered.chars() {
72        if ch.is_ascii_alphanumeric() || ch == '_' || ch == '-' {
73            out.push(ch);
74            last_dash = false;
75        } else if !last_dash {
76            out.push('-');
77            last_dash = true;
78        }
79    }
80    out.trim_matches('-').to_string()
81}
82
83pub fn display_name_from_id(id: &str) -> String {
84    id.split(['-', '_', '.'])
85        .filter(|s| !s.is_empty())
86        .map(|s| {
87            let mut chars = s.chars();
88            match chars.next() {
89                Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
90                None => String::new(),
91            }
92        })
93        .collect::<Vec<_>>()
94        .join(" ")
95}