diff --git a/Cargo.lock b/Cargo.lock index 558e63c21..35493036d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4259,6 +4259,7 @@ dependencies = [ "os_info", "rfd", "ruffle_core", + "ruffle_frontend_utils", "ruffle_render", "ruffle_render_wgpu", "ruffle_video_software", @@ -4280,6 +4281,13 @@ dependencies = [ "winit", ] +[[package]] +name = "ruffle_frontend_utils" +version = "0.1.0" +dependencies = [ + "toml_edit 0.22.9", +] + [[package]] name = "ruffle_gc_arena" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index febb94b09..50e25f307 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,8 @@ members = [ "scanner", "exporter", + "frontend-utils", + "render", "render/canvas", "render/naga-agal", diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 220ffacfd..4d2e2403e 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -23,6 +23,7 @@ ruffle_core = { path = "../core", features = ["audio", "clap", "mp3", "nellymose ruffle_render = { path = "../render", features = ["clap"] } ruffle_render_wgpu = { path = "../render/wgpu", features = ["clap"] } ruffle_video_software = { path = "../video/software", optional = true } +ruffle_frontend_utils = { path = "../frontend-utils" } tracing = { workspace = true } tracing-subscriber = { workspace = true } tracing-appender = "0.2.3" diff --git a/desktop/src/preferences/read.rs b/desktop/src/preferences/read.rs index 2855fae48..f36696c1b 100644 --- a/desktop/src/preferences/read.rs +++ b/desktop/src/preferences/read.rs @@ -1,194 +1,7 @@ use crate::preferences::{Bookmark, SavedGlobalPreferences}; -use std::fmt; +use ruffle_frontend_utils::parse::{ParseContext, ParseResult, ReadExt}; use std::str::FromStr; -use toml_edit::{ArrayOfTables, DocumentMut, Item, Table, TableLike}; - -#[derive(Debug, PartialEq)] -pub struct ParseResult { - pub result: T, - pub warnings: Vec, -} - -impl ParseResult { - fn add_warning(&mut self, message: String) { - self.warnings.push(message); - } -} - -#[derive(Default)] -pub struct ParseContext { - warnings: Vec, - /// Path of the current item being parsed - path: Vec<&'static str>, -} - -impl ParseContext { - pub fn push_key(&mut self, key: &'static str) { - self.path.push(key); - } - - pub fn pop_key(&mut self) { - let _ = self.path.pop(); - } - - pub fn path(&self) -> String { - self.path.join(".") - } - - pub fn add_warning(&mut self, warning: String) { - self.warnings.push(warning); - } -} - -pub trait ReadExt<'a> { - fn get_impl(&'a self, key: &str) -> Option<&'a Item>; - - fn get_table_like( - &'a self, - cx: &mut ParseContext, - key: &'static str, - fun: impl FnOnce(&mut ParseContext, &dyn TableLike), - ) { - if let Some(item) = self.get_impl(key) { - cx.push_key(key); - - if let Some(table) = item.as_table_like() { - fun(cx, table); - } else { - cx.add_warning(format!( - "Invalid {}: expected table but found {}", - cx.path(), - item.type_name() - )); - } - - cx.pop_key(); - } - } - - fn get_array_of_tables( - &'a self, - cx: &mut ParseContext, - key: &'static str, - fun: impl FnOnce(&mut ParseContext, &ArrayOfTables), - ) { - if let Some(item) = self.get_impl(key) { - cx.push_key(key); - - if let Some(array) = item.as_array_of_tables() { - fun(cx, array); - } else { - cx.add_warning(format!( - "Invalid {}: expected array of tables but found {}", - cx.path(), - item.type_name() - )); - } - - cx.pop_key(); - } - } - - fn parse_from_str(&'a self, cx: &mut ParseContext, key: &'static str) -> Option { - cx.push_key(key); - - let res = if let Some(item) = self.get_impl(key) { - if let Some(str) = item.as_str() { - if let Ok(value) = str.parse::() { - Some(value) - } else { - cx.add_warning(format!("Invalid {}: unsupported value {str:?}", cx.path())); - None - } - } else { - cx.add_warning(format!( - "Invalid {}: expected string but found {}", - cx.path(), - item.type_name() - )); - None - } - } else { - None - }; - - cx.pop_key(); - - res - } - - fn get_bool(&'a self, cx: &mut ParseContext, key: &'static str) -> Option { - cx.push_key(key); - - let res = if let Some(item) = self.get_impl(key) { - if let Some(value) = item.as_bool() { - Some(value) - } else { - cx.add_warning(format!( - "Invalid {}: expected boolean but found {}", - cx.path(), - item.type_name() - )); - None - } - } else { - None - }; - - cx.pop_key(); - - res - } - - fn get_float(&'a self, cx: &mut ParseContext, key: &'static str) -> Option { - cx.push_key(key); - - let res = if let Some(item) = self.get_impl(key) { - if let Some(value) = item.as_float() { - Some(value) - } else { - cx.add_warning(format!( - "Invalid {}: expected float but found {}", - cx.path(), - item.type_name() - )); - None - } - } else { - None - }; - - cx.pop_key(); - - res - } -} - -// Implementations for toml_edit types. - -impl<'a> ReadExt<'a> for DocumentMut { - fn get_impl(&'a self, key: &str) -> Option<&'a Item> { - self.get(key) - } -} - -impl<'a> ReadExt<'a> for Item { - fn get_impl(&'a self, key: &str) -> Option<&'a Item> { - self.get(key) - } -} - -impl<'a> ReadExt<'a> for Table { - fn get_impl(&'a self, key: &str) -> Option<&'a Item> { - self.get(key) - } -} - -impl<'a> ReadExt<'a> for dyn TableLike + 'a { - fn get_impl(&'a self, key: &str) -> Option<&'a Item> { - self.get(key) - } -} +use toml_edit::DocumentMut; /// Read the given preferences into a **guaranteed valid** `SavedGlobalPreferences`, /// recording any possible warnings encountered along the way. diff --git a/frontend-utils/Cargo.toml b/frontend-utils/Cargo.toml new file mode 100644 index 000000000..ef104c13d --- /dev/null +++ b/frontend-utils/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "ruffle_frontend_utils" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[lints] +workspace = true + +[dependencies] +toml_edit = { version = "0.22.9", features = ["parse"] } diff --git a/frontend-utils/src/lib.rs b/frontend-utils/src/lib.rs new file mode 100644 index 000000000..ea868482d --- /dev/null +++ b/frontend-utils/src/lib.rs @@ -0,0 +1 @@ +pub mod parse; diff --git a/frontend-utils/src/parse.rs b/frontend-utils/src/parse.rs new file mode 100644 index 000000000..d4fa06867 --- /dev/null +++ b/frontend-utils/src/parse.rs @@ -0,0 +1,190 @@ +use std::fmt; +use std::str::FromStr; +use toml_edit::{ArrayOfTables, DocumentMut, Item, Table, TableLike}; + +#[derive(Debug, PartialEq)] +pub struct ParseResult { + pub result: T, + pub warnings: Vec, +} + +impl ParseResult { + pub fn add_warning(&mut self, message: String) { + self.warnings.push(message); + } +} + +#[derive(Default)] +pub struct ParseContext { + pub warnings: Vec, + /// Path of the current item being parsed + pub path: Vec<&'static str>, +} + +impl ParseContext { + pub fn push_key(&mut self, key: &'static str) { + self.path.push(key); + } + + pub fn pop_key(&mut self) { + let _ = self.path.pop(); + } + + pub fn path(&self) -> String { + self.path.join(".") + } + + pub fn add_warning(&mut self, warning: String) { + self.warnings.push(warning); + } +} + +pub trait ReadExt<'a> { + fn get_impl(&'a self, key: &str) -> Option<&'a Item>; + + fn get_table_like( + &'a self, + cx: &mut ParseContext, + key: &'static str, + fun: impl FnOnce(&mut ParseContext, &dyn TableLike), + ) { + if let Some(item) = self.get_impl(key) { + cx.push_key(key); + + if let Some(table) = item.as_table_like() { + fun(cx, table); + } else { + cx.add_warning(format!( + "Invalid {}: expected table but found {}", + cx.path(), + item.type_name() + )); + } + + cx.pop_key(); + } + } + + fn get_array_of_tables( + &'a self, + cx: &mut ParseContext, + key: &'static str, + fun: impl FnOnce(&mut ParseContext, &ArrayOfTables), + ) { + if let Some(item) = self.get_impl(key) { + cx.push_key(key); + + if let Some(array) = item.as_array_of_tables() { + fun(cx, array); + } else { + cx.add_warning(format!( + "Invalid {}: expected array of tables but found {}", + cx.path(), + item.type_name() + )); + } + + cx.pop_key(); + } + } + + fn parse_from_str(&'a self, cx: &mut ParseContext, key: &'static str) -> Option { + cx.push_key(key); + + let res = if let Some(item) = self.get_impl(key) { + if let Some(str) = item.as_str() { + if let Ok(value) = str.parse::() { + Some(value) + } else { + cx.add_warning(format!("Invalid {}: unsupported value {str:?}", cx.path())); + None + } + } else { + cx.add_warning(format!( + "Invalid {}: expected string but found {}", + cx.path(), + item.type_name() + )); + None + } + } else { + None + }; + + cx.pop_key(); + + res + } + + fn get_bool(&'a self, cx: &mut ParseContext, key: &'static str) -> Option { + cx.push_key(key); + + let res = if let Some(item) = self.get_impl(key) { + if let Some(value) = item.as_bool() { + Some(value) + } else { + cx.add_warning(format!( + "Invalid {}: expected boolean but found {}", + cx.path(), + item.type_name() + )); + None + } + } else { + None + }; + + cx.pop_key(); + + res + } + + fn get_float(&'a self, cx: &mut ParseContext, key: &'static str) -> Option { + cx.push_key(key); + + let res = if let Some(item) = self.get_impl(key) { + if let Some(value) = item.as_float() { + Some(value) + } else { + cx.add_warning(format!( + "Invalid {}: expected float but found {}", + cx.path(), + item.type_name() + )); + None + } + } else { + None + }; + + cx.pop_key(); + + res + } +} + +// Implementations for toml_edit types. + +impl<'a> ReadExt<'a> for DocumentMut { + fn get_impl(&'a self, key: &str) -> Option<&'a Item> { + self.get(key) + } +} + +impl<'a> ReadExt<'a> for Item { + fn get_impl(&'a self, key: &str) -> Option<&'a Item> { + self.get(key) + } +} + +impl<'a> ReadExt<'a> for Table { + fn get_impl(&'a self, key: &str) -> Option<&'a Item> { + self.get(key) + } +} + +impl<'a> ReadExt<'a> for dyn TableLike + 'a { + fn get_impl(&'a self, key: &str) -> Option<&'a Item> { + self.get(key) + } +}