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,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()),
})
}
}