cargo-gra 0.6.2

Cargo subcommand for gtk-rust-app.
Documentation
// SPDX-License-Identifier: GPL-3.0-or-later

use std::{collections::HashMap, fs::File, io::Read, path::Path};

use serde::Deserialize;
use toml::Value;

#[derive(Debug, Clone)]
pub struct ProjectDescriptor {
    pub package: PackageDescriptor,
    pub app: AppDescriptor,
    pub settings: Option<HashMap<String, Value>>,
    pub actions: Option<HashMap<String, ActionDescriptor>>,
}

#[derive(Debug, Deserialize, Clone)]
#[serde(rename(serialize = "component"))]
pub struct CargoToml {
    pub package: PackageDescriptor,
}

#[derive(Debug, Deserialize, Clone)]
#[serde(rename(serialize = "component"))]
pub struct AppToml {
    pub app: AppDescriptor,
    pub settings: Option<HashMap<String, Value>>,
    pub actions: Option<HashMap<String, ActionDescriptor>>,
}

#[derive(Debug, Deserialize, Clone)]
pub struct PackageDescriptor {
    pub name: String,
    pub version: String,
    pub authors: Option<Vec<String>>,
    pub homepage: Option<String>,
    pub license: Option<String>,
    pub repository: Option<String>,
}

#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct AppDescriptor {
    // appdata
    pub id: String,
    pub name: Option<String>,
    pub generic_name: Option<String>,
    pub summary: String,
    pub description: String,
    pub categories: Vec<String>,
    pub metadata_license: String,
    pub screenshots: Option<Vec<Screenshot>>,
    pub releases: Option<Vec<Release>>,
    pub content_rating: Option<Vec<ContentRating>>,
    pub requires: Vec<Recommend>,
    pub recommends: Vec<Recommend>,
    pub permissions: Vec<String>,
    pub mimetype: Option<String>,

    // flatpak manifest
    pub flatpak_runtime_version: Option<String>,
    pub flatpak_modules: Option<Vec<String>>,

    // misc
    pub resources: Option<String>,
}

#[derive(Debug, Deserialize, Clone)]
pub struct Release {
    pub version: String,
    pub date: String,
    pub description: String,
}

#[derive(Debug, Deserialize, Clone)]
pub struct Screenshot {
    #[serde(rename = "type")]
    pub type_: Option<String>,
    pub url: String,
}

#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct ContentRating {
    pub id: String,
    pub value: String,
}

#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub enum Recommend {
    Display(String),
    DisplayLength(String),
    Control(String),
}

#[derive(Debug, Deserialize, Clone)]
pub struct ActionDescriptor {
    pub state: Option<String>,
    #[serde(rename = "type")]
    pub type_: Option<String>,
    pub accelerators: Option<Vec<String>>,
}

pub fn parse_project_descriptor(
    cargo_toml_path: &Path,
    app_toml_path: &Path,
) -> std::io::Result<ProjectDescriptor> {
    let mut cargo_file = match File::open(cargo_toml_path) {
        Ok(f) => f,
        Err(e) => {
            eprintln!("[gra] Could not read {:?}: {}", cargo_toml_path, e);
            return Err(e);
        }
    };
    let mut app_file = match File::open(app_toml_path) {
        Ok(f) => f,
        Err(e) => {
            eprintln!("[gra] Could not read {:?}: {}", app_toml_path, e);
            return Err(e);
        }
    };
    let mut cargo_toml_content = String::new();
    cargo_file.read_to_string(&mut cargo_toml_content)?;
    let mut app_toml_content = String::new();
    app_file.read_to_string(&mut app_toml_content)?;
    match parse_project_descriptor_str(&cargo_toml_content, &app_toml_content) {
        Err(e) => {
            eprintln!("[gra] Error: {}.\n{}", e, cargo_toml_content);
            Err(e)
        }
        Ok(p) => Ok(p),
    }
}

pub fn parse_project_descriptor_str(
    cargo_toml: &str,
    app_toml: &str,
) -> std::io::Result<ProjectDescriptor> {
    let cargo_toml: CargoToml = toml::from_str(cargo_toml)
        .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, format!("{}.", e)))?;
    let app_toml: AppToml = toml::from_str(app_toml)
        .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, format!("{}.", e)))?;
    Ok(ProjectDescriptor {
        package: cargo_toml.package,
        app: app_toml.app,
        settings: app_toml.settings,
        actions: app_toml.actions,
    })
}