Skip to content

Commit ae83d00

Browse files
authored
feat: add support to TOML config file Tauri.toml, closes #4806 (#4813)
1 parent c04d034 commit ae83d00

File tree

18 files changed

+350
-136
lines changed

18 files changed

+350
-136
lines changed

.changes/config-toml.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"tauri": minor
3+
"tauri-utils": minor
4+
"tauri-macros": minor
5+
"tauri-codegen": minor
6+
"tauri-build": minor
7+
"cli.rs": minor
8+
"cli.js": minor
9+
---
10+
11+
Added support to configuration files in TOML format (Tauri.toml file).

.changes/utils-parse-refactor.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri-utils": minor
3+
---
4+
5+
Refactored the `config::parse` module.

core/tauri-build/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@ semver = "1"
3434
codegen = [ "tauri-codegen", "quote" ]
3535
isolation = [ "tauri-codegen/isolation", "tauri-utils/isolation" ]
3636
config-json5 = [ "tauri-utils/config-json5" ]
37+
config-toml = [ "tauri-utils/config-toml" ]

core/tauri-build/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
202202
println!("cargo:rerun-if-changed=tauri.conf.json");
203203
#[cfg(feature = "config-json5")]
204204
println!("cargo:rerun-if-changed=tauri.conf.json5");
205+
#[cfg(feature = "config-toml")]
206+
println!("cargo:rerun-if-changed=Tauri.toml");
205207

206208
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
207209
let mobile = target_os == "ios" || target_os == "android";

core/tauri-codegen/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ compression = [ "brotli", "tauri-utils/compression" ]
4040
isolation = [ "tauri-utils/isolation" ]
4141
shell-scope = [ "regex" ]
4242
config-json5 = [ "tauri-utils/config-json5" ]
43+
config-toml = [ "tauri-utils/config-toml" ]

core/tauri-macros/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ compression = [ "tauri-codegen/compression" ]
2929
isolation = [ "tauri-codegen/isolation" ]
3030
shell-scope = [ "tauri-codegen/shell-scope" ]
3131
config-json5 = [ "tauri-codegen/config-json5", "tauri-utils/config-json5" ]
32+
config-toml = [ "tauri-codegen/config-toml", "tauri-utils/config-toml" ]

core/tauri-macros/src/context.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use syn::{
1111
LitStr, PathArguments, PathSegment, Token,
1212
};
1313
use tauri_codegen::{context_codegen, get_config, ContextData};
14-
use tauri_utils::config::parse::does_supported_extension_exist;
14+
use tauri_utils::config::parse::does_supported_file_name_exist;
1515

