Skip to content

Commit dad9aba

Browse files
authored
Merge pull request #232 from noahbald/feat/231-implement-state-actions
feat: #231 implement state actions
2 parents cc1e8f3 + e6c4b62 commit dad9aba

25 files changed

+388
-197
lines changed

.github/workflows/rust.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
- name: Run tests
5151
run: cargo test --verbose --workspace --profile release --target=${{ matrix.settings.target }} --exclude oxvg_napi --exclude oxvg_wasm
5252
- name: Build
53-
run: cargo build --verbose --bins --profile release --target=${{ matrix.settings.target }}
53+
run: cargo build --verbose --bins --profile release --target=${{ matrix.settings.target }} --package oxvg
5454
- name: List
5555
run: ls -R
5656
shell: bash

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/oxvg/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ oxvg_ast = { workspace = true, features = [
2727
"serialize",
2828
"clap",
2929
] }
30+
oxvg_collections = { workspace = true }
3031
oxvg_optimiser = { workspace = true, features = ["clap", "serde"] }
3132
oxvg_lint = { workspace = true, features = ["clap", "serde", "lsp"] }
3233

crates/oxvg/src/commands/action.rs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use oxvg_ast::{
1111
parse::roxmltree::parse_with_options,
1212
xmlwriter::{Indent, Options, Space},
1313
};
14+
use oxvg_collections::atom::Atom;
1415
use roxmltree::ParsingOptions;
1516

