aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2025-08-27 10:59:16 +0300
committerIvan Molodetskikh <yalterz@gmail.com>2025-08-27 10:59:28 +0300
commitc637d4ce9978d2ceb9aa678c37dd69374d479bd6 (patch)
treee56705f2e12accae51e45a906bbe6222acf29651
parenta03a7bd1a321ccb6eab91d1279abdb55ee1814f9 (diff)
downloadniri-c637d4ce9978d2ceb9aa678c37dd69374d479bd6.tar.gz
niri-c637d4ce9978d2ceb9aa678c37dd69374d479bd6.tar.bz2
niri-c637d4ce9978d2ceb9aa678c37dd69374d479bd6.zip
config: Move some stuff to utils
-rw-r--r--niri-config/src/animations.rs3
-rw-r--r--niri-config/src/binds.rs2
-rw-r--r--niri-config/src/input.rs3
-rw-r--r--niri-config/src/layout.rs3
-rw-r--r--niri-config/src/lib.rs162
-rw-r--r--niri-config/src/utils.rs160
6 files changed, 169 insertions, 164 deletions
diff --git a/niri-config/src/animations.rs b/niri-config/src/animations.rs
index a119ba19..9a870834 100644
--- a/niri-config/src/animations.rs
+++ b/niri-config/src/animations.rs
@@ -1,7 +1,8 @@
use knuffel::errors::DecodeError;
use knuffel::Decode as _;
-use crate::{expect_only_children, parse_arg_node, FloatOrInt};
+use crate::utils::{expect_only_children, parse_arg_node};
+use crate::FloatOrInt;
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
pub struct Animations {
diff --git a/niri-config/src/binds.rs b/niri-config/src/binds.rs
index 2cdc96cd..f476ac2d 100644
--- a/niri-config/src/binds.rs
+++ b/niri-config/src/binds.rs
@@ -12,7 +12,7 @@ use smithay::input::keyboard::keysyms::KEY_NoSymbol;
use smithay::input::keyboard::xkb::{keysym_from_name, KEYSYM_CASE_INSENSITIVE};
use smithay::input::keyboard::Keysym;
-use crate::expect_only_children;
+use crate::utils::expect_only_children;
#[derive(Debug, Default, PartialEq)]
pub struct Binds(pub Vec<Bind>);
diff --git a/niri-config/src/input.rs b/niri-config/src/input.rs
index c5d4a038..61f1a4b8 100644
--- a/niri-config/src/input.rs
+++ b/niri-config/src/input.rs
@@ -5,7 +5,8 @@ use smithay::input::keyboard::XkbConfig;
use smithay::reexports::input;
use crate::binds::Modifiers;
-use crate::{FloatOrInt, Percent};
+use crate::utils::Percent;
+use crate::FloatOrInt;
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
pub struct Input {
diff --git a/niri-config/src/layout.rs b/niri-config/src/layout.rs
index 6f9c9135..768199d5 100644
--- a/niri-config/src/layout.rs
+++ b/niri-config/src/layout.rs
@@ -4,7 +4,8 @@ use niri_ipc::{ColumnDisplay, SizeChange};
use crate::appearance::{
Border, FocusRing, InsertHint, Shadow, TabIndicator, DEFAULT_BACKGROUND_COLOR,
};
-use crate::{expect_only_children, Color, FloatOrInt};
+use crate::utils::expect_only_children;
+use crate::{Color, FloatOrInt};
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
pub struct Layout {
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs
index 1f44b041..dfc9ab2d 100644
--- a/niri-config/src/lib.rs
+++ b/niri-config/src/lib.rs
@@ -5,10 +5,9 @@ use std::ffi::OsStr;
use std::fs::{self, File};
use std::io::Write;
use std::path::{Path, PathBuf};
-use std::str::FromStr;
use knuffel::errors::DecodeError;
-use miette::{miette, Context, IntoDiagnostic};
+use miette::{Context, IntoDiagnostic};
pub mod animations;
pub mod appearance;
@@ -33,6 +32,7 @@ pub use crate::input::{Input, ModKey, ScrollMethod, TrackLayout, WarpMouseToFocu
pub use crate::layer_rule::LayerRule;
pub use crate::layout::*;
pub use crate::output::{Output, OutputName, Outputs, Position, Vrr};
+pub use crate::utils::FloatOrInt;
pub use crate::window_rule::{FloatingPosition, RelativeTo, WindowRule};
#[derive(knuffel::Decode, Debug, PartialEq)]
@@ -89,13 +89,6 @@ pub struct Config {
pub workspaces: Vec<Workspace>,
}
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub struct Percent(pub f64);
-
-// MIN and MAX generics are only used during parsing to check the value.
-#[derive(Debug, Default, Clone, Copy, PartialEq)]
-pub struct FloatOrInt<const MIN: i32, const MAX: i32>(pub f64);
-
#[derive(knuffel::Decode, Debug, Clone, PartialEq, Eq)]
pub struct SpawnAtStartup {
#[knuffel(arguments)]
@@ -210,72 +203,6 @@ pub struct Workspace {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkspaceName(pub String);
-impl<S: knuffel::traits::ErrorSpan, const MIN: i32, const MAX: i32> knuffel::DecodeScalar<S>
- for FloatOrInt<MIN, MAX>
-{
- fn type_check(
- type_name: &Option<knuffel::span::Spanned<knuffel::ast::TypeName, S>>,
- ctx: &mut knuffel::decode::Context<S>,
- ) {
- if let Some(type_name) = &type_name {
- ctx.emit_error(DecodeError::unexpected(
- type_name,
- "type name",
- "no type name expected for this node",
- ));
- }
- }
-
- fn raw_decode(
- val: &knuffel::span::Spanned<knuffel::ast::Literal, S>,
- ctx: &mut knuffel::decode::Context<S>,
- ) -> Result<Self, DecodeError<S>> {
- match &**val {
- knuffel::ast::Literal::Int(ref value) => match value.try_into() {
- Ok(v) => {
- if (MIN..=MAX).contains(&v) {
- Ok(FloatOrInt(f64::from(v)))
- } else {
- ctx.emit_error(DecodeError::conversion(
- val,
- format!("value must be between {MIN} and {MAX}"),
- ));
- Ok(FloatOrInt::default())
- }
- }
- Err(e) => {
- ctx.emit_error(DecodeError::conversion(val, e));
- Ok(FloatOrInt::default())
- }
- },
- knuffel::ast::Literal::Decimal(ref value) => match value.try_into() {
- Ok(v) => {
- if (f64::from(MIN)..=f64::from(MAX)).contains(&v) {
- Ok(FloatOrInt(v))
- } else {
- ctx.emit_error(DecodeError::conversion(
- val,
- format!("value must be between {MIN} and {MAX}"),
- ));
- Ok(FloatOrInt::default())
- }
- }
- Err(e) => {
- ctx.emit_error(DecodeError::conversion(val, e));
- Ok(FloatOrInt::default())
- }
- },
- _ => {
- ctx.emit_error(DecodeError::unsupported(
- val,
- "Unsupported value, only numbers are recognized",
- ));
- Ok(FloatOrInt::default())
- }
- }
- }
-}
-
#[derive(Debug, Clone)]
pub enum ConfigPath {
/// Explicitly set config path.
@@ -418,74 +345,6 @@ impl Default for Config {
}
}
-fn expect_only_children<S>(
- node: &knuffel::ast::SpannedNode<S>,
- ctx: &mut knuffel::decode::Context<S>,
-) where
- S: knuffel::traits::ErrorSpan,
-{
- if let Some(type_name) = &node.type_name {
- ctx.emit_error(DecodeError::unexpected(
- type_name,
- "type name",
- "no type name expected for this node",
- ));
- }
-
- for val in node.arguments.iter() {
- ctx.emit_error(DecodeError::unexpected(
- &val.literal,
- "argument",
- "no arguments expected for this node",
- ))
- }
-
- for name in node.properties.keys() {
- ctx.emit_error(DecodeError::unexpected(
- name,
- "property",
- "no properties expected for this node",
- ))
- }
-}
-
-fn parse_arg_node<S: knuffel::traits::ErrorSpan, T: knuffel::traits::DecodeScalar<S>>(
- name: &str,
- node: &knuffel::ast::SpannedNode<S>,
- ctx: &mut knuffel::decode::Context<S>,
-) -> Result<T, DecodeError<S>> {
- let mut iter_args = node.arguments.iter();
- let val = iter_args.next().ok_or_else(|| {
- DecodeError::missing(node, format!("additional argument `{name}` is required"))
- })?;
-
- let value = knuffel::traits::DecodeScalar::decode(val, ctx)?;
-
- if let Some(val) = iter_args.next() {
- ctx.emit_error(DecodeError::unexpected(
- &val.literal,
- "argument",
- "unexpected argument",
- ));
- }
- for name in node.properties.keys() {
- ctx.emit_error(DecodeError::unexpected(
- name,
- "property",
- format!("unexpected property `{}`", name.escape_default()),
- ));
- }
- for child in node.children() {
- ctx.emit_error(DecodeError::unexpected(
- child,
- "node",
- format!("unexpected node `{}`", child.node_name.escape_default()),
- ));
- }
-
- Ok(value)
-}
-
impl<S: knuffel::traits::ErrorSpan> knuffel::DecodeScalar<S> for WorkspaceName {
fn type_check(
type_name: &Option<knuffel::span::Spanned<knuffel::ast::TypeName, S>>,
@@ -537,23 +396,6 @@ impl<S: knuffel::traits::ErrorSpan> knuffel::DecodeScalar<S> for WorkspaceName {
}
}
-impl FromStr for Percent {
- type Err = miette::Error;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- let Some((value, empty)) = s.split_once('%') else {
- return Err(miette!("value must end with '%'"));
- };
-
- if !empty.is_empty() {
- return Err(miette!("trailing characters after '%' are not allowed"));
- }
-
- let value: f64 = value.parse().map_err(|_| miette!("error parsing value"))?;
- Ok(Percent(value / 100.))
- }
-}
-
#[cfg(test)]
mod tests {
use insta::assert_debug_snapshot;
diff --git a/niri-config/src/utils.rs b/niri-config/src/utils.rs
index cce4f398..77baac5e 100644
--- a/niri-config/src/utils.rs
+++ b/niri-config/src/utils.rs
@@ -1,7 +1,16 @@
use std::str::FromStr;
+use knuffel::errors::DecodeError;
+use miette::miette;
use regex::Regex;
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Percent(pub f64);
+
+// MIN and MAX generics are only used during parsing to check the value.
+#[derive(Debug, Default, Clone, Copy, PartialEq)]
+pub struct FloatOrInt<const MIN: i32, const MAX: i32>(pub f64);
+
/// `Regex` that implements `PartialEq` by its string form.
#[derive(Debug, Clone)]
pub struct RegexEq(pub Regex);
@@ -21,3 +30,154 @@ impl FromStr for RegexEq {
Regex::from_str(s).map(Self)
}
}
+
+impl FromStr for Percent {
+ type Err = miette::Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let Some((value, empty)) = s.split_once('%') else {
+ return Err(miette!("value must end with '%'"));
+ };
+
+ if !empty.is_empty() {
+ return Err(miette!("trailing characters after '%' are not allowed"));
+ }
+
+ let value: f64 = value.parse().map_err(|_| miette!("error parsing value"))?;
+ Ok(Percent(value / 100.))
+ }
+}
+
+impl<S: knuffel::traits::ErrorSpan, const MIN: i32, const MAX: i32> knuffel::DecodeScalar<S>
+ for FloatOrInt<MIN, MAX>
+{
+ fn type_check(
+ type_name: &Option<knuffel::span::Spanned<knuffel::ast::TypeName, S>>,
+ ctx: &mut knuffel::decode::Context<S>,
+ ) {
+ if let Some(type_name) = &type_name {
+ ctx.emit_error(DecodeError::unexpected(
+ type_name,
+ "type name",
+ "no type name expected for this node",
+ ));
+ }
+ }
+
+ fn raw_decode(
+ val: &knuffel::span::Spanned<knuffel::ast::Literal, S>,
+ ctx: &mut knuffel::decode::Context<S>,
+ ) -> Result<Self, DecodeError<S>> {
+ match &**val {
+ knuffel::ast::Literal::Int(ref value) => match value.try_into() {
+ Ok(v) => {
+ if (MIN..=MAX).contains(&v) {
+ Ok(FloatOrInt(f64::from(v)))
+ } else {
+ ctx.emit_error(DecodeError::conversion(
+ val,
+ format!("value must be between {MIN} and {MAX}"),
+ ));
+ Ok(FloatOrInt::default())
+ }
+ }
+ Err(e) => {
+ ctx.emit_error(DecodeError::conversion(val, e));
+ Ok(FloatOrInt::default())
+ }
+ },
+ knuffel::ast::Literal::Decimal(ref value) => match value.try_into() {
+ Ok(v) => {
+ if (f64::from(MIN)..=f64::from(MAX)).contains(&v) {
+ Ok(FloatOrInt(v))
+ } else {
+ ctx.emit_error(DecodeError::conversion(
+ val,
+ format!("value must be between {MIN} and {MAX}"),
+ ));
+ Ok(FloatOrInt::default())
+ }
+ }
+ Err(e) => {
+ ctx.emit_error(DecodeError::conversion(val, e));
+ Ok(FloatOrInt::default())
+ }
+ },
+ _ => {
+ ctx.emit_error(DecodeError::unsupported(
+ val,
+ "Unsupported value, only numbers are recognized",
+ ));
+ Ok(FloatOrInt::default())
+ }
+ }
+ }
+}
+
+pub fn expect_only_children<S>(
+ node: &knuffel::ast::SpannedNode<S>,
+ ctx: &mut knuffel::decode::Context<S>,
+) where
+ S: knuffel::traits::ErrorSpan,
+{
+ if let Some(type_name) = &node.type_name {
+ ctx.emit_error(DecodeError::unexpected(
+ type_name,
+ "type name",
+ "no type name expected for this node",
+ ));
+ }
+
+ for val in node.arguments.iter() {
+ ctx.emit_error(DecodeError::unexpected(
+ &val.literal,
+ "argument",
+ "no arguments expected for this node",
+ ))
+ }
+
+ for name in node.properties.keys() {
+ ctx.emit_error(DecodeError::unexpected(
+ name,
+ "property",
+ "no properties expected for this node",
+ ))
+ }
+}
+
+pub fn parse_arg_node<S: knuffel::traits::ErrorSpan, T: knuffel::traits::DecodeScalar<S>>(
+ name: &str,
+ node: &knuffel::ast::SpannedNode<S>,
+ ctx: &mut knuffel::decode::Context<S>,
+) -> Result<T, DecodeError<S>> {
+ let mut iter_args = node.arguments.iter();
+ let val = iter_args.next().ok_or_else(|| {
+ DecodeError::missing(node, format!("additional argument `{name}` is required"))
+ })?;
+
+ let value = knuffel::traits::DecodeScalar::decode(val, ctx)?;
+
+ if let Some(val) = iter_args.next() {
+ ctx.emit_error(DecodeError::unexpected(
+ &val.literal,
+ "argument",
+ "unexpected argument",
+ ));
+ }
+ for name in node.properties.keys() {
+ ctx.emit_error(DecodeError::unexpected(
+ name,
+ "property",
+ format!("unexpected property `{}`", name.escape_default()),
+ ));
+ }
+ for child in node.children() {
+ ctx.emit_error(DecodeError::unexpected(
+ child,
+ "node",
+ format!("unexpected node `{}`", child.node_name.escape_default()),
+ ));
+ }
+
+ Ok(value)
+}