1616
pub(crate) struct ContextItems {
1717
config_file: PathBuf,
@@ -36,7 +36,7 @@ impl Parse for ContextItems {
3636
VarError::NotUnicode(_) => "CARGO_MANIFEST_DIR env var contained invalid utf8".into(),
3737
})
3838
.and_then(|path| {
39-
if does_supported_extension_exist(&path) {
39+
if does_supported_file_name_exist(&path) {
4040
Ok(path)
4141
} else {
4242
Err(format!(

core/tauri-utils/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ getrandom = { version = "0.2", optional = true, features = [ "std" ] }
2929
serialize-to-javascript = { version = "=0.1.1", optional = true }
3030
ctor = "0.1"
3131
json5 = { version = "0.4", optional = true }
32+
toml = { version = "0.5", optional = true }
3233
json-patch = "0.2"
3334
glob = { version = "0.3.0", optional = true }
3435
walkdir = { version = "2", optional = true }
@@ -56,4 +57,5 @@ schema = [ "schemars" ]
5657
isolation = [ "aes-gcm", "getrandom", "serialize-to-javascript" ]
5758
process-relaunch-dangerous-allow-symlink-macos = [ ]
5859
config-json5 = [ "json5" ]
60+
config-toml = [ "toml" ]
5961
resources = [ "glob", "walkdir" ]

core/tauri-utils/src/config.rs

Lines changed: 121 additions & 59 deletions
Large diffs are not rendered by default.

core/tauri-utils/src/config/parse.rs

Lines changed: 145 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,75 @@ use std::path::{Path, PathBuf};
1111
use thiserror::Error;
1212

1313
/// All extensions that are possibly supported, but perhaps not enabled.
14-
pub const EXTENSIONS_SUPPORTED: &[&str] = &["json", "json5"];
14+
pub const EXTENSIONS_SUPPORTED: &[&str] = &["json", "json5", "toml"];
1515

16-
/// All extensions that are currently enabled.
17-
pub const EXTENSIONS_ENABLED: &[&str] = &[
18-
"json",
16+
/// All configuration formats that are possibly supported, but perhaps not enabled.
17+
pub const SUPPORTED_FORMATS: &[ConfigFormat] =
18+
&[ConfigFormat::Json, ConfigFormat::Json5, ConfigFormat::Toml];
19+
20+
/// All configuration formats that are currently enabled.
21+
pub const ENABLED_FORMATS: &[ConfigFormat] = &[
22+
ConfigFormat::Json,
1923
#[cfg(feature = "config-json5")]
20-
"json5",
24+
ConfigFormat::Json5,
25+
#[cfg(feature = "config-toml")]
26+
ConfigFormat::Toml,
2127
];
2228

29+
/// The available configuration formats.
30+
#[derive(Debug, Copy, Clone)]
31+
pub enum ConfigFormat {
32+
/// The default JSON (tauri.conf.json) format.
33+
Json,
34+
/// The JSON5 (tauri.conf.json5) format.
35+
Json5,
36+
/// The TOML (Tauri.toml file) format.
37+
Toml,
38+
}
39+
40+
impl ConfigFormat {
41+
/// Maps the config format to its file name.
42+
pub fn into_file_name(self) -> &'static str {
43+
match self {
44+
Self::Json => "tauri.conf.json",
45+
Self::Json5 => "tauri.conf.json5",
46+
Self::Toml => "Tauri.toml",
47+
}
48+
}
49+
50+
fn into_platform_file_name(self) -> &'static str {
51+
match self {
52+
Self::Json => {
53+
if cfg!(target_os = "macos") {
54+
"tauri.macos.conf.json"
55+
} else if cfg!(windows) {
56+
"tauri.windows.conf.json"
57+
} else {
58+
"tauri.linux.conf.json"
59+
}
60+
}
61+
Self::Json5 => {
62+
if cfg!(target_os = "macos") {
63+
"tauri.macos.conf.json5"
64+
} else if cfg!(windows) {
65+
"tauri.windows.conf.json5"
66+
} else {
67+
"tauri.linux.conf.json5"
68+
}
69+
}
70+
Self::Toml => {
71+
if cfg!(target_os = "macos") {
72+
"Tauri.macos.toml"
73+
} else if cfg!(windows) {
74+
"Tauri.windows.toml"
75+
} else {
76+
"Tauri.linux.toml"
77+
}
78+
}
79+
}
80+
}
81+
}
82+
2383
/// Represents all the errors that can happen while reading the config.
2484
#[derive(Debug, Error)]
2585
#[non_exhaustive]
@@ -45,7 +105,18 @@ pub enum ConfigError {
45105
error: ::json5::Error,
46106
},
47107

48-
/// Unknown file extension encountered.
108+
/// Failed to parse from TOML.
109+
#[cfg(feature = "config-toml")]
110+
#[error("unable to parse toml Tauri config file at {path} because {error}")]
111+
FormatToml {
112+
/// The path that failed to parse into TOML.
113+
path: PathBuf,
114+
115+
/// The parsing [`toml::Error`].
116+
error: ::toml::de::Error,
117+
},
118+
119+
/// Unknown config file name encountered.
49120
#[error("unsupported format encountered {0}")]
50121
UnsupportedFormat(String),
51122

@@ -81,32 +152,21 @@ pub enum ConfigError {
81152
///
82153
/// [JSON Merge Patch (RFC 7396)]: https://datatracker.ietf.org/doc/html/rfc7396.
83154
pub fn read_from(root_dir: PathBuf) -> Result<Value, ConfigError> {
84-
let mut config: Value = parse_value(root_dir.join("tauri.conf.json"))?;
85-
if let Some(platform_config) = read_platform(root_dir)? {
155+
let mut config: Value = parse_value(root_dir.join("tauri.conf.json"))?.0;
156+
if let Some((platform_config, _)) = read_platform(root_dir)? {
86157
merge(&mut config, &platform_config);
87158
}
88159
Ok(config)
89160
}
90161

91-
/// Gets the platform configuration file name.
92-
pub fn get_platform_config_filename() -> &'static str {
93-
if cfg!(target_os = "macos") {
94-
"tauri.macos.conf.json"
95-
} else if cfg!(windows) {
96-
"tauri.windows.conf.json"
97-
} else {
98-
"tauri.linux.conf.json"
99-
}
100-
}
101-
102162
/// Reads the platform-specific configuration file from the given root directory if it exists.
103163
///
104164
/// Check [`read_from`] for more information.
105-
pub fn read_platform(root_dir: PathBuf) -> Result<Option<Value>, ConfigError> {
106-
let platform_config_path = root_dir.join(get_platform_config_filename());
107-
if does_supported_extension_exist(&platform_config_path) {
108-
let platform_config: Value = parse_value(platform_config_path)?;
109-
Ok(Some(platform_config))
165+
pub fn read_platform(root_dir: PathBuf) -> Result<Option<(Value, PathBuf)>, ConfigError> {
166+
let platform_config_path = root_dir.join(ConfigFormat::Json.into_platform_file_name());
167+
if does_supported_file_name_exist(&platform_config_path) {
168+
let (platform_config, path): (Value, PathBuf) = parse_value(platform_config_path)?;
169+
Ok(Some((platform_config, path)))
110170
} else {
111171
Ok(None)
112172
}
@@ -116,11 +176,21 @@ pub fn read_platform(root_dir: PathBuf) -> Result<Option<Value>, ConfigError> {
116176
///
117177
/// The passed path is expected to be the path to the "default" configuration format, in this case
118178
/// JSON with `.json`.
119-
pub fn does_supported_extension_exist(path: impl Into<PathBuf>) -> bool {
179+
pub fn does_supported_file_name_exist(path: impl Into<PathBuf>) -> bool {
120180
let path = path.into();
121-
EXTENSIONS_ENABLED
181+
let source_file_name = path.file_name().unwrap().to_str().unwrap();
182+
let lookup_platform_config = ENABLED_FORMATS
122183
.iter()
123-
.any(|ext| path.with_extension(ext).exists())
184+
.any(|format| source_file_name == format.into_platform_file_name());
185+
ENABLED_FORMATS.iter().any(|format| {
186+
path
187+
.with_file_name(if lookup_platform_config {
188+
format.into_platform_file_name()
189+
} else {
190+
format.into_file_name()
191+
})
192+
.exists()
193+
})
124194
}
125195

126196
/// Parse the config from path, including alternative formats.
@@ -133,18 +203,39 @@ pub fn does_supported_extension_exist(path: impl Into<PathBuf>) -> bool {
133203
/// 2. Check if `tauri.conf.json5` exists
134204
/// a. Parse it with `json5`
135205
/// b. Return error if all above steps failed
136-
/// 3. Return error if all above steps failed
137-
pub fn parse(path: impl Into<PathBuf>) -> Result<Config, ConfigError> {
206+
/// 3. Check if `Tauri.json` exists
207+
/// a. Parse it with `toml`
208+
/// b. Return error if all above steps failed
209+
/// 4. Return error if all above steps failed
210+
pub fn parse(path: impl Into<PathBuf>) -> Result<(Config, PathBuf), ConfigError> {
138211
do_parse(path.into())
139212
}
140213

141214
/// See [`parse`] for specifics, returns a JSON [`Value`] instead of [`Config`].
142-
pub fn parse_value(path: impl Into<PathBuf>) -> Result<Value, ConfigError> {
215+
pub fn parse_value(path: impl Into<PathBuf>) -> Result<(Value, PathBuf), ConfigError> {
143216
do_parse(path.into())
144217
}
145218

146-
fn do_parse<D: DeserializeOwned>(path: PathBuf) -> Result<D, ConfigError> {
147-
let json5 = path.with_extension("json5");
219+
fn do_parse<D: DeserializeOwned>(path: PathBuf) -> Result<(D, PathBuf), ConfigError> {
220+
let file_name = path
221+
.file_name()
222+
.map(OsStr::to_string_lossy)
223+
.unwrap_or_default();
224+
let lookup_platform_config = ENABLED_FORMATS
225+
.iter()
226+
.any(|format| file_name == format.into_platform_file_name());
227+
228+
let json5 = path.with_file_name(if lookup_platform_config {
229+
ConfigFormat::Json5.into_platform_file_name()
230+
} else {
231+
ConfigFormat::Json5.into_file_name()
232+
});
233+
let toml = path.with_file_name(if lookup_platform_config {
234+
ConfigFormat::Toml.into_platform_file_name()
235+
} else {
236+
ConfigFormat::Toml.into_file_name()
237+
});
238+
148239
let path_ext = path
149240
.extension()
150241
.map(OsStr::to_string_lossy)
@@ -171,19 +262,31 @@ fn do_parse<D: DeserializeOwned>(path: PathBuf) -> Result<D, ConfigError> {
171262
}
172263
};
173264

174-
json
265+
json.map(|j| (j, path))
175266
} else if json5.exists() {
176267
#[cfg(feature = "config-json5")]
177268
{
178269
let raw = read_to_string(&json5)?;
179-
do_parse_json5(&raw, &path)
270+
do_parse_json5(&raw, &path).map(|config| (config, json5))
180271
}
181272

182273
#[cfg(not(feature = "config-json5"))]
183274
Err(ConfigError::DisabledFormat {
184275
extension: ".json5".into(),
185276
feature: "config-json5".into(),
186277
})
278+
} else if toml.exists() {
279+
#[cfg(feature = "config-toml")]
280+
{
281+
let raw = read_to_string(&toml)?;
282+
do_parse_toml(&raw, &path).map(|config| (config, toml))
283+
}
284+
285+
#[cfg(not(feature = "config-toml"))]
286+
Err(ConfigError::DisabledFormat {
287+
extension: ".toml".into(),
288+
feature: "config-toml".into(),
289+
})
187290
} else if !EXTENSIONS_SUPPORTED.contains(&path_ext.as_ref()) {
188291
Err(ConfigError::UnsupportedFormat(path_ext.to_string()))
189292
} else {
@@ -241,6 +344,14 @@ fn do_parse_json5<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, Conf
241344
})
242345
}
243346

347+
#[cfg(feature = "config-toml")]
348+
fn do_parse_toml<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {
349+
::toml::from_str(raw).map_err(|error| ConfigError::FormatToml {
350+
path: path.into(),
351+
error,
352+
})
353+
}
354+
244355
/// Helper function to wrap IO errors from [`std::fs::read_to_string`] into a [`ConfigError`].
245356
fn read_to_string(path: &Path) -> Result<String, ConfigError> {
246357
std::fs::read_to_string(path).map_err(|error| ConfigError::Io {

0 commit comments

Comments
 (0)