1617
use crate::{
@@ -177,15 +178,38 @@ impl RunCommand for ActionList {
177178
async fn run(self, _: Config) -> anyhow::Result<()> {
178179
let parts: HashSet<_> = self.command_list.into_iter().collect();
179180

180-
if parts.is_empty() || parts.contains(SELECT) {
181+
if parts.is_empty() || parts.contains(FORGET) {
181182
println!("# Select\n");
182-
println!(include_str!("../../../oxvg_actions/src/spec/select.md"));
183+
println!(include_str!(
184+
"../../../oxvg_actions/src/spec/state/forget.md"
185+
));
186+
}
187+
if parts.is_empty() || parts.contains(SELECT) {
188+
println!("# Forget\n");
189+
println!(include_str!(
190+
"../../../oxvg_actions/src/spec/state/select.md"
191+
));
192+
}
193+
if parts.is_empty() || parts.contains(SELECT_MORE) {
194+
println!("# Forget\n");
195+
println!(include_str!(
196+
"../../../oxvg_actions/src/spec/state/select-more.md"
197+
));
198+
}
199+
if parts.is_empty() || parts.contains(DESELECT) {
200+
println!("# Forget\n");
201+
println!(include_str!(
202+
"../../../oxvg_actions/src/spec/state/deselect.md"
203+
));
183204
}
184205
Ok(())
185206
}
186207
}
187208

209+
const FORGET: &str = "-forget";
188210
const SELECT: &str = "-select";
211+
const SELECT_MORE: &str = "-select-more";
212+
const DESELECT: &str = "-deselect";
189213

190214
fn parse(command_list: Vec<String>) -> anyhow::Result<Vec<oxvg_actions::Action<'static>>> {
191215
let mut actions = Vec::with_capacity(
@@ -196,16 +220,20 @@ fn parse(command_list: Vec<String>) -> anyhow::Result<Vec<oxvg_actions::Action<'
196220
);
197221
let mut parts = command_list.into_iter().peekable();
198222
while let Some(action) = parts.next() {
223+
let mut get_part = || {
224+
parts
225+
.next()
226+
.ok_or_else(|| anyhow::anyhow!("`{action}` missing query"))
227+
.map(Atom::from)
228+
};
199229
if !action.starts_with('-') {
200230
return Err(anyhow::anyhow!("Expected command name, found {action}"));
201231
}
202232
actions.push(match action.as_str() {
203-
SELECT => oxvg_actions::Action::Select(
204-
parts
205-
.next()
206-
.ok_or_else(|| anyhow::anyhow!("`{action}` missing query"))?
207-
.into(),
208-
),
233+
FORGET => oxvg_actions::Action::Forget,
234+
SELECT => oxvg_actions::Action::Select(get_part()?),
235+
SELECT_MORE => oxvg_actions::Action::SelectMore(get_part()?),
236+
DESELECT => oxvg_actions::Action::Deselect,
209237
_ => return Err(anyhow::anyhow!("Unknown action `{action}`")),
210238
});
211239
}

crates/oxvg_actions/src/actions.rs

Lines changed: 21 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,14 @@
1-
use oxvg_ast::{
2-
arena::Allocator,
3-
node::Ref,
4-
serialize::{Node, PrinterOptions, ToValue},
5-
};
6-
use oxvg_collections::{
7-
atom::Atom,
8-
attribute::{
9-
core_attrs::Integer,
10-
list_of::{ListOf, SpaceOrComma},
11-
},
12-
};
1+
use oxvg_ast::{arena::Allocator, node::Ref, serialize::Node};
2+
use oxvg_collections::atom::Atom;
133

14-
use oxvg_parse::Parse;
154
#[cfg(feature = "wasm")]
165
use tsify::Tsify;
176

7+
mod state;
8+
189
use crate::{
1910
error::Error,
20-
state::{DerivedState, State, StateElement},
21-
utils::create_oxvg_attr,
11+
state::{DerivedState, State},
2212
};
2313

2414
/// An actor holds a reference to a document to act upon.
@@ -38,16 +28,28 @@ pub struct Actor<'input, 'arena> {
3828
#[derive(Debug, Clone)]
3929
/// An action is a method that an actor can execute upon a document
4030
pub enum Action<'input> {
31+
/// See [`Actor::forget`]
32+
Forget,
4133
/// See [`Actor::select`]
4234
Select(Atom<'input>),
35+
/// See [`Actor::select_more`]
36+
SelectMore(Atom<'input>),
37+
/// See [`Actor::deselect`]
38+
Deselect,
4339
}
4440

4541
#[cfg(feature = "napi")]
4642
#[napi]
4743
/// An action is a method that an actor can execute upon a document
4844
pub enum ActionNapi {
45+
/// See [`Actor::forget`]
46+
Forget,
4947
/// See [`Actor::select`]
5048
Select(String),
49+
/// See [`Actor::select_more`]
50+
SelectMore(String),
51+
/// See [`Actor::deselect`]
52+
Deselect,
5153
}
5254

5355
impl<'input, 'arena> Actor<'input, 'arena> {
@@ -96,97 +98,11 @@ impl<'input, 'arena> Actor<'input, 'arena> {
9698
/// When the associated action fails
9799
pub fn dispatch(&mut self, action: Action<'input>) -> Result<(), Error<'input>> {
98100
match action {
99-
Action::Select(query) => self.select(query.as_str()),
101+
Action::Forget => self.forget(),
102+
Action::Select(query) => return self.select(query.as_str()),
103+
Action::SelectMore(query) => return self.select_more(query.as_str()),
104+
Action::Deselect => self.deselect(),
100105
}
101-
}
102-
103-
/// Updates the state of the actor to point to the elements matching the given selector.
104-
/// Elements can also be selected by a space/comma separated list of allocation-id
105-
/// integers.
106-
///
107-
/// # Errors
108-
///
109-
/// When root element is missing or the query cannot be parsed.
110-
///
111-
/// # Spec
112-
///
113-
#[doc = include_str!("./spec/select.md")]
114-
pub fn select(&mut self, query: &str) -> Result<(), Error<'input>> {
115-
let Some(root) = self.root.element() else {
116-
return Err(Error::NoRootElement);
117-
};
118-
self.state
119-
.record(&Action::Select(query.to_string().into()), &self.allocator);
120-
121-
let selections: ListOf<Integer, SpaceOrComma> =
122-
if query.chars().next().is_some_and(|c| c.is_ascii_digit()) {
123-
ListOf::parse_string(query).map_err(|err| Error::ParseError(err.to_string()))?
124-
} else {
125-
let elements = root
126-
.select(query)
127-
.map_err(|_| Error::InvalidSelector(query.to_string()))?;
128-
129-
#[allow(clippy::cast_possible_wrap)]
130-
let selections: Vec<_> = elements.map(|e| e.id() as Integer).collect();
131-
132-
ListOf {
133-
list: selections,
134-
separator: SpaceOrComma,
135-
}
136-
};
137-
self.state
138-
.get_selections(&self.allocator)
139-
.set_attribute(create_oxvg_attr(
140-
StateElement::SELECTION_IDS,
141-
selections
142-
.to_value_string(PrinterOptions::default())
143-
.map_err(|err| Error::SerializeError(err.to_string()))?
144-
.into(),
145-
));
146-
self.state.embed(self.root)?;
147106
Ok(())
148107
}
149108
}
150-
151-
#[test]
152-
fn select_empty() {
153-
oxvg_ast::parse::roxmltree::parse(
154-
r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100"/>"#,
155-
|root, allocator| {
156-
let mut actor = Actor::new(root, allocator).unwrap();
157-
158-
actor.select("svg").unwrap();
159-
insta::assert_snapshot!(actor.root.serialize().unwrap());
160-
161-
actor.select("1").unwrap();
162-
insta::assert_snapshot!(actor.root.serialize().unwrap());
163-
},
164-
)
165-
.unwrap();
166-
}
167-
168-
#[test]
169-
fn select() {
170-
oxvg_ast::parse::roxmltree::parse(
171-
r#"<svg xmlns="http://www.w3.org/2000/svg">
172-
<g color="black"/>
173-
<g color="BLACK"/>
174-
<path fill="rgb(64 64 64)"/>
175-
<path fill="rgb(64, 64, 64)"/>
176-
<path fill="rgb(86.27451%,86.666667%,87.058824%)"/>
177-
<path fill="rgb(-255,100,500)"/>
178-
</svg>"#,
179-
|root, allocator| {
180-
let mut actor = Actor::new(root, allocator).unwrap();
181-
182-
actor.select("path").unwrap();
183-
insta::assert_snapshot!(actor.root.serialize().unwrap());
184-
insta::assert_debug_snapshot!(actor.derive_state().unwrap());
185-
186-
actor.select("7, 9").unwrap();
187-
insta::assert_snapshot!(actor.root.serialize().unwrap());
188-
insta::assert_debug_snapshot!(actor.derive_state().unwrap());
189-
},
190-
)
191-
.unwrap();
192-
}

crates/oxvg_actions/src/snapshots/oxvg_actions__actions__select-2.snap renamed to crates/oxvg_actions/src/actions/snapshots/oxvg_actions__actions__state__test__select-2.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
source: crates/oxvg_actions/src/actions.rs
2+
source: crates/oxvg_actions/src/actions/state.rs
33
expression: actor.derive_state().unwrap()
44
---
55
DerivedState {

crates/oxvg_actions/src/snapshots/oxvg_actions__actions__select-3.snap renamed to crates/oxvg_actions/src/actions/snapshots/oxvg_actions__actions__state__test__select-3.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
source: crates/oxvg_actions/src/actions.rs
2+
source: crates/oxvg_actions/src/actions/state.rs
33
expression: actor.root.serialize().unwrap()
44
---
5-
<svg xmlns="http://www.w3.org/2000/svg" xmlns:oxvg="https://oxvg.noahwbaldwin.me"><g color="#000"/><g color="#000"/><path fill="#404040"/><path fill="#404040"/><path fill="#dcddde"/><path fill="#0064ff"/><oxvg:state><oxvg:history><oxvg:action oxvg:id="Select"><oxvg:arg>path</oxvg:arg></oxvg:action><oxvg:action oxvg:id="Select"><oxvg:arg>7, 9</oxvg:arg></oxvg:action></oxvg:history><oxvg:selection oxvg:ids="7, 9"/></oxvg:state></svg>
5+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:oxvg="https://oxvg.noahwbaldwin.me"><g color="#000"/><g color="#000"/><path fill="#404040"/><path fill="#404040"/><path fill="#dcddde"/><path fill="#0064ff"/><oxvg:state><oxvg:history><oxvg:action oxvg:id="Select"><oxvg:arg>path</oxvg:arg></oxvg:action><oxvg:action oxvg:id="Select"><oxvg:arg>7, 9</oxvg:arg></oxvg:action></oxvg:history><oxvg:selection oxvg:ids="7 9"/></oxvg:state></svg>

crates/oxvg_actions/src/snapshots/oxvg_actions__actions__select-4.snap renamed to crates/oxvg_actions/src/actions/snapshots/oxvg_actions__actions__state__test__select-4.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
source: crates/oxvg_actions/src/actions.rs
2+
source: crates/oxvg_actions/src/actions/state.rs
33
expression: actor.derive_state().unwrap()
44
---
55
DerivedState {

crates/oxvg_actions/src/snapshots/oxvg_actions__actions__select.snap renamed to crates/oxvg_actions/src/actions/snapshots/oxvg_actions__actions__state__test__select.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
source: crates/oxvg_actions/src/actions.rs
2+
source: crates/oxvg_actions/src/actions/state.rs
33
expression: actor.root.serialize().unwrap()
44
---
5-
<svg xmlns="http://www.w3.org/2000/svg" xmlns:oxvg="https://oxvg.noahwbaldwin.me"><g color="#000"/><g color="#000"/><path fill="#404040"/><path fill="#404040"/><path fill="#dcddde"/><path fill="#0064ff"/><oxvg:state><oxvg:history><oxvg:action oxvg:id="Select"><oxvg:arg>path</oxvg:arg></oxvg:action></oxvg:history><oxvg:selection oxvg:ids="7, 9, 11, 13"/></oxvg:state></svg>
5+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:oxvg="https://oxvg.noahwbaldwin.me"><g color="#000"/><g color="#000"/><path fill="#404040"/><path fill="#404040"/><path fill="#dcddde"/><path fill="#0064ff"/><oxvg:state><oxvg:history><oxvg:action oxvg:id="Select"><oxvg:arg>path</oxvg:arg></oxvg:action></oxvg:history><oxvg:selection oxvg:ids="7 9 11 13"/></oxvg:state></svg>

crates/oxvg_actions/src/snapshots/oxvg_actions__actions__select_empty-2.snap renamed to crates/oxvg_actions/src/actions/snapshots/oxvg_actions__actions__state__test__select_empty-2.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
source: crates/oxvg_actions/src/actions.rs
2+
source: crates/oxvg_actions/src/actions/state.rs
33
expression: actor.root.serialize().unwrap()
44
---
55
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100" xmlns:oxvg="https://oxvg.noahwbaldwin.me"><oxvg:state><oxvg:history><oxvg:action oxvg:id="Select"><oxvg:arg>svg</oxvg:arg></oxvg:action><oxvg:action oxvg:id="Select"><oxvg:arg>1</oxvg:arg></oxvg:action></oxvg:history><oxvg:selection oxvg:ids="1"/></oxvg:state></svg>

0 commit comments

Comments
 (0)