feat!: hide JSON schema generation behind feature flag (#563)
Some checks are pending
Deploy docs / build-and-deploy (push) Waiting to run
Merge checks / Checks (push) Waiting to run
Merge checks / Validate nix flake (push) Waiting to run
Merge checks / Validate bat assets (push) Waiting to run
Merge checks / Validate JSON schemas (push) Waiting to run

This hides the JSON schema generation for the config file behind a new
`json-schema` feature flag. This is not fundamental to the functioning
of this tool and also there's already a schema generated
[here](https://github.com/mfontanini/presenterm/blob/master/config-file-schema.json)
so there's no need to have this available in the tool when released. If
anyone does want the JSON schema for some specific version of the tool,
they can always clone the repo and `cargo run --features json-schema`.

This also removes the `serde_with` dependency and instead hand rolls the
`SerializeDisplay` and `DeserializeFromStr` macros manually. These are
very few lines long and doesn't make importing that crate worth it.

Overall these 2 changes cause the number of crates to go down by
something like 20 which is great.
This commit is contained in:
Matias Fontanini 2025-04-22 19:38:05 -07:00 committed by GitHub
commit abc132e9b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 152 additions and 218 deletions

179
Cargo.lock generated
View File

@ -17,21 +17,6 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.18" version = "0.6.18"
@ -172,19 +157,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [
"android-tzdata",
"iana-time-zone",
"num-traits",
"serde",
"windows-link",
]
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.31" version = "4.5.31"
@ -251,12 +223,6 @@ dependencies = [
"unicode_categories", "unicode_categories",
] ]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.4.2" version = "1.4.2"
@ -292,41 +258,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "darling"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.3.11" version = "0.3.11"
@ -334,7 +265,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
dependencies = [ dependencies = [
"powerfmt", "powerfmt",
"serde",
] ]
[[package]] [[package]]
@ -456,12 +386,6 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.2" version = "0.15.2"
@ -480,35 +404,6 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "iana-time-zone"
version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "image" name = "image"
version = "0.25.5" version = "0.25.5"
@ -525,17 +420,6 @@ dependencies = [
"zune-jpeg", "zune-jpeg",
] ]
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
"serde",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.7.1" version = "2.7.1"
@ -543,8 +427,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.15.2", "hashbrown",
"serde",
] ]
[[package]] [[package]]
@ -568,16 +451,6 @@ version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.170" version = "0.2.170"
@ -755,7 +628,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016"
dependencies = [ dependencies = [
"base64", "base64",
"indexmap 2.7.1", "indexmap",
"quick-xml", "quick-xml",
"serde", "serde",
"time", "time",
@ -804,7 +677,6 @@ dependencies = [
"schemars", "schemars",
"serde", "serde",
"serde_json", "serde_json",
"serde_with",
"serde_yaml", "serde_yaml",
"sixel-rs", "sixel-rs",
"socket2", "socket2",
@ -1048,43 +920,13 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_with"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa"
dependencies = [
"base64",
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.7.1",
"serde",
"serde_derive",
"serde_json",
"serde_with_macros",
"time",
]
[[package]]
name = "serde_with_macros"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "serde_yaml" name = "serde_yaml"
version = "0.9.34+deprecated" version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [ dependencies = [
"indexmap 2.7.1", "indexmap",
"itoa", "itoa",
"ryu", "ryu",
"serde", "serde",
@ -1508,21 +1350,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-link"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.52.0" version = "0.52.0"

View File

@ -23,11 +23,10 @@ sixel-rs = { version = "0.4.1", optional = true }
merge-struct = "0.1.0" merge-struct = "0.1.0"
itertools = "0.14" itertools = "0.14"
once_cell = "1.19" once_cell = "1.19"
schemars = "0.8" schemars = { version = "0.8", optional = true }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.9" serde_yaml = "0.9"
serde_json = "1.0" serde_json = "1.0"
serde_with = "3.6"
syntect = { version = "5.2", features = ["parsing", "default-themes", "regex-onig", "plist-load"], default-features = false } syntect = { version = "5.2", features = ["parsing", "default-themes", "regex-onig", "plist-load"], default-features = false }
socket2 = "0.5.8" socket2 = "0.5.8"
strum = { version = "0.27", features = ["derive"] } strum = { version = "0.27", features = ["derive"] }
@ -45,6 +44,7 @@ rstest = { version = "0.25", default-features = false }
[features] [features]
default = [] default = []
sixel = ["sixel-rs"] sixel = ["sixel-rs"]
json-schema = ["dep:schemars"]
[profile.dev] [profile.dev]
opt-level = 0 opt-level = 0

View File

@ -1,10 +1,12 @@
#!/bin/bash #!/bin/bash
set -euo pipefail
script_dir=$(dirname "$0") script_dir=$(dirname "$0")
root_dir="${script_dir}/../" root_dir="${script_dir}/../"
current_schema=$(mktemp) current_schema=$(mktemp)
cargo run -q -- --generate-config-file-schema >"$current_schema" cargo run --features json-schema -q -- --generate-config-file-schema >"$current_schema"
diff=$(diff --color=always -u "${root_dir}/config-file-schema.json" "$current_schema") diff=$(diff --color=always -u "${root_dir}/config-file-schema.json" "$current_schema")
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then

View File

@ -15,9 +15,7 @@ use crate::{
}, },
theme::{Alignment, CodeBlockStyle}, theme::{Alignment, CodeBlockStyle},
}; };
use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use serde_with::DeserializeFromStr;
use std::{cell::RefCell, convert::Infallible, fmt::Write, ops::Range, path::PathBuf, rc::Rc, str::FromStr}; use std::{cell::RefCell, convert::Infallible, fmt::Write, ops::Range, path::PathBuf, rc::Rc, str::FromStr};
use strum::{EnumDiscriminants, EnumIter}; use strum::{EnumDiscriminants, EnumIter};
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
@ -440,7 +438,8 @@ impl Snippet {
} }
/// The language of a code snippet. /// The language of a code snippet.
#[derive(Clone, Debug, PartialEq, Eq, EnumIter, PartialOrd, Ord, DeserializeFromStr, JsonSchema)] #[derive(Clone, Debug, PartialEq, Eq, EnumIter, PartialOrd, Ord)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub enum SnippetLanguage { pub enum SnippetLanguage {
Ada, Ada,
Asp, Asp,
@ -509,6 +508,8 @@ pub enum SnippetLanguage {
Zsh, Zsh,
} }
crate::utils::impl_deserialize_from_str!(SnippetLanguage);
impl FromStr for SnippetLanguage { impl FromStr for SnippetLanguage {
type Err = Infallible; type Err = Infallible;

View File

@ -1,8 +1,6 @@
use super::listener::{Command, CommandDiscriminants}; use super::listener::{Command, CommandDiscriminants};
use crate::config::KeyBindingsConfig; use crate::config::KeyBindingsConfig;
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, poll, read}; use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, poll, read};
use schemars::JsonSchema;
use serde_with::DeserializeFromStr;
use std::{fmt, io, iter, mem, str::FromStr, time::Duration}; use std::{fmt, io, iter, mem, str::FromStr, time::Duration};
/// A keyboard command listener. /// A keyboard command listener.
@ -162,8 +160,11 @@ enum BindingMatch {
None, None,
} }
#[derive(Clone, Debug, PartialEq, Eq, DeserializeFromStr, JsonSchema)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct KeyBinding(#[schemars(with = "String")] Vec<KeyMatcher>); #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct KeyBinding(#[cfg_attr(feature = "json-schema", schemars(with = "String"))] Vec<KeyMatcher>);
crate::utils::impl_deserialize_from_str!(KeyBinding);
impl KeyBinding { impl KeyBinding {
fn match_events(&self, mut events: &[KeyEvent]) -> BindingMatch { fn match_events(&self, mut events: &[KeyEvent]) -> BindingMatch {

View File

@ -7,7 +7,6 @@ use crate::{
}, },
}; };
use clap::ValueEnum; use clap::ValueEnum;
use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::{
collections::{BTreeMap, HashMap}, collections::{BTreeMap, HashMap},
@ -16,7 +15,8 @@ use std::{
path::Path, path::Path,
}; };
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)] #[derive(Clone, Debug, Default, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct Config { pub struct Config {
/// The default configuration for the presentation. /// The default configuration for the presentation.
@ -73,7 +73,8 @@ pub enum ConfigLoadError {
Invalid(#[from] serde_yaml::Error), Invalid(#[from] serde_yaml::Error),
} }
#[derive(Clone, Debug, Deserialize, JsonSchema)] #[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct DefaultsConfig { pub struct DefaultsConfig {
/// The theme to use by default in every presentation unless overridden. /// The theme to use by default in every presentation unless overridden.
@ -81,7 +82,7 @@ pub struct DefaultsConfig {
/// Override the terminal font size when in windows or when using sixel. /// Override the terminal font size when in windows or when using sixel.
#[serde(default = "default_terminal_font_size")] #[serde(default = "default_terminal_font_size")]
#[validate(range(min = 1))] #[cfg_attr(feature = "json-schema", validate(range(min = 1)))]
pub terminal_font_size: u8, pub terminal_font_size: u8,
/// The image protocol to use. /// The image protocol to use.
@ -132,7 +133,8 @@ impl Default for DefaultsConfig {
} }
/// The configuration for lists when incremental lists are enabled. /// The configuration for lists when incremental lists are enabled.
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)] #[derive(Clone, Debug, Default, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct IncrementalListsConfig { pub struct IncrementalListsConfig {
/// Whether to pause before a list begins. /// Whether to pause before a list begins.
@ -149,7 +151,8 @@ fn default_terminal_font_size() -> u8 {
} }
/// The alignment to use when `defaults.max_columns` is set. /// The alignment to use when `defaults.max_columns` is set.
#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)] #[derive(Clone, Copy, Debug, Default, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum MaxColumnsAlignment { pub enum MaxColumnsAlignment {
/// Align the presentation to the left. /// Align the presentation to the left.
@ -164,7 +167,8 @@ pub enum MaxColumnsAlignment {
} }
/// The alignment to use when `defaults.max_rows` is set. /// The alignment to use when `defaults.max_rows` is set.
#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)] #[derive(Clone, Copy, Debug, Default, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum MaxRowsAlignment { pub enum MaxRowsAlignment {
/// Align the presentation to the top. /// Align the presentation to the top.
@ -178,7 +182,8 @@ pub enum MaxRowsAlignment {
Bottom, Bottom,
} }
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)] #[derive(Clone, Debug, Default, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum ValidateOverflows { pub enum ValidateOverflows {
#[default] #[default]
@ -188,7 +193,8 @@ pub enum ValidateOverflows {
WhenDeveloping, WhenDeveloping,
} }
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)] #[derive(Clone, Debug, Default, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct OptionsConfig { pub struct OptionsConfig {
/// Whether slides are automatically terminated when a slide title is found. /// Whether slides are automatically terminated when a slide title is found.
@ -214,7 +220,8 @@ pub struct OptionsConfig {
pub auto_render_languages: Vec<SnippetLanguage>, pub auto_render_languages: Vec<SnippetLanguage>,
} }
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)] #[derive(Clone, Debug, Default, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct SnippetConfig { pub struct SnippetConfig {
/// The properties for snippet execution. /// The properties for snippet execution.
@ -230,7 +237,8 @@ pub struct SnippetConfig {
pub render: SnippetRenderConfig, pub render: SnippetRenderConfig,
} }
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)] #[derive(Clone, Debug, Default, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct SnippetExecConfig { pub struct SnippetExecConfig {
/// Whether to enable snippet execution. /// Whether to enable snippet execution.
@ -241,7 +249,8 @@ pub struct SnippetExecConfig {
pub custom: BTreeMap<SnippetLanguage, LanguageSnippetExecutionConfig>, pub custom: BTreeMap<SnippetLanguage, LanguageSnippetExecutionConfig>,
} }
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)] #[derive(Clone, Debug, Default, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct SnippetExecReplaceConfig { pub struct SnippetExecReplaceConfig {
/// Whether to enable snippet replace-executions, which automatically run code snippets without /// Whether to enable snippet replace-executions, which automatically run code snippets without
@ -249,7 +258,8 @@ pub struct SnippetExecReplaceConfig {
pub enable: bool, pub enable: bool,
} }
#[derive(Clone, Debug, Deserialize, JsonSchema)] #[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct SnippetRenderConfig { pub struct SnippetRenderConfig {
/// The number of threads to use when rendering. /// The number of threads to use when rendering.
@ -267,7 +277,8 @@ pub(crate) fn default_snippet_render_threads() -> usize {
2 2
} }
#[derive(Clone, Debug, Deserialize, JsonSchema)] #[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct TypstConfig { pub struct TypstConfig {
/// The pixels per inch when rendering latex/typst formulas. /// The pixels per inch when rendering latex/typst formulas.
@ -285,7 +296,8 @@ pub(crate) fn default_typst_ppi() -> u32 {
300 300
} }
#[derive(Clone, Debug, Deserialize, JsonSchema)] #[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct MermaidConfig { pub struct MermaidConfig {
/// The scaling parameter to be used in the mermaid CLI. /// The scaling parameter to be used in the mermaid CLI.
@ -308,7 +320,8 @@ pub(crate) fn default_u16_max() -> u16 {
} }
/// The snippet execution configuration for a specific programming language. /// The snippet execution configuration for a specific programming language.
#[derive(Clone, Debug, Deserialize, JsonSchema)] #[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
pub struct LanguageSnippetExecutionConfig { pub struct LanguageSnippetExecutionConfig {
/// The filename to use for the snippet input file. /// The filename to use for the snippet input file.
pub filename: String, pub filename: String,
@ -324,7 +337,8 @@ pub struct LanguageSnippetExecutionConfig {
pub hidden_line_prefix: Option<String>, pub hidden_line_prefix: Option<String>,
} }
#[derive(Clone, Debug, Default, Deserialize, ValueEnum, JsonSchema)] #[derive(Clone, Debug, Default, Deserialize, ValueEnum)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum ImageProtocol { pub enum ImageProtocol {
/// Automatically detect the best image protocol to use. /// Automatically detect the best image protocol to use.
@ -378,7 +392,8 @@ impl TryFrom<&ImageProtocol> for GraphicsMode {
} }
} }
#[derive(Clone, Debug, Deserialize, JsonSchema)] #[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct KeyBindingsConfig { pub struct KeyBindingsConfig {
/// The keys that cause the presentation to move forwards. /// The keys that cause the presentation to move forwards.
@ -465,7 +480,8 @@ impl Default for KeyBindingsConfig {
} }
} }
#[derive(Clone, Debug, Deserialize, JsonSchema)] #[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct SpeakerNotesConfig { pub struct SpeakerNotesConfig {
/// The address in which to listen for speaker note events. /// The address in which to listen for speaker note events.
@ -492,7 +508,8 @@ impl Default for SpeakerNotesConfig {
} }
/// The export configuration. /// The export configuration.
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)] #[derive(Clone, Debug, Default, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct ExportConfig { pub struct ExportConfig {
/// The dimensions to use for presentation exports. /// The dimensions to use for presentation exports.
@ -504,7 +521,8 @@ pub struct ExportConfig {
} }
/// The policy for pauses when exporting. /// The policy for pauses when exporting.
#[derive(Clone, Debug, Default, Deserialize, JsonSchema)] #[derive(Clone, Debug, Default, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields, rename_all = "snake_case")] #[serde(deny_unknown_fields, rename_all = "snake_case")]
pub enum PauseExportPolicy { pub enum PauseExportPolicy {
/// Whether to ignore pauses. /// Whether to ignore pauses.
@ -516,7 +534,8 @@ pub enum PauseExportPolicy {
} }
/// The dimensions to use for presentation exports. /// The dimensions to use for presentation exports.
#[derive(Clone, Debug, Deserialize, JsonSchema)] #[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct ExportDimensionsConfig { pub struct ExportDimensionsConfig {
/// The number of rows. /// The number of rows.
@ -527,9 +546,9 @@ pub struct ExportDimensionsConfig {
} }
// The slide transition configuration. // The slide transition configuration.
#[derive(Clone, Debug, Deserialize, JsonSchema)] #[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(tag = "style")] #[serde(tag = "style", deny_unknown_fields)]
pub struct SlideTransitionConfig { pub struct SlideTransitionConfig {
/// The amount of time to take to perform the transition. /// The amount of time to take to perform the transition.
#[serde(default = "default_transition_duration_millis")] #[serde(default = "default_transition_duration_millis")]
@ -544,9 +563,9 @@ pub struct SlideTransitionConfig {
} }
// The slide transition style configuration. // The slide transition style configuration.
#[derive(Clone, Debug, Deserialize, JsonSchema)] #[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)] #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
#[serde(tag = "style", rename_all = "snake_case")] #[serde(tag = "style", rename_all = "snake_case", deny_unknown_fields)]
pub enum SlideTransitionStyleConfig { pub enum SlideTransitionStyleConfig {
/// Slide horizontally. /// Slide horizontally.
SlideHorizontal, SlideHorizontal,

View File

@ -52,6 +52,7 @@ mod third_party;
mod tools; mod tools;
mod transitions; mod transitions;
mod ui; mod ui;
mod utils;
const DEFAULT_THEME: &str = "dark"; const DEFAULT_THEME: &str = "dark";
const DEFAULT_EXPORT_PIXELS_PER_COLUMN: u16 = 20; const DEFAULT_EXPORT_PIXELS_PER_COLUMN: u16 = 20;
@ -80,6 +81,7 @@ struct Cli {
/// Generate a JSON schema for the configuration file. /// Generate a JSON schema for the configuration file.
#[clap(long)] #[clap(long)]
#[cfg(feature = "json-schema")]
generate_config_file_schema: bool, generate_config_file_schema: bool,
/// Use presentation mode. /// Use presentation mode.
@ -348,11 +350,13 @@ fn overflow_validation_enabled(mode: &PresentMode, config: &ValidateOverflows) -
} }
fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> { fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
#[cfg(feature = "json-schema")]
if cli.generate_config_file_schema { if cli.generate_config_file_schema {
let schema = schemars::schema_for!(Config); let schema = schemars::schema_for!(Config);
serde_json::to_writer_pretty(io::stdout(), &schema).map_err(|e| format!("failed to write schema: {e}"))?; serde_json::to_writer_pretty(io::stdout(), &schema).map_err(|e| format!("failed to write schema: {e}"))?;
return Ok(()); return Ok(());
} else if cli.acknowledgements { }
if cli.acknowledgements {
let acknowledgements = include_bytes!("../bat/acknowledgements.txt"); let acknowledgements = include_bytes!("../bat/acknowledgements.txt");
println!("{}", String::from_utf8_lossy(acknowledgements)); println!("{}", String::from_utf8_lossy(acknowledgements));
return Ok(()); return Ok(());

View File

@ -2,7 +2,6 @@ use super::registry::LoadThemeError;
use crate::markdown::text_style::{Color, Colors, UndefinedPaletteColorError}; use crate::markdown::text_style::{Color, Colors, UndefinedPaletteColorError};
use hex::{FromHex, FromHexError}; use hex::{FromHex, FromHexError};
use serde::{Deserialize, Serialize, de::Visitor}; use serde::{Deserialize, Serialize, de::Visitor};
use serde_with::{DeserializeFromStr, SerializeDisplay};
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
fmt, fs, fmt, fs,
@ -508,9 +507,12 @@ impl<'de> Deserialize<'de> for FooterContent {
} }
} }
#[derive(Clone, Debug, SerializeDisplay, DeserializeFromStr)] #[derive(Clone, Debug)]
pub(crate) struct FooterTemplate(pub(crate) Vec<FooterTemplateChunk>); pub(crate) struct FooterTemplate(pub(crate) Vec<FooterTemplateChunk>);
crate::utils::impl_deserialize_from_str!(FooterTemplate);
crate::utils::impl_serialize_from_display!(FooterTemplate);
impl FromStr for FooterTemplate { impl FromStr for FooterTemplate {
type Err = ParseFooterTemplateError; type Err = ParseFooterTemplateError;
@ -801,7 +803,7 @@ pub(super) struct ColorPalette {
pub(super) classes: BTreeMap<String, RawColors>, pub(super) classes: BTreeMap<String, RawColors>,
} }
#[derive(Debug, Clone, PartialEq, Eq, SerializeDisplay, DeserializeFromStr)] #[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum RawColor { pub(crate) enum RawColor {
Color(Color), Color(Color),
Palette(String), Palette(String),
@ -809,6 +811,9 @@ pub(crate) enum RawColor {
BackgroundClass(String), BackgroundClass(String),
} }
crate::utils::impl_deserialize_from_str!(RawColor);
crate::utils::impl_serialize_from_display!(RawColor);
impl RawColor { impl RawColor {
fn new_palette(name: &str) -> Result<Self, ParseColorError> { fn new_palette(name: &str) -> Result<Self, ParseColorError> {
if name.is_empty() { Err(ParseColorError::PaletteColorEmpty) } else { Ok(Self::Palette(name.into())) } if name.is_empty() { Err(ParseColorError::PaletteColorEmpty) } else { Ok(Self::Palette(name.into())) }

75
src/utils.rs Normal file
View File

@ -0,0 +1,75 @@
use serde::{Deserializer, Serializer};
use std::{
fmt::{self, Display},
marker::PhantomData,
str::FromStr,
};
macro_rules! impl_deserialize_from_str {
($ty:ty) => {
impl<'de> serde::de::Deserialize<'de> for $ty {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
$crate::utils::deserialize_from_str(deserializer)
}
}
};
}
macro_rules! impl_serialize_from_display {
($ty:ty) => {
impl serde::Serialize for $ty {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
$crate::utils::serialize_display(self, serializer)
}
}
};
}
pub(crate) use impl_deserialize_from_str;
pub(crate) use impl_serialize_from_display;
// Same behavior as serde_with::DeserializeFromStr
pub(crate) fn deserialize_from_str<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: FromStr,
T::Err: Display,
{
struct Visitor<S>(PhantomData<S>);
impl<S> serde::de::Visitor<'_> for Visitor<S>
where
S: FromStr,
<S as FromStr>::Err: Display,
{
type Value = S;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "a string")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
value.parse::<S>().map_err(serde::de::Error::custom)
}
}
deserializer.deserialize_str(Visitor(PhantomData))
}
// Same behavior as serde_with::SerializeDisplay
pub(crate) fn serialize_display<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where
T: Display,
S: Serializer,
{
serializer.serialize_str(&value.to_string())
}