1
0
Fork 0
forked from wry/wry

config: change default config to use toml-based configuration

This commit is contained in:
Julian Orth 2024-03-13 19:30:34 +01:00
parent e24a61bc62
commit 3cebf651c5
58 changed files with 14093 additions and 145 deletions

View file

@ -0,0 +1,65 @@
use {
crate::{
config::error::SpannedError,
toml::{
toml_parser::{ErrorHandler, ParserError},
toml_span::{Span, Spanned},
},
},
ahash::AHashSet,
error_reporter::Report,
std::{cell::RefCell, convert::Infallible, error::Error},
};
pub struct Context<'a> {
pub input: &'a [u8],
pub used: RefCell<Used>,
}
#[derive(Default)]
pub struct Used {
pub outputs: Vec<Spanned<String>>,
pub inputs: Vec<Spanned<String>>,
pub drm_devices: Vec<Spanned<String>>,
pub keymaps: Vec<Spanned<String>>,
pub defined_outputs: AHashSet<Spanned<String>>,
pub defined_inputs: AHashSet<Spanned<String>>,
pub defined_drm_devices: AHashSet<Spanned<String>>,
pub defined_keymaps: AHashSet<Spanned<String>>,
}
impl<'a> Context<'a> {
pub fn error<E: Error>(&self, cause: Spanned<E>) -> SpannedError<'a, E> {
self.error2(cause.span, cause.value)
}
pub fn error2<E: Error>(&self, span: Span, cause: E) -> SpannedError<'a, E> {
SpannedError {
input: self.input.into(),
span,
cause: Some(cause),
}
}
pub fn error3(&self, span: Span) -> SpannedError<'a, Infallible> {
SpannedError {
input: self.input.into(),
span,
cause: None,
}
}
}
impl<'a> ErrorHandler for Context<'a> {
fn handle(&self, err: Spanned<ParserError>) {
log::warn!("{}", Report::new(self.error(err)));
}
fn redefinition(&self, err: Spanned<ParserError>, prev: Span) {
log::warn!("{}", Report::new(self.error(err)));
log::info!(
"Previous definition here: {}",
Report::new(self.error3(prev))
);
}
}

View file

@ -0,0 +1,89 @@
use {
crate::toml::toml_span::Span,
bstr::ByteSlice,
error_reporter::Report,
std::{
borrow::Cow,
error::Error,
fmt::{Display, Formatter},
ops::Deref,
},
};
#[derive(Debug)]
pub struct SpannedError<'a, E> {
pub input: Cow<'a, [u8]>,
pub span: Span,
pub cause: Option<E>,
}
impl<'a, E: Error> Display for SpannedError<'a, E> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let original = self.input.deref();
let span = self.span;
let (line, column) = translate_position(original, span.lo);
let line_num = line + 1;
let col_num = column + 1;
let gutter = line_num.to_string().len();
let content = original
.split(|c| *c == b'\n')
.nth(line)
.expect("valid line number");
if let Some(cause) = &self.cause {
write!(f, "{}: ", Report::new(cause))?;
}
writeln!(f, "At line {}, column {}:", line_num, col_num)?;
for _ in 0..=gutter {
write!(f, " ")?;
}
writeln!(f, "|")?;
write!(f, "{} | ", line_num)?;
writeln!(f, "{}", content.as_bstr())?;
for _ in 0..=gutter {
write!(f, " ")?;
}
write!(f, "|")?;
for _ in 0..=column {
write!(f, " ")?;
}
write!(f, "^")?;
for _ in (span.lo + 1)..(span.hi.min(span.lo + content.len() - column)) {
write!(f, "^")?;
}
Ok(())
}
}
impl<'a, E: Error> Error for SpannedError<'a, E> {}
fn translate_position(input: &[u8], index: usize) -> (usize, usize) {
if input.is_empty() {
return (0, index);
}
let safe_index = index.min(input.len() - 1);
let column_offset = index - safe_index;
let index = safe_index;
let nl = input[0..index]
.iter()
.rev()
.enumerate()
.find(|(_, b)| **b == b'\n')
.map(|(nl, _)| index - nl - 1);
let line_start = match nl {
Some(nl) => nl + 1,
None => 0,
};
let line = input[0..line_start].iter().filter(|b| **b == b'\n').count();
let column = std::str::from_utf8(&input[line_start..=index])
.map(|s| s.chars().count() - 1)
.unwrap_or_else(|_| index - line_start);
let column = column + column_offset;
(line, column)
}

View file

