#![feature(box_syntax)]
#![allow(clippy::large_enum_variant)]
extern crate handlebars;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value;
use handlebars::Handlebars;
use handlebars::RenderError;
const TEMPLATE: &str = r#"
App(
# Automatically generated by `fam` crate.
appid="{{{appid}}}",
apptype={{{apptype}}},
{{#if name}}name="{{{name}}}", {{/if}}
{{~#if stack_size}}stack_size={{{stack_size}}}, {{/if}}
{{~#if icon}}icon="{{{icon}}}", {{/if}}
{{~#if entry_point}}entry_point="{{{entry_point}}}", {{/if}}
{{~#if order}}order={{{order}}}, {{/if}}
{{~#if sources.0 }}sources=[{{#each sources}}"{{{this}}}"{{#if @last}}{{else}}, {{/if}}{{/each}}], {{/if}}
{{~#if flags.0 }}flags=[{{#each flags}}"{{{this}}}"{{#if @last}}{{else}}, {{/if}}{{/each}}], {{/if}}
{{~#if cdefines.0 }}cdefines=[{{#each cdefines}}"{{{this}}}"{{#if @last}}{{else}}, {{/if}}{{/each}}], {{/if}}
{{~#if requires.0 }}requires=[{{#each requires}}"{{{this}}}"{{#if @last}}{{else}}, {{/if}}{{/each}}], {{/if}}
{{~#if conflicts.0 }}conflicts=[{{#each conflicts}}"{{{this}}}"{{#if @last}}{{else}}, {{/if}}{{/each}}], {{/if}}
{{~#if provides.0 }}provides=[{{#each provides}}"{{{this}}}"{{#if @last}}{{else}}, {{/if}}{{/each}}], {{/if}}
{{~#if sdk_headers.0 }}sdk_headers=[{{#each sdk_headers}}"{{{this}}}"{{#if @last}}{{else}}, {{/if}}{{/each}}], {{/if}}
{{~#if fap_libs.0 }}fap_libs=[{{#each fap_libs}}"{{{this}}}"{{#if @last}}{{else}}, {{/if}}{{/each}}], {{/if}}
{{~#if fap_version}}fap_version={{{fap_version}}}, {{/if}}
{{~#if fap_icon}}fap_icon="{{{fap_icon}}}", {{/if}}
{{~#if fap_category}}fap_category="{{{fap_category}}}", {{/if}}
{{~#if fap_description}}fap_description="{{{fap_description}}}", {{/if}}
{{~#if fap_author}}fap_author="{{{fap_author}}}", {{/if}}
{{~#if fap_weburl}}fap_weburl="{{{fap_weburl}}}", {{/if}}
)
"#;
#[derive(Serialize, Deserialize, Clone)]
pub enum Manifest {
Json(Value),
#[cfg(feature = "toml")]
Toml(toml::Value),
Struct {
name: Option<String>,
appid: String,
apptype: String,
stack_size: Option<usize>,
icon: Option<String>,
entry_point: Option<String>,
#[serde(default)]
flags: Vec<String>,
#[serde(default)]
cdefines: Vec<String>,
#[serde(default)]
requires: Vec<String>,
#[serde(default)]
conflicts: Vec<String>,
#[serde(default)]
provides: Vec<String>,
order: Option<isize>,
#[serde(default)]
sdk_headers: Vec<String>,
#[serde(default)]
sources: Vec<String>,
fap_version: Option<(String, String)>,
fap_icon: Option<String>,
#[serde(default)]
fap_libs: Vec<String>,
fap_category: Option<String>,
fap_description: Option<String>,
fap_author: Option<String>,
fap_weburl: Option<String>,
},
}
impl Manifest {
pub fn try_to_string(&self) -> Result<String, RenderError> {
match self {
#[cfg(feature = "toml")]
Manifest::Toml(toml) => render_raw_toml(toml),
Manifest::Json(json) => render_raw_json(json),
Manifest::Struct { name,
appid,
apptype,
stack_size,
icon,
entry_point,
flags,
cdefines,
requires,
conflicts,
provides,
order,
sdk_headers,
sources,
fap_version,
fap_icon,
fap_libs,
fap_category,
fap_description,
fap_author,
fap_weburl, } => {
let json = serde_json::json!({
"name": name,
"appid": appid,
"apptype": apptype,
"stack_size": stack_size,
"icon": icon,
"entry_point": entry_point,
"flags": flags,
"cdefines": cdefines,
"requires": requires,
"conflicts": conflicts,
"provides": provides,
"order": order,
"sdk_headers": sdk_headers,
"sources": sources,
"fap_version": fap_version,
"fap_icon": fap_icon,
"fap_libs": fap_libs,
"fap_category": fap_category,
"fap_description": fap_description,
"fap_author": fap_author,
"fap_weburl": fap_weburl,
});
render_raw_json(&json)
},
}
}
}
macro_rules! field {
($key:ident, str) => {
pub fn $key(&self) -> Option<&str> {
match self {
Self::Struct { $key, .. } => Some($key.as_str()),
Self::Json(value) => {
value.as_object()
.map(|o| o.get(stringify!($key)).map(|v| v.as_str()).flatten())
.flatten()
},
#[cfg(feature = "toml")]
Self::Toml(value) => {
value.as_table()
.map(|o| o.get(stringify!($key)).map(|v| v.as_str()).flatten())
.flatten()
},
}
}
};
($key:ident, opt str) => {
pub fn $key(&self) -> Option<&str> {
match self {
Self::Struct { $key, .. } => Some($key.as_deref()).flatten(),
Self::Json(value) => {
value.as_object()
.map(|o| o.get(stringify!($key)).map(|v| v.as_str()).flatten())
.flatten()
},
#[cfg(feature = "toml")]
Self::Toml(value) => {
value.as_table()
.map(|o| o.get(stringify!($key)).map(|v| v.as_str()).flatten())
.flatten()
},
}
}
};
($key:ident, iter String) => {
pub fn $key(&self) -> Box<dyn Iterator<Item = String> + '_> {
match self {
Self::Struct { $key, .. } => box $key.into_iter().cloned(),
Self::Json(value) => {
box value.as_object()
.map(|o| {
o.get(stringify!($key))
.map(|v| serde_json::from_value::<Vec<String>>(v.to_owned()).ok())
.flatten()
})
.flatten()
.map(|arr| arr.into_iter())
.unwrap_or(Vec::with_capacity(0).into_iter().into())
},
#[cfg(feature = "toml")]
Self::Toml(value) => todo!(), }
}
};
}
impl Manifest {
field!(appid, str);
field!(apptype, str);
field!(name, opt str);
field!(icon, opt str);
field!(entry_point, opt str);
field!(fap_icon, opt str);
field!(fap_category, opt str);
field!(fap_description, opt str);
field!(fap_author, opt str);
field!(fap_weburl, opt str);
field!(flags, iter String);
field!(cdefines, iter String);
field!(requires, iter String);
field!(conflicts, iter String);
field!(provides, iter String);
field!(sdk_headers, iter String);
field!(sources, iter String);
field!(fap_libs, iter String);
pub fn fap_version(&self) -> Option<(String, String)> {
match self {
Self::Struct { fap_version, .. } => fap_version.to_owned(),
Self::Json(value) => {
value.as_object().and_then(|o| {
o.get("fap_version")
.and_then(|v| serde_json::from_value::<(String, String)>(v.to_owned()).ok())
})
},
#[cfg(feature = "toml")]
Self::Toml(value) => {
value.as_table()
.map(|o| {
o.get("fap_version")
.map(|v| {
v.as_array()
.map(|arr| {
arr.get(0)
.map(|a| {
arr.get(1)
.map(|b| (a.as_str().map(ToOwned::to_owned), b.as_str().map(ToOwned::to_owned)))
.filter(|(a, b)| a.is_some() && b.is_some())
.map(|(a, b)| (a.unwrap(), b.unwrap()))
})
.flatten()
})
.flatten()
})
.flatten()
})
.flatten()
},
}
}
}
impl From<Value> for Manifest {
fn from(metadata: Value) -> Self { Self::Json(metadata) }
}
pub fn render_raw_json(json: &Value) -> Result<String, RenderError> {
let reg = Handlebars::new();
reg.render_template(TEMPLATE, json)
}
#[cfg(feature = "toml")]
impl From<toml::Value> for Manifest {
fn from(metadata: toml::Value) -> Self { Self::Toml(metadata) }
}
#[cfg(feature = "toml")]
pub fn render_raw_toml(toml: &toml::Value) -> Result<String, RenderError> {
let reg = Handlebars::new();
reg.render_template(TEMPLATE, toml)
}