Query
This commit is contained in:
parent
c47e4cb496
commit
6d64d3e8e9
@ -95,6 +95,7 @@ fn global(math: Module, calc: Module) -> Module {
|
||||
global.define("counter", meta::counter);
|
||||
global.define("numbering", meta::numbering);
|
||||
global.define("state", meta::state);
|
||||
global.define("query", meta::query);
|
||||
|
||||
// Symbols.
|
||||
global.define("sym", symbols::sym());
|
||||
|
@ -8,6 +8,7 @@ mod heading;
|
||||
mod link;
|
||||
mod numbering;
|
||||
mod outline;
|
||||
mod query;
|
||||
mod reference;
|
||||
mod state;
|
||||
|
||||
@ -19,6 +20,7 @@ pub use self::heading::*;
|
||||
pub use self::link::*;
|
||||
pub use self::numbering::*;
|
||||
pub use self::outline::*;
|
||||
pub use self::query::*;
|
||||
pub use self::reference::*;
|
||||
pub use self::state::*;
|
||||
|
||||
|
69
library/src/meta/query.rs
Normal file
69
library/src/meta/query.rs
Normal file
@ -0,0 +1,69 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Find elements in the document.
|
||||
///
|
||||
/// Display: Query
|
||||
/// Category: meta
|
||||
/// Returns: content
|
||||
#[func]
|
||||
pub fn query(
|
||||
/// The thing to search for.
|
||||
target: Target,
|
||||
/// A function to format the results with.
|
||||
format: Func,
|
||||
) -> Value {
|
||||
QueryNode::new(target.0, format).pack().into()
|
||||
}
|
||||
|
||||
/// A query target.
|
||||
struct Target(Selector);
|
||||
|
||||
cast_from_value! {
|
||||
Target,
|
||||
label: Label => Self(Selector::Label(label)),
|
||||
func: Func => {
|
||||
let Some(id) = func.id() else {
|
||||
return Err("this function is not selectable".into());
|
||||
};
|
||||
|
||||
if !Content::new(id).can::<dyn Locatable>() {
|
||||
Err(eco_format!("cannot query for {}s", id.name))?;
|
||||
}
|
||||
|
||||
Self(Selector::Node(id, None))
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes a query.
|
||||
///
|
||||
/// Display: Query
|
||||
/// Category: special
|
||||
#[node(Locatable, Show)]
|
||||
pub struct QueryNode {
|
||||
/// The thing to search for.
|
||||
#[required]
|
||||
pub target: Selector,
|
||||
|
||||
/// The function to format the results with.
|
||||
#[required]
|
||||
pub format: Func,
|
||||
}
|
||||
|
||||
impl Show for QueryNode {
|
||||
fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||
if !vt.introspector.init() {
|
||||
return Ok(Content::empty());
|
||||
}
|
||||
|
||||
let id = self.0.stable_id().unwrap();
|
||||
let target = self.target();
|
||||
let (before, after) = vt.introspector.query_split(target, id);
|
||||
let func = self.format();
|
||||
let args = Args::new(func.span(), [encode(before), encode(after)]);
|
||||
Ok(func.call_detached(vt.world, args)?.display())
|
||||
}
|
||||
}
|
||||
|
||||
fn encode(list: Vec<&Content>) -> Value {
|
||||
Value::Array(list.into_iter().cloned().map(Value::Content).collect())
|
||||
}
|
@ -166,6 +166,22 @@ impl Introspector {
|
||||
self.all().filter(|node| selector.matches(node)).collect()
|
||||
}
|
||||
|
||||
/// Query for all metadata matches before the given id.
|
||||
pub fn query_split(
|
||||
&self,
|
||||
selector: Selector,
|
||||
id: StableId,
|
||||
) -> (Vec<&Content>, Vec<&Content>) {
|
||||
let mut iter = self.all();
|
||||
let before = iter
|
||||
.by_ref()
|
||||
.take_while(|node| node.stable_id() != Some(id))
|
||||
.filter(|node| selector.matches(node))
|
||||
.collect();
|
||||
let after = iter.filter(|node| selector.matches(node)).collect();
|
||||
(before, after)
|
||||
}
|
||||
|
||||
/// Find the page number for the given stable id.
|
||||
pub fn page(&self, id: StableId) -> NonZeroUsize {
|
||||
self.location(id).page
|
||||
|
BIN
tests/ref/meta/query.png
Normal file
BIN
tests/ref/meta/query.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 118 KiB |
30
tests/typ/meta/query.typ
Normal file
30
tests/typ/meta/query.typ
Normal file
@ -0,0 +1,30 @@
|
||||
// Test the query function.
|
||||
|
||||
---
|
||||
#set page(
|
||||
paper: "a7",
|
||||
margin: (y: 1cm, x: 0.5cm),
|
||||
header: {
|
||||
smallcaps[Typst Academy]
|
||||
h(1fr)
|
||||
query(heading, (before, after) => {
|
||||
let elem = if before.len() != 0 {
|
||||
before.last()
|
||||
} else if after.len() != 0 {
|
||||
after.first()
|
||||
}
|
||||
emph(elem.body)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
#outline()
|
||||
|
||||
= Introduction
|
||||
#lorem(35)
|
||||
|
||||
= Background
|
||||
#lorem(35)
|
||||
|
||||
= Approach
|
||||
#lorem(60)
|
Loading…
x
Reference in New Issue
Block a user