use std::{collections::HashMap, path::PathBuf};
use serde::Deserialize;
use serde_json::Value as JsonValue;
use url::Url;
#[derive(PartialEq, Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum WindowUrl {
External(Url),
App(PathBuf),
}
impl Default for WindowUrl {
fn default() -> Self {
Self::App("index.html".into())
}
}
#[derive(PartialEq, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct WindowConfig {
#[serde(default = "default_window_label")]
pub label: String,
#[serde(default)]
pub url: WindowUrl,
pub x: Option<f64>,
pub y: Option<f64>,
#[serde(default = "default_width")]
pub width: f64,
#[serde(default = "default_height")]
pub height: f64,
pub min_width: Option<f64>,
pub min_height: Option<f64>,
pub max_width: Option<f64>,
pub max_height: Option<f64>,
#[serde(default = "default_resizable")]
pub resizable: bool,
#[serde(default = "default_title")]
pub title: String,
#[serde(default)]
pub fullscreen: bool,
#[serde(default)]
pub transparent: bool,
#[serde(default)]
pub maximized: bool,
#[serde(default = "default_visible")]
pub visible: bool,
#[serde(default = "default_decorations")]
pub decorations: bool,
#[serde(default)]
pub always_on_top: bool,
}
fn default_window_label() -> String {
"main".to_string()
}
fn default_width() -> f64 {
800f64
}
fn default_height() -> f64 {
600f64
}
fn default_resizable() -> bool {
true
}
fn default_visible() -> bool {
true
}
fn default_decorations() -> bool {
true
}
fn default_title() -> String {
"Tauri App".to_string()
}
impl Default for WindowConfig {
fn default() -> Self {
Self {
label: default_window_label(),
url: WindowUrl::default(),
x: None,
y: None,
width: default_width(),
height: default_height(),
min_width: None,
min_height: None,
max_width: None,
max_height: None,
resizable: default_resizable(),
title: default_title(),
fullscreen: false,
transparent: false,
maximized: false,
visible: default_visible(),
decorations: default_decorations(),
always_on_top: false,
}
}
}
#[derive(PartialEq, Deserialize, Debug, Clone)]
#[serde(tag = "updater", rename_all = "camelCase")]
pub struct UpdaterConfig {
#[serde(default)]
pub active: bool,
#[serde(default = "default_updater_dialog")]
pub dialog: bool,
#[serde(default)]
pub endpoints: Option<Vec<String>>,
#[serde(default)]
pub pubkey: Option<String>,
}
fn default_updater_dialog() -> bool {
true
}
impl Default for UpdaterConfig {
fn default() -> Self {
Self {
active: false,
dialog: true,
endpoints: None,
pubkey: None,
}
}
}
#[derive(PartialEq, Deserialize, Debug, Default, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CliArg {
pub short: Option<char>,
pub name: String,
pub description: Option<String>,
pub long_description: Option<String>,
pub takes_value: Option<bool>,
pub multiple: Option<bool>,
pub multiple_occurrences: Option<bool>,
pub number_of_values: Option<u64>,
pub possible_values: Option<Vec<String>>,
pub min_values: Option<u64>,
pub max_values: Option<u64>,
pub required: Option<bool>,
pub required_unless_present: Option<String>,
pub required_unless_present_all: Option<Vec<String>>,
pub required_unless_present_any: Option<Vec<String>>,
pub conflicts_with: Option<String>,
pub conflicts_with_all: Option<Vec<String>>,
pub requires: Option<String>,
pub requires_all: Option<Vec<String>>,
pub requires_if: Option<Vec<String>>,
pub required_if_eq: Option<Vec<String>>,
pub require_equals: Option<bool>,
pub index: Option<u64>,
}
#[derive(PartialEq, Deserialize, Debug, Clone)]
#[serde(tag = "cli", rename_all = "camelCase")]
#[allow(missing_docs)]
pub struct CliConfig {
pub description: Option<String>,
pub long_description: Option<String>,
pub before_help: Option<String>,
pub after_help: Option<String>,
pub args: Option<Vec<CliArg>>,
pub subcommands: Option<HashMap<String, CliConfig>>,
}
impl CliConfig {
pub fn args(&self) -> Option<&Vec<CliArg>> {
self.args.as_ref()
}
pub fn subcommands(&self) -> Option<&HashMap<String, CliConfig>> {
self.subcommands.as_ref()
}
pub fn description(&self) -> Option<&String> {
self.description.as_ref()
}
pub fn long_description(&self) -> Option<&String> {
self.description.as_ref()
}
pub fn before_help(&self) -> Option<&String> {
self.before_help.as_ref()
}
pub fn after_help(&self) -> Option<&String> {
self.after_help.as_ref()
}
}
#[derive(PartialEq, Deserialize, Debug)]
#[serde(tag = "bundle", rename_all = "camelCase")]
pub struct BundleConfig {
pub identifier: String,
}
impl Default for BundleConfig {
fn default() -> Self {
Self {
identifier: String::from(""),
}
}
}
fn default_window_config() -> Vec<WindowConfig> {
vec![Default::default()]
}
#[derive(PartialEq, Deserialize, Debug)]
#[serde(tag = "tauri", rename_all = "camelCase")]
pub struct TauriConfig {
#[serde(default = "default_window_config")]
pub windows: Vec<WindowConfig>,
#[serde(default)]
pub cli: Option<CliConfig>,
#[serde(default)]
pub bundle: BundleConfig,
#[serde(default)]
pub updater: UpdaterConfig,
}
impl Default for TauriConfig {
fn default() -> Self {
Self {
windows: default_window_config(),
cli: None,
bundle: BundleConfig::default(),
updater: UpdaterConfig::default(),
}
}
}
#[derive(PartialEq, Deserialize, Debug)]
#[serde(tag = "build", rename_all = "camelCase")]
pub struct BuildConfig {
#[serde(default = "default_dev_path")]
pub dev_path: String,
#[serde(default = "default_dist_path")]
pub dist_dir: String,
#[serde(default)]
pub with_global_tauri: bool,
}
fn default_dev_path() -> String {
"http://localhost:8080".to_string()
}
fn default_dist_path() -> String {
"../dist".to_string()
}
impl Default for BuildConfig {
fn default() -> Self {
Self {
dev_path: default_dev_path(),
dist_dir: default_dist_path(),
with_global_tauri: false,
}
}
}
#[derive(Debug, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Config {
#[serde(default)]
pub tauri: TauriConfig,
#[serde(default)]
pub build: BuildConfig,
#[serde(default)]
pub plugins: PluginConfig,
}
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
pub struct PluginConfig(pub HashMap<String, JsonValue>);
#[cfg(feature = "build")]
mod build {
use std::convert::identity;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens, TokenStreamExt};
use super::*;
fn str_lit(s: impl AsRef<str>) -> TokenStream {
let s = s.as_ref();
quote! { #s.into() }
}
fn opt_lit(item: Option<&impl ToTokens>) -> TokenStream {
match item {
None => quote! { ::core::option::Option::None },
Some(item) => quote! { ::core::option::Option::Some(#item) },
}
}
fn opt_str_lit(item: Option<impl AsRef<str>>) -> TokenStream {
opt_lit(item.map(str_lit).as_ref())
}
fn opt_vec_str_lit(item: Option<impl IntoIterator<Item = impl AsRef<str>>>) -> TokenStream {
opt_lit(item.map(|list| vec_lit(list, str_lit)).as_ref())
}
fn vec_lit<Raw, Tokens>(
list: impl IntoIterator<Item = Raw>,
map: impl Fn(Raw) -> Tokens,
) -> TokenStream
where
Tokens: ToTokens,
{
let items = list.into_iter().map(map);
quote! { vec![#(#items),*] }
}
fn map_lit<Map, Key, Value, TokenStreamKey, TokenStreamValue, FuncKey, FuncValue>(
map_type: TokenStream,
map: Map,
map_key: FuncKey,
map_value: FuncValue,
) -> TokenStream
where
<Map as IntoIterator>::IntoIter: ExactSizeIterator,
Map: IntoIterator<Item = (Key, Value)>,
TokenStreamKey: ToTokens,
TokenStreamValue: ToTokens,
FuncKey: Fn(Key) -> TokenStreamKey,
FuncValue: Fn(Value) -> TokenStreamValue,
{
let ident = quote::format_ident!("map");
let map = map.into_iter();
if map.len() > 0 {
let items = map.map(|(key, value)| {
let key = map_key(key);
let value = map_value(value);
quote! { #ident.insert(#key, #value); }
});
quote! {{
let mut #ident = #map_type::new();
#(#items)*
#ident
}}
} else {
quote! { #map_type::new() }
}
}
fn json_value_number_lit(num: &serde_json::Number) -> TokenStream {
let prefix = quote! { ::serde_json::Value };
if num.is_u64() {
let num = num.as_u64().unwrap();
quote! { #prefix::Number(#num.into()) }
} else if num.is_i64() {
let num = num.as_i64().unwrap();
quote! { #prefix::Number(#num.into()) }
} else if num.is_f64() {
let num = num.as_f64().unwrap();
quote! { #prefix::Number(#num.into()) }
} else {
quote! { #prefix::Null }
}
}
fn json_value_lit(jv: &JsonValue) -> TokenStream {
let prefix = quote! { ::serde_json::Value };
match jv {
JsonValue::Null => quote! { #prefix::Null },
JsonValue::Bool(bool) => quote! { #prefix::Bool(#bool) },
JsonValue::Number(number) => json_value_number_lit(number),
JsonValue::String(str) => {
let s = str_lit(str);
quote! { #prefix::String(#s) }
}
JsonValue::Array(vec) => {
let items = vec.iter().map(json_value_lit);
quote! { #prefix::Array(vec![#(#items),*]) }
}
JsonValue::Object(map) => {
let map = map_lit(quote! { ::serde_json::Map }, map, str_lit, json_value_lit);
quote! { #prefix::Object(#map) }
}
}
}
macro_rules! literal_struct {
($tokens:ident, $struct:ident, $($field:ident),+) => {
$tokens.append_all(quote! {
::tauri::api::config::$struct {
$($field: #$field),+
}
});
};
}
impl ToTokens for WindowUrl {
fn to_tokens(&self, tokens: &mut TokenStream) {
let prefix = quote! { ::tauri::api::config::WindowUrl };
tokens.append_all(match self {
Self::App(path) => {
let path = path.to_string_lossy().to_string();
quote! { #prefix::App(::std::path::PathBuf::from(#path)) }
}
Self::External(url) => {
let url = url.as_str();
quote! { #prefix::External(#url.parse().unwrap()) }
}
})
}
}
impl ToTokens for WindowConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let label = str_lit(&self.label);
let url = &self.url;
let x = opt_lit(self.x.as_ref());
let y = opt_lit(self.y.as_ref());
let width = self.width;
let height = self.height;
let min_width = opt_lit(self.min_width.as_ref());
let min_height = opt_lit(self.min_height.as_ref());
let max_width = opt_lit(self.max_width.as_ref());
let max_height = opt_lit(self.min_height.as_ref());
let resizable = self.resizable;
let title = str_lit(&self.title);
let fullscreen = self.fullscreen;
let transparent = self.transparent;
let maximized = self.maximized;
let visible = self.visible;
let decorations = self.decorations;
let always_on_top = self.always_on_top;
literal_struct!(
tokens,
WindowConfig,
label,
url,
x,
y,
width,
height,
min_width,
min_height,
max_width,
max_height,
resizable,
title,
fullscreen,
transparent,
maximized,
visible,
decorations,
always_on_top
);
}
}
impl ToTokens for CliArg {
fn to_tokens(&self, tokens: &mut TokenStream) {
let short = opt_lit(self.short.as_ref());
let name = str_lit(&self.name);
let description = opt_str_lit(self.description.as_ref());
let long_description = opt_str_lit(self.long_description.as_ref());
let takes_value = opt_lit(self.takes_value.as_ref());
let multiple = opt_lit(self.multiple.as_ref());
let multiple_occurrences = opt_lit(self.multiple_occurrences.as_ref());
let number_of_values = opt_lit(self.number_of_values.as_ref());
let possible_values = opt_vec_str_lit(self.possible_values.as_ref());
let min_values = opt_lit(self.min_values.as_ref());
let max_values = opt_lit(self.max_values.as_ref());
let required = opt_lit(self.required.as_ref());
let required_unless_present = opt_str_lit(self.required_unless_present.as_ref());
let required_unless_present_all = opt_vec_str_lit(self.required_unless_present_all.as_ref());
let required_unless_present_any = opt_vec_str_lit(self.required_unless_present_any.as_ref());
let conflicts_with = opt_str_lit(self.conflicts_with.as_ref());
let conflicts_with_all = opt_vec_str_lit(self.conflicts_with_all.as_ref());
let requires = opt_str_lit(self.requires.as_ref());
let requires_all = opt_vec_str_lit(self.requires_all.as_ref());
let requires_if = opt_vec_str_lit(self.requires_if.as_ref());
let required_if_eq = opt_vec_str_lit(self.required_if_eq.as_ref());
let require_equals = opt_lit(self.require_equals.as_ref());
let index = opt_lit(self.index.as_ref());
literal_struct!(
tokens,
CliArg,
short,
name,
description,
long_description,
takes_value,
multiple,
multiple_occurrences,
number_of_values,
possible_values,
min_values,
max_values,
required,
required_unless_present,
required_unless_present_all,
required_unless_present_any,
conflicts_with,
conflicts_with_all,
requires,
requires_all,
requires_if,
required_if_eq,
require_equals,
index
);
}
}
impl ToTokens for CliConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let description = opt_str_lit(self.description.as_ref());
let long_description = opt_str_lit(self.long_description.as_ref());
let before_help = opt_str_lit(self.before_help.as_ref());
let after_help = opt_str_lit(self.after_help.as_ref());
let args = {
let args = self.args.as_ref().map(|args| {
let arg = args.iter().map(|a| quote! { #a });
quote! { vec![#(#arg),*] }
});
opt_lit(args.as_ref())
};
let subcommands = opt_lit(
self
.subcommands
.as_ref()
.map(|map| {
map_lit(
quote! { ::std::collections::HashMap },
map,
str_lit,
identity,
)
})
.as_ref(),
);
literal_struct!(
tokens,
CliConfig,
description,
long_description,
before_help,
after_help,
args,
subcommands
);
}
}
impl ToTokens for BundleConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let identifier = str_lit(&self.identifier);
literal_struct!(tokens, BundleConfig, identifier);
}
}
impl ToTokens for BuildConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let dev_path = str_lit(&self.dev_path);
let dist_dir = str_lit(&self.dist_dir);
let with_global_tauri = self.with_global_tauri;
literal_struct!(tokens, BuildConfig, dev_path, dist_dir, with_global_tauri);
}
}
impl ToTokens for UpdaterConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let active = self.active;
let dialog = self.dialog;
let pubkey = opt_str_lit(self.pubkey.as_ref());
let endpoints = opt_vec_str_lit(self.endpoints.as_ref());
literal_struct!(tokens, UpdaterConfig, active, dialog, pubkey, endpoints);
}
}
impl ToTokens for TauriConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let windows = vec_lit(&self.windows, identity);
let cli = opt_lit(self.cli.as_ref());
let bundle = &self.bundle;
let updater = &self.updater;
literal_struct!(tokens, TauriConfig, windows, cli, bundle, updater);
}
}
impl ToTokens for PluginConfig {
fn to_tokens(&self, tokens: &mut TokenStream) {
let config = map_lit(
quote! { ::std::collections::HashMap },
&self.0,
str_lit,
json_value_lit,
);
tokens.append_all(quote! { ::tauri::api::config::PluginConfig(#config) })
}
}
impl ToTokens for Config {
fn to_tokens(&self, tokens: &mut TokenStream) {
let tauri = &self.tauri;
let build = &self.build;
let plugins = &self.plugins;
literal_struct!(tokens, Config, tauri, build, plugins);
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_defaults() {
let t_config = TauriConfig::default();
let b_config = BuildConfig::default();
let d_path = default_dev_path();
let d_windows = default_window_config();
let d_title = default_title();
let d_bundle = BundleConfig::default();
let d_updater = UpdaterConfig::default();
let tauri = TauriConfig {
windows: vec![WindowConfig {
label: "main".to_string(),
url: WindowUrl::default(),
x: None,
y: None,
width: 800f64,
height: 600f64,
min_width: None,
min_height: None,
max_width: None,
max_height: None,
resizable: true,
title: String::from("Tauri App"),
fullscreen: false,
transparent: false,
maximized: false,
visible: true,
decorations: true,
always_on_top: false,
}],
bundle: BundleConfig {
identifier: String::from(""),
},
cli: None,
updater: UpdaterConfig {
active: false,
dialog: true,
pubkey: None,
endpoints: None,
},
};
let build = BuildConfig {
dev_path: String::from("http://localhost:8080"),
dist_dir: String::from("../dist"),
with_global_tauri: false,
};
assert_eq!(t_config, tauri);
assert_eq!(b_config, build);
assert_eq!(d_bundle, tauri.bundle);
assert_eq!(d_updater, tauri.updater);
assert_eq!(d_path, String::from("http://localhost:8080"));
assert_eq!(d_title, tauri.windows[0].title);
assert_eq!(d_windows, tauri.windows);
}
}