@ -0,0 +1,281 @@
use {
crate::{
config::context::Context,
toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
ahash::AHashSet,
error_reporter::Report,
indexmap::IndexMap,
thiserror::Error,
};
pub struct Extractor<'v> {
cx: &'v Context<'v>,
table: &'v IndexMap<Spanned<String>, Spanned<Value>>,
used: Vec<&'static str>,
log_unused: bool,
span: Span,
}
impl<'v> Extractor<'v> {
pub fn new(
cx: &'v Context<'v>,
span: Span,
table: &'v IndexMap<Spanned<String>, Spanned<Value>>,
) -> Self {
Self {
cx,
table,
used: Default::default(),
log_unused: true,
span,
}
}
fn get(&mut self, name: &'static str) -> Option<&'v Spanned<Value>> {
let v = self.table.get(name);
if v.is_some() {
self.used.push(name);
}
v
}
pub fn ignore_unused(&mut self) {
self.log_unused = false;
}
pub fn extract<E: Extractable<'v>, U>(&mut self, e: E) -> Result<E::Output, Spanned<U>>
where
ExtractorError: Into<U>,
{
e.extract(self).map_err(|e| e.map(|e| e.into()))
}
pub fn extract_or_ignore<E: Extractable<'v>, U>(
&mut self,
e: E,
) -> Result<E::Output, Spanned<U>>
where
ExtractorError: Into<U>,
{
let res = self.extract(e);
if res.is_err() {
self.ignore_unused();
}
res
}
}
impl<'v> Drop for Extractor<'v> {
fn drop(&mut self) {
if !self.log_unused {
return;
}
if self.used.len() == self.table.len() {
return;
}
let used: AHashSet<_> = self.used.iter().copied().collect();
for key in self.table.keys() {
if !used.contains(key.value.as_str()) {
#[derive(Debug, Error)]
#[error("Ignoring unknown key {0}")]
struct Err<'a>(&'a str);
let err = self.cx.error2(key.span, Err(&key.value));
log::warn!("{}", Report::new(err));
}
}
}
}
#[derive(Debug, Error, Eq, PartialEq)]
pub enum ExtractorError {
#[error("Missing field {0}")]
MissingField(&'static str),
#[error("Expected {0} but found {1}")]
Expected(&'static str, &'static str),
#[error("Value must fit in a u32")]
U32,
#[error("Value must fit in a u64")]
U64,
#[error("Value must fit in a i32")]
I32,
}
pub trait Extractable<'v> {
type Output;
fn extract(
self,
extractor: &mut Extractor<'v>,
) -> Result<Self::Output, Spanned<ExtractorError>>;
}
impl<'v, T, F> Extractable<'v> for F
where
F: FnOnce(&mut Extractor<'v>) -> Result<T, Spanned<ExtractorError>>,
{
type Output = T;
fn extract(
self,
extractor: &mut Extractor<'v>,
) -> Result<Self::Output, Spanned<ExtractorError>> {
self(extractor)
}
}
pub fn val(
name: &'static str,
) -> impl for<'v> FnOnce(&mut Extractor<'v>) -> Result<Spanned<&'v Value>, Spanned<ExtractorError>>
{
move |extractor: &mut Extractor| match extractor.get(name) {
None => Err(ExtractorError::MissingField(name).spanned(extractor.span)),
Some(v) => Ok(v.as_ref()),
}
}
macro_rules! ty {
($f:ident, $lt:lifetime, $ty:ident, $ret:ty, $v:ident, $map:expr, $name:expr) => {
#[allow(dead_code)]
pub fn $f(
name: &'static str,
) -> impl for<$lt> FnOnce(&mut Extractor<$lt>) -> Result<Spanned<$ret>, Spanned<ExtractorError>> {
move |extractor: &mut Extractor| {
val(name)(extractor).and_then(|v| match v.value {
Value::$ty($v) => Ok($map.spanned(v.span)),
_ => Err(ExtractorError::Expected($name, v.value.name()).spanned(v.span)),
})
}
}
};
}
ty!(str, 'a, String, &'a str, v, v.as_str(), "a string");
ty!(int, 'a, Integer, i64, v, *v, "an integer");
ty!(flt, 'a, Float, f64, v, *v, "a float");
ty!(bol, 'a, Boolean, bool, v, *v, "a boolean");
ty!(arr, 'a, Array, &'a [Spanned<Value>], v, &**v, "an array");
ty!(tbl, 'a, Table, &'a IndexMap<Spanned<String>, Spanned<Value>>, v, v, "a table");
pub fn fltorint(
name: &'static str,
) -> impl for<'a> FnOnce(&mut Extractor<'a>) -> Result<Spanned<f64>, Spanned<ExtractorError>> {
move |extractor: &mut Extractor| {
val(name)(extractor).and_then(|v| match *v.value {
Value::Float(f) => Ok(f.spanned(v.span)),
Value::Integer(i) => Ok((i as f64).spanned(v.span)),
_ => Err(
ExtractorError::Expected("a float or an integer", v.value.name()).spanned(v.span),
),
})
}
}
macro_rules! int {
($f:ident, $ty:ident, $err:ident) => {
#[allow(dead_code)]
pub fn $f(
name: &'static str,
) -> impl for<'v> FnOnce(&mut Extractor<'v>) -> Result<Spanned<$ty>, Spanned<ExtractorError>> {
move |extractor: &mut Extractor| {
int(name)(extractor).and_then(|v| {
v.value
.try_into()
.map(|n: $ty| n.spanned(v.span))
.map_err(|_| ExtractorError::$err.spanned(v.span))
})
}
}
};
}
int!(n32, u32, U32);
int!(n64, u64, U64);
int!(s32, i32, I32);
pub fn recover<F>(f: F) -> Recover<F> {
Recover(f)
}
pub struct Recover<E>(E);
impl<'v, E> Extractable<'v> for Recover<E>
where
E: Extractable<'v>,
<E as Extractable<'v>>::Output: Default,
{
type Output = <E as Extractable<'v>>::Output;
fn extract(
self,
extractor: &mut Extractor<'v>,
) -> Result<Self::Output, Spanned<ExtractorError>> {
match self.0.extract(extractor) {
Ok(v) => Ok(v),
Err(e) => {
log::warn!("{}", extractor.cx.error(e));
Ok(<E as Extractable<'v>>::Output::default())
}
}
}
}
pub fn opt<F>(f: F) -> Opt<F> {
Opt(f)
}
pub struct Opt<E>(E);
impl<'v, E> Extractable<'v> for Opt<E>
where
E: Extractable<'v>,
{
type Output = Option<<E as Extractable<'v>>::Output>;
fn extract(
self,
extractor: &mut Extractor<'v>,
) -> Result<Self::Output, Spanned<ExtractorError>> {
match self.0.extract(extractor) {
Ok(v) => Ok(Some(v)),
Err(e) if matches!(e.value, ExtractorError::MissingField(_)) => Ok(None),
Err(e) => Err(e),
}
}
}
macro_rules! tuples {
($($idx:tt: $name:ident,)*) => {
impl<'v, $($name,)*> Extractable<'v> for ($($name,)*)
where $($name: Extractable<'v>,)*
{
type Output = ($($name::Output,)*);
#[allow(non_snake_case)]
fn extract(self, extractor: &mut Extractor<'v>) -> Result<Self::Output, Spanned<ExtractorError>> {
$(
let $name = self.$idx.extract(extractor);
)*
Ok((
$(
$name?,
)*
))
}
}
};
}
tuples!(0:T0,);
tuples!(0:T0,1:T1,);
tuples!(0:T0,1:T1,2:T2,);
tuples!(0:T0,1:T1,2:T2,3:T3,);
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,);
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,);
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,);
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7,);
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7,8:T8,);
tuples!(0:T0,1:T1,2:T2,3:T3,4:T4,5:T5,6:T6,7:T7,8:T8,9:T9,);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,118 @@
use {
crate::toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
indexmap::IndexMap,
std::{
error::Error,
fmt::{self, Display, Formatter},
},
};
#[derive(Copy, Clone, Debug)]
pub enum DataType {
String,
Integer,
Float,
Boolean,
Array,
Table,
}
impl Display for DataType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let s = match self {
DataType::String => "a string",
DataType::Integer => "an integer",
DataType::Float => "a float",
DataType::Boolean => "a bool",
DataType::Array => "an array",
DataType::Table => "a table",
};
f.write_str(s)
}
}
pub struct DataTypes(&'static [DataType]);
impl Display for DataTypes {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let d = self.0;
match d.len() {
0 => Ok(()),
1 => d[0].fmt(f),
2 => write!(f, "{} or {}", d[0], d[1]),
_ => {
let mut first = true;
#[allow(clippy::needless_range_loop)]
for i in 0..d.len() - 1 {
if !first {
f.write_str(", ")?;
}
first = false;
d[i].fmt(f)?;
}
write!(f, ", or {}", d[d.len() - 1])
}
}
}
}
#[derive(Debug)]
pub struct UnexpectedDataType(&'static [DataType], DataType);
impl Display for UnexpectedDataType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Expected {} but found {}", DataTypes(self.0), self.1)
}
}
impl Error for UnexpectedDataType {}
pub type ParseResult<P> = Result<<P as Parser>::Value, Spanned<<P as Parser>::Error>>;
pub trait Parser {
type Value;
type Error: From<UnexpectedDataType>;
const EXPECTED: &'static [DataType];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
let _ = string;
expected(self, span, DataType::String)
}
fn parse_integer(&mut self, span: Span, integer: i64) -> ParseResult<Self> {
let _ = integer;
expected(self, span, DataType::Integer)
}
fn parse_float(&mut self, span: Span, float: f64) -> ParseResult<Self> {
let _ = float;
expected(self, span, DataType::Float)
}
fn parse_bool(&mut self, span: Span, bool: bool) -> ParseResult<Self> {
let _ = bool;
expected(self, span, DataType::Boolean)
}
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let _ = array;
expected(self, span, DataType::Array)
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let _ = table;
expected(self, span, DataType::Table)
}
}
fn expected<P: Parser + ?Sized>(_p: &P, span: Span, actual: DataType) -> ParseResult<P> {
Err(P::Error::from(UnexpectedDataType(P::EXPECTED, actual)).spanned(span))
}

View file

@ -0,0 +1,48 @@
use {
crate::{
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
toml::toml_span::Span,
},
thiserror::Error,
};
pub mod action;
mod color;
pub mod config;
mod connector;
mod connector_match;
mod drm_device;
mod drm_device_match;
mod env;
pub mod exec;
mod gfx_api;
mod idle;
mod input;
mod input_match;
pub mod keymap;
mod log_level;
mod mode;
pub mod modified_keysym;
mod output;
mod output_match;
pub mod shortcuts;
mod status;
mod theme;
#[derive(Debug, Error)]
pub enum StringParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
}
pub struct StringParser;
impl Parser for StringParser {
type Value = String;
type Error = StringParserError;
const EXPECTED: &'static [DataType] = &[DataType::String];
fn parse_string(&mut self, _span: Span, string: &str) -> ParseResult<Self> {
Ok(string.to_string())
}
}

View file

@ -0,0 +1,308 @@
use {
crate::{
config::{
context::Context,
extractor::{arr, bol, n32, opt, str, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
connector::{ConnectorParser, ConnectorParserError},
drm_device::{DrmDeviceParser, DrmDeviceParserError},
drm_device_match::{DrmDeviceMatchParser, DrmDeviceMatchParserError},
env::{EnvParser, EnvParserError},
exec::{ExecParser, ExecParserError},
gfx_api::{GfxApiParser, GfxApiParserError},
idle::{IdleParser, IdleParserError},
input::{InputParser, InputParserError},
keymap::{KeymapParser, KeymapParserError},
log_level::{LogLevelParser, LogLevelParserError},
output::{OutputParser, OutputParserError},
status::{StatusParser, StatusParserError},
theme::{ThemeParser, ThemeParserError},
StringParser, StringParserError,
},
Action,
},
toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::Axis::{Horizontal, Vertical},
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ActionParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
StringParser(#[from] StringParserError),
#[error("Unknown type {0}")]
UnknownType(String),
#[error("Unknown simple action {0}")]
UnknownSimpleAction(String),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error("Could not parse the exec action")]
Exec(#[from] ExecParserError),
#[error("Could not parse the configure-connector action")]
ConfigureConnector(#[from] ConnectorParserError),
#[error("Could not parse the configure-input action")]
ConfigureInput(#[from] InputParserError),
#[error("Could not parse the configure-output action")]
ConfigureOutput(#[from] OutputParserError),
#[error("Could not parse the environment variables")]
Env(#[from] EnvParserError),
#[error("Could not parse a set-keymap action")]
SetKeymap(#[from] KeymapParserError),
#[error("Could not parse a set-status action")]
Status(#[from] StatusParserError),
#[error("Could not parse a set-theme action")]
Theme(#[from] ThemeParserError),
#[error("Could not parse a set-log-level action")]
SetLogLevel(#[from] LogLevelParserError),
#[error("Could not parse a set-gfx-api action")]
GfxApi(#[from] GfxApiParserError),
#[error("Could not parse a configure-drm-device action")]
DrmDevice(#[from] DrmDeviceParserError),
#[error("Could not parse a set-render-device action")]
SetRenderDevice(#[from] DrmDeviceMatchParserError),
#[error("Could not parse a configure-idle action")]
ConfigureIdle(#[from] IdleParserError),
}
pub struct ActionParser<'a>(pub &'a Context<'a>);
impl ActionParser<'_> {
fn parse_simple_cmd(&self, span: Span, string: &str) -> ParseResult<Self> {
use {crate::config::SimpleCommand::*, jay_config::Direction::*};
let cmd = match string {
"focus-left" => Focus(Left),
"focus-down" => Focus(Down),
"focus-up" => Focus(Up),
"focus-right" => Focus(Right),
"move-left" => Move(Left),
"move-down" => Move(Down),
"move-up" => Move(Up),
"move-right" => Move(Right),
"split-horizontal" => Split(Horizontal),
"split-vertical" => Split(Vertical),
"toggle-split" => ToggleSplit,
"toggle-mono" => ToggleMono,
"toggle-fullscreen" => ToggleFullscreen,
"focus-parent" => FocusParent,
"close" => Close,
"disable-pointer-constraint" => DisablePointerConstraint,
"toggle-floating" => ToggleFloating,
"quit" => Quit,
"reload-config-toml" => ReloadConfigToml,
"reload-config-so" => ReloadConfigSo,
"none" => None,
_ => {
return Err(ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span))
}
};
Ok(Action::SimpleCommand { cmd })
}
fn parse_multi(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut actions = vec![];
for el in array {
actions.push(el.parse(self)?);
}
Ok(Action::Multi { actions })
}
fn parse_exec(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let exec = ext
.extract(val("exec"))?
.parse_map(&mut ExecParser(self.0))?;
Ok(Action::Exec { exec })
}
fn parse_switch_to_vt(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let num = ext.extract(n32("num"))?.value;
Ok(Action::SwitchToVt { num })
}
fn parse_show_workspace(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let name = ext.extract(str("name"))?.value.to_string();
Ok(Action::ShowWorkspace { name })
}
fn parse_move_to_workspace(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let name = ext.extract(str("name"))?.value.to_string();
Ok(Action::ShowWorkspace { name })
}
fn parse_configure_connector(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let con = ext
.extract(val("connector"))?
.parse_map(&mut ConnectorParser(self.0))?;
Ok(Action::ConfigureConnector { con })
}
fn parse_configure_input(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let input = ext.extract(val("input"))?.parse_map(&mut InputParser {
cx: self.0,
tag_ok: false,
})?;
Ok(Action::ConfigureInput { input })
}
fn parse_configure_idle(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let idle = ext
.extract(val("idle"))?
.parse_map(&mut IdleParser(self.0))?;
Ok(Action::ConfigureIdle { idle })
}
fn parse_configure_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let out = ext.extract(val("output"))?.parse_map(&mut OutputParser {
cx: self.0,
name_ok: false,
})?;
Ok(Action::ConfigureOutput { out })
}
fn parse_set_env(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let env = ext.extract(val("env"))?.parse_map(&mut EnvParser)?;
Ok(Action::SetEnv { env })
}
fn parse_unset_env(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
struct P;
impl Parser for P {
type Value = Vec<String>;
type Error = ActionParserError;
const EXPECTED: &'static [DataType] = &[DataType::Array, DataType::String];
fn parse_string(&mut self, _span: Span, string: &str) -> ParseResult<Self> {
Ok(vec![string.to_string()])
}
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut res = vec![];
for v in array {
res.push(v.parse_map(&mut StringParser)?);
}
Ok(res)
}
}
let env = ext.extract(val("env"))?.parse_map(&mut P)?;
Ok(Action::UnsetEnv { env })
}
fn parse_set_keymap(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let map = ext.extract(val("map"))?.parse_map(&mut KeymapParser {
cx: self.0,
definition: false,
})?;
Ok(Action::SetKeymap { map })
}
fn parse_set_status(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let status = match ext.extract(opt(val("status")))? {
None => None,
Some(v) => Some(v.parse_map(&mut StatusParser(self.0))?),
};
Ok(Action::SetStatus { status })
}
fn parse_set_theme(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let theme = ext
.extract(val("theme"))?
.parse_map(&mut ThemeParser(self.0))?;
Ok(Action::SetTheme {
theme: Box::new(theme),
})
}
fn parse_set_log_level(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let level = ext.extract(val("level"))?.parse_map(&mut LogLevelParser)?;
Ok(Action::SetLogLevel { level })
}
fn parse_set_gfx_api(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let api = ext.extract(val("api"))?.parse_map(&mut GfxApiParser)?;
Ok(Action::SetGfxApi { api })
}
fn parse_set_render_device(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let dev = ext
.extract(val("dev"))?
.parse_map(&mut DrmDeviceMatchParser(self.0))?;
Ok(Action::SetRenderDevice { dev })
}
fn parse_configure_direct_scanout(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let enabled = ext.extract(bol("enabled"))?.value;
Ok(Action::ConfigureDirectScanout { enabled })
}
fn parse_configure_drm_device(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let dev = ext.extract(val("dev"))?.parse_map(&mut DrmDeviceParser {
cx: self.0,
name_ok: false,
})?;
Ok(Action::ConfigureDrmDevice { dev })
}
}
impl<'a> Parser for ActionParser<'a> {
type Value = Action;
type Error = ActionParserError;
const EXPECTED: &'static [DataType] = &[DataType::String, DataType::Array, DataType::Table];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
self.parse_simple_cmd(span, string)
}
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
self.parse_multi(span, array)
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let ty = ext.extract_or_ignore(str("type"))?;
let res = match ty.value {
"simple" => {
let cmd = ext.extract(str("cmd"))?;
self.parse_simple_cmd(cmd.span, cmd.value)
}
"multi" => {
let actions = ext.extract(arr("actions"))?;
self.parse_multi(actions.span, actions.value)
}
"exec" => self.parse_exec(&mut ext),
"switch-to-vt" => self.parse_switch_to_vt(&mut ext),
"show-workspace" => self.parse_show_workspace(&mut ext),
"move-to-workspace" => self.parse_move_to_workspace(&mut ext),
"configure-connector" => self.parse_configure_connector(&mut ext),
"configure-input" => self.parse_configure_input(&mut ext),
"configure-output" => self.parse_configure_output(&mut ext),
"set-env" => self.parse_set_env(&mut ext),
"unset-env" => self.parse_unset_env(&mut ext),
"set-keymap" => self.parse_set_keymap(&mut ext),
"set-status" => self.parse_set_status(&mut ext),
"set-theme" => self.parse_set_theme(&mut ext),
"set-log-level" => self.parse_set_log_level(&mut ext),
"set-gfx-api" => self.parse_set_gfx_api(&mut ext),
"configure-direct-scanout" => self.parse_configure_direct_scanout(&mut ext),
"configure-drm-device" => self.parse_configure_drm_device(&mut ext),
"set-render-device" => self.parse_set_render_device(&mut ext),
"configure-idle" => self.parse_configure_idle(&mut ext),
v => {
ext.ignore_unused();
return Err(ActionParserError::UnknownType(v.to_string()).spanned(ty.span));
}
};
drop(ext);
res
}
}

View file

@ -0,0 +1,58 @@
use {
crate::{
config::{
context::Context,
extractor::ExtractorError,
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
toml::toml_span::{Span, SpannedExt},
},
jay_config::theme::Color,
std::{num::ParseIntError, ops::Range},
thiserror::Error,
};
pub struct ColorParser<'a>(pub &'a Context<'a>);
#[derive(Debug, Error)]
pub enum ColorParserError {
#[error(transparent)]
DataType(#[from] UnexpectedDataType),
#[error(transparent)]
Extractor(#[from] ExtractorError),
#[error("Color must start with `#`")]
Prefix,
#[error("String must have length 4, 5, 6, or 9")]
Length,
#[error(transparent)]
ParseIntError(#[from] ParseIntError),
}
impl Parser for ColorParser<'_> {
type Value = Color;
type Error = ColorParserError;
const EXPECTED: &'static [DataType] = &[DataType::String];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
let hex = match string.strip_prefix("#") {
Some(s) => s,
_ => return Err(ColorParserError::Prefix.spanned(span)),
};
let d = |range: Range<usize>| {
u8::from_str_radix(&hex[range], 16)
.map_err(|e| ColorParserError::ParseIntError(e).spanned(span))
};
let s = |range: Range<usize>| {
let v = d(range)?;
Ok((v << 4) | v)
};
let (r, g, b, a) = match hex.len() {
3 => (s(0..1)?, s(1..2)?, s(2..3)?, u8::MAX),
4 => (s(0..1)?, s(1..2)?, s(2..3)?, s(3..4)?),
6 => (d(0..2)?, d(2..4)?, d(4..6)?, u8::MAX),
8 => (d(0..2)?, d(2..4)?, d(4..6)?, d(4..8)?),
_ => return Err(ColorParserError::Length.spanned(span)),
};
Ok(Color::new_straight(r, g, b, a))
}
}

View file

@ -0,0 +1,279 @@
use {
crate::{
config::{
context::Context,
extractor::{arr, bol, opt, recover, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
action::ActionParser,
connector::ConnectorsParser,
drm_device::DrmDevicesParser,
drm_device_match::DrmDeviceMatchParser,
env::EnvParser,
gfx_api::GfxApiParser,
idle::IdleParser,
input::InputsParser,
keymap::KeymapParser,
log_level::LogLevelParser,
output::OutputsParser,
shortcuts::{ShortcutsParser, ShortcutsParserError},
status::StatusParser,
theme::ThemeParser,
},
spanned::SpannedErrorExt,
Action, Config, Theme,
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ConfigParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extractor(#[from] ExtractorError),
#[error("Could not parse the shortcuts")]
ParseShortcuts(#[source] ShortcutsParserError),
}
pub struct ConfigParser<'a>(pub &'a Context<'a>);
impl ConfigParser<'_> {
fn parse_action(&self, name: &str, action: Option<Spanned<&Value>>) -> Option<Action> {
match action {
None => None,
Some(value) => match value.parse(&mut ActionParser(self.0)) {
Ok(v) => Some(v),
Err(e) => {
log::warn!("Could not parse the {name} action: {}", self.0.error(e));
None
}
},
}
}
}
impl Parser for ConfigParser<'_> {
type Value = Config;
type Error = ConfigParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (
(
keymap_val,
shortcuts_val,
on_graphics_init_val,
status_val,
outputs_val,
connectors_val,
workspace_capture,
env_val,
on_startup_val,
keymaps_val,
),
(
log_level_val,
theme_val,
gfx_api_val,
drm_devices_val,
direct_scanout,
render_device_val,
inputs_val,
on_idle_val,
_,
idle_val,
),
) = ext.extract((
(
opt(val("keymap")),
opt(val("shortcuts")),
opt(val("on-graphics-initialized")),
opt(val("status")),
opt(val("outputs")),
opt(val("connectors")),
recover(opt(bol("workspace-capture"))),
opt(val("env")),
opt(val("on-startup")),
recover(opt(arr("keymaps"))),
),
(
opt(val("log-level")),
opt(val("theme")),
opt(val("gfx-api")),
opt(val("drm-devices")),
recover(opt(bol("direct-scanout"))),
opt(val("render-device")),
opt(val("inputs")),
opt(val("on-idle")),
opt(val("$schema")),
opt(val("idle")),
),
))?;
let mut keymap = None;
if let Some(value) = keymap_val {
match value.parse(&mut KeymapParser {
cx: self.0,
definition: false,
}) {
Ok(m) => keymap = Some(m),
Err(e) => {
log::warn!("Could not parse the keymap: {}", self.0.error(e));
}
}
}
let mut shortcuts = vec![];
if let Some(value) = shortcuts_val {
shortcuts = value
.parse(&mut ShortcutsParser(self.0))
.map_spanned_err(ConfigParserError::ParseShortcuts)?;
}
if shortcuts.is_empty() {
log::warn!("Config defines no shortcuts");
}
let on_graphics_initialized =
self.parse_action("on-graphics-initialized", on_graphics_init_val);
let on_idle = self.parse_action("on-idle", on_idle_val);
let on_startup = self.parse_action("on-startup", on_startup_val);
let mut status = None;
if let Some(value) = status_val {
match value.parse(&mut StatusParser(self.0)) {
Ok(v) => status = Some(v),
Err(e) => log::warn!("Could not parse the status config: {}", self.0.error(e)),
}
}
let mut outputs = vec![];
if let Some(value) = outputs_val {
match value.parse(&mut OutputsParser(self.0)) {
Ok(v) => outputs = v,
Err(e) => log::warn!("Could not parse the outputs: {}", self.0.error(e)),
}
}
let mut connectors = vec![];
if let Some(value) = connectors_val {
match value.parse(&mut ConnectorsParser(self.0)) {
Ok(v) => connectors = v,
Err(e) => log::warn!("Could not parse the connectors: {}", self.0.error(e)),
}
}
let mut env = vec![];
if let Some(value) = env_val {
match value.parse(&mut EnvParser) {
Ok(v) => env = v,
Err(e) => log::warn!(
"Could not parse the environment variables: {}",
self.0.error(e)
),
}
}
let mut keymaps = vec![];
if let Some(value) = keymaps_val {
for value in value.value {
match value.parse(&mut KeymapParser {
cx: self.0,
definition: true,
}) {
Ok(m) => keymaps.push(m),
Err(e) => {
log::warn!("Could not parse a keymap: {}", self.0.error(e));
}
}
}
}
let mut log_level = None;
if let Some(value) = log_level_val {
match value.parse(&mut LogLevelParser) {
Ok(v) => log_level = Some(v),
Err(e) => {
log::warn!("Could not parse the log level: {}", self.0.error(e));
}
}
}
let mut theme = Theme::default();
if let Some(value) = theme_val {
match value.parse(&mut ThemeParser(self.0)) {
Ok(v) => theme = v,
Err(e) => {
log::warn!("Could not parse the theme: {}", self.0.error(e));
}
}
}
let mut gfx_api = None;
if let Some(value) = gfx_api_val {
match value.parse(&mut GfxApiParser) {
Ok(v) => gfx_api = Some(v),
Err(e) => {
log::warn!("Could not parse the graphics API: {}", self.0.error(e));
}
}
}
let mut drm_devices = vec![];
if let Some(value) = drm_devices_val {
match value.parse(&mut DrmDevicesParser(self.0)) {
Ok(v) => drm_devices = v,
Err(e) => {
log::warn!("Could not parse the drm devices: {}", self.0.error(e));
}
}
}
let mut render_device = None;
if let Some(value) = render_device_val {
match value.parse(&mut DrmDeviceMatchParser(self.0)) {
Ok(v) => render_device = Some(v),
Err(e) => {
log::warn!("Could not parse the render device: {}", self.0.error(e));
}
}
}
let mut inputs = vec![];
if let Some(value) = inputs_val {
match value.parse(&mut InputsParser(self.0)) {
Ok(v) => inputs = v,
Err(e) => {
log::warn!("Could not parse the inputs: {}", self.0.error(e));
}
}
}
let mut idle = None;
if let Some(value) = idle_val {
match value.parse(&mut IdleParser(self.0)) {
Ok(v) => idle = Some(v),
Err(e) => {
log::warn!("Could not parse the idle timeout: {}", self.0.error(e));
}
}
}
Ok(Config {
keymap,
shortcuts,
on_graphics_initialized,
on_idle,
status,
outputs,
connectors,
workspace_capture: workspace_capture.despan().unwrap_or(true),
env,
on_startup,
keymaps,
log_level,
theme,
gfx_api,
drm_devices,
direct_scanout_enabled: direct_scanout.despan(),
render_device,
inputs,
idle,
})
}
}

View file

@ -0,0 +1,83 @@
use {
crate::{
config::{
context::Context,
extractor::{bol, opt, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::connector_match::{ConnectorMatchParser, ConnectorMatchParserError},
ConfigConnector,
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ConnectorParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error(transparent)]
Match(#[from] ConnectorMatchParserError),
}
pub struct ConnectorParser<'a>(pub &'a Context<'a>);
impl<'a> Parser for ConnectorParser<'a> {
type Value = ConfigConnector;
type Error = ConnectorParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (match_val, enabled) = ext.extract((val("match"), opt(bol("enabled"))))?;
Ok(ConfigConnector {
match_: match_val.parse_map(&mut ConnectorMatchParser(self.0))?,
enabled: enabled.despan().unwrap_or(true),
})
}
}
pub struct ConnectorsParser<'a>(pub &'a Context<'a>);
impl<'a> Parser for ConnectorsParser<'a> {
type Value = Vec<ConfigConnector>;
type Error = ConnectorParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut res = vec![];
for el in array {
match el.parse(&mut ConnectorParser(self.0)) {
Ok(o) => res.push(o),
Err(e) => {
log::warn!("Could not parse connector: {}", self.0.error(e));
}
}
}
Ok(res)
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
log::warn!(
"`connectors` value should be an array: {}",
self.0.error3(span)
);
ConnectorParser(self.0)
.parse_table(span, table)
.map(|v| vec![v])
}
}

View file

@ -0,0 +1,57 @@
use {
crate::{
config::{
context::Context,
extractor::{opt, str, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
ConnectorMatch,
},
toml::{
toml_span::{Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ConnectorMatchParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct ConnectorMatchParser<'a>(pub &'a Context<'a>);
impl<'a> Parser for ConnectorMatchParser<'a> {
type Value = ConnectorMatch;
type Error = ConnectorMatchParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut res = vec![];
for el in array {
match el.parse(self) {
Ok(m) => res.push(m),
Err(e) => {
log::error!("Could not parse match rule: {}", self.0.error(e));
}
}
}
Ok(ConnectorMatch::Any(res))
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (connector,) = ext.extract((opt(str("name")),))?;
Ok(ConnectorMatch::All {
connector: connector.map(|v| v.value.to_owned()),
})
}
}

View file

@ -0,0 +1,126 @@
use {
crate::{
config::{
context::Context,
extractor::{bol, opt, recover, str, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
drm_device_match::{DrmDeviceMatchParser, DrmDeviceMatchParserError},
gfx_api::GfxApiParser,
},
ConfigDrmDevice,
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum DrmDeviceParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error(transparent)]
Match(#[from] DrmDeviceMatchParserError),
}
pub struct DrmDeviceParser<'a> {
pub cx: &'a Context<'a>,
pub name_ok: bool,
}
impl<'a> Parser for DrmDeviceParser<'a> {
type Value = ConfigDrmDevice;
type Error = DrmDeviceParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.cx, span, table);
let (name, match_val, direct_scanout_enabled, gfx_api_val) = ext.extract((
opt(str("name")),
val("match"),
recover(opt(bol("direct-scanout"))),
opt(val("gfx-api")),
))?;
let gfx_api = match gfx_api_val {
Some(api) => match api.parse(&mut GfxApiParser) {
Ok(m) => Some(m),
Err(e) => {
log::warn!("Could not parse graphics API: {}", self.cx.error(e));
None
}
},
None => None,
};
if let Some(name) = name {
if self.name_ok {
self.cx
.used
.borrow_mut()
.defined_drm_devices
.insert(name.into());
} else {
log::warn!(
"DRM device names have no effect in this position (did you mean match.name?): {}",
self.cx.error3(name.span)
);
}
}
Ok(ConfigDrmDevice {
name: name.despan().map(|v| v.to_string()),
match_: match_val.parse_map(&mut DrmDeviceMatchParser(self.cx))?,
direct_scanout_enabled: direct_scanout_enabled.despan(),
gfx_api,
})
}
}
pub struct DrmDevicesParser<'a>(pub &'a Context<'a>);
impl<'a> Parser for DrmDevicesParser<'a> {
type Value = Vec<ConfigDrmDevice>;
type Error = DrmDeviceParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut res = vec![];
for el in array {
match el.parse(&mut DrmDeviceParser {
cx: self.0,
name_ok: true,
}) {
Ok(o) => res.push(o),
Err(e) => {
log::warn!("Could not parse drm device: {}", self.0.error(e));
}
}
}
Ok(res)
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
log::warn!(
"`drm-devices` value should be an array: {}",
self.0.error3(span)
);
DrmDeviceParser {
cx: self.0,
name_ok: true,
}
.parse_table(span, table)
.map(|v| vec![v])
}
}

View file

@ -0,0 +1,74 @@
use {
crate::{
config::{
context::Context,
extractor::{n32, opt, recover, str, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
DrmDeviceMatch,
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum DrmDeviceMatchParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct DrmDeviceMatchParser<'a>(pub &'a Context<'a>);
impl Parser for DrmDeviceMatchParser<'_> {
type Value = DrmDeviceMatch;
type Error = DrmDeviceMatchParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Table];
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut res = vec![];
for el in array {
match el.parse(self) {
Ok(m) => res.push(m),
Err(e) => {
log::error!("Could not parse match rule: {}", self.0.error(e));
}
}
}
Ok(DrmDeviceMatch::Any(res))
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (name, syspath, vendor, vendor_name, model, model_name, devnode) = ext.extract((
recover(opt(str("name"))),
recover(opt(str("syspath"))),
recover(opt(n32("pci-vendor"))),
recover(opt(str("vendor"))),
recover(opt(n32("pci-model"))),
recover(opt(str("model"))),
recover(opt(str("devnode"))),
))?;
if let Some(name) = name {
self.0.used.borrow_mut().drm_devices.push(name.into());
}
Ok(DrmDeviceMatch::All {
name: name.despan_into(),
syspath: syspath.despan_into(),
vendor: vendor.despan(),
vendor_name: vendor_name.despan_into(),
model: model.despan(),
model_name: model_name.despan_into(),
devnode: devnode.despan_into(),
})
}
}

View file

@ -0,0 +1,42 @@
use {
crate::{
config::{
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{StringParser, StringParserError},
},
toml::{
toml_span::{Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum EnvParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
String(#[from] StringParserError),
}
pub struct EnvParser;
impl Parser for EnvParser {
type Value = Vec<(String, String)>;
type Error = EnvParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
_span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut envs = vec![];
for (k, v) in table {
envs.push((k.value.to_string(), v.parse_map(&mut StringParser)?));
}
Ok(envs)
}
}

View file

@ -0,0 +1,91 @@
use {
crate::{
config::{
context::Context,
extractor::{arr, opt, str, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
env::{EnvParser, EnvParserError},
StringParser, StringParserError,
},
Exec,
},
toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ExecParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extractor(#[from] ExtractorError),
#[error(transparent)]
String(#[from] StringParserError),
#[error(transparent)]
Env(#[from] EnvParserError),
#[error("Array cannot be empty")]
Empty,
}
pub struct ExecParser<'a>(pub &'a Context<'a>);
impl Parser for ExecParser<'_> {
type Value = Exec;
type Error = ExecParserError;
const EXPECTED: &'static [DataType] = &[DataType::String, DataType::Array, DataType::Table];
fn parse_string(&mut self, _span: Span, string: &str) -> ParseResult<Self> {
Ok(Exec {
prog: string.to_string(),
args: vec![],
envs: vec![],
})
}
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
if array.is_empty() {
return Err(ExecParserError::Empty.spanned(span));
}
let prog = array[0].parse_map(&mut StringParser)?;
let mut args = vec![];
for v in &array[1..] {
args.push(v.parse_map(&mut StringParser)?);
}
Ok(Exec {
prog,
args,
envs: vec![],
})
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (prog, args_val, envs_val) =
ext.extract((str("prog"), opt(arr("args")), opt(val("env"))))?;
let mut args = vec![];
if let Some(args_val) = args_val {
for arg in args_val.value {
args.push(arg.parse_map(&mut StringParser)?);
}
}
let envs = match envs_val {
None => vec![],
Some(e) => e.parse_map(&mut EnvParser)?,
};
Ok(Exec {
prog: prog.value.to_string(),
args,
envs,
})
}
}

View file

@ -0,0 +1,34 @@
use {
crate::{
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
toml::toml_span::{Span, SpannedExt},
},
jay_config::video::GfxApi,
thiserror::Error,
};
pub struct GfxApiParser;
#[derive(Debug, Error)]
pub enum GfxApiParserError {
#[error(transparent)]
DataType(#[from] UnexpectedDataType),
#[error("Unknown API {0}")]
Unknown(String),
}
impl Parser for GfxApiParser {
type Value = GfxApi;
type Error = GfxApiParserError;
const EXPECTED: &'static [DataType] = &[DataType::String];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
use GfxApi::*;
let api = match string.to_ascii_lowercase().as_str() {
"opengl" => OpenGl,
"vulkan" => Vulkan,
_ => return Err(GfxApiParserError::Unknown(string.to_string()).spanned(span)),
};
Ok(api)
}
}

View file

@ -0,0 +1,45 @@
use {
crate::{
config::{
context::Context,
extractor::{n64, opt, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
std::time::Duration,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum IdleParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct IdleParser<'a>(pub &'a Context<'a>);
impl Parser for IdleParser<'_> {
type Value = Duration;
type Error = IdleParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (minutes, seconds) = ext.extract((opt(n64("minutes")), opt(n64("seconds"))))?;
let idle = Duration::from_secs(
minutes.despan().unwrap_or_default() * 60 + seconds.despan().unwrap_or_default(),
);
Ok(idle)
}
}

View file

@ -0,0 +1,205 @@
use {
crate::{
config::{
context::Context,
extractor::{bol, fltorint, opt, recover, str, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::input_match::{InputMatchParser, InputMatchParserError},
Input,
},
toml::{
toml_span::{DespanExt, Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::input::acceleration::{ACCEL_PROFILE_ADAPTIVE, ACCEL_PROFILE_FLAT},
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum InputParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error(transparent)]
Match(#[from] InputMatchParserError),
#[error("Transform matrix must have exactly two rows")]
TwoRows,
#[error("Transform matrix must have exactly two columns")]
TwoColumns,
#[error("Transform matrix entries must be floats")]
Float,
}
pub struct InputParser<'a> {
pub cx: &'a Context<'a>,
pub tag_ok: bool,
}
impl<'a> Parser for InputParser<'a> {
type Value = Input;
type Error = InputParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.cx, span, table);
let (
(
tag,
match_val,
accel_profile,
accel_speed,
tap_enabled,
tap_drag_enabled,
tap_drag_lock_enabled,
left_handed,
natural_scrolling,
px_per_wheel_scroll,
),
(transform_matrix,),
) = ext.extract((
(
opt(str("tag")),
val("match"),
recover(opt(str("accel-profile"))),
recover(opt(fltorint("accel-speed"))),
recover(opt(bol("tap-enabled"))),
recover(opt(bol("tap-drag-enabled"))),
recover(opt(bol("tap-drag-lock-enabled"))),
recover(opt(bol("left-handed"))),
recover(opt(bol("natural-scrolling"))),
recover(opt(fltorint("px-per-wheel-scroll"))),
),
(recover(opt(val("transform-matrix"))),),
))?;
let accel_profile = match accel_profile {
None => None,
Some(p) => match p.value.to_ascii_lowercase().as_str() {
"flat" => Some(ACCEL_PROFILE_FLAT),
"adaptive" => Some(ACCEL_PROFILE_ADAPTIVE),
v => {
log::warn!("Unknown accel-profile {v}: {}", self.cx.error3(p.span));
None
}
},
};
let transform_matrix = match transform_matrix {
None => None,
Some(matrix) => match matrix.parse(&mut TransformMatrixParser) {
Ok(v) => Some(v),
Err(e) => {
log::warn!("Could not parse transform matrix: {}", self.cx.error(e));
None
}
},
};
if let Some(tag) = tag {
if self.tag_ok {
self.cx.used.borrow_mut().defined_inputs.insert(tag.into());
} else {
log::warn!(
"Input tags have no effect in this position (did you mean match.tag?): {}",
self.cx.error3(tag.span)
);
}
}
Ok(Input {
tag: tag.despan_into(),
match_: match_val.parse_map(&mut InputMatchParser(self.cx))?,
accel_profile,
accel_speed: accel_speed.despan(),
tap_enabled: tap_enabled.despan(),
tap_drag_enabled: tap_drag_enabled.despan(),
tap_drag_lock_enabled: tap_drag_lock_enabled.despan(),
left_handed: left_handed.despan(),
natural_scrolling: natural_scrolling.despan(),
px_per_wheel_scroll: px_per_wheel_scroll.despan(),
transform_matrix,
})
}
}
pub struct InputsParser<'a>(pub &'a Context<'a>);
impl<'a> Parser for InputsParser<'a> {
type Value = Vec<Input>;
type Error = InputParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut res = vec![];
for el in array {
match el.parse(&mut InputParser {
cx: self.0,
tag_ok: true,
}) {
Ok(o) => res.push(o),
Err(e) => {
log::warn!("Could not parse output: {}", self.0.error(e));
}
}
}
Ok(res)
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
log::warn!(
"`outputs` value should be an array: {}",
self.0.error3(span)
);
InputParser {
cx: self.0,
tag_ok: true,
}
.parse_table(span, table)
.map(|v| vec![v])
}
}
struct TransformMatrixParser;
impl Parser for TransformMatrixParser {
type Value = [[f64; 2]; 2];
type Error = InputParserError;
const EXPECTED: &'static [DataType] = &[DataType::Array];
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
if array.len() != 2 {
return Err(InputParserError::TwoRows.spanned(span));
}
Ok([
array[0].parse(&mut TransformMatrixRowParser)?,
array[1].parse(&mut TransformMatrixRowParser)?,
])
}
}
struct TransformMatrixRowParser;
impl Parser for TransformMatrixRowParser {
type Value = [f64; 2];
type Error = InputParserError;
const EXPECTED: &'static [DataType] = &[DataType::Array];
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
if array.len() != 2 {
return Err(InputParserError::TwoColumns.spanned(span));
}
let extract = |v: &Spanned<Value>| match v.value {
Value::Float(f) => Ok(f),
Value::Integer(f) => Ok(f as _),
_ => Err(InputParserError::Float.spanned(v.span)),
};
Ok([extract(&array[0])?, extract(&array[1])?])
}
}

View file

@ -0,0 +1,98 @@
use {
crate::{
config::{
context::Context,
extractor::{bol, opt, str, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
InputMatch,
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum InputMatchParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct InputMatchParser<'a>(pub &'a Context<'a>);
impl<'a> Parser for InputMatchParser<'a> {
type Value = InputMatch;
type Error = InputMatchParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut res = vec![];
for el in array {
match el.parse(self) {
Ok(m) => res.push(m),
Err(e) => {
log::error!("Could not parse match rule: {}", self.0.error(e));
}
}
}
Ok(InputMatch::Any(res))
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (
(
tag,
name,
syspath,
devnode,
is_keyboard,
is_pointer,
is_touch,
is_tablet_tool,
is_tablet_pad,
is_gesture,
),
(is_switch,),
) = ext.extract((
(
opt(str("tag")),
opt(str("name")),
opt(str("syspath")),
opt(str("devnode")),
opt(bol("is-keyboard")),
opt(bol("is-pointer")),
opt(bol("is-touch")),
opt(bol("is-tablet-tool")),
opt(bol("is-tablet-pad")),
opt(bol("is-gesture")),
),
(opt(bol("is-switch")),),
))?;
if let Some(tag) = tag {
self.0.used.borrow_mut().inputs.push(tag.into());
}
Ok(InputMatch::All {
tag: tag.despan_into(),
name: name.despan_into(),
syspath: syspath.despan_into(),
devnode: devnode.despan_into(),
is_keyboard: is_keyboard.despan(),
is_pointer: is_pointer.despan(),
is_touch: is_touch.despan(),
is_tablet_tool: is_tablet_tool.despan(),
is_tablet_pad: is_tablet_pad.despan(),
is_gesture: is_gesture.despan(),
is_switch: is_switch.despan(),
})
}
}

View file

@ -0,0 +1,123 @@
use {
crate::{
config::{
context::Context,
extractor::{opt, str, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
ConfigKeymap,
},
toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::{
config_dir,
keyboard::{parse_keymap, Keymap},
},
std::{io, path::PathBuf},
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum KeymapParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extractor(#[from] ExtractorError),
#[error("The keymap is invalid")]
Invalid,
#[error("Keymap table must contain at least one of `name`, `map`")]
MissingField,
#[error("Keymap must have both `name` and `map` fields in this context")]
DefinitionRequired,
#[error("Could not read {0}")]
ReadFile(String, #[source] io::Error),
}
pub struct KeymapParser<'a> {
pub cx: &'a Context<'a>,
pub definition: bool,
}
impl Parser for KeymapParser<'_> {
type Value = ConfigKeymap;
type Error = KeymapParserError;
const EXPECTED: &'static [DataType] = &[DataType::String, DataType::Table];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
Ok(ConfigKeymap::Literal(parse(span, string)?))
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.cx, span, table);
let (mut name_val, mut map_val, mut path) =
ext.extract((opt(str("name")), opt(str("map")), opt(str("path"))))?;
if map_val.is_some() && path.is_some() {
log::warn!(
"Both `name` and `path` are specified. Ignoring `path`: {}",
self.cx.error3(span)
);
path = None;
}
let file_content;
if let Some(path) = path {
let mut root = PathBuf::from(config_dir());
root.push(path.value);
file_content = match std::fs::read_to_string(&root) {
Ok(c) => c,
Err(e) => {
return Err(KeymapParserError::ReadFile(root.display().to_string(), e)
.spanned(path.span))
}
};
map_val = Some(file_content.as_str().spanned(path.span));
}
if self.definition && (name_val.is_none() || map_val.is_none()) {
return Err(KeymapParserError::DefinitionRequired.spanned(span));
}
if !self.definition && map_val.is_some() {
if let Some(val) = name_val {
log::warn!(
"Cannot use both `name` and `map` in this position. Ignoring `name`: {}",
self.cx.error3(val.span)
);
}
name_val = None;
}
if let Some(name) = name_val {
if self.definition {
self.cx
.used
.borrow_mut()
.defined_keymaps
.insert(name.into());
} else {
self.cx.used.borrow_mut().keymaps.push(name.into());
}
}
let res = match (name_val, map_val) {
(Some(name_val), Some(map_val)) => ConfigKeymap::Defined {
name: name_val.value.to_string(),
map: parse(map_val.span, map_val.value)?,
},
(Some(name_val), None) => ConfigKeymap::Named(name_val.value.to_string()),
(None, Some(map_val)) => ConfigKeymap::Literal(parse(map_val.span, map_val.value)?),
(None, None) => return Err(KeymapParserError::MissingField.spanned(span)),
};
Ok(res)
}
}
fn parse(span: Span, string: &str) -> Result<Keymap, Spanned<KeymapParserError>> {
let map = parse_keymap(string);
match map.is_valid() {
true => Ok(map),
false => Err(KeymapParserError::Invalid.spanned(span)),
}
}

View file

@ -0,0 +1,37 @@
use {
crate::{
config::parser::{DataType, ParseResult, Parser, UnexpectedDataType},
toml::toml_span::{Span, SpannedExt},
},
jay_config::logging::LogLevel,
thiserror::Error,
};
pub struct LogLevelParser;
#[derive(Debug, Error)]
pub enum LogLevelParserError {
#[error(transparent)]
DataType(#[from] UnexpectedDataType),
#[error("Unknown log level {0}")]
Unknown(String),
}
impl Parser for LogLevelParser {
type Value = LogLevel;
type Error = LogLevelParserError;
const EXPECTED: &'static [DataType] = &[DataType::String];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
use LogLevel::*;
let level = match string.to_ascii_lowercase().as_str() {
"error" => Error,
"warn" | "warning" => Warn,
"info" => Info,
"debug" => Debug,
"trace" => Trace,
_ => return Err(LogLevelParserError::Unknown(string.to_string()).spanned(span)),
};
Ok(level)
}
}

View file

@ -0,0 +1,47 @@
use {
crate::{
config::{
context::Context,
extractor::{flt, opt, s32, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
Mode,
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ModeParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct ModeParser<'a>(pub &'a Context<'a>);
impl<'a> Parser for ModeParser<'a> {
type Value = Mode;
type Error = ModeParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (width, height, refresh_rate) =
ext.extract((s32("width"), s32("height"), opt(flt("refresh-rate"))))?;
Ok(Mode {
width: width.value,
height: height.value,
refresh_rate: refresh_rate.despan(),
})
}
}

View file

@ -0,0 +1,71 @@
use {
crate::{
config::{
keysyms::KEYSYMS,
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
},
toml::toml_span::{Span, SpannedExt},
},
jay_config::keyboard::{
mods::{Modifiers, ALT, CAPS, CTRL, LOCK, LOGO, MOD1, MOD2, MOD3, MOD4, MOD5, NUM, SHIFT},
ModifiedKeySym,
},
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ModifiedKeysymParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error("You cannot use more than one non-modifier key")]
MoreThanOneSym,
#[error("You must specify exactly one non-modifier key")]
MissingSym,
#[error("Unknown keysym {0}")]
UnknownKeysym(String),
}
pub struct ModifiedKeysymParser;
impl Parser for ModifiedKeysymParser {
type Value = ModifiedKeySym;
type Error = ModifiedKeysymParserError;
const EXPECTED: &'static [DataType] = &[DataType::String];
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
let mut modifiers = Modifiers(0);
let mut sym = None;
for part in string.split("-") {
let modifier = match part {
"shift" => SHIFT,
"lock" => LOCK,
"ctrl" => CTRL,
"mod1" => MOD1,
"mod2" => MOD2,
"mod3" => MOD3,
"mod4" => MOD4,
"mod5" => MOD5,
"caps" => CAPS,
"alt" => ALT,
"num" => NUM,
"logo" => LOGO,
_ => match KEYSYMS.get(part) {
Some(new) if sym.is_none() => {
sym = Some(*new);
continue;
}
Some(_) => return Err(ModifiedKeysymParserError::MoreThanOneSym.spanned(span)),
_ => {
return Err(ModifiedKeysymParserError::UnknownKeysym(part.to_string())
.spanned(span))
}
},
};
modifiers |= modifier;
}
match sym {
Some(s) => Ok(modifiers | s),
None => Err(ModifiedKeysymParserError::MissingSym.spanned(span)),
}
}
}

View file

@ -0,0 +1,150 @@
use {
crate::{
config::{
context::Context,
extractor::{fltorint, opt, recover, s32, str, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
mode::ModeParser,
output_match::{OutputMatchParser, OutputMatchParserError},
},
Output,
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::video::Transform,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum OutputParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error(transparent)]
Match(#[from] OutputMatchParserError),
}
pub struct OutputParser<'a> {
pub cx: &'a Context<'a>,
pub name_ok: bool,
}
impl<'a> Parser for OutputParser<'a> {
type Value = Output;
type Error = OutputParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.cx, span, table);
let (name, match_val, x, y, scale, transform, mode) = ext.extract((
opt(str("name")),
val("match"),
recover(opt(s32("x"))),
recover(opt(s32("y"))),
recover(opt(fltorint("scale"))),
recover(opt(str("transform"))),
opt(val("mode")),
))?;
let transform = match transform {
None => None,
Some(t) => match t.value {
"none" => Some(Transform::None),
"rotate-90" => Some(Transform::Rotate90),
"rotate-180" => Some(Transform::Rotate180),
"rotate-270" => Some(Transform::Rotate270),
"flip" => Some(Transform::Flip),
"flip-rotate-90" => Some(Transform::FlipRotate90),
"flip-rotate-180" => Some(Transform::FlipRotate180),
"flip-rotate-270" => Some(Transform::FlipRotate270),
_ => {
log::warn!("Unknown transform {}: {}", t.value, self.cx.error3(t.span));
None
}
},
};
let mode = match mode {
Some(mode) => match mode.parse(&mut ModeParser(self.cx)) {
Ok(m) => Some(m),
Err(e) => {
log::warn!("Could not parse mode: {}", self.cx.error(e));
None
}
},
None => None,
};
if let Some(name) = name {
if self.name_ok {
self.cx
.used
.borrow_mut()
.defined_outputs
.insert(name.into());
} else {
log::warn!(
"Output names have no effect in this position (did you mean match.name?): {}",
self.cx.error3(name.span)
);
}
}
Ok(Output {
name: name.despan().map(|v| v.to_string()),
match_: match_val.parse_map(&mut OutputMatchParser(self.cx))?,
x: x.despan(),
y: y.despan(),
scale: scale.despan(),
transform,
mode,
})
}
}
pub struct OutputsParser<'a>(pub &'a Context<'a>);
impl<'a> Parser for OutputsParser<'a> {
type Value = Vec<Output>;
type Error = OutputParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut res = vec![];
for el in array {
match el.parse(&mut OutputParser {
cx: self.0,
name_ok: true,
}) {
Ok(o) => res.push(o),
Err(e) => {
log::warn!("Could not parse output: {}", self.0.error(e));
}
}
}
Ok(res)
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
log::warn!(
"`outputs` value should be an array: {}",
self.0.error3(span)
);
OutputParser {
cx: self.0,
name_ok: true,
}
.parse_table(span, table)
.map(|v| vec![v])
}
}

View file

@ -0,0 +1,70 @@
use {
crate::{
config::{
context::Context,
extractor::{opt, str, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
OutputMatch,
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum OutputMatchParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extract(#[from] ExtractorError),
}
pub struct OutputMatchParser<'a>(pub &'a Context<'a>);
impl<'a> Parser for OutputMatchParser<'a> {
type Value = OutputMatch;
type Error = OutputMatchParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Table];
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
let mut res = vec![];
for el in array {
match el.parse(self) {
Ok(m) => res.push(m),
Err(e) => {
log::error!("Could not parse match rule: {}", self.0.error(e));
}
}
}
Ok(OutputMatch::Any(res))
}
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (name, connector, serial_number, manufacturer, model) = ext.extract((
opt(str("name")),
opt(str("connector")),
opt(str("serial-number")),
opt(str("manufacturer")),
opt(str("model")),
))?;
if let Some(name) = name {
self.0.used.borrow_mut().outputs.push(name.into());
}
Ok(OutputMatch::All {
name: name.despan_into(),
connector: connector.despan_into(),
serial_number: serial_number.despan_into(),
manufacturer: manufacturer.despan_into(),
model: model.despan_into(),
})
}
}

View file

@ -0,0 +1,72 @@
use {
crate::{
config::{
context::Context,
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{action::ActionParser, modified_keysym::ModifiedKeysymParser},
Action,
},
toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::keyboard::ModifiedKeySym,
std::collections::HashSet,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum ShortcutsParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
}
pub struct ShortcutsParser<'a>(pub &'a Context<'a>);
impl Parser for ShortcutsParser<'_> {
type Value = Vec<(ModifiedKeySym, Action)>;
type Error = ShortcutsParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
_span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut used_keys = HashSet::<Spanned<ModifiedKeySym>>::new();
let mut res = vec![];
for (key, value) in table.iter() {
let keysym = match ModifiedKeysymParser.parse_string(key.span, &key.value) {
Ok(k) => k,
Err(e) => {
log::warn!("Could not parse keysym: {}", self.0.error(e));
continue;
}
};
let action = match value.parse(&mut ActionParser(self.0)) {
Ok(a) => a,
Err(e) => {
log::warn!(
"Could not parse action for keysym {}: {}",
key.value,
self.0.error(e)
);
continue;
}
};
let spanned = keysym.spanned(key.span);
if let Some(prev) = used_keys.get(&spanned) {
log::warn!(
"Duplicate key overrides previous definition: {}",
self.0.error3(spanned.span)
);
log::info!("Previous definition here: {}", self.0.error3(prev.span));
}
used_keys.insert(spanned);
res.push((keysym, action));
}
Ok(res)
}
}

View file

@ -0,0 +1,81 @@
use {
crate::{
config::{
context::Context,
extractor::{opt, recover, str, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::exec::{ExecParser, ExecParserError},
Status,
},
toml::{
toml_span::{Span, Spanned, SpannedExt},
toml_value::Value,
},
},
indexmap::IndexMap,
jay_config::status::MessageFormat,
thiserror::Error,
};
#[derive(Debug, Error)]
pub enum StatusParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Exec(#[from] ExecParserError),
#[error(transparent)]
Extract(#[from] ExtractorError),
#[error("Expected `plain`, `pango`, or `i3bar` but found {0}")]
UnknownFormat(String),
}
pub struct StatusParser<'a>(pub &'a Context<'a>);
impl Parser for StatusParser<'_> {
type Value = Status;
type Error = StatusParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (format, exec, separator) = ext.extract((
opt(str("format")),
val("exec"),
recover(opt(str("i3bar-separator"))),
))?;
let format = match format {
Some(f) => match f.value {
"plain" => MessageFormat::Plain,
"pango" => MessageFormat::Pango,
"i3bar" => MessageFormat::I3Bar,
_ => {
return Err(
StatusParserError::UnknownFormat(f.value.to_string()).spanned(f.span)
)
}
},
_ => MessageFormat::Plain,
};
let exec = exec.parse_map(&mut ExecParser(self.0))?;
let separator = match separator {
None => None,
Some(sep) if format == MessageFormat::I3Bar => Some(sep.value.to_string()),
Some(sep) => {
log::warn!(
"Separator has no effect for format {format:?}: {}",
self.0.error3(sep.span)
);
None
}
};
Ok(Status {
format,
exec,
separator,
})
}
}

View file

@ -0,0 +1,119 @@
use {
crate::{
config::{
context::Context,
extractor::{opt, recover, s32, str, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::color::ColorParser,
Theme,
},
toml::{
toml_span::{DespanExt, Span, Spanned},
toml_value::Value,
},
},
indexmap::IndexMap,
thiserror::Error,
};
pub struct ThemeParser<'a>(pub &'a Context<'a>);
#[derive(Debug, Error)]
pub enum ThemeParserError {
#[error(transparent)]
Expected(#[from] UnexpectedDataType),
#[error(transparent)]
Extractor(#[from] ExtractorError),
}
impl Parser for ThemeParser<'_> {
type Value = Theme;
type Error = ThemeParserError;
const EXPECTED: &'static [DataType] = &[DataType::Table];
fn parse_table(
&mut self,
span: Span,
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (
(
attention_requested_bg_color,
bg_color,
bar_bg_color,
bar_status_text_color,
border_color,
captured_focused_title_bg_color,
captured_unfocused_title_bg_color,
focused_inactive_title_bg_color,
focused_inactive_title_text_color,
focused_title_bg_color,
),
(
focused_title_text_color,
separator_color,
unfocused_title_bg_color,
unfocused_title_text_color,
border_width,
title_height,
font,
),
) = ext.extract((
(
opt(val("attention-requested-bg-color")),
opt(val("bg-color")),
opt(val("bar-bg-color")),
opt(val("bar-status-text-color")),
opt(val("border-color")),
opt(val("captured-focused-title-bg-color")),
opt(val("captured-unfocused-title-bg-color")),
opt(val("focused-inactive-title-bg-color")),
opt(val("focused-inactive-title-text-color")),
opt(val("focused-title-bg-color")),
),
(
opt(val("focused-title-text-color")),
opt(val("separator-color")),
opt(val("unfocused-title-bg-color")),
opt(val("unfocused-title-text-color")),
recover(opt(s32("border-width"))),
recover(opt(s32("title-height"))),
recover(opt(str("font"))),
),
))?;
macro_rules! color {
($e:expr) => {
match $e {
None => None,
Some(v) => match v.parse(&mut ColorParser(self.0)) {
Ok(v) => Some(v),
Err(e) => {
log::warn!("Could not parse a color: {}", self.0.error(e));
None
}
},
}
};
}
Ok(Theme {
attention_requested_bg_color: color!(attention_requested_bg_color),
bg_color: color!(bg_color),
bar_bg_color: color!(bar_bg_color),
bar_status_text_color: color!(bar_status_text_color),
border_color: color!(border_color),
captured_focused_title_bg_color: color!(captured_focused_title_bg_color),
captured_unfocused_title_bg_color: color!(captured_unfocused_title_bg_color),
focused_inactive_title_bg_color: color!(focused_inactive_title_bg_color),
focused_inactive_title_text_color: color!(focused_inactive_title_text_color),
focused_title_bg_color: color!(focused_title_bg_color),
focused_title_text_color: color!(focused_title_text_color),
separator_color: color!(separator_color),
unfocused_title_bg_color: color!(unfocused_title_bg_color),
unfocused_title_text_color: color!(unfocused_title_text_color),
border_width: border_width.despan(),
title_height: title_height.despan(),
font: font.map(|f| f.value.to_string()),
})
}
}

View file

@ -0,0 +1,63 @@
use crate::{
config::parser::{ParseResult, Parser},
toml::{toml_span::Spanned, toml_value::Value},
};
impl Spanned<&Value> {
pub fn parse<P: Parser>(&self, parser: &mut P) -> ParseResult<P> {
self.value.parse(self.span, parser)
}
pub fn parse_map<P: Parser, E>(
&self,
parser: &mut P,
) -> Result<<P as Parser>::Value, Spanned<E>>
where
<P as Parser>::Error: Into<E>,
{
self.parse(parser).map_spanned_err(|e| e.into())
}
}
impl Spanned<Value> {
pub fn parse<P: Parser>(&self, parser: &mut P) -> ParseResult<P> {
self.as_ref().parse(parser)
}
pub fn parse_map<P: Parser, E>(
&self,
parser: &mut P,
) -> Result<<P as Parser>::Value, Spanned<E>>
where
<P as Parser>::Error: Into<E>,
{
self.as_ref().parse_map(parser)
}
}
pub trait SpannedErrorExt {
type T;
type E;
fn map_spanned_err<U, F>(self, f: F) -> Result<Self::T, Spanned<U>>
where
F: FnOnce(Self::E) -> U;
}
impl<T, E> SpannedErrorExt for Result<T, Spanned<E>> {
type T = T;
type E = E;
fn map_spanned_err<U, F>(self, f: F) -> Result<Self::T, Spanned<U>>
where
F: FnOnce(Self::E) -> U,
{
match self {
Ok(v) => Ok(v),
Err(e) => Err(Spanned {
span: e.span,
value: f(e.value),
}),
}
}
}

View file

@ -0,0 +1,17 @@
use crate::{
config::parser::{ParseResult, Parser},
toml::{toml_span::Span, toml_value::Value},
};
impl Value {
pub fn parse<P: Parser>(&self, span: Span, parser: &mut P) -> ParseResult<P> {
match self {
Value::String(a) => parser.parse_string(span, a),
Value::Integer(a) => parser.parse_integer(span, *a),
Value::Float(a) => parser.parse_float(span, *a),
Value::Boolean(a) => parser.parse_bool(span, *a),
Value::Array(a) => parser.parse_array(span, a),
Value::Table(a) => parser.parse_table(span, a),
}
}
}