130 lines
4.3 KiB
Rust
130 lines
4.3 KiB
Rust
//! An app's manifest: who it is and what it asks for.
|
|
|
|
use crate::Error;
|
|
use crate::capability::{Capabilities, Capability};
|
|
use crate::fields::{Accent, AppId, Command, FileName, Text, Version};
|
|
use crate::toml::{self, Entry, Value};
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub struct Manifest {
|
|
pub id: AppId,
|
|
pub name: Text<64>,
|
|
pub version: Version,
|
|
pub binary: FileName,
|
|
/// What the shell runs it as.
|
|
pub command: Command,
|
|
pub author: Option<Text<64>>,
|
|
pub icon: Option<FileName>,
|
|
/// Denied = not installed.
|
|
pub required: Capabilities,
|
|
/// The owner decides each.
|
|
pub optional: Capabilities,
|
|
pub accent: Option<Accent>,
|
|
}
|
|
|
|
/// Fields as they are read, before the required ones are known present.
|
|
#[derive(Default)]
|
|
struct Draft {
|
|
id: Option<AppId>,
|
|
name: Option<Text<64>>,
|
|
version: Option<Version>,
|
|
binary: Option<FileName>,
|
|
command: Option<Command>,
|
|
author: Option<Text<64>>,
|
|
icon: Option<FileName>,
|
|
required: Option<Capabilities>,
|
|
optional: Option<Capabilities>,
|
|
accent: Option<Accent>,
|
|
}
|
|
|
|
/// Sets `slot` once; a second value for the same key is an error.
|
|
fn set<T>(slot: &mut Option<T>, value: Option<T>, line: usize) -> Result<(), Error> {
|
|
if slot.is_some() {
|
|
return Err(Error::Duplicate { line });
|
|
}
|
|
*slot = Some(value.ok_or(Error::Invalid { line })?);
|
|
Ok(())
|
|
}
|
|
|
|
fn text<'a>(value: Value<'a>) -> Option<&'a str> {
|
|
match value {
|
|
Value::Str(s) => Some(s),
|
|
Value::List(_) => None,
|
|
}
|
|
}
|
|
|
|
/// A list of capability names, each known and listed once.
|
|
pub(crate) fn capabilities(value: Value, line: usize) -> Result<Capabilities, Error> {
|
|
let Value::List(list) = value else {
|
|
return Err(Error::Invalid { line });
|
|
};
|
|
let mut set = Capabilities::NONE;
|
|
for name in list.iter() {
|
|
let c = Capability::from_name(name).ok_or(Error::Unknown { line })?;
|
|
if set.contains(c) {
|
|
return Err(Error::Duplicate { line });
|
|
}
|
|
set = set.with(c);
|
|
}
|
|
Ok(set)
|
|
}
|
|
|
|
impl Draft {
|
|
fn take(&mut self, e: Entry) -> Result<(), Error> {
|
|
let l = e.line;
|
|
let s = text(e.value);
|
|
match (e.table, e.key) {
|
|
("app", "id") => set(&mut self.id, s.and_then(AppId::new), l),
|
|
("app", "name") => set(&mut self.name, s.and_then(Text::new), l),
|
|
("app", "version") => set(&mut self.version, s.and_then(Version::parse), l),
|
|
("app", "binary") => set(&mut self.binary, s.and_then(FileName::new), l),
|
|
("app", "command") => set(&mut self.command, s.and_then(Command::new), l),
|
|
("app", "author") => set(&mut self.author, s.and_then(Text::new), l),
|
|
("app", "icon") => set(&mut self.icon, s.and_then(FileName::new), l),
|
|
("capabilities", "required") => {
|
|
set(&mut self.required, Some(capabilities(e.value, l)?), l)
|
|
}
|
|
("capabilities", "optional") => {
|
|
set(&mut self.optional, Some(capabilities(e.value, l)?), l)
|
|
}
|
|
("appearance", "accent") => set(&mut self.accent, s.and_then(Accent::parse), l),
|
|
_ => Err(Error::Unknown { line: l }),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Manifest {
|
|
pub fn parse(text: &str) -> Result<Self, Error> {
|
|
let mut d = Draft::default();
|
|
toml::parse(text, |e| d.take(e))?;
|
|
let binary = d.binary.ok_or(Error::Missing("binary"))?;
|
|
let command = match d.command {
|
|
Some(c) => c,
|
|
// A binary name is a command name when it fits the stricter rules.
|
|
None => Command::new(binary.as_str()).ok_or(Error::Missing("command"))?,
|
|
};
|
|
let required = d.required.unwrap_or_default();
|
|
let optional = d.optional.unwrap_or_default();
|
|
if !required.intersect(optional).is_empty() {
|
|
return Err(Error::Conflict);
|
|
}
|
|
Ok(Self {
|
|
id: d.id.ok_or(Error::Missing("id"))?,
|
|
name: d.name.ok_or(Error::Missing("name"))?,
|
|
version: d.version.ok_or(Error::Missing("version"))?,
|
|
binary,
|
|
command,
|
|
author: d.author,
|
|
icon: d.icon,
|
|
required,
|
|
optional,
|
|
accent: d.accent,
|
|
})
|
|
}
|
|
|
|
/// Everything it asks for.
|
|
pub fn requested(&self) -> Capabilities {
|
|
self.required.union(self.optional)
|
|
}
|
|
}
|