config: add client-rule infrastructure
This commit is contained in:
parent
17e715cde4
commit
fd2163d658
32 changed files with 1804 additions and 27 deletions
|
|
@ -66,6 +66,7 @@ pub enum SimpleCommand {
|
|||
ToggleFloatAboveFullscreen,
|
||||
SetFloatPinned(bool),
|
||||
ToggleFloatPinned,
|
||||
KillClient,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -198,6 +199,34 @@ pub enum OutputMatch {
|
|||
},
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct GenericMatch<Match> {
|
||||
pub name: Option<String>,
|
||||
pub not: Option<Box<Match>>,
|
||||
pub all: Option<Vec<Match>>,
|
||||
pub any: Option<Vec<Match>>,
|
||||
pub exactly: Option<MatchExactly<Match>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MatchExactly<Match> {
|
||||
pub num: usize,
|
||||
pub list: Vec<Match>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClientRule {
|
||||
pub name: Option<String>,
|
||||
pub match_: ClientMatch,
|
||||
pub action: Option<Action>,
|
||||
pub latch: Option<Action>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct ClientMatch {
|
||||
pub generic: GenericMatch<Self>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DrmDeviceMatch {
|
||||
Any(Vec<DrmDeviceMatch>),
|
||||
|
|
@ -395,6 +424,7 @@ pub struct Config {
|
|||
pub float: Option<Float>,
|
||||
pub named_actions: Vec<NamedAction>,
|
||||
pub max_action_depth: u64,
|
||||
pub client_rules: Vec<ClientRule>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ use {
|
|||
|
||||
pub mod action;
|
||||
mod actions;
|
||||
mod client_match;
|
||||
mod client_rule;
|
||||
mod color;
|
||||
pub mod color_management;
|
||||
pub mod config;
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ impl ActionParser<'_> {
|
|||
"pin-float" => SetFloatPinned(true),
|
||||
"unpin-float" => SetFloatPinned(false),
|
||||
"toggle-float-pinned" => ToggleFloatPinned,
|
||||
"kill-client" => KillClient,
|
||||
_ => {
|
||||
return Err(
|
||||
ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span)
|
||||
|
|
|
|||
104
toml-config/src/config/parsers/client_match.rs
Normal file
104
toml-config/src/config/parsers/client_match.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
ClientMatch, GenericMatch, MatchExactly,
|
||||
context::Context,
|
||||
extractor::{Extractor, ExtractorError, arr, n32, opt, str, val},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
},
|
||||
toml::{
|
||||
toml_span::{DespanExt, Span, Spanned},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ClientMatchParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
}
|
||||
|
||||
pub struct ClientMatchParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl Parser for ClientMatchParser<'_> {
|
||||
type Value = ClientMatch;
|
||||
type Error = ClientMatchParserError;
|
||||
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 ((name, not_val, all_val, any_val, exactly_val),) = ext.extract(((
|
||||
opt(str("name")),
|
||||
opt(val("not")),
|
||||
opt(arr("all")),
|
||||
opt(arr("any")),
|
||||
opt(val("exactly")),
|
||||
),))?;
|
||||
let mut not = None;
|
||||
if let Some(value) = not_val {
|
||||
not = Some(Box::new(value.parse(&mut ClientMatchParser(self.0))?));
|
||||
}
|
||||
macro_rules! list {
|
||||
($val:expr) => {{
|
||||
let mut list = None;
|
||||
if let Some(value) = $val {
|
||||
let mut res = vec![];
|
||||
for value in value.value {
|
||||
res.push(value.parse(&mut ClientMatchParser(self.0))?);
|
||||
}
|
||||
list = Some(res);
|
||||
}
|
||||
list
|
||||
}};
|
||||
}
|
||||
let all = list!(all_val);
|
||||
let any = list!(any_val);
|
||||
let mut exactly = None;
|
||||
if let Some(value) = exactly_val {
|
||||
exactly = Some(value.parse(&mut ClientMatchExactlyParser(self.0))?);
|
||||
}
|
||||
Ok(ClientMatch {
|
||||
generic: GenericMatch {
|
||||
name: name.despan_into(),
|
||||
not,
|
||||
all,
|
||||
any,
|
||||
exactly,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClientMatchExactlyParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl Parser for ClientMatchExactlyParser<'_> {
|
||||
type Value = MatchExactly<ClientMatch>;
|
||||
type Error = ClientMatchParserError;
|
||||
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 (num, list_val) = ext.extract((n32("num"), arr("list")))?;
|
||||
let mut list = vec![];
|
||||
for el in list_val.value {
|
||||
list.push(el.parse(&mut ClientMatchParser(self.0))?);
|
||||
}
|
||||
Ok(MatchExactly {
|
||||
num: num.value as _,
|
||||
list,
|
||||
})
|
||||
}
|
||||
}
|
||||
104
toml-config/src/config/parsers/client_rule.rs
Normal file
104
toml-config/src/config/parsers/client_rule.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
ClientMatch, ClientRule,
|
||||
context::Context,
|
||||
extractor::{Extractor, ExtractorError, opt, str, val},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
parsers::{
|
||||
action::{ActionParser, ActionParserError},
|
||||
client_match::{ClientMatchParser, ClientMatchParserError},
|
||||
},
|
||||
spanned::SpannedErrorExt,
|
||||
},
|
||||
toml::{
|
||||
toml_span::{DespanExt, Span, Spanned},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ClientRuleParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
#[error(transparent)]
|
||||
Match(#[from] ClientMatchParserError),
|
||||
#[error(transparent)]
|
||||
Action(ActionParserError),
|
||||
#[error(transparent)]
|
||||
Latch(ActionParserError),
|
||||
}
|
||||
|
||||
pub struct ClientRuleParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl Parser for ClientRuleParser<'_> {
|
||||
type Value = ClientRule;
|
||||
type Error = ClientRuleParserError;
|
||||
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 (name, match_val, action_val, latch_val) = ext.extract((
|
||||
opt(str("name")),
|
||||
opt(val("match")),
|
||||
opt(val("action")),
|
||||
opt(val("latch")),
|
||||
))?;
|
||||
let mut action = None;
|
||||
if let Some(value) = action_val {
|
||||
action = Some(
|
||||
value
|
||||
.parse(&mut ActionParser(self.0))
|
||||
.map_spanned_err(ClientRuleParserError::Action)?,
|
||||
);
|
||||
}
|
||||
let mut latch = None;
|
||||
if let Some(value) = latch_val {
|
||||
latch = Some(
|
||||
value
|
||||
.parse(&mut ActionParser(self.0))
|
||||
.map_spanned_err(ClientRuleParserError::Latch)?,
|
||||
);
|
||||
}
|
||||
let match_ = match match_val {
|
||||
None => ClientMatch::default(),
|
||||
Some(m) => m.parse_map(&mut ClientMatchParser(self.0))?,
|
||||
};
|
||||
Ok(ClientRule {
|
||||
name: name.despan_into(),
|
||||
match_,
|
||||
action,
|
||||
latch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClientRulesParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl Parser for ClientRulesParser<'_> {
|
||||
type Value = Vec<ClientRule>;
|
||||
type Error = ClientRuleParserError;
|
||||
const EXPECTED: &'static [DataType] = &[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 ClientRuleParser(self.0)) {
|
||||
Ok(o) => res.push(o),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse client rule: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ use {
|
|||
parsers::{
|
||||
action::ActionParser,
|
||||
actions::ActionsParser,
|
||||
client_rule::ClientRulesParser,
|
||||
color_management::ColorManagementParser,
|
||||
connector::ConnectorsParser,
|
||||
drm_device::DrmDevicesParser,
|
||||
|
|
@ -120,7 +121,7 @@ impl Parser for ConfigParser<'_> {
|
|||
ui_drag_val,
|
||||
xwayland_val,
|
||||
),
|
||||
(color_management_val, float_val, actions_val, max_action_depth_val),
|
||||
(color_management_val, float_val, actions_val, max_action_depth_val, client_rules_val),
|
||||
) = ext.extract((
|
||||
(
|
||||
opt(val("keymap")),
|
||||
|
|
@ -163,6 +164,7 @@ impl Parser for ConfigParser<'_> {
|
|||
opt(val("float")),
|
||||
opt(val("actions")),
|
||||
recover(opt(int("max-action-depth"))),
|
||||
opt(val("clients")),
|
||||
),
|
||||
))?;
|
||||
let mut keymap = None;
|
||||
|
|
@ -419,6 +421,13 @@ impl Parser for ConfigParser<'_> {
|
|||
}
|
||||
max_action_depth = value.value as _;
|
||||
}
|
||||
let mut client_rules = vec![];
|
||||
if let Some(value) = client_rules_val {
|
||||
match value.parse(&mut ClientRulesParser(self.0)) {
|
||||
Ok(v) => client_rules = v,
|
||||
Err(e) => log::warn!("Could not parse the client rules: {}", self.0.error(e)),
|
||||
}
|
||||
}
|
||||
Ok(Config {
|
||||
keymap,
|
||||
repeat_rate,
|
||||
|
|
@ -453,6 +462,7 @@ impl Parser for ConfigParser<'_> {
|
|||
float,
|
||||
named_actions,
|
||||
max_action_depth,
|
||||
client_rules,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ pub struct OutputMatchParser<'a>(pub &'a Context<'a>);
|
|||
impl Parser for OutputMatchParser<'_> {
|
||||
type Value = OutputMatch;
|
||||
type Error = OutputMatchParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Table];
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table, DataType::Array];
|
||||
|
||||
fn parse_array(&mut self, _span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
let mut res = vec![];
|
||||
|
|
|
|||
|
|
@ -1,17 +1,27 @@
|
|||
#![allow(clippy::len_zero, clippy::single_char_pattern, clippy::collapsible_if)]
|
||||
#![allow(
|
||||
clippy::len_zero,
|
||||
clippy::single_char_pattern,
|
||||
clippy::collapsible_if,
|
||||
clippy::collapsible_else_if
|
||||
)]
|
||||
|
||||
mod config;
|
||||
mod rules;
|
||||
mod toml;
|
||||
|
||||
use {
|
||||
crate::config::{
|
||||
Action, Config, ConfigConnector, ConfigDrmDevice, ConfigKeymap, ConnectorMatch,
|
||||
DrmDeviceMatch, Exec, Input, InputMatch, Output, OutputMatch, Shortcut, SimpleCommand,
|
||||
Status, Theme, parse_config,
|
||||
crate::{
|
||||
config::{
|
||||
Action, ClientRule, Config, ConfigConnector, ConfigDrmDevice, ConfigKeymap,
|
||||
ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, Output, OutputMatch, Shortcut,
|
||||
SimpleCommand, Status, Theme, parse_config,
|
||||
},
|
||||
rules::{MatcherTemp, RuleMapper},
|
||||
},
|
||||
ahash::{AHashMap, AHashSet},
|
||||
error_reporter::Report,
|
||||
jay_config::{
|
||||
client::Client,
|
||||
config, config_dir,
|
||||
exec::{Command, set_env, unset_env},
|
||||
get_workspace,
|
||||
|
|
@ -79,6 +89,16 @@ impl Action {
|
|||
}
|
||||
|
||||
fn into_fn_impl<B: FnBuilder>(self, state: &Rc<State>) -> B {
|
||||
macro_rules! client_action {
|
||||
($name:ident, $opt:expr) => {{
|
||||
let state = state.clone();
|
||||
B::new(move || {
|
||||
if let Some($name) = state.client.get() {
|
||||
$opt
|
||||
}
|
||||
})
|
||||
}};
|
||||
}
|
||||
let s = state.persistent.seat;
|
||||
match self {
|
||||
Action::SimpleCommand { cmd } => match cmd {
|
||||
|
|
@ -115,6 +135,7 @@ impl Action {
|
|||
SimpleCommand::ToggleFloatAboveFullscreen => B::new(toggle_float_above_fullscreen),
|
||||
SimpleCommand::SetFloatPinned(pinned) => B::new(move || s.set_float_pinned(pinned)),
|
||||
SimpleCommand::ToggleFloatPinned => B::new(move || s.toggle_float_pinned()),
|
||||
SimpleCommand::KillClient => client_action!(c, c.kill()),
|
||||
},
|
||||
Action::Multi { actions } => {
|
||||
let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect();
|
||||
|
|
@ -666,6 +687,8 @@ struct State {
|
|||
|
||||
action_depth_max: u64,
|
||||
action_depth: Cell<u64>,
|
||||
|
||||
client: Cell<Option<Client>>,
|
||||
}
|
||||
|
||||
impl Drop for State {
|
||||
|
|
@ -871,6 +894,16 @@ impl State {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn with_client(&self, client: Client, check: bool, f: impl FnOnce()) {
|
||||
let mut opt = Some(client);
|
||||
if check && client.does_not_exist() {
|
||||
opt = None;
|
||||
}
|
||||
self.client.set(opt);
|
||||
f();
|
||||
self.client.set(None);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
|
|
@ -887,6 +920,8 @@ struct PersistentState {
|
|||
binds: RefCell<AHashSet<ModifiedKeySym>>,
|
||||
#[expect(clippy::type_complexity)]
|
||||
actions: RefCell<AHashMap<Rc<String>, Rc<dyn Fn()>>>,
|
||||
client_rules: Cell<Vec<MatcherTemp<ClientRule>>>,
|
||||
client_rule_mapper: RefCell<Option<RuleMapper<ClientRule>>>,
|
||||
}
|
||||
|
||||
fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
|
||||
|
|
@ -967,7 +1002,11 @@ fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
|
|||
io_outputs: Default::default(),
|
||||
action_depth_max: config.max_action_depth,
|
||||
action_depth: Cell::new(0),
|
||||
client: Default::default(),
|
||||
});
|
||||
let (client_rules, client_rule_mapper) = state.create_rules(&config.client_rules);
|
||||
persistent.client_rules.set(client_rules);
|
||||
*state.persistent.client_rule_mapper.borrow_mut() = Some(client_rule_mapper);
|
||||
state.set_status(&config.status);
|
||||
persistent.actions.borrow_mut().clear();
|
||||
for a in config.named_actions {
|
||||
|
|
@ -1190,10 +1229,15 @@ pub fn configure() {
|
|||
seat: default_seat(),
|
||||
binds: Default::default(),
|
||||
actions: Default::default(),
|
||||
client_rules: Default::default(),
|
||||
client_rule_mapper: Default::default(),
|
||||
});
|
||||
{
|
||||
let p = persistent.clone();
|
||||
on_unload(move || p.actions.borrow_mut().clear());
|
||||
on_unload(move || {
|
||||
p.actions.borrow_mut().clear();
|
||||
p.client_rule_mapper.borrow_mut().take();
|
||||
});
|
||||
}
|
||||
load_config(true, &persistent);
|
||||
}
|
||||
|
|
|
|||
258
toml-config/src/rules.rs
Normal file
258
toml-config/src/rules.rs
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
use {
|
||||
crate::{
|
||||
State,
|
||||
config::{ClientMatch, ClientRule, GenericMatch},
|
||||
},
|
||||
ahash::{AHashMap, AHashSet},
|
||||
jay_config::client::{ClientCriterion, ClientMatcher},
|
||||
std::{mem::ManuallyDrop, rc::Rc},
|
||||
};
|
||||
|
||||
impl State {
|
||||
pub fn create_rules<R>(self: &Rc<Self>, rules: &[R]) -> (Vec<MatcherTemp<R>>, RuleMapper<R>)
|
||||
where
|
||||
R: Rule,
|
||||
{
|
||||
let mut names = AHashMap::new();
|
||||
for (idx, rule) in rules.iter().enumerate() {
|
||||
if let Some(name) = rule.name() {
|
||||
names.insert(name.to_string(), idx);
|
||||
}
|
||||
}
|
||||
let mut mapper = RuleMapper {
|
||||
state: self.clone(),
|
||||
names,
|
||||
pending: Default::default(),
|
||||
mapped: Default::default(),
|
||||
};
|
||||
let mut matchers = vec![];
|
||||
for idx in 0..rules.len() {
|
||||
if let Some(matcher) = mapper.map_rule(rules, idx) {
|
||||
matchers.push(MatcherTemp(matcher));
|
||||
}
|
||||
}
|
||||
(matchers, mapper)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Rule: Sized + 'static {
|
||||
type Match;
|
||||
type Matcher: Copy + 'static;
|
||||
type Criterion<'a>;
|
||||
|
||||
const NAME_UPPER: &str;
|
||||
const NAME_LOWER: &str;
|
||||
|
||||
fn name(&self) -> Option<&str>;
|
||||
fn match_(&self) -> &Self::Match;
|
||||
fn generic(m: &Self::Match) -> &GenericMatch<Self::Match>;
|
||||
fn map_custom(
|
||||
state: &Rc<State>,
|
||||
all: &mut Vec<MatcherTemp<Self>>,
|
||||
match_: &Self::Match,
|
||||
) -> Option<()>;
|
||||
fn create(c: Self::Criterion<'_>) -> Self::Matcher;
|
||||
fn destroy(m: Self::Matcher);
|
||||
fn bind(&self, state: &Rc<State>, matcher: Self::Matcher);
|
||||
|
||||
fn gen_matcher(m: Self::Matcher) -> Self::Criterion<'static>;
|
||||
fn gen_not<'a, 'b: 'a>(m: &'a Self::Criterion<'b>) -> Self::Criterion<'a>;
|
||||
fn gen_all<'a, 'b: 'a>(m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a>;
|
||||
fn gen_any<'a, 'b: 'a>(m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a>;
|
||||
fn gen_exactly<'a, 'b: 'a>(n: usize, m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a>;
|
||||
}
|
||||
|
||||
impl Rule for ClientRule {
|
||||
type Match = ClientMatch;
|
||||
type Matcher = ClientMatcher;
|
||||
type Criterion<'a> = ClientCriterion<'a>;
|
||||
|
||||
const NAME_UPPER: &str = "Client";
|
||||
const NAME_LOWER: &str = "client";
|
||||
|
||||
fn name(&self) -> Option<&str> {
|
||||
self.name.as_deref()
|
||||
}
|
||||
|
||||
fn match_(&self) -> &Self::Match {
|
||||
&self.match_
|
||||
}
|
||||
|
||||
fn generic(m: &Self::Match) -> &GenericMatch<Self::Match> {
|
||||
&m.generic
|
||||
}
|
||||
|
||||
fn map_custom(
|
||||
_state: &Rc<State>,
|
||||
_all: &mut Vec<MatcherTemp<Self>>,
|
||||
_match_: &Self::Match,
|
||||
) -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn create(c: Self::Criterion<'_>) -> Self::Matcher {
|
||||
c.to_matcher()
|
||||
}
|
||||
|
||||
fn destroy(m: Self::Matcher) {
|
||||
m.destroy();
|
||||
}
|
||||
|
||||
fn bind(&self, state: &Rc<State>, matcher: Self::Matcher) {
|
||||
let state = state.clone();
|
||||
macro_rules! latch {
|
||||
($g:ident, $client:ident) => {
|
||||
let g = $g.clone();
|
||||
let state = state.clone();
|
||||
$client.latch(move || {
|
||||
state.with_client($client.client(), true, || g());
|
||||
});
|
||||
};
|
||||
}
|
||||
if let Some(action) = &self.action {
|
||||
let f = action.clone().into_fn(&state);
|
||||
if let Some(action) = &self.latch {
|
||||
let g = action.clone().into_rc_fn(&state);
|
||||
let state = state.clone();
|
||||
matcher.bind(move |client| {
|
||||
state.with_client(client.client(), false, &f);
|
||||
latch!(g, client);
|
||||
});
|
||||
} else {
|
||||
matcher.bind(move |client| {
|
||||
state.with_client(client.client(), false, &f);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if let Some(action) = &self.latch {
|
||||
let g = action.clone().into_rc_fn(&state);
|
||||
matcher.bind(move |client| {
|
||||
latch!(g, client);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_matcher(m: Self::Matcher) -> Self::Criterion<'static> {
|
||||
ClientCriterion::Matcher(m)
|
||||
}
|
||||
|
||||
fn gen_not<'a, 'b: 'a>(m: &'a Self::Criterion<'b>) -> Self::Criterion<'a> {
|
||||
ClientCriterion::Not(m)
|
||||
}
|
||||
|
||||
fn gen_all<'a, 'b: 'a>(m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a> {
|
||||
ClientCriterion::All(m)
|
||||
}
|
||||
|
||||
fn gen_any<'a, 'b: 'a>(m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a> {
|
||||
ClientCriterion::Any(m)
|
||||
}
|
||||
|
||||
fn gen_exactly<'a, 'b: 'a>(n: usize, m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a> {
|
||||
ClientCriterion::Exactly(n, m)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RuleMapper<R>
|
||||
where
|
||||
R: Rule,
|
||||
{
|
||||
state: Rc<State>,
|
||||
names: AHashMap<String, usize>,
|
||||
pending: AHashSet<usize>,
|
||||
mapped: AHashMap<usize, R::Matcher>,
|
||||
}
|
||||
|
||||
pub struct MatcherTemp<R>(R::Matcher)
|
||||
where
|
||||
R: Rule;
|
||||
|
||||
impl<R> Drop for MatcherTemp<R>
|
||||
where
|
||||
R: Rule,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
R::destroy(self.0);
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> RuleMapper<R>
|
||||
where
|
||||
R: Rule,
|
||||
{
|
||||
fn map_rule(&mut self, rules: &[R], idx: usize) -> Option<R::Matcher> {
|
||||
if let Some(matcher) = self.mapped.get(&idx) {
|
||||
return Some(*matcher);
|
||||
}
|
||||
if !self.pending.insert(idx) {
|
||||
if let Some(name) = rules.get(idx).and_then(|r| r.name()) {
|
||||
log::error!("{} rule `{name}` has a loop", R::NAME_UPPER);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
let rule = &rules[idx];
|
||||
let matcher = self.map_match(rules, rule.match_())?;
|
||||
self.mapped.insert(idx, matcher);
|
||||
rule.bind(&self.state, matcher);
|
||||
Some(matcher)
|
||||
}
|
||||
|
||||
fn map_temporary_match(&mut self, rules: &[R], matcher: &R::Match) -> Option<MatcherTemp<R>> {
|
||||
self.map_match(rules, matcher).map(MatcherTemp)
|
||||
}
|
||||
|
||||
fn map_match(&mut self, rules: &[R], matcher: &R::Match) -> Option<R::Matcher> {
|
||||
let mut all = vec![];
|
||||
self.map_generic_match(rules, &mut all, R::generic(matcher))?;
|
||||
R::map_custom(&self.state, &mut all, matcher)?;
|
||||
if all.len() == 1 {
|
||||
return Some(ManuallyDrop::new(all.pop().unwrap()).0);
|
||||
}
|
||||
let all: Vec<_> = all.iter().map(|m| R::gen_matcher(m.0)).collect();
|
||||
Some(R::create(R::gen_all(&all)))
|
||||
}
|
||||
|
||||
fn map_generic_match(
|
||||
&mut self,
|
||||
rules: &[R],
|
||||
all: &mut Vec<MatcherTemp<R>>,
|
||||
matcher: &GenericMatch<R::Match>,
|
||||
) -> Option<()> {
|
||||
let m = |c: R::Criterion<'_>| MatcherTemp(R::create(c));
|
||||
if let Some(name) = &matcher.name {
|
||||
let Some(&idx) = self.names.get(&**name) else {
|
||||
log::error!("There is no {} rule named `{name}`", R::NAME_LOWER);
|
||||
return None;
|
||||
};
|
||||
let matcher = self.map_rule(rules, idx)?;
|
||||
all.push(m(R::gen_matcher(matcher)));
|
||||
}
|
||||
if let Some(not) = &matcher.not {
|
||||
let matcher = self.map_temporary_match(rules, not)?;
|
||||
all.push(m(R::gen_not(&R::gen_matcher(matcher.0))));
|
||||
}
|
||||
if let Some(list) = &matcher.all {
|
||||
for match_ in list {
|
||||
all.push(self.map_temporary_match(rules, match_)?);
|
||||
}
|
||||
}
|
||||
if let Some(list) = &matcher.any {
|
||||
let mut any = vec![];
|
||||
for match_ in list {
|
||||
any.push(self.map_temporary_match(rules, match_)?);
|
||||
}
|
||||
let any: Vec<_> = any.iter().map(|m| R::gen_matcher(m.0)).collect();
|
||||
all.push(m(R::gen_any(&any)));
|
||||
}
|
||||
if let Some(exactly) = &matcher.exactly {
|
||||
let mut list = vec![];
|
||||
for match_ in &exactly.list {
|
||||
list.push(self.map_temporary_match(rules, match_)?);
|
||||
}
|
||||
let list: Vec<_> = list.iter().map(|m| R::gen_matcher(m.0)).collect();
|
||||
all.push(m(R::gen_exactly(exactly.num, &list)))
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue