feat: introduce Token struct
This commit is contained in:
parent
9004de9930
commit
6c27a9257f
23 changed files with 586 additions and 262 deletions
|
|
@ -1,6 +1,6 @@
|
|||
use crate::syntax::{SyntaxElement, SyntaxKind, SyntaxToken};
|
||||
use crate::syntax::SyntaxKind;
|
||||
|
||||
use super::AffiliatedKeyword;
|
||||
use super::{filter_token, AffiliatedKeyword, Token};
|
||||
|
||||
impl AffiliatedKeyword {
|
||||
///
|
||||
|
|
@ -8,13 +8,16 @@ impl AffiliatedKeyword {
|
|||
/// use orgize::{Org, ast::AffiliatedKeyword};
|
||||
///
|
||||
/// let keyword = Org::parse("#+CAPTION: VALUE\nabc").first_node::<AffiliatedKeyword>().unwrap();
|
||||
/// assert_eq!(keyword.key().unwrap().text(), "CAPTION");
|
||||
/// assert_eq!(keyword.key(), "CAPTION");
|
||||
/// ```
|
||||
pub fn key(&self) -> Option<SyntaxToken> {
|
||||
self.syntax.children_with_tokens().find_map(|it| match it {
|
||||
SyntaxElement::Token(t) if t.kind() == SyntaxKind::TEXT => Some(t),
|
||||
_ => None,
|
||||
})
|
||||
pub fn key(&self) -> Token {
|
||||
self.syntax
|
||||
.children_with_tokens()
|
||||
.find_map(filter_token(SyntaxKind::TEXT))
|
||||
.unwrap_or_else(|| {
|
||||
debug_assert!(false, "keyword must contains TEXT");
|
||||
Token::default()
|
||||
})
|
||||
}
|
||||
|
||||
///
|
||||
|
|
@ -24,17 +27,14 @@ impl AffiliatedKeyword {
|
|||
/// let keyword = Org::parse("#+CAPTION: VALUE\nabc").first_node::<AffiliatedKeyword>().unwrap();
|
||||
/// assert!(keyword.optional().is_none());
|
||||
/// let keyword = Org::parse("#+CAPTION[OPTIONAL]: VALUE\nabc").first_node::<AffiliatedKeyword>().unwrap();
|
||||
/// assert_eq!(keyword.optional().unwrap().text(), "OPTIONAL");
|
||||
/// assert_eq!(keyword.optional().unwrap(), "OPTIONAL");
|
||||
/// ```
|
||||
pub fn optional(&self) -> Option<SyntaxToken> {
|
||||
pub fn optional(&self) -> Option<Token> {
|
||||
self.syntax
|
||||
.children_with_tokens()
|
||||
.skip_while(|it| it.kind() != SyntaxKind::L_BRACKET)
|
||||
.nth(1)
|
||||
.and_then(|it| match it {
|
||||
SyntaxElement::Token(t) if t.kind() == SyntaxKind::TEXT => Some(t),
|
||||
_ => None,
|
||||
})
|
||||
.and_then(filter_token(SyntaxKind::TEXT))
|
||||
}
|
||||
|
||||
///
|
||||
|
|
@ -42,17 +42,14 @@ impl AffiliatedKeyword {
|
|||
/// use orgize::{Org, ast::AffiliatedKeyword};
|
||||
///
|
||||
/// let keyword = Org::parse("#+CAPTION: VALUE\nabc").first_node::<AffiliatedKeyword>().unwrap();
|
||||
/// assert_eq!(keyword.value().unwrap().text(), " VALUE");
|
||||
/// assert_eq!(keyword.value().unwrap(), " VALUE");
|
||||
/// let keyword = Org::parse("#+CAPTION[OPTIONAL]:VALUE\nabc").first_node::<AffiliatedKeyword>().unwrap();
|
||||
/// assert_eq!(keyword.value().unwrap().text(), "VALUE");
|
||||
/// assert_eq!(keyword.value().unwrap(), "VALUE");
|
||||
/// ```
|
||||
pub fn value(&self) -> Option<SyntaxToken> {
|
||||
pub fn value(&self) -> Option<Token> {
|
||||
self.syntax
|
||||
.children_with_tokens()
|
||||
.filter_map(|it| match it {
|
||||
SyntaxElement::Token(t) if t.kind() == SyntaxKind::TEXT => Some(t),
|
||||
_ => None,
|
||||
})
|
||||
.filter_map(filter_token(SyntaxKind::TEXT))
|
||||
.last()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
58
src/ast/clock.rs
Normal file
58
src/ast/clock.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use rowan::ast::support;
|
||||
|
||||
use crate::{ast::Token, SyntaxKind};
|
||||
|
||||
use super::{Clock, Timestamp};
|
||||
|
||||
impl Clock {
|
||||
pub fn value(&self) -> Option<Timestamp> {
|
||||
support::child(&self.syntax)
|
||||
}
|
||||
|
||||
/// ```rust
|
||||
/// use orgize::{Org, ast::Clock};
|
||||
///
|
||||
/// let clock = Org::parse("CLOCK: [2003-09-16 Tue 09:39]").first_node::<Clock>().unwrap();
|
||||
/// assert!(clock.duration().is_none());
|
||||
/// let clock = Org::parse("CLOCK: [2003-09-16 Tue 09:39] =>12:00").first_node::<Clock>().unwrap();
|
||||
/// assert_eq!(clock.duration().unwrap(), "12:00");
|
||||
///
|
||||
/// ```
|
||||
pub fn duration(&self) -> Option<Token> {
|
||||
self.syntax
|
||||
.children_with_tokens()
|
||||
.skip_while(|t| t.kind() != SyntaxKind::DOUBLE_ARROW)
|
||||
.skip(1)
|
||||
.find(|t| t.kind() != SyntaxKind::WHITESPACE)
|
||||
.map(|e| {
|
||||
debug_assert!(e.kind() == SyntaxKind::TEXT);
|
||||
Token(e.into_token())
|
||||
})
|
||||
}
|
||||
|
||||
/// ```rust
|
||||
/// use orgize::{Org, ast::Clock};
|
||||
///
|
||||
/// let clock = Org::parse("CLOCK: [2003-09-16 Tue 09:39]").first_node::<Clock>().unwrap();
|
||||
/// assert!(!clock.is_closed());
|
||||
/// let clock = Org::parse("CLOCK: [2003-09-16 Tue 09:39] =>12:00").first_node::<Clock>().unwrap();
|
||||
/// assert!(clock.is_closed());
|
||||
/// ```
|
||||
pub fn is_closed(&self) -> bool {
|
||||
self.syntax
|
||||
.children_with_tokens()
|
||||
.any(|t| t.kind() == SyntaxKind::DOUBLE_ARROW)
|
||||
}
|
||||
|
||||
/// ```rust
|
||||
/// use orgize::{Org, ast::Clock};
|
||||
///
|
||||
/// let clock = Org::parse("CLOCK: [2003-09-16 Tue 09:39]").first_node::<Clock>().unwrap();
|
||||
/// assert!(clock.is_running());
|
||||
/// let clock = Org::parse("CLOCK: [2003-09-16 Tue 09:39] =>12:00").first_node::<Clock>().unwrap();
|
||||
/// assert!(!clock.is_running());
|
||||
/// ```
|
||||
pub fn is_running(&self) -> bool {
|
||||
!self.is_closed()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use super::{filter_token, SyntaxKind::*};
|
||||
use crate::{ast::PropertyDrawer, syntax::SyntaxToken};
|
||||
use super::{filter_token, SyntaxKind, Token};
|
||||
use crate::ast::PropertyDrawer;
|
||||
|
||||
impl PropertyDrawer {
|
||||
/// ```rust
|
||||
|
|
@ -11,12 +11,12 @@ impl PropertyDrawer {
|
|||
/// let drawer = org.first_node::<PropertyDrawer>().unwrap();
|
||||
/// assert_eq!(drawer.iter().count(), 2);
|
||||
/// ```
|
||||
pub fn iter(&self) -> impl Iterator<Item = (SyntaxToken, SyntaxToken)> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = (Token, Token)> {
|
||||
self.node_properties().filter_map(|property| {
|
||||
let mut texts = property
|
||||
.syntax
|
||||
.children_with_tokens()
|
||||
.filter_map(filter_token(TEXT));
|
||||
.filter_map(filter_token(SyntaxKind::TEXT));
|
||||
|
||||
Some((texts.next()?, texts.next()?))
|
||||
})
|
||||
|
|
@ -27,12 +27,11 @@ impl PropertyDrawer {
|
|||
///
|
||||
/// let org = Org::parse("* Heading\n:PROPERTIES:\n:CUSTOM_ID: someid\n:ID: id\n:END:");
|
||||
/// let drawer = org.first_node::<PropertyDrawer>().unwrap();
|
||||
/// assert_eq!(drawer.get("CUSTOM_ID").unwrap().text(), "someid");
|
||||
/// assert_eq!(drawer.get("ID").unwrap().text(), "id");
|
||||
/// assert_eq!(drawer.get("CUSTOM_ID").unwrap(), "someid");
|
||||
/// assert_eq!(drawer.get("ID").unwrap(), "id");
|
||||
/// ```
|
||||
pub fn get(&self, key: &str) -> Option<SyntaxToken> {
|
||||
self.iter()
|
||||
.find_map(|(k, v)| (k.text() == key).then_some(v))
|
||||
pub fn get(&self, key: &str) -> Option<Token> {
|
||||
self.iter().find_map(|(k, v)| (k == key).then_some(v))
|
||||
}
|
||||
|
||||
/// ```rust
|
||||
|
|
@ -44,10 +43,8 @@ impl PropertyDrawer {
|
|||
/// assert_eq!(map.len(), 1);
|
||||
/// assert_eq!(map.get("CUSTOM_ID").unwrap(), "id");
|
||||
/// ```
|
||||
pub fn to_hash_map(&self) -> HashMap<String, String> {
|
||||
self.iter()
|
||||
.map(|(k, v)| (k.text().into(), v.text().into()))
|
||||
.collect()
|
||||
pub fn to_hash_map(&self) -> HashMap<Token, Token> {
|
||||
self.iter().collect()
|
||||
}
|
||||
|
||||
#[cfg(feature = "indexmap")]
|
||||
|
|
@ -57,11 +54,11 @@ impl PropertyDrawer {
|
|||
/// let org = Org::parse("* Heading\n:PROPERTIES:\n:CUSTOM_ID: someid\n:ID: id\n:END:");
|
||||
/// let drawer = org.first_node::<PropertyDrawer>().unwrap();
|
||||
/// let map = drawer.to_index_map();
|
||||
/// assert_eq!(map.get_index(1).unwrap(), (&"ID".to_string(), &"id".to_string()));
|
||||
/// let item1 = map.get_index(1).unwrap();
|
||||
/// assert_eq!(item1.0, "ID");
|
||||
/// assert_eq!(item1.1, "id");
|
||||
/// ```
|
||||
pub fn to_index_map(&self) -> indexmap::IndexMap<String, String> {
|
||||
self.iter()
|
||||
.map(|(k, v)| (k.text().into(), v.text().into()))
|
||||
.collect()
|
||||
pub fn to_index_map(&self) -> indexmap::IndexMap<Token, Token> {
|
||||
self.iter().collect()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,8 @@ impl Entity {
|
|||
.syntax
|
||||
.children_with_tokens()
|
||||
.find_map(filter_token(SyntaxKind::TEXT))?;
|
||||
let token = token.text();
|
||||
|
||||
ENTITIES.iter().find(|i| i.0 == token)
|
||||
ENTITIES.iter().find(|i| i.0 == token.as_ref())
|
||||
}
|
||||
|
||||
/// Entity name
|
||||
|
|
@ -24,10 +23,13 @@ impl Entity {
|
|||
/// assert_eq!(e.name(), " ");
|
||||
/// ```
|
||||
pub fn name(&self) -> &str {
|
||||
self.entity().map(|e| e.0).unwrap_or_else(|| {
|
||||
debug_assert!(false);
|
||||
""
|
||||
})
|
||||
self.entity().map_or_else(
|
||||
|| {
|
||||
debug_assert!(false);
|
||||
""
|
||||
},
|
||||
|e| e.0,
|
||||
)
|
||||
}
|
||||
|
||||
/// Entity LaTeX representation
|
||||
|
|
@ -39,10 +41,13 @@ impl Entity {
|
|||
/// assert_eq!(e.latex(), "\\textperiodcentered{}");
|
||||
/// ```
|
||||
pub fn latex(&self) -> &str {
|
||||
self.entity().map(|e| e.1).unwrap_or_else(|| {
|
||||
debug_assert!(false);
|
||||
""
|
||||
})
|
||||
self.entity().map_or_else(
|
||||
|| {
|
||||
debug_assert!(false);
|
||||
""
|
||||
},
|
||||
|e| e.1,
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether entity needs to be in math mode
|
||||
|
|
@ -56,10 +61,13 @@ impl Entity {
|
|||
/// assert!(e.is_latex_math());
|
||||
/// ```
|
||||
pub fn is_latex_math(&self) -> bool {
|
||||
self.entity().map(|e| e.2).unwrap_or_else(|| {
|
||||
debug_assert!(false);
|
||||
false
|
||||
})
|
||||
self.entity().map_or_else(
|
||||
|| {
|
||||
debug_assert!(false);
|
||||
false
|
||||
},
|
||||
|e| e.2,
|
||||
)
|
||||
}
|
||||
|
||||
/// Entity HTML representation
|
||||
|
|
@ -71,10 +79,13 @@ impl Entity {
|
|||
/// assert_eq!(e.html(), "§");
|
||||
/// ```
|
||||
pub fn html(&self) -> &str {
|
||||
self.entity().map(|e| e.3).unwrap_or_else(|| {
|
||||
debug_assert!(false);
|
||||
""
|
||||
})
|
||||
self.entity().map_or_else(
|
||||
|| {
|
||||
debug_assert!(false);
|
||||
""
|
||||
},
|
||||
|e| e.3,
|
||||
)
|
||||
}
|
||||
|
||||
/// Entity ASCII representation
|
||||
|
|
@ -86,10 +97,13 @@ impl Entity {
|
|||
/// assert_eq!(e.ascii(), "section");
|
||||
/// ```
|
||||
pub fn ascii(&self) -> &str {
|
||||
self.entity().map(|e| e.4).unwrap_or_else(|| {
|
||||
debug_assert!(false);
|
||||
""
|
||||
})
|
||||
self.entity().map_or_else(
|
||||
|| {
|
||||
debug_assert!(false);
|
||||
""
|
||||
},
|
||||
|e| e.4,
|
||||
)
|
||||
}
|
||||
|
||||
/// Entity Latin1 encoding representation
|
||||
|
|
@ -103,10 +117,13 @@ impl Entity {
|
|||
/// assert_eq!(e.latin1(), ">");
|
||||
/// ```
|
||||
pub fn latin1(&self) -> &str {
|
||||
self.entity().map(|e| e.5).unwrap_or_else(|| {
|
||||
debug_assert!(false);
|
||||
""
|
||||
})
|
||||
self.entity().map_or_else(
|
||||
|| {
|
||||
debug_assert!(false);
|
||||
""
|
||||
},
|
||||
|e| e.5,
|
||||
)
|
||||
}
|
||||
|
||||
/// Entity UTF-8 encoding representation
|
||||
|
|
@ -120,10 +137,13 @@ impl Entity {
|
|||
/// assert_eq!(e.utf8(), "›");
|
||||
/// ```
|
||||
pub fn utf8(&self) -> &str {
|
||||
self.entity().map(|e| e.6).unwrap_or_else(|| {
|
||||
debug_assert!(false);
|
||||
""
|
||||
})
|
||||
self.entity().map_or_else(
|
||||
|| {
|
||||
debug_assert!(false);
|
||||
""
|
||||
},
|
||||
|e| e.6,
|
||||
)
|
||||
}
|
||||
|
||||
/// Entity contains optional brackets
|
||||
|
|
|
|||
|
|
@ -180,7 +180,6 @@ const nodes = [
|
|||
{
|
||||
struct: "Link",
|
||||
kind: ["LINK"],
|
||||
token: [["path", "LINK_PATH"]],
|
||||
},
|
||||
{
|
||||
struct: "Cookie",
|
||||
|
|
@ -198,14 +197,9 @@ const nodes = [
|
|||
struct: "Macros",
|
||||
kind: ["MACROS"],
|
||||
},
|
||||
{
|
||||
struct: "MacrosArgument",
|
||||
kind: ["MACROS_ARGUMENT"],
|
||||
},
|
||||
{
|
||||
struct: "Snippet",
|
||||
kind: ["SNIPPET"],
|
||||
token: [["name", "TEXT"]],
|
||||
},
|
||||
{
|
||||
struct: "Target",
|
||||
|
|
@ -276,10 +270,10 @@ use rowan::ast::{support, AstChildren, AstNode};
|
|||
use crate::syntax::{OrgLanguage, SyntaxKind, SyntaxKind::*, SyntaxNode, SyntaxToken};
|
||||
|
||||
fn affiliated_keyword(node: &SyntaxNode, filter: impl Fn(&str) -> bool) -> Option<AffiliatedKeyword> {
|
||||
node.children()
|
||||
.take_while(|n| n.kind() == SyntaxKind::AFFILIATED_KEYWORD)
|
||||
.filter_map(AffiliatedKeyword::cast)
|
||||
.find(|k| matches!(k.key(), Some(k) if filter(k.text())))
|
||||
node.children()
|
||||
.take_while(|n| n.kind() == SyntaxKind::AFFILIATED_KEYWORD)
|
||||
.filter_map(AffiliatedKeyword::cast)
|
||||
.find(|k| filter(&k.key()))
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -308,10 +302,10 @@ impl ${node.struct} {
|
|||
}
|
||||
`;
|
||||
for (const [method, kind] of node.token || []) {
|
||||
content += ` pub fn ${method}(&self) -> Option<SyntaxToken> { support::token(&self.syntax, ${kind}) }\n`;
|
||||
content += ` pub fn ${method}(&self) -> Option<super::Token> { super::token(&self.syntax, ${kind}) }\n`;
|
||||
}
|
||||
for (const [method, kind] of node.last_token || []) {
|
||||
content += ` pub fn ${method}(&self) -> Option<SyntaxToken> { super::last_token(&self.syntax, ${kind}) }\n`;
|
||||
content += ` pub fn ${method}(&self) -> Option<super::Token> { super::last_token(&self.syntax, ${kind}) }\n`;
|
||||
}
|
||||
for (const [method, kind] of node.parent || []) {
|
||||
content += ` pub fn ${method}(&self) -> Option<${kind}> { self.syntax.parent().and_then(${kind}::cast) }\n`;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ fn affiliated_keyword(
|
|||
node.children()
|
||||
.take_while(|n| n.kind() == SyntaxKind::AFFILIATED_KEYWORD)
|
||||
.filter_map(AffiliatedKeyword::cast)
|
||||
.find(|k| matches!(k.key(), Some(k) if filter(k.text())))
|
||||
.find(|k| filter(&k.key()))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
|
|
@ -717,8 +717,8 @@ impl Comment {
|
|||
pub fn end(&self) -> u32 {
|
||||
self.syntax.text_range().end().into()
|
||||
}
|
||||
pub fn text(&self) -> Option<SyntaxToken> {
|
||||
support::token(&self.syntax, TEXT)
|
||||
pub fn text(&self) -> Option<super::Token> {
|
||||
super::token(&self.syntax, TEXT)
|
||||
}
|
||||
pub fn post_blank(&self) -> usize {
|
||||
super::blank_lines(&self.syntax)
|
||||
|
|
@ -796,8 +796,8 @@ impl FixedWidth {
|
|||
pub fn end(&self) -> u32 {
|
||||
self.syntax.text_range().end().into()
|
||||
}
|
||||
pub fn text(&self) -> Option<SyntaxToken> {
|
||||
support::token(&self.syntax, TEXT)
|
||||
pub fn text(&self) -> Option<super::Token> {
|
||||
super::token(&self.syntax, TEXT)
|
||||
}
|
||||
pub fn post_blank(&self) -> usize {
|
||||
super::blank_lines(&self.syntax)
|
||||
|
|
@ -1257,9 +1257,6 @@ impl Link {
|
|||
pub fn end(&self) -> u32 {
|
||||
self.syntax.text_range().end().into()
|
||||
}
|
||||
pub fn path(&self) -> Option<SyntaxToken> {
|
||||
support::token(&self.syntax, LINK_PATH)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
|
|
@ -1362,31 +1359,6 @@ impl Macros {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct MacrosArgument {
|
||||
pub(crate) syntax: SyntaxNode,
|
||||
}
|
||||
impl AstNode for MacrosArgument {
|
||||
type Language = OrgLanguage;
|
||||
fn can_cast(kind: SyntaxKind) -> bool {
|
||||
kind == MACROS_ARGUMENT
|
||||
}
|
||||
fn cast(node: SyntaxNode) -> Option<MacrosArgument> {
|
||||
Self::can_cast(node.kind()).then(|| MacrosArgument { syntax: node })
|
||||
}
|
||||
fn syntax(&self) -> &SyntaxNode {
|
||||
&self.syntax
|
||||
}
|
||||
}
|
||||
impl MacrosArgument {
|
||||
pub fn begin(&self) -> u32 {
|
||||
self.syntax.text_range().start().into()
|
||||
}
|
||||
pub fn end(&self) -> u32 {
|
||||
self.syntax.text_range().end().into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Snippet {
|
||||
pub(crate) syntax: SyntaxNode,
|
||||
|
|
@ -1410,9 +1382,6 @@ impl Snippet {
|
|||
pub fn end(&self) -> u32 {
|
||||
self.syntax.text_range().end().into()
|
||||
}
|
||||
pub fn name(&self) -> Option<SyntaxToken> {
|
||||
support::token(&self.syntax, TEXT)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
|
|
@ -1588,8 +1557,8 @@ impl Code {
|
|||
pub fn end(&self) -> u32 {
|
||||
self.syntax.text_range().end().into()
|
||||
}
|
||||
pub fn text(&self) -> Option<SyntaxToken> {
|
||||
support::token(&self.syntax, TEXT)
|
||||
pub fn text(&self) -> Option<super::Token> {
|
||||
super::token(&self.syntax, TEXT)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1616,34 +1585,34 @@ impl Timestamp {
|
|||
pub fn end(&self) -> u32 {
|
||||
self.syntax.text_range().end().into()
|
||||
}
|
||||
pub fn year_start(&self) -> Option<SyntaxToken> {
|
||||
support::token(&self.syntax, TIMESTAMP_YEAR)
|
||||
pub fn year_start(&self) -> Option<super::Token> {
|
||||
super::token(&self.syntax, TIMESTAMP_YEAR)
|
||||
}
|
||||
pub fn month_start(&self) -> Option<SyntaxToken> {
|
||||
support::token(&self.syntax, TIMESTAMP_MONTH)
|
||||
pub fn month_start(&self) -> Option<super::Token> {
|
||||
super::token(&self.syntax, TIMESTAMP_MONTH)
|
||||
}
|
||||
pub fn day_start(&self) -> Option<SyntaxToken> {
|
||||
support::token(&self.syntax, TIMESTAMP_DAY)
|
||||
pub fn day_start(&self) -> Option<super::Token> {
|
||||
super::token(&self.syntax, TIMESTAMP_DAY)
|
||||
}
|
||||
pub fn hour_start(&self) -> Option<SyntaxToken> {
|
||||
support::token(&self.syntax, TIMESTAMP_HOUR)
|
||||
pub fn hour_start(&self) -> Option<super::Token> {
|
||||
super::token(&self.syntax, TIMESTAMP_HOUR)
|
||||
}
|
||||
pub fn minute_start(&self) -> Option<SyntaxToken> {
|
||||
support::token(&self.syntax, TIMESTAMP_MINUTE)
|
||||
pub fn minute_start(&self) -> Option<super::Token> {
|
||||
super::token(&self.syntax, TIMESTAMP_MINUTE)
|
||||
}
|
||||
pub fn year_end(&self) -> Option<SyntaxToken> {
|
||||
pub fn year_end(&self) -> Option<super::Token> {
|
||||
super::last_token(&self.syntax, TIMESTAMP_YEAR)
|
||||
}
|
||||
pub fn month_end(&self) -> Option<SyntaxToken> {
|
||||
pub fn month_end(&self) -> Option<super::Token> {
|
||||
super::last_token(&self.syntax, TIMESTAMP_MONTH)
|
||||
}
|
||||
pub fn day_end(&self) -> Option<SyntaxToken> {
|
||||
pub fn day_end(&self) -> Option<super::Token> {
|
||||
super::last_token(&self.syntax, TIMESTAMP_DAY)
|
||||
}
|
||||
pub fn hour_end(&self) -> Option<SyntaxToken> {
|
||||
pub fn hour_end(&self) -> Option<super::Token> {
|
||||
super::last_token(&self.syntax, TIMESTAMP_HOUR)
|
||||
}
|
||||
pub fn minute_end(&self) -> Option<SyntaxToken> {
|
||||
pub fn minute_end(&self) -> Option<super::Token> {
|
||||
super::last_token(&self.syntax, TIMESTAMP_MINUTE)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
use rowan::NodeOrToken;
|
||||
|
||||
use crate::{
|
||||
syntax::{SyntaxKind, SyntaxToken},
|
||||
SyntaxElement,
|
||||
};
|
||||
use crate::{syntax::SyntaxKind, SyntaxElement};
|
||||
|
||||
use super::{filter_token, Headline, Timestamp};
|
||||
use super::{filter_token, Headline, Timestamp, Token};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum TodoType {
|
||||
|
|
@ -28,20 +25,22 @@ impl Headline {
|
|||
self.syntax
|
||||
.children_with_tokens()
|
||||
.find_map(filter_token(SyntaxKind::HEADLINE_STARS))
|
||||
.map(|stars| stars.text().len())
|
||||
.unwrap_or_else(|| {
|
||||
debug_assert!(false, "headline must contains starts token");
|
||||
0
|
||||
})
|
||||
.map_or_else(
|
||||
|| {
|
||||
debug_assert!(false, "headline must contains starts token");
|
||||
0
|
||||
},
|
||||
|stars| stars.len(),
|
||||
)
|
||||
}
|
||||
|
||||
/// ```rust
|
||||
/// use orgize::{Org, ast::Headline};
|
||||
///
|
||||
/// let hdl = Org::parse("* TODO a").first_node::<Headline>().unwrap();
|
||||
/// assert_eq!(hdl.todo_keyword().unwrap().text(), "TODO");
|
||||
/// assert_eq!(hdl.todo_keyword().unwrap(), "TODO");
|
||||
/// ```
|
||||
pub fn todo_keyword(&self) -> Option<SyntaxToken> {
|
||||
pub fn todo_keyword(&self) -> Option<Token> {
|
||||
self.syntax
|
||||
.children_with_tokens()
|
||||
.find_map(|elem| match elem {
|
||||
|
|
@ -49,7 +48,7 @@ impl Headline {
|
|||
if tk.kind() == SyntaxKind::HEADLINE_KEYWORD_TODO
|
||||
|| tk.kind() == SyntaxKind::HEADLINE_KEYWORD_DONE =>
|
||||
{
|
||||
Some(tk)
|
||||
Some(Token(Some(tk)))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
|
|
@ -131,7 +130,7 @@ impl Headline {
|
|||
/// assert!(!hdl.is_archived());
|
||||
/// ```
|
||||
pub fn is_archived(&self) -> bool {
|
||||
self.tags().any(|t| t.text() == "ARCHIVE")
|
||||
self.tags().any(|t| t == "ARCHIVE")
|
||||
}
|
||||
|
||||
/// Returns this headline's closed timestamp, or `None` if not set.
|
||||
|
|
@ -165,7 +164,7 @@ impl Headline {
|
|||
/// assert_eq!(tags_vec("* TODO :tag: :a2%:"), vec!["tag".to_string(), "a2%".to_string()]);
|
||||
/// assert_eq!(tags_vec("* title :tag:a2%:"), vec!["tag".to_string(), "a2%".to_string()]);
|
||||
/// ```
|
||||
pub fn tags(&self) -> impl Iterator<Item = SyntaxToken> {
|
||||
pub fn tags(&self) -> impl Iterator<Item = Token> {
|
||||
self.syntax
|
||||
.children()
|
||||
.find(|n| n.kind() == SyntaxKind::HEADLINE_TAGS)
|
||||
|
|
@ -180,13 +179,13 @@ impl Headline {
|
|||
/// use orgize::{Org, ast::Headline};
|
||||
///
|
||||
/// let hdl = Org::parse("* [#A]").first_node::<Headline>().unwrap();
|
||||
/// assert_eq!(hdl.priority().unwrap().text(), "A");
|
||||
/// assert_eq!(hdl.priority().unwrap(), "A");
|
||||
/// let hdl = Org::parse("** DONE [#B]::").first_node::<Headline>().unwrap();
|
||||
/// assert_eq!(hdl.priority().unwrap().text(), "B");
|
||||
/// assert_eq!(hdl.priority().unwrap(), "B");
|
||||
/// let hdl = Org::parse("* [#破]").first_node::<Headline>().unwrap();
|
||||
/// assert_eq!(hdl.priority().unwrap().text(), "破");
|
||||
/// assert_eq!(hdl.priority().unwrap(), "破");
|
||||
/// ```
|
||||
pub fn priority(&self) -> Option<SyntaxToken> {
|
||||
pub fn priority(&self) -> Option<Token> {
|
||||
self.syntax
|
||||
.children()
|
||||
.find(|n| n.kind() == SyntaxKind::HEADLINE_PRIORITY)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::syntax::{SyntaxElement, SyntaxKind, SyntaxToken};
|
||||
use crate::syntax::SyntaxKind;
|
||||
|
||||
use super::InlineCall;
|
||||
use super::{filter_token, InlineCall, Token};
|
||||
|
||||
impl InlineCall {
|
||||
///
|
||||
|
|
@ -8,15 +8,78 @@ impl InlineCall {
|
|||
/// use orgize::{Org, ast::InlineCall};
|
||||
///
|
||||
/// let call = Org::parse("call_square(4)").first_node::<InlineCall>().unwrap();
|
||||
/// assert_eq!(call.call().unwrap().text(), "square");
|
||||
/// assert_eq!(call.call(), "square");
|
||||
/// ```
|
||||
pub fn call(&self) -> Option<SyntaxToken> {
|
||||
pub fn call(&self) -> Token {
|
||||
self.syntax
|
||||
.children_with_tokens()
|
||||
.filter_map(|it| match it {
|
||||
SyntaxElement::Token(t) if t.kind() == SyntaxKind::TEXT => Some(t),
|
||||
_ => None,
|
||||
})
|
||||
.filter_map(filter_token(SyntaxKind::TEXT))
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| {
|
||||
debug_assert!(false, "inline call must contains two TEXT");
|
||||
Token::default()
|
||||
})
|
||||
}
|
||||
|
||||
///
|
||||
/// ```rust
|
||||
/// use orgize::{Org, ast::InlineCall};
|
||||
///
|
||||
/// let call = Org::parse("call_square[:results output](4)").first_node::<InlineCall>().unwrap();
|
||||
/// assert_eq!(call.inside_header().unwrap(), ":results output");
|
||||
/// ```
|
||||
pub fn inside_header(&self) -> Option<Token> {
|
||||
self.syntax
|
||||
.children_with_tokens()
|
||||
.skip_while(|e| e.kind() != SyntaxKind::L_BRACKET)
|
||||
.nth(1)
|
||||
.map(|e| {
|
||||
debug_assert!(e.kind() == SyntaxKind::TEXT);
|
||||
Token(e.into_token())
|
||||
})
|
||||
}
|
||||
|
||||
///
|
||||
/// ```rust
|
||||
/// use orgize::{Org, ast::InlineCall};
|
||||
///
|
||||
/// let call = Org::parse("call_square(4)").first_node::<InlineCall>().unwrap();
|
||||
/// assert_eq!(call.arguments(), "4");
|
||||
/// ```
|
||||
pub fn arguments(&self) -> Token {
|
||||
self.syntax
|
||||
.children_with_tokens()
|
||||
.skip_while(|e| e.kind() != SyntaxKind::L_PARENS)
|
||||
.nth(1)
|
||||
.map_or_else(
|
||||
|| {
|
||||
debug_assert!(false);
|
||||
Token::default()
|
||||
},
|
||||
|e| {
|
||||
debug_assert!(e.kind() == SyntaxKind::TEXT);
|
||||
Token(e.into_token())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
///
|
||||
/// ```rust
|
||||
/// use orgize::{Org, ast::InlineCall};
|
||||
///
|
||||
/// let call = Org::parse("call_square[:results output](4)[:results html]").first_node::<InlineCall>().unwrap();
|
||||
/// assert_eq!(call.end_header().unwrap(), ":results html");
|
||||
/// ```
|
||||
pub fn end_header(&self) -> Option<Token> {
|
||||
self.syntax
|
||||
.children_with_tokens()
|
||||
.skip_while(|e| e.kind() != SyntaxKind::L_BRACKET)
|
||||
.skip(1)
|
||||
.skip_while(|e| e.kind() != SyntaxKind::L_BRACKET)
|
||||
.nth(1)
|
||||
.map(|e| {
|
||||
debug_assert!(e.kind() == SyntaxKind::TEXT);
|
||||
Token(e.into_token())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
68
src/ast/inline_src.rs
Normal file
68
src/ast/inline_src.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
use crate::SyntaxKind;
|
||||
|
||||
use super::{filter_token, InlineSrc, Token};
|
||||
|
||||
impl InlineSrc {
|
||||
/// Language of the code
|
||||
///
|
||||
/// ```rust
|
||||
/// use orgize::{Org, ast::InlineSrc};
|
||||
///
|
||||
/// let s = Org::parse("src_C{int a = 0;}").first_node::<InlineSrc>().unwrap();
|
||||
/// assert_eq!(s.language(), "C");
|
||||
/// let s = Org::parse("src_xml[:exports code]{<tag>text</tag>}").first_node::<InlineSrc>().unwrap();
|
||||
/// assert_eq!(s.language(), "xml");
|
||||
/// ```
|
||||
pub fn language(&self) -> Token {
|
||||
self.syntax
|
||||
.children_with_tokens()
|
||||
.nth(1)
|
||||
.and_then(filter_token(SyntaxKind::TEXT))
|
||||
.unwrap_or_else(|| {
|
||||
debug_assert!(false, "inline src must contains TEXT");
|
||||
Token::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Optional header arguments
|
||||
///
|
||||
/// ```rust
|
||||
/// use orgize::{Org, ast::InlineSrc};
|
||||
///
|
||||
/// let s = Org::parse("src_C{int a = 0;}").first_node::<InlineSrc>().unwrap();
|
||||
/// assert!(s.parameters().is_none());
|
||||
/// let s = Org::parse("src_xml[:exports code]{<tag>text</tag>}").first_node::<InlineSrc>().unwrap();
|
||||
/// assert_eq!(s.parameters().unwrap(), ":exports code");
|
||||
/// ```
|
||||
pub fn parameters(&self) -> Option<Token> {
|
||||
self.syntax
|
||||
.children_with_tokens()
|
||||
.skip_while(|n| n.kind() != SyntaxKind::L_BRACKET)
|
||||
.nth(1)
|
||||
.map(|n| {
|
||||
debug_assert!(n.kind() == SyntaxKind::TEXT);
|
||||
Token(n.into_token())
|
||||
})
|
||||
}
|
||||
|
||||
/// Source code
|
||||
///
|
||||
/// ```rust
|
||||
/// use orgize::{Org, ast::InlineSrc};
|
||||
///
|
||||
/// let s = Org::parse("src_C{int a = 0;}").first_node::<InlineSrc>().unwrap();
|
||||
/// assert_eq!(s.value(), "int a = 0;");
|
||||
/// let s = Org::parse("src_xml[:exports code]{<tag>text</tag>}").first_node::<InlineSrc>().unwrap();
|
||||
/// assert_eq!(s.value(), "<tag>text</tag>");
|
||||
/// ```
|
||||
pub fn value(&self) -> Token {
|
||||
self.syntax
|
||||
.children_with_tokens()
|
||||
.filter_map(filter_token(SyntaxKind::TEXT))
|
||||
.last()
|
||||
.unwrap_or_else(|| {
|
||||
debug_assert!(false, "inline src must contains TEXT");
|
||||
Token::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,31 @@
|
|||
use rowan::ast::{support, AstNode};
|
||||
|
||||
use super::{AffiliatedKeyword, Link, Paragraph};
|
||||
use super::{AffiliatedKeyword, Link, Paragraph, Token};
|
||||
use crate::syntax::SyntaxKind;
|
||||
|
||||
impl Link {
|
||||
/// Returns link destination
|
||||
///
|
||||
/// ```rust
|
||||
/// use orgize::{Org, ast::Link};
|
||||
///
|
||||
/// let link = Org::parse("[[#id]]").first_node::<Link>().unwrap();
|
||||
/// assert_eq!(link.path(), "#id");
|
||||
/// let link = Org::parse("[[https://google.com]]").first_node::<Link>().unwrap();
|
||||
/// assert_eq!(link.path(), "https://google.com");
|
||||
/// let link = Org::parse("[[https://google.com][Google]]").first_node::<Link>().unwrap();
|
||||
/// assert_eq!(link.path(), "https://google.com");
|
||||
/// ```
|
||||
pub fn path(&self) -> Token {
|
||||
support::token(&self.syntax, SyntaxKind::LINK_PATH).map_or_else(
|
||||
|| {
|
||||
debug_assert!(false, "link must contains LINK_PATH");
|
||||
Token::default()
|
||||
},
|
||||
|e| Token(Some(e)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns `true` if link contains description
|
||||
///
|
||||
/// ```rust
|
||||
|
|
@ -35,10 +57,9 @@ impl Link {
|
|||
".ppm", ".webp", ".avif", ".svg",
|
||||
];
|
||||
|
||||
self.path()
|
||||
.map(|path| IMAGE_SUFFIX.iter().any(|e| path.text().ends_with(e)))
|
||||
.unwrap_or_default()
|
||||
&& !self.has_description()
|
||||
let path = self.path();
|
||||
|
||||
IMAGE_SUFFIX.iter().any(|e| path.ends_with(e)) && !self.has_description()
|
||||
}
|
||||
|
||||
/// Returns caption keyword in this link
|
||||
|
|
@ -47,7 +68,7 @@ impl Link {
|
|||
/// use orgize::{Org, ast::Link};
|
||||
///
|
||||
/// let link = Org::parse("#+CAPTION: image link\n[[file:/home/dominik/images/jupiter.jpg]]").first_node::<Link>().unwrap();
|
||||
/// assert_eq!(link.caption().unwrap().value().unwrap().text(), " image link");
|
||||
/// assert_eq!(link.caption().unwrap().value().unwrap(), " image link");
|
||||
/// ```
|
||||
pub fn caption(&self) -> Option<AffiliatedKeyword> {
|
||||
// TODO: support other element type
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use super::{filter_token, List, ListItem};
|
||||
use crate::{syntax::SyntaxKind, SyntaxElement, SyntaxToken};
|
||||
use super::{filter_token, List, ListItem, Token};
|
||||
use crate::{syntax::SyntaxKind, SyntaxElement};
|
||||
|
||||
impl List {
|
||||
/// Returns `true` if this list is an ordered link
|
||||
|
|
@ -17,11 +17,13 @@ impl List {
|
|||
/// assert!(list.is_ordered());
|
||||
/// ```
|
||||
pub fn is_ordered(&self) -> bool {
|
||||
self.items()
|
||||
.next()
|
||||
.and_then(|item| item.bullet())
|
||||
.map(|bullet| bullet.text().starts_with(|c: char| c.is_ascii_digit()))
|
||||
.unwrap_or_default()
|
||||
self.items().next().map_or_else(
|
||||
|| {
|
||||
debug_assert!(false, "list muts contains LIST_ITEM");
|
||||
false
|
||||
},
|
||||
|item| item.bullet().starts_with(|c: char| c.is_ascii_digit()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns `true` if this list contains a TAG
|
||||
|
|
@ -35,14 +37,17 @@ impl List {
|
|||
/// assert!(!list.is_descriptive());
|
||||
/// ```
|
||||
pub fn is_descriptive(&self) -> bool {
|
||||
self.items()
|
||||
.next()
|
||||
.map(|item| {
|
||||
self.items().next().map_or_else(
|
||||
|| {
|
||||
debug_assert!(false, "list must contains LIST_ITEM");
|
||||
false
|
||||
},
|
||||
|item| {
|
||||
item.syntax
|
||||
.children()
|
||||
.any(|it| it.kind() == SyntaxKind::LIST_ITEM_TAG)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -59,38 +64,44 @@ impl ListItem {
|
|||
self.syntax
|
||||
.children_with_tokens()
|
||||
.find_map(filter_token(SyntaxKind::LIST_ITEM_INDENT))
|
||||
.map(|t| t.text().len())
|
||||
.unwrap_or_else(|| {
|
||||
debug_assert!(false, "list must contains indent token");
|
||||
0
|
||||
})
|
||||
.map_or_else(
|
||||
|| {
|
||||
debug_assert!(false, "list item must contains LIST_ITEM_INDENT");
|
||||
0
|
||||
},
|
||||
|t| t.len(),
|
||||
)
|
||||
}
|
||||
|
||||
/// ```rust
|
||||
/// use orgize::{Org, ast::ListItem};
|
||||
///
|
||||
/// let item = Org::parse("- some tag").first_node::<ListItem>().unwrap();
|
||||
/// assert_eq!(item.bullet().unwrap().text(), "- ");
|
||||
/// assert_eq!(item.bullet(), "- ");
|
||||
/// let item = Org::parse("2. [X] item 2").first_node::<ListItem>().unwrap();
|
||||
/// assert_eq!(item.bullet().unwrap().text(), "2. ");
|
||||
/// assert_eq!(item.bullet(), "2. ");
|
||||
/// ```
|
||||
pub fn bullet(&self) -> Option<SyntaxToken> {
|
||||
pub fn bullet(&self) -> Token {
|
||||
self.syntax
|
||||
.children_with_tokens()
|
||||
.find_map(filter_token(SyntaxKind::LIST_ITEM_BULLET))
|
||||
.unwrap_or_else(|| {
|
||||
debug_assert!(false, "list item must contains LIST_ITEM_BULLET");
|
||||
Token::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// ```rust
|
||||
/// use orgize::{Org, ast::ListItem};
|
||||
///
|
||||
/// let item = Org::parse("- [-] item 1").first_node::<ListItem>().unwrap();
|
||||
/// assert_eq!(item.checkbox().unwrap().text(), "-");
|
||||
/// assert_eq!(item.checkbox().unwrap(), "-");
|
||||
/// let item = Org::parse("2. [X] item 2").first_node::<ListItem>().unwrap();
|
||||
/// assert_eq!(item.checkbox().unwrap().text(), "X");
|
||||
/// assert_eq!(item.checkbox().unwrap(), "X");
|
||||
/// let item = Org::parse("3) [ ] item 3").first_node::<ListItem>().unwrap();
|
||||
/// assert_eq!(item.checkbox().unwrap().text(), " ");
|
||||
/// assert_eq!(item.checkbox().unwrap(), " ");
|
||||
/// ```
|
||||
pub fn checkbox(&self) -> Option<SyntaxToken> {
|
||||
pub fn checkbox(&self) -> Option<Token> {
|
||||
self.syntax
|
||||
.children()
|
||||
.find(|n| n.kind() == SyntaxKind::LIST_ITEM_CHECK_BOX)
|
||||
|
|
@ -100,7 +111,7 @@ impl ListItem {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn counter(&self) -> Option<SyntaxToken> {
|
||||
pub fn counter(&self) -> Option<Token> {
|
||||
self.syntax
|
||||
.children()
|
||||
.find(|n| n.kind() == SyntaxKind::LIST_ITEM_COUNTER)
|
||||
|
|
|
|||
38
src/ast/macros.rs
Normal file
38
src/ast/macros.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use crate::SyntaxKind;
|
||||
|
||||
use super::{filter_token, Macros, Token};
|
||||
|
||||
impl Macros {
|
||||
/// ```rust
|
||||
/// use orgize::{Org, ast::Macros};
|
||||
///
|
||||
/// let m = Org::parse("{{{title}}}").first_node::<Macros>().unwrap();
|
||||
/// assert_eq!(m.key(), "title");
|
||||
/// let m = Org::parse("{{{two_arg_macro(1, 2)}}}").first_node::<Macros>().unwrap();
|
||||
/// assert_eq!(m.key(), "two_arg_macro");
|
||||
/// ```
|
||||
pub fn key(&self) -> Token {
|
||||
self.syntax
|
||||
.children_with_tokens()
|
||||
.find_map(filter_token(SyntaxKind::TEXT))
|
||||
.unwrap_or_else(|| {
|
||||
debug_assert!(false, "macros must contains TEXT");
|
||||
Token::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// ```rust
|
||||
/// use orgize::{Org, ast::Macros};
|
||||
///
|
||||
/// let m = Org::parse("{{{title}}}").first_node::<Macros>().unwrap();
|
||||
/// assert!(m.args().is_none());
|
||||
/// let m = Org::parse("{{{two_arg_macro(1, 2)}}}").first_node::<Macros>().unwrap();
|
||||
/// assert_eq!(m.args().unwrap(), "1, 2");
|
||||
/// ```
|
||||
pub fn args(&self) -> Option<Token> {
|
||||
self.syntax
|
||||
.children_with_tokens()
|
||||
.filter_map(filter_token(SyntaxKind::TEXT))
|
||||
.nth(1)
|
||||
}
|
||||
}
|
||||
107
src/ast/mod.rs
107
src/ast/mod.rs
|
|
@ -3,24 +3,37 @@ mod generated;
|
|||
|
||||
|
||||
mod affiliated_keyword;
|
||||
mod clock;
|
||||
mod drawer;
|
||||
mod entity;
|
||||
mod headline;
|
||||
mod inline_call;
|
||||
mod inline_src;
|
||||
mod link;
|
||||
mod list;
|
||||
mod macros;
|
||||
mod planning;
|
||||
mod snippet;
|
||||
mod table;
|
||||
mod timestamp;
|
||||
|
||||
use std::{
|
||||
borrow::{Borrow, Cow},
|
||||
fmt::Debug,
|
||||
hash::Hash,
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
pub use generated::*;
|
||||
pub use headline::*;
|
||||
pub use rowan::ast::support::*;
|
||||
pub use timestamp::*;
|
||||
|
||||
use crate::syntax::{SyntaxKind, SyntaxNode};
|
||||
use rowan::{ast::AstNode, Language, NodeOrToken};
|
||||
use crate::{
|
||||
syntax::{SyntaxKind, SyntaxNode},
|
||||
SyntaxToken,
|
||||
};
|
||||
use rowan::{ast::AstNode, NodeOrToken};
|
||||
|
||||
pub fn blank_lines(parent: &SyntaxNode) -> usize {
|
||||
parent
|
||||
|
|
@ -33,22 +46,94 @@ pub fn last_child<N: AstNode>(parent: &rowan::SyntaxNode<N::Language>) -> Option
|
|||
parent.children().filter_map(N::cast).last()
|
||||
}
|
||||
|
||||
pub fn last_token<L: Language>(
|
||||
parent: &rowan::SyntaxNode<L>,
|
||||
kind: L::Kind,
|
||||
) -> Option<rowan::SyntaxToken<L>> {
|
||||
pub fn last_token(parent: &SyntaxNode, kind: SyntaxKind) -> Option<Token> {
|
||||
parent
|
||||
.children_with_tokens()
|
||||
.filter_map(filter_token(kind))
|
||||
.last()
|
||||
}
|
||||
|
||||
pub fn filter_token<L: Language>(
|
||||
kind: L::Kind,
|
||||
) -> impl Fn(NodeOrToken<rowan::SyntaxNode<L>, rowan::SyntaxToken<L>>) -> Option<rowan::SyntaxToken<L>>
|
||||
{
|
||||
pub fn token(parent: &SyntaxNode, kind: SyntaxKind) -> Option<Token> {
|
||||
rowan::ast::support::token(parent, kind).map(|t| Token(Some(t)))
|
||||
}
|
||||
|
||||
pub fn filter_token(
|
||||
kind: SyntaxKind,
|
||||
) -> impl Fn(NodeOrToken<SyntaxNode, SyntaxToken>) -> Option<Token> {
|
||||
move |elem| match elem {
|
||||
NodeOrToken::Token(tk) if tk.kind() == kind => Some(tk),
|
||||
NodeOrToken::Token(tk) if tk.kind() == kind => Some(Token(Some(tk))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple wrapper of `Option<SyntaxToken>`
|
||||
///
|
||||
/// It acts like a `token.text()` when inner is `Some(token)`, and an empty string when `None`.
|
||||
#[derive(Default, Eq)]
|
||||
pub struct Token(pub(crate) Option<SyntaxToken>);
|
||||
|
||||
impl AsRef<str> for Token {
|
||||
fn as_ref(&self) -> &str {
|
||||
match &self.0 {
|
||||
Some(t) => t.text(),
|
||||
None => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for Token {
|
||||
fn borrow(&self) -> &str {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Token {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.as_ref().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq<&'a str> for Token {
|
||||
fn eq(&self, other: &&'a str) -> bool {
|
||||
self.as_ref() == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<String> for Token {
|
||||
fn eq(&self, other: &String) -> bool {
|
||||
self.as_ref() == other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Token> for Token {
|
||||
fn eq(&self, other: &Token) -> bool {
|
||||
self.as_ref() == other.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Token {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.as_ref().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PartialEq<Cow<'a, str>> for Token {
|
||||
fn eq(&self, other: &Cow<'a, str>) -> bool {
|
||||
self.as_ref() == other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<str> for Token {
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
self.as_ref() == other
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Token {
|
||||
type Target = str;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &str {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ impl Planning {
|
|||
/// .unwrap()
|
||||
/// .deadline()
|
||||
/// .unwrap();
|
||||
/// assert_eq!(s.day_start().unwrap().text(), "08");
|
||||
/// assert_eq!(s.day_start().unwrap(), "08");
|
||||
/// ```
|
||||
pub fn deadline(&self) -> Option<Timestamp> {
|
||||
self.syntax
|
||||
|
|
@ -35,7 +35,7 @@ impl Planning {
|
|||
/// .unwrap()
|
||||
/// .scheduled()
|
||||
/// .unwrap();
|
||||
/// assert_eq!(s.year_start().unwrap().text(), "2019");
|
||||
/// assert_eq!(s.year_start().unwrap(), "2019");
|
||||
/// ```
|
||||
pub fn scheduled(&self) -> Option<Timestamp> {
|
||||
self.syntax
|
||||
|
|
@ -55,7 +55,7 @@ impl Planning {
|
|||
/// .unwrap()
|
||||
/// .closed()
|
||||
/// .unwrap();
|
||||
/// assert_eq!(s.month_start().unwrap().text(), "04");
|
||||
/// assert_eq!(s.month_start().unwrap(), "04");
|
||||
/// ```
|
||||
pub fn closed(&self) -> Option<Timestamp> {
|
||||
self.syntax
|
||||
|
|
|
|||
|
|
@ -1,18 +1,40 @@
|
|||
use crate::syntax::{SyntaxKind, SyntaxToken};
|
||||
use crate::syntax::SyntaxKind;
|
||||
|
||||
use super::{filter_token, Snippet};
|
||||
use super::{filter_token, Snippet, Token};
|
||||
|
||||
impl Snippet {
|
||||
/// ```rust
|
||||
/// use orgize::{Org, ast::Snippet};
|
||||
///
|
||||
/// let snippet = Org::parse("@@BACKEND:VALUE@@").first_node::<Snippet>().unwrap();
|
||||
/// assert_eq!(snippet.value().unwrap().text(), "VALUE");
|
||||
/// assert_eq!(snippet.backend(), "BACKEND");
|
||||
/// ```
|
||||
pub fn value(&self) -> Option<SyntaxToken> {
|
||||
pub fn backend(&self) -> Token {
|
||||
self.syntax
|
||||
.children_with_tokens()
|
||||
.find_map(filter_token(SyntaxKind::TEXT))
|
||||
.unwrap_or_else(|| {
|
||||
debug_assert!(false, "snippet must contains TEXT");
|
||||
Token::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// ```rust
|
||||
/// use orgize::{Org, ast::Snippet};
|
||||
///
|
||||
/// let snippet = Org::parse("@@BACKEND:@@").first_node::<Snippet>().unwrap();
|
||||
/// assert_eq!(snippet.value(), "");
|
||||
/// let snippet = Org::parse("@@BACKEND:VALUE@@").first_node::<Snippet>().unwrap();
|
||||
/// assert_eq!(snippet.value(), "VALUE");
|
||||
/// ```
|
||||
pub fn value(&self) -> Token {
|
||||
self.syntax
|
||||
.children_with_tokens()
|
||||
.filter_map(filter_token(SyntaxKind::TEXT))
|
||||
.nth(1)
|
||||
.unwrap_or_else(|| {
|
||||
debug_assert!(false, "snippet must contains two TEXT");
|
||||
Token::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ impl Timestamp {
|
|||
self.syntax
|
||||
.children_with_tokens()
|
||||
.find_map(filter_token(SyntaxKind::TIMESTAMP_REPEATER_MARK))
|
||||
.map(|t| match t.text() {
|
||||
.map(|t| match t.as_ref() {
|
||||
"++" => RepeaterType::CatchUp,
|
||||
"+" => RepeaterType::Cumulate,
|
||||
".+" => RepeaterType::Restart,
|
||||
|
|
@ -178,7 +178,7 @@ impl Timestamp {
|
|||
self.syntax
|
||||
.children_with_tokens()
|
||||
.find_map(filter_token(SyntaxKind::TIMESTAMP_DELAY_MARK))
|
||||
.map(|t| match t.text() {
|
||||
.map(|t| match t.as_ref() {
|
||||
"-" => DelayType::All,
|
||||
"--" => DelayType::First,
|
||||
_ => {
|
||||
|
|
@ -259,13 +259,13 @@ impl Timestamp {
|
|||
pub fn start_to_chrono(&self) -> Option<chrono::NaiveDateTime> {
|
||||
Some(chrono::NaiveDateTime::new(
|
||||
chrono::NaiveDate::from_ymd_opt(
|
||||
self.year_start()?.text().parse().ok()?,
|
||||
self.month_start()?.text().parse().ok()?,
|
||||
self.day_start()?.text().parse().ok()?,
|
||||
self.year_start()?.parse().ok()?,
|
||||
self.month_start()?.parse().ok()?,
|
||||
self.day_start()?.parse().ok()?,
|
||||
)?,
|
||||
chrono::NaiveTime::from_hms_opt(
|
||||
self.hour_start()?.text().parse().ok()?,
|
||||
self.minute_start()?.text().parse().ok()?,
|
||||
self.hour_start()?.parse().ok()?,
|
||||
self.minute_start()?.parse().ok()?,
|
||||
0,
|
||||
)?,
|
||||
))
|
||||
|
|
@ -284,13 +284,13 @@ impl Timestamp {
|
|||
pub fn end_to_chrono(&self) -> Option<chrono::NaiveDateTime> {
|
||||
Some(chrono::NaiveDateTime::new(
|
||||
chrono::NaiveDate::from_ymd_opt(
|
||||
self.year_end()?.text().parse().ok()?,
|
||||
self.month_end()?.text().parse().ok()?,
|
||||
self.day_end()?.text().parse().ok()?,
|
||||
self.year_end()?.parse().ok()?,
|
||||
self.month_end()?.parse().ok()?,
|
||||
self.day_end()?.parse().ok()?,
|
||||
)?,
|
||||
chrono::NaiveTime::from_hms_opt(
|
||||
self.hour_end()?.text().parse().ok()?,
|
||||
self.minute_end()?.text().parse().ok()?,
|
||||
self.hour_end()?.parse().ok()?,
|
||||
self.minute_end()?.parse().ok()?,
|
||||
0,
|
||||
)?,
|
||||
))
|
||||
|
|
|
|||
|
|
@ -168,10 +168,8 @@ impl Traverser for HtmlExport {
|
|||
#[tracing::instrument(skip(self, ctx))]
|
||||
fn snippet(&mut self, event: WalkEvent<&Snippet>, ctx: &mut TraversalContext) {
|
||||
if let WalkEvent::Enter(snippet) = event {
|
||||
if matches!(snippet.name(), Some(name) if name.text().eq_ignore_ascii_case("html")) {
|
||||
if let Some(value) = snippet.value() {
|
||||
self.output += value.text()
|
||||
}
|
||||
if snippet.backend().eq_ignore_ascii_case("html") {
|
||||
self.output += &snippet.value();
|
||||
}
|
||||
return ctx.skip();
|
||||
};
|
||||
|
|
@ -238,17 +236,16 @@ impl Traverser for HtmlExport {
|
|||
match event {
|
||||
WalkEvent::Enter(link) => {
|
||||
let path = link.path();
|
||||
let path = path.as_ref().map(|path| path.text()).unwrap_or_default();
|
||||
|
||||
if link.is_image() {
|
||||
let _ = write!(&mut self.output, r#"<img src="{}">"#, HtmlEscape(path));
|
||||
let _ = write!(&mut self.output, r#"<img src="{}">"#, HtmlEscape(&path));
|
||||
return ctx.skip();
|
||||
}
|
||||
|
||||
let _ = write!(&mut self.output, r#"<a href="{}">"#, HtmlEscape(path));
|
||||
let _ = write!(&mut self.output, r#"<a href="{}">"#, HtmlEscape(&path));
|
||||
|
||||
if !link.has_description() {
|
||||
let _ = write!(&mut self.output, "{}</a>", HtmlEscape(path));
|
||||
let _ = write!(&mut self.output, "{}</a>", HtmlEscape(&path));
|
||||
return ctx.skip();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use nom::{
|
|||
branch::alt,
|
||||
bytes::complete::tag,
|
||||
character::complete::{digit1, line_ending, space0},
|
||||
combinator::{eof, map, opt},
|
||||
combinator::{eof, map, opt, recognize},
|
||||
sequence::tuple,
|
||||
IResult,
|
||||
};
|
||||
|
|
@ -26,9 +26,7 @@ pub fn clock_node(input: Input) -> IResult<Input, GreenElement, ()> {
|
|||
space0,
|
||||
double_arrow_token,
|
||||
space0,
|
||||
digit1,
|
||||
colon_token,
|
||||
digit1,
|
||||
recognize(tuple((digit1, colon_token, digit1))),
|
||||
))),
|
||||
space0,
|
||||
alt((line_ending, eof)),
|
||||
|
|
@ -41,13 +39,11 @@ pub fn clock_node(input: Input) -> IResult<Input, GreenElement, ()> {
|
|||
b.text(clock);
|
||||
b.ws(ws_);
|
||||
b.push(timestamp);
|
||||
if let Some((ws, double_arrow, ws_, hour, colon, minute)) = duration {
|
||||
if let Some((ws, double_arrow, ws_, time)) = duration {
|
||||
b.ws(ws);
|
||||
b.push(double_arrow);
|
||||
b.ws(ws_);
|
||||
b.text(hour);
|
||||
b.push(colon);
|
||||
b.text(minute);
|
||||
b.text(time);
|
||||
}
|
||||
b.ws(ws__);
|
||||
b.nl(nl);
|
||||
|
|
@ -125,9 +121,7 @@ fn parse() {
|
|||
WHITESPACE@53..54 " "
|
||||
DOUBLE_ARROW@54..56 "=>"
|
||||
WHITESPACE@56..58 " "
|
||||
TEXT@58..59 "1"
|
||||
COLON@59..60 ":"
|
||||
TEXT@60..62 "00"
|
||||
TEXT@58..62 "1:00"
|
||||
NEW_LINE@62..63 "\n"
|
||||
BLANK_LINE@63..64 "\n"
|
||||
"###
|
||||
|
|
|
|||
|
|
@ -141,8 +141,7 @@ impl<'a> Iterator for ElementPositions<'a> {
|
|||
let previous = self.pos;
|
||||
self.pos = iter
|
||||
.next()
|
||||
.map(|i| i + self.pos)
|
||||
.unwrap_or_else(|| self.input.s.len());
|
||||
.map_or_else(|| self.input.s.len(), |i| i + self.pos);
|
||||
|
||||
debug_assert!(
|
||||
previous < self.pos && self.pos <= self.input.s.len(),
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ fn parse() {
|
|||
let to_link = to_ast::<Link>(link_node);
|
||||
|
||||
let link = to_link("[[#id]]");
|
||||
assert_eq!(link.path().as_ref().map(|x| x.text()), Some("#id"));
|
||||
insta::assert_debug_snapshot!(
|
||||
link.syntax,
|
||||
@r###"
|
||||
|
|
|
|||
|
|
@ -30,10 +30,7 @@ pub fn macros_node(input: Input) -> IResult<Input, GreenElement, ()> {
|
|||
children.push(l_curly3);
|
||||
children.push(name.text_token());
|
||||
if let Some((l_parens, argument, r_parens)) = argument {
|
||||
children.push(node(
|
||||
MACROS_ARGUMENT,
|
||||
[l_parens, argument.text_token(), r_parens],
|
||||
));
|
||||
children.extend([l_parens, argument.text_token(), r_parens]);
|
||||
}
|
||||
children.push(r_curly3);
|
||||
node(MACROS, children)
|
||||
|
|
@ -64,10 +61,9 @@ fn test() {
|
|||
MACROS@0..22
|
||||
L_CURLY3@0..3 "{{{"
|
||||
TEXT@3..16 "one_arg_macro"
|
||||
MACROS_ARGUMENT@16..19
|
||||
L_PARENS@16..17 "("
|
||||
TEXT@17..18 "1"
|
||||
R_PARENS@18..19 ")"
|
||||
L_PARENS@16..17 "("
|
||||
TEXT@17..18 "1"
|
||||
R_PARENS@18..19 ")"
|
||||
R_CURLY3@19..22 "}}}"
|
||||
"###
|
||||
);
|
||||
|
|
@ -78,10 +74,9 @@ fn test() {
|
|||
MACROS@0..25
|
||||
L_CURLY3@0..3 "{{{"
|
||||
TEXT@3..16 "two_arg_macro"
|
||||
MACROS_ARGUMENT@16..22
|
||||
L_PARENS@16..17 "("
|
||||
TEXT@17..21 "1, 2"
|
||||
R_PARENS@21..22 ")"
|
||||
L_PARENS@16..17 "("
|
||||
TEXT@17..21 "1, 2"
|
||||
R_PARENS@21..22 ")"
|
||||
R_CURLY3@22..25 "}}}"
|
||||
"###
|
||||
);
|
||||
|
|
@ -92,10 +87,9 @@ fn test() {
|
|||
MACROS@0..28
|
||||
L_CURLY3@0..3 "{{{"
|
||||
TEXT@3..16 "two_arg_macro"
|
||||
MACROS_ARGUMENT@16..25
|
||||
L_PARENS@16..17 "("
|
||||
TEXT@17..24 "1\\,a, 2"
|
||||
R_PARENS@24..25 ")"
|
||||
L_PARENS@16..17 "("
|
||||
TEXT@17..24 "1\\,a, 2"
|
||||
R_PARENS@24..25 ")"
|
||||
R_CURLY3@25..28 "}}}"
|
||||
"###
|
||||
);
|
||||
|
|
|
|||
|
|
@ -189,7 +189,6 @@ pub enum SyntaxKind {
|
|||
FN_REF,
|
||||
LATEX_FRAGMENT,
|
||||
MACROS,
|
||||
MACROS_ARGUMENT,
|
||||
SNIPPET,
|
||||
TARGET,
|
||||
BOLD,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue