Compare commits
No commits in common. "master" and "gh-pages" have entirely different histories.
8
.github/dependabot.yml
vendored
8
.github/dependabot.yml
vendored
|
@ -1,8 +0,0 @@
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: cargo
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
time: "09:00"
|
|
||||||
open-pull-requests-limit: 10
|
|
35
.github/workflows/rust.yml
vendored
35
.github/workflows/rust.yml
vendored
|
@ -1,35 +0,0 @@
|
||||||
name: Rust
|
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
format:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Run rustfmt
|
|
||||||
run: cargo fmt -- --check
|
|
||||||
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
profile: minimal
|
|
||||||
override: true
|
|
||||||
|
|
||||||
- name: Cache target/
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
path: target
|
|
||||||
key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
|
|
||||||
|
|
||||||
- name: Run Test
|
|
||||||
run: cargo test --all-features
|
|
34
.github/workflows/wasm.yml
vendored
34
.github/workflows/wasm.yml
vendored
|
@ -1,34 +0,0 @@
|
||||||
name: Wasm
|
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: wasm
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: "14"
|
|
||||||
|
|
||||||
- name: Install
|
|
||||||
run: |
|
|
||||||
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
|
||||||
yarn
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
wasm-pack build --target web --out-dir wasm/pkg -- --features wasm
|
|
||||||
yarn build
|
|
||||||
|
|
||||||
- name: Deploy to gh pages
|
|
||||||
uses: JamesIves/github-pages-deploy-action@4.1.5
|
|
||||||
with:
|
|
||||||
branch: gh-pages
|
|
||||||
folder: wasm/lib
|
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -1,7 +0,0 @@
|
||||||
/target
|
|
||||||
**/*.rs.bk
|
|
||||||
Cargo.lock
|
|
||||||
|
|
||||||
benches/*.org
|
|
||||||
.gdb_history
|
|
||||||
perf.data*
|
|
51
Cargo.toml
51
Cargo.toml
|
@ -1,51 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "orgize"
|
|
||||||
version = "0.9.0"
|
|
||||||
authors = ["PoiScript <poiscript@gmail.com>"]
|
|
||||||
description = "A Rust library for parsing orgmode files."
|
|
||||||
repository = "https://github.com/PoiScript/orgize"
|
|
||||||
readme = "README.md"
|
|
||||||
edition = "2018"
|
|
||||||
license = "MIT"
|
|
||||||
keywords = ["orgmode", "emacs", "parser"]
|
|
||||||
exclude = ["/wasm", "/.github"]
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
all-features = true
|
|
||||||
|
|
||||||
[badges]
|
|
||||||
travis-ci = { repository = "PoiScript/orgize" }
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["cdylib", "rlib"]
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
# Tell `rustc` to optimize for small code size.
|
|
||||||
opt-level = "s"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["ser"]
|
|
||||||
wasm = ["serde-wasm-bindgen", "wasm-bindgen", "wee_alloc"]
|
|
||||||
ser = ["serde", "serde_indextree", "indexmap/serde-1"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bytecount = "0.6"
|
|
||||||
chrono = { version = "0.4", optional = true }
|
|
||||||
indextree = "4.3"
|
|
||||||
jetscii = "0.5"
|
|
||||||
lazy_static = "1.4"
|
|
||||||
memchr = "2.4"
|
|
||||||
nom = { version = "7.0", default-features = false, features = ["std"] }
|
|
||||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
|
||||||
serde_indextree = { version = "0.2", optional = true }
|
|
||||||
syntect = { version = "4.6", optional = true }
|
|
||||||
indexmap = { version = "1.7", features = ["serde-1"], optional = true }
|
|
||||||
# wasm stuff
|
|
||||||
serde-wasm-bindgen = { version = "0.3", optional = true }
|
|
||||||
wasm-bindgen = { version = "0.2", optional = true }
|
|
||||||
wee_alloc = { version = "0.4", optional = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
pretty_assertions = "1.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
slugify = "0.1"
|
|
21
LICENSE
21
LICENSE
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2019-2021 Alex Lin (poi)
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
210
README.md
210
README.md
|
@ -1,210 +0,0 @@
|
||||||
# Orgize
|
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/PoiScript/orgize.svg?branch=master)](https://travis-ci.org/PoiScript/orgize)
|
|
||||||
[![Crates.io](https://img.shields.io/crates/v/orgize.svg)](https://crates.io/crates/orgize)
|
|
||||||
[![Document](https://docs.rs/orgize/badge.svg)](https://docs.rs/orgize)
|
|
||||||
|
|
||||||
A Rust library for parsing orgmode files.
|
|
||||||
|
|
||||||
[Live demo](https://orgize.herokuapp.com/)
|
|
||||||
|
|
||||||
## Parse
|
|
||||||
|
|
||||||
To parse a orgmode string, simply invoking the `Org::parse` function:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use orgize::Org;
|
|
||||||
|
|
||||||
Org::parse("* DONE Title :tag:");
|
|
||||||
```
|
|
||||||
|
|
||||||
or `Org::parse_custom`:
|
|
||||||
|
|
||||||
``` rust
|
|
||||||
use orgize::{Org, ParseConfig};
|
|
||||||
|
|
||||||
Org::parse_custom(
|
|
||||||
"* TASK Title 1",
|
|
||||||
&ParseConfig {
|
|
||||||
// custom todo keywords
|
|
||||||
todo_keywords: (vec!["TASK".to_string()], vec![]),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Iter
|
|
||||||
|
|
||||||
`Org::iter` function will returns an iterator of `Event`s, which is
|
|
||||||
a simple wrapper of `Element`.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use orgize::Org;
|
|
||||||
|
|
||||||
for event in Org::parse("* DONE Title :tag:").iter() {
|
|
||||||
// handling the event
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note**: whether an element is container or not, it will appears twice in one loop.
|
|
||||||
One as `Event::Start(element)`, one as `Event::End(element)`.
|
|
||||||
|
|
||||||
## Render html
|
|
||||||
|
|
||||||
You can call the `Org::write_html` function to generate html directly, which
|
|
||||||
uses the `DefaultHtmlHandler` internally:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use orgize::Org;
|
|
||||||
|
|
||||||
let mut writer = Vec::new();
|
|
||||||
Org::parse("* title\n*section*").write_html(&mut writer).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
String::from_utf8(writer).unwrap(),
|
|
||||||
"<main><h1>title</h1><section><p><b>section</b></p></section></main>"
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Render html with custom `HtmlHandler`
|
|
||||||
|
|
||||||
To customize html rendering, simply implementing `HtmlHandler` trait and passing
|
|
||||||
it to the `Org::wirte_html_custom` function.
|
|
||||||
|
|
||||||
The following code demonstrates how to add a id for every headline and return
|
|
||||||
own error type while rendering.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use std::convert::From;
|
|
||||||
use std::io::{Error as IOError, Write};
|
|
||||||
use std::string::FromUtf8Error;
|
|
||||||
|
|
||||||
use orgize::export::{DefaultHtmlHandler, HtmlHandler};
|
|
||||||
use orgize::{Element, Org};
|
|
||||||
use slugify::slugify;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum MyError {
|
|
||||||
IO(IOError),
|
|
||||||
Heading,
|
|
||||||
Utf8(FromUtf8Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
// From<std::io::Error> trait is required for custom error type
|
|
||||||
impl From<IOError> for MyError {
|
|
||||||
fn from(err: IOError) -> Self {
|
|
||||||
MyError::IO(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<FromUtf8Error> for MyError {
|
|
||||||
fn from(err: FromUtf8Error) -> Self {
|
|
||||||
MyError::Utf8(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct MyHtmlHandler(DefaultHtmlHandler);
|
|
||||||
|
|
||||||
impl HtmlHandler<MyError> for MyHtmlHandler {
|
|
||||||
fn start<W: Write>(&mut self, mut w: W, element: &Element) -> Result<(), MyError> {
|
|
||||||
if let Element::Title(title) = element {
|
|
||||||
if title.level > 6 {
|
|
||||||
return Err(MyError::Heading);
|
|
||||||
} else {
|
|
||||||
write!(
|
|
||||||
w,
|
|
||||||
"<h{0}><a id=\"{1}\" href=\"#{1}\">",
|
|
||||||
title.level,
|
|
||||||
slugify!(&title.raw),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// fallthrough to default handler
|
|
||||||
self.0.start(w, element)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn end<W: Write>(&mut self, mut w: W, element: &Element) -> Result<(), MyError> {
|
|
||||||
if let Element::Title(title) = element {
|
|
||||||
write!(w, "</a></h{}>", title.level)?;
|
|
||||||
} else {
|
|
||||||
self.0.end(w, element)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<(), MyError> {
|
|
||||||
let mut writer = Vec::new();
|
|
||||||
let mut handler = MyHtmlHandler::default();
|
|
||||||
Org::parse("* title\n*section*").wirte_html_custom(&mut writer, &mut handler)?;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
String::from_utf8(writer)?,
|
|
||||||
"<main><h1><a id=\"title\" href=\"#title\">title</a></h1>\
|
|
||||||
<section><p><b>section</b></p></section></main>"
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note**: as I mentioned above, each element will appears two times while iterating.
|
|
||||||
And handler will silently ignores all end events from non-container elements.
|
|
||||||
|
|
||||||
So if you want to change how a non-container element renders, just redefine the `start`
|
|
||||||
function and leave the `end` function unchanged.
|
|
||||||
|
|
||||||
## Serde
|
|
||||||
|
|
||||||
`Org` struct have already implemented serde's `Serialize` trait. It means you can
|
|
||||||
serialize it into any format supported by serde, such as json:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use orgize::Org;
|
|
||||||
use serde_json::{json, to_string};
|
|
||||||
|
|
||||||
let org = Org::parse("I 'm *bold*.");
|
|
||||||
println!("{}", to_string(&org).unwrap());
|
|
||||||
|
|
||||||
// {
|
|
||||||
// "type": "document",
|
|
||||||
// "children": [{
|
|
||||||
// "type": "section",
|
|
||||||
// "children": [{
|
|
||||||
// "type": "paragraph",
|
|
||||||
// "children":[{
|
|
||||||
// "type": "text",
|
|
||||||
// "value":"I 'm "
|
|
||||||
// }, {
|
|
||||||
// "type": "bold",
|
|
||||||
// "children":[{
|
|
||||||
// "type": "text",
|
|
||||||
// "value": "bold"
|
|
||||||
// }]
|
|
||||||
// }, {
|
|
||||||
// "type":"text",
|
|
||||||
// "value":"."
|
|
||||||
// }]
|
|
||||||
// }]
|
|
||||||
// }]
|
|
||||||
// }
|
|
||||||
```
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
By now, orgize provides four features:
|
|
||||||
|
|
||||||
+ `ser`: adds the ability to serialize `Org` and other elements using `serde`, enabled by default.
|
|
||||||
|
|
||||||
+ `chrono`: adds the ability to convert `Datetime` into `chrono` structs, disabled by default.
|
|
||||||
|
|
||||||
+ `syntect`: provides `SyntectHtmlHandler` for highlighting code block, disabled by default.
|
|
||||||
|
|
||||||
+ `indexmap`: Uses `IndexMap` instead of `HashMap` for properties to preserve their order, disabled by default.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT
|
|
|
@ -1,30 +0,0 @@
|
||||||
#![feature(test)]
|
|
||||||
|
|
||||||
extern crate test;
|
|
||||||
|
|
||||||
use orgize::Org;
|
|
||||||
use test::Bencher;
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn org_syntax(b: &mut Bencher) {
|
|
||||||
// wget https://orgmode.org/worg/sources/dev/org-syntax.org
|
|
||||||
b.iter(|| {
|
|
||||||
Org::parse(include_str!("org-syntax.org"));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn doc(b: &mut Bencher) {
|
|
||||||
// wget https://orgmode.org/worg/sources/doc.org
|
|
||||||
b.iter(|| {
|
|
||||||
Org::parse(include_str!("doc.org"));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn org_faq(b: &mut Bencher) {
|
|
||||||
// wget https://orgmode.org/worg/sources/org-faq.org
|
|
||||||
b.iter(|| {
|
|
||||||
Org::parse(include_str!("org-faq.org"));
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
# Orgize implementation status
|
|
||||||
|
|
||||||
Check out https://orgmode.org/worg/dev/org-syntax.html for more information.
|
|
||||||
|
|
||||||
- [x] Headline
|
|
||||||
- [X] Objects insides headline title
|
|
||||||
- [ ] Affiliated Keywords
|
|
||||||
|
|
||||||
## Greater Elements
|
|
||||||
- [x] Greater Blocks
|
|
||||||
- [X] Drawers and Property Drawers
|
|
||||||
- [x] Dynamic Blocks
|
|
||||||
- [x] Footnote Definitions
|
|
||||||
- [ ] Inlinetasks
|
|
||||||
- [ ] Objects insides inlinetask title
|
|
||||||
- [x] Plain Lists and Items
|
|
||||||
- [x] Nested List
|
|
||||||
- [ ] Nested List Indentation
|
|
||||||
- [ ] Tag
|
|
||||||
- [ ] Counter
|
|
||||||
- [ ] Counter set
|
|
||||||
- [X] Property Drawers
|
|
||||||
- [X] Tables
|
|
||||||
|
|
||||||
## Elements
|
|
||||||
|
|
||||||
- [x] Babel Call
|
|
||||||
- [x] Blocks
|
|
||||||
- [ ] Escape characters (`#`,`*`, etc)
|
|
||||||
- [ ] Line numbers
|
|
||||||
- [X] Clock, Diary Sexp and Planning
|
|
||||||
- [x] Comments
|
|
||||||
- [x] Fixed Width Areas
|
|
||||||
- [x] Horizontal Rules
|
|
||||||
- [x] Keywords
|
|
||||||
- [ ] LaTeX Environments
|
|
||||||
- [X] Node Properties
|
|
||||||
- [x] Paragraphs
|
|
||||||
- [X] Table Rows
|
|
||||||
|
|
||||||
## Objects
|
|
||||||
|
|
||||||
- [ ] Entities and LaTeX Fragments
|
|
||||||
- [x] Export Snippets
|
|
||||||
- [x] Footnote References
|
|
||||||
- [x] Inline Babel Calls and Source Blocks
|
|
||||||
- [ ] Line Breaks
|
|
||||||
- [x] Links
|
|
||||||
- [x] Regular link
|
|
||||||
- [ ] Plain link
|
|
||||||
- [ ] Angle link
|
|
||||||
- [ ] Radio link
|
|
||||||
- [x] Macros
|
|
||||||
- [x] Targets and Radio Targets
|
|
||||||
- [x] Statistics Cookies
|
|
||||||
- [ ] Subscript and Superscript
|
|
||||||
- [X] Table Cells
|
|
||||||
- [x] Timestamps
|
|
||||||
- [x] Text Markup
|
|
||||||
|
|
||||||
## Export
|
|
||||||
|
|
||||||
- [x] HTML
|
|
||||||
- [X] Org
|
|
||||||
- [ ] LaTeX
|
|
||||||
- [X] JSON, (via Serde)
|
|
||||||
|
|
||||||
## Extra
|
|
||||||
|
|
||||||
- [X] Syntax Highlighting
|
|
942
docs/SYNTAX.md
942
docs/SYNTAX.md
|
@ -1,942 +0,0 @@
|
||||||
# Table of Contents
|
|
||||||
|
|
||||||
1. [Headlines and Sections](#Headlines_and_Sections)
|
|
||||||
2. [Affiliated Keywords](#Affiliated_keywords)
|
|
||||||
3. [Greater Elements](#Greater_Elements)
|
|
||||||
1. [Greater Blocks](#Greater_Blocks)
|
|
||||||
2. [Drawers and Property Drawers](#Drawers)
|
|
||||||
3. [Dynamic Blocks](#Dynamic_Blocks)
|
|
||||||
4. [Footnote Definitions](#Footnote_Definitions)
|
|
||||||
5. [Inlinetasks](#Inlinetasks)
|
|
||||||
6. [Plain Lists and Items](#Plain_Lists_and_Items)
|
|
||||||
7. [Property Drawers](#Property_Drawers)
|
|
||||||
8. [Tables](#Tables)
|
|
||||||
4. [Elements](#Elements)
|
|
||||||
1. [Babel Call](#Babel_Call)
|
|
||||||
2. [Blocks](#Blocks)
|
|
||||||
3. [Clock, Diary Sexp and Planning](#Clock,_Diary_Sexp_and_Planning)
|
|
||||||
4. [Comments](#Comments)
|
|
||||||
5. [Fixed Width Areas](#Fixed_Width_Areas)
|
|
||||||
6. [Horizontal Rules](#Horizontal_Rules)
|
|
||||||
7. [Keywords](#Keywords)
|
|
||||||
8. [LaTeX Environments](#LaTeX_Environments)
|
|
||||||
9. [Node Properties](#Node_Properties)
|
|
||||||
10. [Paragraphs](#Paragraphs)
|
|
||||||
11. [Table Rows](#Table_Rows)
|
|
||||||
5. [Objects](#Objects)
|
|
||||||
1. [Entities and LaTeX Fragments](#Entities_and_LaTeX_Fragments)
|
|
||||||
2. [Export Snippets](#Export_Snippets)
|
|
||||||
3. [Footnote References](#Footnote_References)
|
|
||||||
4. [Inline Babel Calls and Source
|
|
||||||
Blocks](#Inline_Babel_Calls_and_Source_Blocks)
|
|
||||||
5. [Line Breaks](#Line_Breaks)
|
|
||||||
6. [Links](#Links)
|
|
||||||
7. [Macros](#Macros)
|
|
||||||
8. [Targets and Radio Targets](#Targets_and_Radio_Targets)
|
|
||||||
9. [Statistics Cookies](#Statistics_Cookies)
|
|
||||||
10. [Subscript and Superscript](#Subscript_and_Superscript)
|
|
||||||
11. [Table Cells](#Table_Cells)
|
|
||||||
12. [Timestamps](#Timestamp)
|
|
||||||
13. [Text Markup](#Emphasis_Markers)
|
|
||||||
|
|
||||||
This document describes and comments Org syntax as it is currently read by its
|
|
||||||
parser (Org Elements) and, therefore, by the export framework. It also includes
|
|
||||||
a few comments on that syntax.
|
|
||||||
|
|
||||||
A core concept in this syntax is that only headlines, sections, planning lines
|
|
||||||
and property drawers are context-free<sup><a id="fnr.1" class="footref"
|
|
||||||
href="#fn.1">1</a></sup><sup>, </sup><sup><a id="fnr.2" class="footref"
|
|
||||||
href="#fn.2">2</a></sup>. Every other syntactical part only exists within
|
|
||||||
specific environments.
|
|
||||||
|
|
||||||
Three categories are used to classify these environments: **Greater elements**,
|
|
||||||
**elements**, and **objects**, from the broadest scope to the narrowest. The
|
|
||||||
word **element** is used for both Greater and non-Greater elements, the context
|
|
||||||
should make that clear.
|
|
||||||
|
|
||||||
The paragraph is the unit of measurement. An element defines syntactical parts
|
|
||||||
that are at the same level as a paragraph, i.e. which cannot contain or be
|
|
||||||
included in a paragraph. An object is a part that could be included in an
|
|
||||||
element. Greater elements are all parts that can contain an element.
|
|
||||||
|
|
||||||
Empty lines belong to the largest element ending before them. For example, in a
|
|
||||||
list, empty lines between items belong are part of the item before them, but
|
|
||||||
empty lines at the end of a list belong to the plain list element.
|
|
||||||
|
|
||||||
Unless specified otherwise, case is not significant.
|
|
||||||
|
|
||||||
<a id="Headlines_and_Sections"></a>
|
|
||||||
|
|
||||||
# Headlines and Sections
|
|
||||||
|
|
||||||
A headline is defined as:
|
|
||||||
|
|
||||||
STARS KEYWORD PRIORITY TITLE TAGS
|
|
||||||
|
|
||||||
STARS is a string starting at column 0, containing at least one asterisk (and up
|
|
||||||
to `org-inlinetask-min-level` if `org-inlinetask` library is loaded) and ended
|
|
||||||
by a space character. The number of asterisks is used to define the level of the
|
|
||||||
headline. It's the sole compulsory part of a headline.
|
|
||||||
|
|
||||||
KEYWORD is a TODO keyword, which has to belong to the list defined in
|
|
||||||
`org-todo-keywords-1`. Case is significant.
|
|
||||||
|
|
||||||
PRIORITY is a priority cookie, i.e. a single letter preceded by a hash sign #
|
|
||||||
and enclosed within square brackets.
|
|
||||||
|
|
||||||
TITLE can be made of any character but a new line. Though, it will match after
|
|
||||||
every other part have been matched.
|
|
||||||
|
|
||||||
TAGS is made of words containing any alpha-numeric character, underscore, at
|
|
||||||
sign, hash sign or percent sign, and separated with colons.
|
|
||||||
|
|
||||||
Examples of valid headlines include:
|
|
||||||
|
|
||||||
*
|
|
||||||
|
|
||||||
** DONE
|
|
||||||
|
|
||||||
*** Some e-mail
|
|
||||||
|
|
||||||
**** TODO [#A] COMMENT Title :tag:a2%:
|
|
||||||
|
|
||||||
If the first word appearing in the title is `COMMENT`, the headline will be
|
|
||||||
considered as **commented**. Case is significant.
|
|
||||||
|
|
||||||
If its title is `org-footnote-section`, it will be considered as a **footnote
|
|
||||||
section**. Case is significant.
|
|
||||||
|
|
||||||
If `ARCHIVE` is one of its tags, it will be considered as **archived**. Case is
|
|
||||||
significant.
|
|
||||||
|
|
||||||
A headline contains directly one section (optionally), followed by any number of
|
|
||||||
deeper level headlines.
|
|
||||||
|
|
||||||
A section contains directly any greater element or element. Only a headline can
|
|
||||||
contain a section. As an exception, text before the first headline in the
|
|
||||||
document also belongs to a section.
|
|
||||||
|
|
||||||
As an example, consider the following document:
|
|
||||||
|
|
||||||
An introduction.
|
|
||||||
|
|
||||||
* A Headline
|
|
||||||
|
|
||||||
Some text.
|
|
||||||
|
|
||||||
** Sub-Topic 1
|
|
||||||
|
|
||||||
** Sub-Topic 2
|
|
||||||
|
|
||||||
*** Additional entry
|
|
||||||
|
|
||||||
Its internal structure could be summarized as:
|
|
||||||
|
|
||||||
(document
|
|
||||||
(section)
|
|
||||||
(headline
|
|
||||||
(section)
|
|
||||||
(headline)
|
|
||||||
(headline
|
|
||||||
(headline))))
|
|
||||||
|
|
||||||
<a id="Affiliated_keywords"></a>
|
|
||||||
|
|
||||||
# Affiliated Keywords
|
|
||||||
|
|
||||||
With the exception of [inlinetasks](#Inlinetasks),
|
|
||||||
[items](#Plain_Lists_and_Items), [planning](#Clock,_Diary_Sexp_and_Planning),
|
|
||||||
[clocks](#Clock,_Diary_Sexp_and_Planning), [node properties](#Node_Properties)
|
|
||||||
and [table rows](#Table_Rows), every other element type can be assigned
|
|
||||||
attributes.
|
|
||||||
|
|
||||||
This is done by adding specific keywords, named **affiliated keywords**, just
|
|
||||||
above the element considered, no blank line allowed.
|
|
||||||
|
|
||||||
Affiliated keywords are built upon one of the following patterns: `#+KEY: VALUE`,
|
|
||||||
`#+KEY[OPTIONAL]: VALUE` or `#+ATTR_BACKEND: VALUE`.
|
|
||||||
|
|
||||||
KEY is either `CAPTION`, `HEADER`, `NAME`, `PLOT` or `RESULTS` string.
|
|
||||||
|
|
||||||
BACKEND is a string constituted of alpha-numeric characters, hyphens or
|
|
||||||
underscores.
|
|
||||||
|
|
||||||
OPTIONAL and VALUE can contain any character but a new line. Only `CAPTION` and
|
|
||||||
`RESULTS` keywords can have an optional value.
|
|
||||||
|
|
||||||
An affiliated keyword can appear more than once if KEY is either `CAPTION` or
|
|
||||||
`HEADER` or if its pattern is `#+ATTR_BACKEND: VALUE`.
|
|
||||||
|
|
||||||
`CAPTION`, `AUTHOR`, `DATE` and `TITLE` keywords can contain objects in their
|
|
||||||
value and their optional value, if applicable.
|
|
||||||
|
|
||||||
<a id="Greater_Elements"></a>
|
|
||||||
|
|
||||||
# Greater Elements
|
|
||||||
|
|
||||||
Unless specified otherwise, greater elements can contain directly any other
|
|
||||||
element or greater element excepted:
|
|
||||||
|
|
||||||
- elements of their own type,
|
|
||||||
- [node properties](#Node_Properties), which can only be found in [property
|
|
||||||
drawers](#Property_Drawers),
|
|
||||||
- [items](#Plain_Lists_and_Items), which can only be found in [plain
|
|
||||||
lists](#Plain_Lists_and_Items).
|
|
||||||
|
|
||||||
<a id="Greater_Blocks"></a>
|
|
||||||
|
|
||||||
## Greater Blocks
|
|
||||||
|
|
||||||
Greater blocks consist in the following pattern:
|
|
||||||
|
|
||||||
#+BEGIN_NAME PARAMETERS
|
|
||||||
CONTENTS
|
|
||||||
#+END_NAME
|
|
||||||
|
|
||||||
NAME can contain any non-whitespace character.
|
|
||||||
|
|
||||||
PARAMETERS can contain any character other than new line, and can be omitted.
|
|
||||||
|
|
||||||
If NAME is `CENTER`, it will be a **center block**. If it is `QUOTE`, it will be
|
|
||||||
a **quote block**.
|
|
||||||
|
|
||||||
If the block is neither a center block, a quote block or a [block
|
|
||||||
element](#Blocks), it will be a **special block**.
|
|
||||||
|
|
||||||
CONTENTS can contain any element, except : a line `#+END_NAME` on its own. Also
|
|
||||||
lines beginning with STARS must be quoted by a comma.
|
|
||||||
|
|
||||||
<a id="Drawers"></a>
|
|
||||||
|
|
||||||
## Drawers and Property Drawers
|
|
||||||
|
|
||||||
Pattern for drawers is:
|
|
||||||
|
|
||||||
:NAME:
|
|
||||||
CONTENTS
|
|
||||||
:END:
|
|
||||||
|
|
||||||
NAME can contain word-constituent characters, hyphens and underscores.
|
|
||||||
|
|
||||||
CONTENTS can contain any element but another drawer.
|
|
||||||
|
|
||||||
<a id="Dynamic_Blocks"></a>
|
|
||||||
|
|
||||||
## Dynamic Blocks
|
|
||||||
|
|
||||||
Pattern for dynamic blocks is:
|
|
||||||
|
|
||||||
#+BEGIN: NAME PARAMETERS
|
|
||||||
CONTENTS
|
|
||||||
#+END:
|
|
||||||
|
|
||||||
NAME cannot contain any whitespace character.
|
|
||||||
|
|
||||||
PARAMETERS can contain any character and can be omitted.
|
|
||||||
|
|
||||||
<a id="Footnote_Definitions"></a>
|
|
||||||
|
|
||||||
## Footnote Definitions
|
|
||||||
|
|
||||||
Pattern for footnote definitions is:
|
|
||||||
|
|
||||||
[fn:LABEL] CONTENTS
|
|
||||||
|
|
||||||
It must start at column 0.
|
|
||||||
|
|
||||||
LABEL is either a number or follows the pattern `fn:WORD`, where word can
|
|
||||||
contain any word-constituent character, hyphens and underscore characters.
|
|
||||||
|
|
||||||
CONTENTS can contain any element excepted another footnote definition. It ends
|
|
||||||
at the next footnote definition, the next headline, two consecutive empty lines
|
|
||||||
or the end of buffer.
|
|
||||||
|
|
||||||
<a id="Inlinetasks"></a>
|
|
||||||
|
|
||||||
## Inlinetasks
|
|
||||||
|
|
||||||
Inlinetasks are defined by `org-inlinetask-min-level` contiguous asterisk
|
|
||||||
characters starting at column 0, followed by a whitespace character.
|
|
||||||
|
|
||||||
Optionally, inlinetasks can be ended with a string constituted of
|
|
||||||
`org-inlinetask-min-level` contiguous asterisk characters starting at column 0,
|
|
||||||
followed by a space and the `END` string.
|
|
||||||
|
|
||||||
Inlinetasks are recognized only after `org-inlinetask` library is loaded.
|
|
||||||
|
|
||||||
<a id="Plain_Lists_and_Items"></a>
|
|
||||||
|
|
||||||
## Plain Lists and Items
|
|
||||||
|
|
||||||
Items are defined by a line starting with the following pattern:
|
|
||||||
`BULLET COUNTER-SET CHECK-BOX TAG`, in which only BULLET is mandatory.
|
|
||||||
|
|
||||||
BULLET is either an asterisk, a hyphen, a plus sign character or follows either
|
|
||||||
the pattern `COUNTER.` or `COUNTER)`. In any case, BULLET is follwed by a
|
|
||||||
whitespace character or line ending.
|
|
||||||
|
|
||||||
COUNTER can be a number or a single letter.
|
|
||||||
|
|
||||||
COUNTER-SET follows the pattern [@COUNTER].
|
|
||||||
|
|
||||||
CHECK-BOX is either a single whitespace character, a `X` character or a hyphen,
|
|
||||||
enclosed within square brackets.
|
|
||||||
|
|
||||||
TAG follows `TAG-TEXT ::` pattern, where TAG-TEXT can contain any character but
|
|
||||||
a new line.
|
|
||||||
|
|
||||||
An item ends before the next item, the first line less or equally indented than
|
|
||||||
its starting line, or two consecutive empty lines. Indentation of lines within
|
|
||||||
other greater elements do not count, neither do inlinetasks boundaries.
|
|
||||||
|
|
||||||
A plain list is a set of consecutive items of the same indentation. It can only
|
|
||||||
directly contain items.
|
|
||||||
|
|
||||||
If first item in a plain list has a counter in its bullet, the plain list will
|
|
||||||
be an **ordered plain-list**. If it contains a tag, it will be a **descriptive
|
|
||||||
list**. Otherwise, it will be an **unordered list**. List types are mutually
|
|
||||||
exclusive.
|
|
||||||
|
|
||||||
For example, consider the following excerpt of an Org document:
|
|
||||||
|
|
||||||
1. item 1
|
|
||||||
2. [X] item 2
|
|
||||||
- some tag :: item 2.1
|
|
||||||
|
|
||||||
Its internal structure is as follows:
|
|
||||||
|
|
||||||
(ordered-plain-list
|
|
||||||
(item)
|
|
||||||
(item
|
|
||||||
(descriptive-plain-list
|
|
||||||
(item))))
|
|
||||||
|
|
||||||
<a id="Property_Drawers"></a>
|
|
||||||
|
|
||||||
## Property Drawers
|
|
||||||
|
|
||||||
Property drawers are a special type of drawer containing properties attached to
|
|
||||||
a headline. They are located right after a [headline](#Headlines_and_Sections)
|
|
||||||
and its [planning](#Clock,_Diary_Sexp_and_Planning) information.
|
|
||||||
|
|
||||||
HEADLINE
|
|
||||||
PROPERTYDRAWER
|
|
||||||
|
|
||||||
HEADLINE
|
|
||||||
PLANNING
|
|
||||||
PROPERTYDRAWER
|
|
||||||
|
|
||||||
PROPERTYDRAWER follows the pattern
|
|
||||||
|
|
||||||
:PROPERTIES:
|
|
||||||
CONTENTS
|
|
||||||
:END:
|
|
||||||
|
|
||||||
where CONTENTS consists of zero or more [node properties](#Node_Properties).
|
|
||||||
|
|
||||||
<a id="Tables"></a>
|
|
||||||
|
|
||||||
## Tables
|
|
||||||
|
|
||||||
Tables start at lines beginning with either a vertical bar or the `+-` string
|
|
||||||
followed by plus or minus signs only, assuming they are not preceded with lines
|
|
||||||
of the same type. These lines can be indented.
|
|
||||||
|
|
||||||
A table starting with a vertical bar has `org` type. Otherwise it has `table.el`
|
|
||||||
type.
|
|
||||||
|
|
||||||
Org tables end at the first line not starting with a vertical bar. Table.el
|
|
||||||
tables end at the first line not starting with either a vertical line or a plus
|
|
||||||
sign. Such lines can be indented.
|
|
||||||
|
|
||||||
An org table can only contain table rows. A table.el table does not contain
|
|
||||||
anything.
|
|
||||||
|
|
||||||
One or more `#+TBLFM: FORMULAS` lines, where `FORMULAS` can contain any
|
|
||||||
character, can follow an org table.
|
|
||||||
|
|
||||||
<a id="Elements"></a>
|
|
||||||
|
|
||||||
# Elements
|
|
||||||
|
|
||||||
Elements cannot contain any other element.
|
|
||||||
|
|
||||||
Only [keywords](#Keywords) whose name belongs to
|
|
||||||
`org-element-document-properties`, [verse blocks](#Blocks) ,
|
|
||||||
[paragraphs](#Paragraphs) and [table rows](#Table_Rows) can contain objects.
|
|
||||||
|
|
||||||
<a id="Babel_Call"></a>
|
|
||||||
|
|
||||||
## Babel Call
|
|
||||||
|
|
||||||
Pattern for babel calls is:
|
|
||||||
|
|
||||||
#+CALL: VALUE
|
|
||||||
|
|
||||||
VALUE is optional. It can contain any character but a new line.
|
|
||||||
|
|
||||||
<a id="Blocks"></a>
|
|
||||||
|
|
||||||
## Blocks
|
|
||||||
|
|
||||||
Like [greater blocks](#Greater_Blocks), pattern for blocks is:
|
|
||||||
|
|
||||||
#+BEGIN_NAME DATA
|
|
||||||
CONTENTS
|
|
||||||
#+END_NAME
|
|
||||||
|
|
||||||
NAME cannot contain any whitespace character.
|
|
||||||
|
|
||||||
1. If NAME is `COMMENT`, it will be a **comment block**.
|
|
||||||
2. If it is `EXAMPLE`, it will be an **example block**.
|
|
||||||
3. If it is `EXPORT`, it will be an **export block**.
|
|
||||||
4. If it is `SRC`, it will be a **source block**.
|
|
||||||
5. If it is `VERSE`, it will be a **verse block**.
|
|
||||||
|
|
||||||
DATA can contain any character but a new line. It can be ommitted, unless the
|
|
||||||
block is either a **source block** or an **export block**.
|
|
||||||
|
|
||||||
In the latter case, it should be constituted of a single word.
|
|
||||||
|
|
||||||
In the former case, it must follow the pattern `LANGUAGE SWITCHES ARGUMENTS`,
|
|
||||||
where SWITCHES and ARGUMENTS are optional.
|
|
||||||
|
|
||||||
LANGUAGE cannot contain any whitespace character.
|
|
||||||
|
|
||||||
SWITCHES is made of any number of `SWITCH` patterns, separated by blank lines.
|
|
||||||
|
|
||||||
A SWITCH pattern is either `-l FORMAT`, where FORMAT can contain any character
|
|
||||||
but a double quote and a new line, `-S` or `+S`, where S stands for a single
|
|
||||||
letter.
|
|
||||||
|
|
||||||
ARGUMENTS can contain any character but a new line.
|
|
||||||
|
|
||||||
CONTENTS can contain any character, including new lines. Though it will only
|
|
||||||
contain Org objects if the block is a verse block. Otherwise, CONTENTS will not
|
|
||||||
be parsed.
|
|
||||||
|
|
||||||
<a id="Clock,_Diary_Sexp_and_Planning"></a>
|
|
||||||
|
|
||||||
## Clock, Diary Sexp and Planning
|
|
||||||
|
|
||||||
A clock follows either of the patterns below:
|
|
||||||
|
|
||||||
CLOCK: INACTIVE-TIMESTAMP
|
|
||||||
CLOCK: INACTIVE-TIMESTAMP-RANGE DURATION
|
|
||||||
|
|
||||||
INACTIVE-TIMESTAMP, resp. INACTIVE-TIMESTAMP-RANGE, is an inactive, resp.
|
|
||||||
inactive range, [timestamp](#Timestamp) object.
|
|
||||||
|
|
||||||
DURATION follows the pattern:
|
|
||||||
|
|
||||||
=> HH:MM
|
|
||||||
|
|
||||||
HH is a number containing any number of digits. MM is a two digit numbers.
|
|
||||||
|
|
||||||
A diary sexp is a line starting at column 0 with `%%(` string. It can then
|
|
||||||
contain any character besides a new line.
|
|
||||||
|
|
||||||
A planning is an element with the following pattern:
|
|
||||||
|
|
||||||
HEADLINE
|
|
||||||
PLANNING
|
|
||||||
|
|
||||||
where HEADLINE is a [headline](#Headlines_and_Sections) element and PLANNING is
|
|
||||||
a line filled with INFO parts, where each of them follows the pattern:
|
|
||||||
|
|
||||||
KEYWORD: TIMESTAMP
|
|
||||||
|
|
||||||
KEYWORD is either `DEADLINE`, `SCHEDULED` or `CLOSED`. TIMESTAMP is a
|
|
||||||
[timestamp](#Timestamp) object.
|
|
||||||
|
|
||||||
In particular, no blank line is allowed between PLANNING and HEADLINE.
|
|
||||||
|
|
||||||
<a id="Comments"></a>
|
|
||||||
|
|
||||||
## Comments
|
|
||||||
|
|
||||||
A **comment line** starts with a hash signe and a whitespace character or an end
|
|
||||||
of line.
|
|
||||||
|
|
||||||
Comments can contain any number of consecutive comment lines.
|
|
||||||
|
|
||||||
<a id="Fixed_Width_Areas"></a>
|
|
||||||
|
|
||||||
## Fixed Width Areas
|
|
||||||
|
|
||||||
A **fixed-width line** start with a colon character and a whitespace or an end
|
|
||||||
of line.
|
|
||||||
|
|
||||||
Fixed width areas can contain any number of consecutive fixed-width lines.
|
|
||||||
|
|
||||||
<a id="Horizontal_Rules"></a>
|
|
||||||
|
|
||||||
## Horizontal Rules
|
|
||||||
|
|
||||||
A horizontal rule is a line made of at least 5 consecutive hyphens. It can be
|
|
||||||
indented.
|
|
||||||
|
|
||||||
<a id="Keywords"></a>
|
|
||||||
|
|
||||||
## Keywords
|
|
||||||
|
|
||||||
Keywords follow the syntax:
|
|
||||||
|
|
||||||
#+KEY: VALUE
|
|
||||||
|
|
||||||
KEY can contain any non-whitespace character, but it cannot be equal to `CALL`
|
|
||||||
or any affiliated keyword.
|
|
||||||
|
|
||||||
VALUE can contain any character excepted a new line.
|
|
||||||
|
|
||||||
If KEY belongs to `org-element-document-properties`, VALUE can contain objects.
|
|
||||||
|
|
||||||
<a id="LaTeX_Environments"></a>
|
|
||||||
|
|
||||||
## LaTeX Environments
|
|
||||||
|
|
||||||
Pattern for LaTeX environments is:
|
|
||||||
|
|
||||||
\begin{NAME} CONTENTS \end{NAME}
|
|
||||||
|
|
||||||
NAME is constituted of alpha-numeric or asterisk characters.
|
|
||||||
|
|
||||||
CONTENTS can contain anything but the `\end{NAME}` string.
|
|
||||||
|
|
||||||
<a id="Node_Properties"></a>
|
|
||||||
|
|
||||||
## Node Properties
|
|
||||||
|
|
||||||
Node properties can only exist in [property drawers](#Property_Drawers). Their
|
|
||||||
pattern is any of the following
|
|
||||||
|
|
||||||
:NAME: VALUE
|
|
||||||
|
|
||||||
:NAME+: VALUE
|
|
||||||
|
|
||||||
:NAME:
|
|
||||||
|
|
||||||
:NAME+:
|
|
||||||
|
|
||||||
NAME can contain any non-whitespace character but cannot end with a plus sign.
|
|
||||||
It cannot be the empty string.
|
|
||||||
|
|
||||||
VALUE can contain anything but a newline character.
|
|
||||||
|
|
||||||
<a id="Paragraphs"></a>
|
|
||||||
|
|
||||||
## Paragraphs
|
|
||||||
|
|
||||||
Paragraphs are the default element, which means that any unrecognized context is
|
|
||||||
a paragraph.
|
|
||||||
|
|
||||||
Empty lines and other elements end paragraphs.
|
|
||||||
|
|
||||||
Paragraphs can contain every type of object.
|
|
||||||
|
|
||||||
<a id="Table_Rows"></a>
|
|
||||||
|
|
||||||
## Table Rows
|
|
||||||
|
|
||||||
A table rows is either constituted of a vertical bar and any number of [table
|
|
||||||
cells](#Table_Cells) or a vertical bar followed by a hyphen.
|
|
||||||
|
|
||||||
In the first case the table row has the **standard** type. In the second case,
|
|
||||||
it has the **rule** type.
|
|
||||||
|
|
||||||
Table rows can only exist in [tables](#Tables).
|
|
||||||
|
|
||||||
<a id="Objects"></a>
|
|
||||||
|
|
||||||
# Objects
|
|
||||||
|
|
||||||
Objects can only be found in the following locations:
|
|
||||||
|
|
||||||
- [affiliated keywords](#Affiliated_keywords) defined in
|
|
||||||
`org-element-parsed-keywords`,
|
|
||||||
- [document properties](#Keywords),
|
|
||||||
- [headline](#Headlines_and_Sections) titles,
|
|
||||||
- [inlinetask](#Inlinetasks) titles,
|
|
||||||
- [item](#Plain_Lists_and_Items) tags,
|
|
||||||
- [paragraphs](#Paragraphs),
|
|
||||||
- [table cells](#Table_Cells),
|
|
||||||
- [table rows](#Table_Rows), which can only contain table cell objects,
|
|
||||||
- [verse blocks](#Blocks).
|
|
||||||
|
|
||||||
Most objects cannot contain objects. Those which can will be specified.
|
|
||||||
|
|
||||||
<a id="Entities_and_LaTeX_Fragments"></a>
|
|
||||||
|
|
||||||
## Entities and LaTeX Fragments
|
|
||||||
|
|
||||||
An entity follows the pattern:
|
|
||||||
|
|
||||||
\NAME POST
|
|
||||||
|
|
||||||
where NAME has a valid association in either `org-entities` or
|
|
||||||
`org-entities-user`.
|
|
||||||
|
|
||||||
POST is the end of line, `{}` string, or a non-alphabetical character. It isn't
|
|
||||||
separated from NAME by a whitespace character.
|
|
||||||
|
|
||||||
A LaTeX fragment can follow multiple patterns:
|
|
||||||
|
|
||||||
\NAME BRACKETS
|
|
||||||
\(CONTENTS\)
|
|
||||||
\[CONTENTS\]
|
|
||||||
$$CONTENTS$$
|
|
||||||
PRE$CHAR$POST
|
|
||||||
PRE$BORDER1 BODY BORDER2$POST
|
|
||||||
|
|
||||||
NAME contains alphabetical characters only and must not have an association in
|
|
||||||
either **org-entities** or **org-entities-user**.
|
|
||||||
|
|
||||||
BRACKETS is optional, and is not separated from NAME with white spaces. It may
|
|
||||||
contain any number of the following patterns:
|
|
||||||
|
|
||||||
[CONTENTS1]
|
|
||||||
{CONTENTS2}
|
|
||||||
|
|
||||||
where CONTENTS1 can contain any characters excepted `{` `}`, `[` `]` and newline
|
|
||||||
and CONTENTS2 can contain any character excepted `{`, `}` and newline.
|
|
||||||
|
|
||||||
CONTENTS can contain any character but cannot contain `\\)` in the second
|
|
||||||
template or `\\]` in the third one.
|
|
||||||
|
|
||||||
PRE is either the beginning of line or a character different from `$`.
|
|
||||||
|
|
||||||
CHAR is a non-whitespace character different from `.`, `,`, `?`, `;`, `'` or a
|
|
||||||
double quote.
|
|
||||||
|
|
||||||
POST is any punctuation (including parentheses and quotes) or space character,
|
|
||||||
or the end of line.
|
|
||||||
|
|
||||||
BORDER1 is a non-whitespace character different from `.`, `,`, `;` and `$`.
|
|
||||||
|
|
||||||
BODY can contain any character excepted `$`, and may not span over more than 3
|
|
||||||
lines.
|
|
||||||
|
|
||||||
BORDER2 is any non-whitespace character different from `,`, `.` and `$`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
> It would introduce incompatibilities with previous Org versions, but support
|
|
||||||
> for `$...$` (and for symmetry, `$$...$$`) constructs ought to be removed.
|
|
||||||
>
|
|
||||||
> They are slow to parse, fragile, redundant and imply false positives. —
|
|
||||||
> ngz
|
|
||||||
|
|
||||||
<a id="Export_Snippets"></a>
|
|
||||||
|
|
||||||
## Export Snippets
|
|
||||||
|
|
||||||
Patter for export snippets is:
|
|
||||||
|
|
||||||
@@NAME:VALUE@@
|
|
||||||
|
|
||||||
NAME can contain any alpha-numeric character and hyphens.
|
|
||||||
|
|
||||||
VALUE can contain anything but `@@` string.
|
|
||||||
|
|
||||||
<a id="Footnote_References"></a>
|
|
||||||
|
|
||||||
## Footnote References
|
|
||||||
|
|
||||||
There are four patterns for footnote references:
|
|
||||||
|
|
||||||
[fn:LABEL]
|
|
||||||
[fn:LABEL:DEFINITION]
|
|
||||||
[fn::DEFINITION]
|
|
||||||
|
|
||||||
LABEL can contain any word constituent character, hyphens and underscores.
|
|
||||||
|
|
||||||
DEFINITION can contain any character. Though opening and closing square brackets
|
|
||||||
must be balanced in it. It can contain any object encountered in a paragraph,
|
|
||||||
even other footnote references.
|
|
||||||
|
|
||||||
If the reference follows the second pattern, it is called an **inline
|
|
||||||
footnote**. If it follows the third one, i.e. if LABEL is omitted, it is an
|
|
||||||
**anonymous footnote**.
|
|
||||||
|
|
||||||
<a id="Inline_Babel_Calls_and_Source_Blocks"></a>
|
|
||||||
|
|
||||||
## Inline Babel Calls and Source Blocks
|
|
||||||
|
|
||||||
Inline Babel calls follow any of the following patterns:
|
|
||||||
|
|
||||||
call_NAME(ARGUMENTS)
|
|
||||||
call_NAME[HEADER](ARGUMENTS)[HEADER]
|
|
||||||
|
|
||||||
NAME can contain any character besides `(`, `)` and `\n`.
|
|
||||||
|
|
||||||
HEADER can contain any character besides `]` and `\n`.
|
|
||||||
|
|
||||||
ARGUMENTS can contain any character besides `)` and `\n`.
|
|
||||||
|
|
||||||
Inline source blocks follow any of the following patterns:
|
|
||||||
|
|
||||||
src_LANG{BODY}
|
|
||||||
src_LANG[OPTIONS]{BODY}
|
|
||||||
|
|
||||||
LANG can contain any non-whitespace character.
|
|
||||||
|
|
||||||
OPTIONS and BODY can contain any character but `\n`.
|
|
||||||
|
|
||||||
<a id="Line_Breaks"></a>
|
|
||||||
|
|
||||||
## Line Breaks
|
|
||||||
|
|
||||||
A line break consists in `\\\SPACE` pattern at the end of an otherwise non-empty
|
|
||||||
line.
|
|
||||||
|
|
||||||
SPACE can contain any number of tabs and spaces, including 0.
|
|
||||||
|
|
||||||
<a id="Links"></a>
|
|
||||||
|
|
||||||
## Links
|
|
||||||
|
|
||||||
There are 4 major types of links:
|
|
||||||
|
|
||||||
PRE1 RADIO POST1 ("radio" link)
|
|
||||||
<PROTOCOL:PATH> ("angle" link)
|
|
||||||
PRE2 PROTOCOL:PATH2 POST2 ("plain" link)
|
|
||||||
[[PATH3]DESCRIPTION] ("regular" link)
|
|
||||||
|
|
||||||
PRE1 and POST1, when they exist, are non alphanumeric characters.
|
|
||||||
|
|
||||||
RADIO is a string matched by some [radio target](#Targets_and_Radio_Targets). It
|
|
||||||
may contain [entities](#Entities_and_LaTeX_Fragments), [latex
|
|
||||||
fragments](#Entities_and_LaTeX_Fragments),
|
|
||||||
[subscript](#Subscript_and_Superscript) and
|
|
||||||
[superscript](#Subscript_and_Superscript).
|
|
||||||
|
|
||||||
PROTOCOL is a string among `org-link-types`.
|
|
||||||
|
|
||||||
PATH can contain any character but `]`, `<`, `>` and `\n`.
|
|
||||||
|
|
||||||
PRE2 and POST2, when they exist, are non word constituent characters.
|
|
||||||
|
|
||||||
PATH2 can contain any non-whitespace character excepted `(`, `)`, `<` and `>`.
|
|
||||||
It must end with a word-constituent character, or any non-whitespace
|
|
||||||
non-punctuation character followed by `/`.
|
|
||||||
|
|
||||||
DESCRIPTION must be enclosed within square brackets. It can contain any
|
|
||||||
character but square brackets. It can contain any object found in a paragraph
|
|
||||||
excepted a [footnote reference](#Footnote_References), a [radio
|
|
||||||
target](#Targets_and_Radio_Targets) and a [line break](#Line_Breaks). It cannot
|
|
||||||
contain another link either, unless it is a plain or angular link.
|
|
||||||
|
|
||||||
DESCRIPTION is optional.
|
|
||||||
|
|
||||||
PATH3 is built according to the following patterns:
|
|
||||||
|
|
||||||
FILENAME ("file" type)
|
|
||||||
PROTOCOL:PATH4 ("PROTOCOL" type)
|
|
||||||
PROTOCOL://PATH4 ("PROTOCOL" type)
|
|
||||||
id:ID ("id" type)
|
|
||||||
#CUSTOM-ID ("custom-id" type)
|
|
||||||
(CODEREF) ("coderef" type)
|
|
||||||
FUZZY ("fuzzy" type)
|
|
||||||
|
|
||||||
FILENAME is a file name, either absolute or relative.
|
|
||||||
|
|
||||||
PATH4 can contain any character besides square brackets.
|
|
||||||
|
|
||||||
ID is constituted of hexadecimal numbers separated with hyphens.
|
|
||||||
|
|
||||||
PATH4, CUSTOM-ID, CODEREF and FUZZY can contain any character besides square
|
|
||||||
brackets.
|
|
||||||
|
|
||||||
<a id="Macros"></a>
|
|
||||||
|
|
||||||
## Macros
|
|
||||||
|
|
||||||
Macros follow the pattern:
|
|
||||||
|
|
||||||
{{{NAME(ARGUMENTS)}}}
|
|
||||||
|
|
||||||
NAME must start with a letter and can be followed by any number of alpha-numeric
|
|
||||||
characters, hyphens and underscores.
|
|
||||||
|
|
||||||
ARGUMENTS can contain anything but `}}}` string. Values within ARGUMENTS are
|
|
||||||
separated by commas. Non-separating commas have to be escaped with a backslash
|
|
||||||
character.
|
|
||||||
|
|
||||||
<a id="Targets_and_Radio_Targets"></a>
|
|
||||||
|
|
||||||
## Targets and Radio Targets
|
|
||||||
|
|
||||||
Radio targets follow the pattern:
|
|
||||||
|
|
||||||
<<<CONTENTS>>>
|
|
||||||
|
|
||||||
CONTENTS can be any character besides `<`, `>` and `\n`. It cannot start or end
|
|
||||||
with a whitespace character. As far as objects go, it can contain [text
|
|
||||||
markup](#Emphasis_Markers), [entities](#Entities_and_LaTeX_Fragments), [latex
|
|
||||||
fragments](#Entities_and_LaTeX_Fragments),
|
|
||||||
[subscript](#Subscript_and_Superscript) and
|
|
||||||
[superscript](#Subscript_and_Superscript) only.
|
|
||||||
|
|
||||||
Targets follow the pattern:
|
|
||||||
|
|
||||||
<<TARGET>>
|
|
||||||
|
|
||||||
TARGET can contain any character besides `<`, `>` and `\n`. It cannot start or
|
|
||||||
end with a whitespace character. It cannot contain any object.
|
|
||||||
|
|
||||||
<a id="Statistics_Cookies"></a>
|
|
||||||
|
|
||||||
## Statistics Cookies
|
|
||||||
|
|
||||||
Statistics cookies follow either pattern:
|
|
||||||
|
|
||||||
[PERCENT%]
|
|
||||||
[NUM1/NUM2]
|
|
||||||
|
|
||||||
PERCENT, NUM1 and NUM2 are numbers or the empty string.
|
|
||||||
|
|
||||||
<a id="Subscript_and_Superscript"></a>
|
|
||||||
|
|
||||||
## Subscript and Superscript
|
|
||||||
|
|
||||||
Pattern for subscript is:
|
|
||||||
|
|
||||||
CHAR_SCRIPT
|
|
||||||
|
|
||||||
Pattern for superscript is:
|
|
||||||
|
|
||||||
CHAR^SCRIPT
|
|
||||||
|
|
||||||
CHAR is any non-whitespace character.
|
|
||||||
|
|
||||||
SCRIPT can be `*` or an expression enclosed in parenthesis (respectively curly
|
|
||||||
brackets), possibly containing balanced parenthesis (respectively curly
|
|
||||||
brackets).
|
|
||||||
|
|
||||||
SCRIPT can also follow the pattern:
|
|
||||||
|
|
||||||
SIGN CHARS FINAL
|
|
||||||
|
|
||||||
SIGN is either a plus sign, a minus sign, or an empty string.
|
|
||||||
|
|
||||||
CHARS is any number of alpha-numeric characters, commas, backslashes and dots,
|
|
||||||
or an empty string.
|
|
||||||
|
|
||||||
FINAL is an alpha-numeric character.
|
|
||||||
|
|
||||||
There is no white space between SIGN, CHARS and FINAL.
|
|
||||||
|
|
||||||
<a id="Table_Cells"></a>
|
|
||||||
|
|
||||||
## Table Cells
|
|
||||||
|
|
||||||
Table cells follow the pattern:
|
|
||||||
|
|
||||||
CONTENTS SPACES|
|
|
||||||
|
|
||||||
CONTENTS can contain any character excepted a vertical bar.
|
|
||||||
|
|
||||||
SPACES contains any number of space characters, including zero. It can be used
|
|
||||||
to align properly the table.
|
|
||||||
|
|
||||||
The final bar may be replaced with a newline character for the last cell in row.
|
|
||||||
|
|
||||||
<a id="Timestamp"></a>
|
|
||||||
|
|
||||||
## Timestamps
|
|
||||||
|
|
||||||
There are seven possible patterns for timestamps:
|
|
||||||
|
|
||||||
<%%(SEXP)> (diary)
|
|
||||||
<DATE TIME REPEATER-OR-DELAY> (active)
|
|
||||||
[DATE TIME REPEATER-OR-DELAY] (inactive)
|
|
||||||
<DATE TIME REPEATER-OR-DELAY>--<DATE TIME REPEATER-OR-DELAY> (active range)
|
|
||||||
<DATE TIME-TIME REPEATER-OR-DELAY> (active range)
|
|
||||||
[DATE TIME REPEATER-OR-DELAY]--[DATE TIME REPEATER-OR-DELAY] (inactive range)
|
|
||||||
[DATE TIME-TIME REPEATER-OR-DELAY] (inactive range)
|
|
||||||
|
|
||||||
SEXP can contain any character excepted `>` and `\n`.
|
|
||||||
|
|
||||||
DATE follows the pattern:
|
|
||||||
|
|
||||||
YYYY-MM-DD DAYNAME
|
|
||||||
|
|
||||||
`Y`, `M` and `D` are digits. DAYNAME can contain any non whitespace-character
|
|
||||||
besides `+`, `-`, `]`, `>`, a digit or `\n`.
|
|
||||||
|
|
||||||
TIME follows the pattern `H:MM`. `H` can be one or two digit long and can start
|
|
||||||
with 0.
|
|
||||||
|
|
||||||
REPEATER-OR-DELAY follows the pattern:
|
|
||||||
|
|
||||||
MARK VALUE UNIT
|
|
||||||
|
|
||||||
MARK is `+` (cumulate type), `++` (catch-up type) or `.+` (restart type) for a
|
|
||||||
repeater, and `-` (all type) or `--` (first type) for warning delays.
|
|
||||||
|
|
||||||
VALUE is a number.
|
|
||||||
|
|
||||||
UNIT is a character among `h` (hour), `d` (day), `w` (week), `m` (month), `y`
|
|
||||||
(year).
|
|
||||||
|
|
||||||
MARK, VALUE and UNIT are not separated by whitespace characters.
|
|
||||||
|
|
||||||
There can be two REPEATER-OR-DELAY in the timestamp: one as a repeater and one
|
|
||||||
as a warning delay.
|
|
||||||
|
|
||||||
<a id="Emphasis_Markers"></a>
|
|
||||||
|
|
||||||
## Text Markup
|
|
||||||
|
|
||||||
Text markup follows the pattern:
|
|
||||||
|
|
||||||
PRE MARKER CONTENTS MARKER POST
|
|
||||||
|
|
||||||
PRE is a whitespace character, `(`, `{` `'` or a double quote. It can also be a
|
|
||||||
beginning of line.
|
|
||||||
|
|
||||||
MARKER is a character among `*` (bold), `=` (verbatim), `/` (italic), `+`
|
|
||||||
(strike-through), `_` (underline), `~` (code).
|
|
||||||
|
|
||||||
CONTENTS is a string following the pattern:
|
|
||||||
|
|
||||||
BORDER BODY BORDER
|
|
||||||
|
|
||||||
BORDER can be any non-whitespace character excepted `,`, `'` or a double quote.
|
|
||||||
|
|
||||||
BODY can contain contain any character but may not span over more than 3 lines.
|
|
||||||
|
|
||||||
BORDER and BODY are not separated by whitespaces.
|
|
||||||
|
|
||||||
CONTENTS can contain any object encountered in a paragraph when markup is
|
|
||||||
**bold**, **italic**, **strike-through** or **underline**.
|
|
||||||
|
|
||||||
POST is a whitespace character, `-`, `.`, `,`, `:`, `!`, `?`, `'`, `)`, `}` or a
|
|
||||||
double quote. It can also be an end of line.
|
|
||||||
|
|
||||||
PRE, MARKER, CONTENTS, MARKER and POST are not separated by whitespace
|
|
||||||
characters.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
> All of this is wrong if `org-emphasis-regexp-components` or
|
|
||||||
> `org-emphasis-alist` are modified.
|
|
||||||
>
|
|
||||||
> This should really be simplified.
|
|
||||||
>
|
|
||||||
> Also, CONTENTS should be anything within code and verbatim emphasis, by
|
|
||||||
> definition. — ngz
|
|
||||||
|
|
||||||
# Footnotes
|
|
||||||
|
|
||||||
<sup><a id="fn.1" href="#fnr.1">1</a></sup> In particular, the parser requires
|
|
||||||
stars at column 0 to be quoted by a comma when they do not define a headline.
|
|
||||||
|
|
||||||
<sup><a id="fn.2" href="#fnr.2">2</a></sup> It also means that only headlines
|
|
||||||
and sections can be recognized just by looking at the beginning of the line.
|
|
||||||
Planning lines and property drawers can be recognized by looking at one or two
|
|
||||||
lines above.
|
|
||||||
|
|
||||||
As a consequence, using `org-element-at-point` or `org-element-context` will
|
|
||||||
move up to the parent headline, and parse top-down from there until context
|
|
||||||
around original location is found.
|
|
|
@ -1,81 +0,0 @@
|
||||||
use std::convert::From;
|
|
||||||
use std::env::args;
|
|
||||||
use std::fs;
|
|
||||||
use std::io::{Error as IOError, Write};
|
|
||||||
use std::result::Result;
|
|
||||||
use std::string::FromUtf8Error;
|
|
||||||
|
|
||||||
use orgize::export::{DefaultHtmlHandler, HtmlHandler};
|
|
||||||
use orgize::{Element, Org};
|
|
||||||
use slugify::slugify;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum MyError {
|
|
||||||
IO(IOError),
|
|
||||||
Heading,
|
|
||||||
Utf8(FromUtf8Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
// From<std::io::Error> trait is required for custom error type
|
|
||||||
impl From<IOError> for MyError {
|
|
||||||
fn from(err: IOError) -> Self {
|
|
||||||
MyError::IO(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<FromUtf8Error> for MyError {
|
|
||||||
fn from(err: FromUtf8Error) -> Self {
|
|
||||||
MyError::Utf8(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct MyHtmlHandler(DefaultHtmlHandler);
|
|
||||||
|
|
||||||
impl HtmlHandler<MyError> for MyHtmlHandler {
|
|
||||||
fn start<W: Write>(&mut self, mut w: W, element: &Element) -> Result<(), MyError> {
|
|
||||||
if let Element::Title(title) = element {
|
|
||||||
if title.level > 6 {
|
|
||||||
return Err(MyError::Heading);
|
|
||||||
} else {
|
|
||||||
write!(
|
|
||||||
w,
|
|
||||||
"<h{0}><a id=\"{1}\" href=\"#{1}\">",
|
|
||||||
title.level,
|
|
||||||
slugify!(&title.raw),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// fallthrough to default handler
|
|
||||||
self.0.start(w, element)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn end<W: Write>(&mut self, mut w: W, element: &Element) -> Result<(), MyError> {
|
|
||||||
if let Element::Title(title) = element {
|
|
||||||
write!(w, "</a></h{}>", title.level)?;
|
|
||||||
} else {
|
|
||||||
self.0.end(w, element)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<(), MyError> {
|
|
||||||
let args: Vec<_> = args().collect();
|
|
||||||
|
|
||||||
if args.len() < 2 {
|
|
||||||
eprintln!("Usage: {} <org-file>", args[0]);
|
|
||||||
} else {
|
|
||||||
let contents = String::from_utf8(fs::read(&args[1])?)?;
|
|
||||||
|
|
||||||
let mut writer = Vec::new();
|
|
||||||
let mut handler = MyHtmlHandler::default();
|
|
||||||
Org::parse(&contents).write_html_custom(&mut writer, &mut handler)?;
|
|
||||||
|
|
||||||
println!("{}", String::from_utf8(writer)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
use orgize::Org;
|
|
||||||
use std::env::args;
|
|
||||||
use std::fs;
|
|
||||||
use std::io::Result;
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
let args: Vec<_> = args().collect();
|
|
||||||
|
|
||||||
if args.len() < 2 {
|
|
||||||
eprintln!("Usage: {} <org-file>", args[0]);
|
|
||||||
} else {
|
|
||||||
let contents = String::from_utf8(fs::read(&args[1])?).unwrap();
|
|
||||||
|
|
||||||
for event in Org::parse(&contents).iter() {
|
|
||||||
println!("{:?}", event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
use orgize::Org;
|
|
||||||
use serde_json::to_string;
|
|
||||||
use std::env::args;
|
|
||||||
use std::fs;
|
|
||||||
use std::io::Result;
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
let args: Vec<_> = args().collect();
|
|
||||||
|
|
||||||
if args.len() < 2 {
|
|
||||||
eprintln!("Usage: {} <org-file>", args[0]);
|
|
||||||
} else {
|
|
||||||
let contents = String::from_utf8(fs::read(&args[1])?).unwrap();
|
|
||||||
println!("{}", to_string(&Org::parse(&contents)).unwrap());
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
3
fuzz/.gitignore
vendored
3
fuzz/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
||||||
target
|
|
||||||
corpus
|
|
||||||
artifacts
|
|
|
@ -1,20 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "orgize-fuzz"
|
|
||||||
version = "0.0.1"
|
|
||||||
authors = ["Automatically generated"]
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[package.metadata]
|
|
||||||
cargo-fuzz = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer-sys.git" }
|
|
||||||
orgize = { path = ".." }
|
|
||||||
|
|
||||||
# Prevent this from interfering with workspaces
|
|
||||||
[workspace]
|
|
||||||
members = ["."]
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "fuzz_target_1"
|
|
||||||
path = "fuzz_targets/fuzz_target_1.rs"
|
|
|
@ -1,14 +0,0 @@
|
||||||
#![no_main]
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate libfuzzer_sys;
|
|
||||||
extern crate orgize;
|
|
||||||
|
|
||||||
use orgize::Org;
|
|
||||||
|
|
||||||
#[cfg_attr(rustfmt, rustfmt_skip)]
|
|
||||||
libfuzzer_sys::fuzz_target!(|data: &[u8]| {
|
|
||||||
if let Ok(s) = std::str::from_utf8(data) {
|
|
||||||
let _ = Org::parse(s);
|
|
||||||
}
|
|
||||||
});
|
|
184
orgize.d.ts
vendored
Normal file
184
orgize.d.ts
vendored
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
declare class Org {
|
||||||
|
free(): void;
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @returns {Org}
|
||||||
|
*/
|
||||||
|
static parse(input: string): Org;
|
||||||
|
/**
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
toJson(): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||||
|
|
||||||
|
interface InitOutput {
|
||||||
|
readonly memory: WebAssembly.Memory;
|
||||||
|
readonly __wbg_org_free: (a: number) => void;
|
||||||
|
readonly org_parse: (a: number, b: number) => number;
|
||||||
|
readonly org_toJson: (a: number) => number;
|
||||||
|
readonly handle: (a: number, b: number) => void;
|
||||||
|
readonly __wbindgen_malloc: (a: number) => number;
|
||||||
|
readonly __wbindgen_realloc: (a: number, b: number, c: number) => number;
|
||||||
|
readonly __wbindgen_exn_store: (a: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
||||||
|
* for everything else, calls `WebAssembly.instantiate` directly.
|
||||||
|
*
|
||||||
|
* @param {InitInput | Promise<InitInput>} module_or_path
|
||||||
|
*
|
||||||
|
* @returns {Promise<InitOutput>}
|
||||||
|
*/
|
||||||
|
declare function init (module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>;
|
||||||
|
|
||||||
|
declare class Handler {
|
||||||
|
text(_text: string): void;
|
||||||
|
code(_item: string): void;
|
||||||
|
cookie(_item: Cookie): void;
|
||||||
|
rule(): void;
|
||||||
|
exampleBlock(_item: Block): void;
|
||||||
|
exportBlock(_item: Block): void;
|
||||||
|
sourceBlock(_item: SourceBlock): void;
|
||||||
|
inlineSrc(_item: InlineSrc): void;
|
||||||
|
link(_item: Link): void;
|
||||||
|
snippet(_item: Snippet): void;
|
||||||
|
timestamp(_item: any): void;
|
||||||
|
verbatim(_item: string): void;
|
||||||
|
fixedWidth(_item: FixedWidth): void;
|
||||||
|
listStart(_item: List): void;
|
||||||
|
listEnd(_item: List): void;
|
||||||
|
tableStart(_item: any): void;
|
||||||
|
tableEnd(_item: any): void;
|
||||||
|
tableRowStart(_item: any): void;
|
||||||
|
tableRowEnd(_item: any): void;
|
||||||
|
tableCellStart(_item: any): void;
|
||||||
|
tableCellEnd(_item: any): void;
|
||||||
|
titleStart(_item: Title): void;
|
||||||
|
titleEnd(_item: Title): void;
|
||||||
|
boldStart(): void;
|
||||||
|
boldEnd(): void;
|
||||||
|
centerBlockStart(_item: any): void;
|
||||||
|
centerBlockEnd(_item: any): void;
|
||||||
|
documentStart(): void;
|
||||||
|
documentEnd(): void;
|
||||||
|
italicStart(): void;
|
||||||
|
italicEnd(): void;
|
||||||
|
listItemStart(): void;
|
||||||
|
listItemEnd(): void;
|
||||||
|
paragraphStart(): void;
|
||||||
|
paragraphEnd(): void;
|
||||||
|
quoteBlockStart(_item: any): void;
|
||||||
|
quoteBlockEnd(_item: any): void;
|
||||||
|
sectionStart(): void;
|
||||||
|
sectionEnd(): void;
|
||||||
|
strikeStart(): void;
|
||||||
|
strikeEnd(): void;
|
||||||
|
underlineStart(): void;
|
||||||
|
underlineEnd(): void;
|
||||||
|
verseBlockStart(_item: any): void;
|
||||||
|
verseBlockEnd(_item: any): void;
|
||||||
|
keyword(_item: Keyword): void;
|
||||||
|
}
|
||||||
|
declare type Title = {
|
||||||
|
level: number;
|
||||||
|
priority?: string;
|
||||||
|
tags?: string[];
|
||||||
|
keyword?: string;
|
||||||
|
raw: string;
|
||||||
|
properties?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
post_blank: number;
|
||||||
|
};
|
||||||
|
declare type List = {
|
||||||
|
ordered: boolean;
|
||||||
|
};
|
||||||
|
declare type Block = {
|
||||||
|
contents: string;
|
||||||
|
};
|
||||||
|
declare type InlineSrc = {
|
||||||
|
lang: string;
|
||||||
|
body: string;
|
||||||
|
};
|
||||||
|
declare type Link = {
|
||||||
|
path: string;
|
||||||
|
desc?: string;
|
||||||
|
};
|
||||||
|
declare type FixedWidth = {
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
declare type Cookie = {
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
declare type SourceBlock = {
|
||||||
|
contents: string;
|
||||||
|
language: string;
|
||||||
|
arguments: string;
|
||||||
|
post_blank: number;
|
||||||
|
};
|
||||||
|
declare type Keyword = {
|
||||||
|
key: string;
|
||||||
|
optional?: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
declare type Snippet = {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare const escapeHtml: (str: string) => string;
|
||||||
|
declare class HtmlHandler extends Handler {
|
||||||
|
result: string;
|
||||||
|
constructor(result?: string);
|
||||||
|
static escape(): string;
|
||||||
|
quoteBlockStart(): void;
|
||||||
|
quoteBlockEnd(): void;
|
||||||
|
centerBlockStart(): void;
|
||||||
|
centerBlockEnd(): void;
|
||||||
|
verseBlockStart(): void;
|
||||||
|
verseBlockEnd(): void;
|
||||||
|
boldStart(): void;
|
||||||
|
boldEnd(): void;
|
||||||
|
documentStart(): void;
|
||||||
|
documentEnd(): void;
|
||||||
|
listStart(list: List): void;
|
||||||
|
listEnd(list: List): void;
|
||||||
|
italicStart(): void;
|
||||||
|
italicEnd(): void;
|
||||||
|
listItemStart(): void;
|
||||||
|
listItemEnd(): void;
|
||||||
|
paragraphStart(): void;
|
||||||
|
paragraphEnd(): void;
|
||||||
|
sectionStart(): void;
|
||||||
|
sectionEnd(): void;
|
||||||
|
strikeStart(): void;
|
||||||
|
strikeEnd(): void;
|
||||||
|
underlineStart(): void;
|
||||||
|
underlineEnd(): void;
|
||||||
|
exampleBlock(block: Block): void;
|
||||||
|
sourceBlock(block: Block): void;
|
||||||
|
inlineSrc(src: InlineSrc): void;
|
||||||
|
code(value: string): void;
|
||||||
|
link(link: Link): void;
|
||||||
|
snippet(snippet: Snippet): void;
|
||||||
|
text(value: string): void;
|
||||||
|
verbatim(value: string): void;
|
||||||
|
fixedWidth(item: FixedWidth): void;
|
||||||
|
rule(): void;
|
||||||
|
cookie(cookie: Cookie): void;
|
||||||
|
titleStart(title: Title): void;
|
||||||
|
titleEnd(title: Title): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const handle: (org: Org | string, handler: Handler) => void;
|
||||||
|
declare const renderHtml: (org: Org | string, handler?: HtmlHandler) => string;
|
||||||
|
declare const keywords: (org: Org | string) => {
|
||||||
|
[key: string]: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Block, Cookie, FixedWidth, Handler, HtmlHandler, InitInput, InitOutput, InlineSrc, Keyword, Link, List, Org, Snippet, SourceBlock, Title, escapeHtml, handle, init, keywords, renderHtml };
|
686
orgize.es.js
Normal file
686
orgize.es.js
Normal file
|
@ -0,0 +1,686 @@
|
||||||
|
let wasm;
|
||||||
|
|
||||||
|
const heap = new Array(32).fill(undefined);
|
||||||
|
|
||||||
|
heap.push(undefined, null, true, false);
|
||||||
|
|
||||||
|
function getObject(idx) { return heap[idx]; }
|
||||||
|
|
||||||
|
let heap_next = heap.length;
|
||||||
|
|
||||||
|
function dropObject(idx) {
|
||||||
|
if (idx < 36) return;
|
||||||
|
heap[idx] = heap_next;
|
||||||
|
heap_next = idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
function takeObject(idx) {
|
||||||
|
const ret = getObject(idx);
|
||||||
|
dropObject(idx);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
||||||
|
|
||||||
|
cachedTextDecoder.decode();
|
||||||
|
|
||||||
|
let cachegetUint8Memory0 = null;
|
||||||
|
function getUint8Memory0() {
|
||||||
|
if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
|
||||||
|
cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
|
||||||
|
}
|
||||||
|
return cachegetUint8Memory0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStringFromWasm0(ptr, len) {
|
||||||
|
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
||||||
|
}
|
||||||
|
|
||||||
|
function addHeapObject(obj) {
|
||||||
|
if (heap_next === heap.length) heap.push(heap.length + 1);
|
||||||
|
const idx = heap_next;
|
||||||
|
heap_next = heap[idx];
|
||||||
|
|
||||||
|
heap[idx] = obj;
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
function debugString(val) {
|
||||||
|
// primitive types
|
||||||
|
const type = typeof val;
|
||||||
|
if (type == 'number' || type == 'boolean' || val == null) {
|
||||||
|
return `${val}`;
|
||||||
|
}
|
||||||
|
if (type == 'string') {
|
||||||
|
return `"${val}"`;
|
||||||
|
}
|
||||||
|
if (type == 'symbol') {
|
||||||
|
const description = val.description;
|
||||||
|
if (description == null) {
|
||||||
|
return 'Symbol';
|
||||||
|
} else {
|
||||||
|
return `Symbol(${description})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type == 'function') {
|
||||||
|
const name = val.name;
|
||||||
|
if (typeof name == 'string' && name.length > 0) {
|
||||||
|
return `Function(${name})`;
|
||||||
|
} else {
|
||||||
|
return 'Function';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// objects
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
const length = val.length;
|
||||||
|
let debug = '[';
|
||||||
|
if (length > 0) {
|
||||||
|
debug += debugString(val[0]);
|
||||||
|
}
|
||||||
|
for(let i = 1; i < length; i++) {
|
||||||
|
debug += ', ' + debugString(val[i]);
|
||||||
|
}
|
||||||
|
debug += ']';
|
||||||
|
return debug;
|
||||||
|
}
|
||||||
|
// Test for built-in
|
||||||
|
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
|
||||||
|
let className;
|
||||||
|
if (builtInMatches.length > 1) {
|
||||||
|
className = builtInMatches[1];
|
||||||
|
} else {
|
||||||
|
// Failed to match the standard '[object ClassName]'
|
||||||
|
return toString.call(val);
|
||||||
|
}
|
||||||
|
if (className == 'Object') {
|
||||||
|
// we're a user defined class or Object
|
||||||
|
// JSON.stringify avoids problems with cycles, and is generally much
|
||||||
|
// easier than looping through ownProperties of `val`.
|
||||||
|
try {
|
||||||
|
return 'Object(' + JSON.stringify(val) + ')';
|
||||||
|
} catch (_) {
|
||||||
|
return 'Object';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// errors
|
||||||
|
if (val instanceof Error) {
|
||||||
|
return `${val.name}: ${val.message}\n${val.stack}`;
|
||||||
|
}
|
||||||
|
// TODO we could test for more things here, like `Set`s and `Map`s.
|
||||||
|
return className;
|
||||||
|
}
|
||||||
|
|
||||||
|
let WASM_VECTOR_LEN = 0;
|
||||||
|
|
||||||
|
let cachedTextEncoder = new TextEncoder('utf-8');
|
||||||
|
|
||||||
|
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
|
||||||
|
? function (arg, view) {
|
||||||
|
return cachedTextEncoder.encodeInto(arg, view);
|
||||||
|
}
|
||||||
|
: function (arg, view) {
|
||||||
|
const buf = cachedTextEncoder.encode(arg);
|
||||||
|
view.set(buf);
|
||||||
|
return {
|
||||||
|
read: arg.length,
|
||||||
|
written: buf.length
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function passStringToWasm0(arg, malloc, realloc) {
|
||||||
|
|
||||||
|
if (realloc === undefined) {
|
||||||
|
const buf = cachedTextEncoder.encode(arg);
|
||||||
|
const ptr = malloc(buf.length);
|
||||||
|
getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
|
||||||
|
WASM_VECTOR_LEN = buf.length;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = arg.length;
|
||||||
|
let ptr = malloc(len);
|
||||||
|
|
||||||
|
const mem = getUint8Memory0();
|
||||||
|
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
for (; offset < len; offset++) {
|
||||||
|
const code = arg.charCodeAt(offset);
|
||||||
|
if (code > 0x7F) break;
|
||||||
|
mem[ptr + offset] = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset !== len) {
|
||||||
|
if (offset !== 0) {
|
||||||
|
arg = arg.slice(offset);
|
||||||
|
}
|
||||||
|
ptr = realloc(ptr, len, len = offset + arg.length * 3);
|
||||||
|
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
|
||||||
|
const ret = encodeString(arg, view);
|
||||||
|
|
||||||
|
offset += ret.written;
|
||||||
|
}
|
||||||
|
|
||||||
|
WASM_VECTOR_LEN = offset;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachegetInt32Memory0 = null;
|
||||||
|
function getInt32Memory0() {
|
||||||
|
if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
|
||||||
|
cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
|
||||||
|
}
|
||||||
|
return cachegetInt32Memory0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _assertClass(instance, klass) {
|
||||||
|
if (!(instance instanceof klass)) {
|
||||||
|
throw new Error(`expected instance of ${klass.name}`);
|
||||||
|
}
|
||||||
|
return instance.ptr;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {Org} org
|
||||||
|
* @param {any} handler
|
||||||
|
*/
|
||||||
|
function handle$1(org, handler) {
|
||||||
|
_assertClass(org, Org);
|
||||||
|
wasm.handle(org.ptr, addHeapObject(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleError(f, args) {
|
||||||
|
try {
|
||||||
|
return f.apply(this, args);
|
||||||
|
} catch (e) {
|
||||||
|
wasm.__wbindgen_exn_store(addHeapObject(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
class Org {
|
||||||
|
|
||||||
|
static __wrap(ptr) {
|
||||||
|
const obj = Object.create(Org.prototype);
|
||||||
|
obj.ptr = ptr;
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
__destroy_into_raw() {
|
||||||
|
const ptr = this.ptr;
|
||||||
|
this.ptr = 0;
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
free() {
|
||||||
|
const ptr = this.__destroy_into_raw();
|
||||||
|
wasm.__wbg_org_free(ptr);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @returns {Org}
|
||||||
|
*/
|
||||||
|
static parse(input) {
|
||||||
|
var ptr0 = passStringToWasm0(input, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
|
var len0 = WASM_VECTOR_LEN;
|
||||||
|
var ret = wasm.org_parse(ptr0, len0);
|
||||||
|
return Org.__wrap(ret);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
toJson() {
|
||||||
|
var ret = wasm.org_toJson(this.ptr);
|
||||||
|
return takeObject(ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function load(module, imports) {
|
||||||
|
if (typeof Response === 'function' && module instanceof Response) {
|
||||||
|
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
||||||
|
try {
|
||||||
|
return await WebAssembly.instantiateStreaming(module, imports);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
if (module.headers.get('Content-Type') != 'application/wasm') {
|
||||||
|
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bytes = await module.arrayBuffer();
|
||||||
|
return await WebAssembly.instantiate(bytes, imports);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const instance = await WebAssembly.instantiate(module, imports);
|
||||||
|
|
||||||
|
if (instance instanceof WebAssembly.Instance) {
|
||||||
|
return { instance, module };
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init(input) {
|
||||||
|
if (typeof input === 'undefined') {
|
||||||
|
input = new URL('orgize_bg.wasm', import.meta.url);
|
||||||
|
}
|
||||||
|
const imports = {};
|
||||||
|
imports.wbg = {};
|
||||||
|
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
|
||||||
|
takeObject(arg0);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_text_9d7be88cb9d0e993 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).text(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_code_bb5df0e20028d1b4 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).code(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_cookie_c40e503c71995ccf = function(arg0, arg1) {
|
||||||
|
getObject(arg0).cookie(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_rule_c48ea58e05284f0a = function(arg0) {
|
||||||
|
getObject(arg0).rule();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_exampleBlock_79db13bf5ce41758 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).exampleBlock(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_exportBlock_f4da62ce962cdc72 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).exportBlock(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_sourceBlock_d66de695a8ba6a28 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).sourceBlock(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_inlineSrc_612eb06e9a588764 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).inlineSrc(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_link_aea6fc46604ae21a = function(arg0, arg1) {
|
||||||
|
getObject(arg0).link(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_snippet_051b7a269fe830d3 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).snippet(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_timestamp_0be7b46dee608dc7 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).timestamp(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_verbatim_5fa7a8bf6797d799 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).verbatim(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_fixedWidth_7cfbc61dd4893d59 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).fixedWidth(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_keyword_9880bffc52b1f72e = function(arg0, arg1) {
|
||||||
|
getObject(arg0).keyword(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_listStart_4fc5893ef927c6af = function(arg0, arg1) {
|
||||||
|
getObject(arg0).listStart(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_listEnd_56585dd32218ccac = function(arg0, arg1) {
|
||||||
|
getObject(arg0).listEnd(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_tableStart_ecb632f749e1843a = function(arg0, arg1) {
|
||||||
|
getObject(arg0).tableStart(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_tableRowStart_07c7f9e025d8d435 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).tableRowStart(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_tableCellStart_c60848b19baf111d = function(arg0, arg1) {
|
||||||
|
getObject(arg0).tableCellStart(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_titleStart_7ae650910466668c = function(arg0, arg1) {
|
||||||
|
getObject(arg0).titleStart(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_titleEnd_8e7c66a7cd410da6 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).titleEnd(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_boldStart_a570a541eee54a04 = function(arg0) {
|
||||||
|
getObject(arg0).boldStart();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_boldEnd_e9de72bd9d6d67aa = function(arg0) {
|
||||||
|
getObject(arg0).boldEnd();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_centerBlockStart_f3b607811bf7c15a = function(arg0, arg1) {
|
||||||
|
getObject(arg0).centerBlockStart(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_centerBlockEnd_c28d268ed13c8d99 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).centerBlockEnd(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_documentStart_ead3479098bdf3cd = function(arg0) {
|
||||||
|
getObject(arg0).documentStart();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_documentEnd_835258edd09a2073 = function(arg0) {
|
||||||
|
getObject(arg0).documentEnd();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_italicStart_098d845d576cfe4d = function(arg0) {
|
||||||
|
getObject(arg0).italicStart();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_italicEnd_a26a9f1a03504572 = function(arg0) {
|
||||||
|
getObject(arg0).italicEnd();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_listItemStart_f7e3e45d4064122a = function(arg0) {
|
||||||
|
getObject(arg0).listItemStart();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_listItemEnd_e5d5d202169afbc4 = function(arg0) {
|
||||||
|
getObject(arg0).listItemEnd();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_paragraphStart_0911d346fe2f0aa8 = function(arg0) {
|
||||||
|
getObject(arg0).paragraphStart();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_paragraphEnd_7e3be6d14519b8f9 = function(arg0) {
|
||||||
|
getObject(arg0).paragraphEnd();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_quoteBlockStart_a85d3fbaae8ddcab = function(arg0, arg1) {
|
||||||
|
getObject(arg0).quoteBlockStart(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_quoteBlockEnd_924f13342cf61145 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).quoteBlockEnd(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_sectionStart_21c356e55c1979de = function(arg0) {
|
||||||
|
getObject(arg0).sectionStart();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_sectionEnd_ebd85494219048b3 = function(arg0) {
|
||||||
|
getObject(arg0).sectionEnd();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_strikeStart_bb876a13c23ae4a0 = function(arg0) {
|
||||||
|
getObject(arg0).strikeStart();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_strikeEnd_b27d2e51423382b0 = function(arg0) {
|
||||||
|
getObject(arg0).strikeEnd();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_underlineStart_061a7650f74f999d = function(arg0) {
|
||||||
|
getObject(arg0).underlineStart();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_underlineEnd_9d755ffb65c1f633 = function(arg0) {
|
||||||
|
getObject(arg0).underlineEnd();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_verseBlockStart_5810dffbfb567d87 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).verseBlockStart(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_verseBlockEnd_750d8dfa93c5b0ab = function(arg0, arg1) {
|
||||||
|
getObject(arg0).verseBlockEnd(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
|
||||||
|
var ret = getStringFromWasm0(arg0, arg1);
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbindgen_number_new = function(arg0) {
|
||||||
|
var ret = arg0;
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
|
||||||
|
var ret = getObject(arg0);
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_set_f1a4ac8f3a605b11 = function(arg0, arg1, arg2) {
|
||||||
|
getObject(arg0)[takeObject(arg1)] = takeObject(arg2);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_new_949bbc1147195c4e = function() {
|
||||||
|
var ret = new Array();
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_new_ac32179a660db4bb = function() {
|
||||||
|
var ret = new Map();
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_new_0b83d3df67ecb33e = function() {
|
||||||
|
var ret = new Object();
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbindgen_is_string = function(arg0) {
|
||||||
|
var ret = typeof(getObject(arg0)) === 'string';
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_push_284486ca27c6aa8b = function(arg0, arg1) {
|
||||||
|
var ret = getObject(arg0).push(getObject(arg1));
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_new_342a24ca698edd87 = function(arg0, arg1) {
|
||||||
|
var ret = new Error(getStringFromWasm0(arg0, arg1));
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_set_a46091b120cc63e9 = function(arg0, arg1, arg2) {
|
||||||
|
var ret = getObject(arg0).set(getObject(arg1), getObject(arg2));
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_fromCodePoint_65bd58e0db7e8253 = function() { return handleError(function (arg0) {
|
||||||
|
var ret = String.fromCodePoint(arg0 >>> 0);
|
||||||
|
return addHeapObject(ret);
|
||||||
|
}, arguments) };
|
||||||
|
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
|
||||||
|
var ret = debugString(getObject(arg1));
|
||||||
|
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
|
var len0 = WASM_VECTOR_LEN;
|
||||||
|
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||||
|
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
|
||||||
|
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
|
||||||
|
input = fetch(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const { instance, module } = await load(await input, imports);
|
||||||
|
|
||||||
|
wasm = instance.exports;
|
||||||
|
init.__wbindgen_wasm_module = module;
|
||||||
|
|
||||||
|
return wasm;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Handler {
|
||||||
|
text(_text) { }
|
||||||
|
code(_item) { }
|
||||||
|
cookie(_item) { }
|
||||||
|
rule() { }
|
||||||
|
exampleBlock(_item) { }
|
||||||
|
exportBlock(_item) { }
|
||||||
|
sourceBlock(_item) { }
|
||||||
|
inlineSrc(_item) { }
|
||||||
|
link(_item) { }
|
||||||
|
snippet(_item) { }
|
||||||
|
timestamp(_item) { }
|
||||||
|
verbatim(_item) { }
|
||||||
|
fixedWidth(_item) { }
|
||||||
|
listStart(_item) { }
|
||||||
|
listEnd(_item) { }
|
||||||
|
tableStart(_item) { }
|
||||||
|
tableEnd(_item) { }
|
||||||
|
tableRowStart(_item) { }
|
||||||
|
tableRowEnd(_item) { }
|
||||||
|
tableCellStart(_item) { }
|
||||||
|
tableCellEnd(_item) { }
|
||||||
|
titleStart(_item) { }
|
||||||
|
titleEnd(_item) { }
|
||||||
|
boldStart() { }
|
||||||
|
boldEnd() { }
|
||||||
|
centerBlockStart(_item) { }
|
||||||
|
centerBlockEnd(_item) { }
|
||||||
|
documentStart() { }
|
||||||
|
documentEnd() { }
|
||||||
|
italicStart() { }
|
||||||
|
italicEnd() { }
|
||||||
|
listItemStart() { }
|
||||||
|
listItemEnd() { }
|
||||||
|
paragraphStart() { }
|
||||||
|
paragraphEnd() { }
|
||||||
|
quoteBlockStart(_item) { }
|
||||||
|
quoteBlockEnd(_item) { }
|
||||||
|
sectionStart() { }
|
||||||
|
sectionEnd() { }
|
||||||
|
strikeStart() { }
|
||||||
|
strikeEnd() { }
|
||||||
|
underlineStart() { }
|
||||||
|
underlineEnd() { }
|
||||||
|
verseBlockStart(_item) { }
|
||||||
|
verseBlockEnd(_item) { }
|
||||||
|
keyword(_item) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
const tags = {
|
||||||
|
"&": "&",
|
||||||
|
"<": "<",
|
||||||
|
">": ">",
|
||||||
|
'"': """,
|
||||||
|
"'": "'",
|
||||||
|
};
|
||||||
|
const replaceTags = (tag) => tags[tag];
|
||||||
|
const escapeHtml = (str) => str.replace(/[&<>"']/g, replaceTags);
|
||||||
|
class HtmlHandler extends Handler {
|
||||||
|
result;
|
||||||
|
constructor(result = "") {
|
||||||
|
super();
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
static escape() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
quoteBlockStart() {
|
||||||
|
this.result += "<blockquote>";
|
||||||
|
}
|
||||||
|
quoteBlockEnd() {
|
||||||
|
this.result += "</blockquote>";
|
||||||
|
}
|
||||||
|
centerBlockStart() {
|
||||||
|
this.result += '<div class="center">';
|
||||||
|
}
|
||||||
|
centerBlockEnd() {
|
||||||
|
this.result += "</div>";
|
||||||
|
}
|
||||||
|
verseBlockStart() {
|
||||||
|
this.result += '<p class="verse">';
|
||||||
|
}
|
||||||
|
verseBlockEnd() {
|
||||||
|
this.result += "</p>";
|
||||||
|
}
|
||||||
|
boldStart() {
|
||||||
|
this.result += "<b>";
|
||||||
|
}
|
||||||
|
boldEnd() {
|
||||||
|
this.result += "</b>";
|
||||||
|
}
|
||||||
|
documentStart() {
|
||||||
|
this.result += "<main>";
|
||||||
|
}
|
||||||
|
documentEnd() {
|
||||||
|
this.result += "</main>";
|
||||||
|
}
|
||||||
|
listStart(list) {
|
||||||
|
this.result += `<${list.ordered ? "o" : "u"}l>`;
|
||||||
|
}
|
||||||
|
listEnd(list) {
|
||||||
|
this.result += `</${list.ordered ? "o" : "u"}l>`;
|
||||||
|
}
|
||||||
|
italicStart() {
|
||||||
|
this.result += "<i>";
|
||||||
|
}
|
||||||
|
italicEnd() {
|
||||||
|
this.result += "</i>";
|
||||||
|
}
|
||||||
|
listItemStart() {
|
||||||
|
this.result += "<li>";
|
||||||
|
}
|
||||||
|
listItemEnd() {
|
||||||
|
this.result += "</li>";
|
||||||
|
}
|
||||||
|
paragraphStart() {
|
||||||
|
this.result += "<p>";
|
||||||
|
}
|
||||||
|
paragraphEnd() {
|
||||||
|
this.result += "</p>";
|
||||||
|
}
|
||||||
|
sectionStart() {
|
||||||
|
this.result += "<section>";
|
||||||
|
}
|
||||||
|
sectionEnd() {
|
||||||
|
this.result += "</section>";
|
||||||
|
}
|
||||||
|
strikeStart() {
|
||||||
|
this.result += "<s>";
|
||||||
|
}
|
||||||
|
strikeEnd() {
|
||||||
|
this.result += "</s>";
|
||||||
|
}
|
||||||
|
underlineStart() {
|
||||||
|
this.result += "<u>";
|
||||||
|
}
|
||||||
|
underlineEnd() {
|
||||||
|
this.result += "</u>";
|
||||||
|
}
|
||||||
|
exampleBlock(block) {
|
||||||
|
this.result += `<pre class="example">${escapeHtml(block.contents)}</pre>`;
|
||||||
|
}
|
||||||
|
sourceBlock(block) {
|
||||||
|
this.result += `<pre class="example">${escapeHtml(block.contents)}</pre>`;
|
||||||
|
}
|
||||||
|
inlineSrc(src) {
|
||||||
|
this.result += `<code class="src src-${src.lang}">${escapeHtml(src.body)}</code>`;
|
||||||
|
}
|
||||||
|
code(value) {
|
||||||
|
this.result += `<code>${escapeHtml(value)}</code>`;
|
||||||
|
}
|
||||||
|
link(link) {
|
||||||
|
this.result += `<a href="${link.path}">${escapeHtml(link.desc || link.path)}</a>`;
|
||||||
|
}
|
||||||
|
snippet(snippet) {
|
||||||
|
if (snippet.name.toLowerCase() === "html") {
|
||||||
|
this.result += snippet.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text(value) {
|
||||||
|
this.result += escapeHtml(value);
|
||||||
|
}
|
||||||
|
verbatim(value) {
|
||||||
|
this.result += `<code>${escapeHtml(value)}</code>`;
|
||||||
|
}
|
||||||
|
fixedWidth(item) {
|
||||||
|
this.result += `<pre class="example">${escapeHtml(item.value)}</pre>`;
|
||||||
|
}
|
||||||
|
rule() {
|
||||||
|
this.result += "<hr>";
|
||||||
|
}
|
||||||
|
cookie(cookie) {
|
||||||
|
this.result += `<code>${escapeHtml(cookie.value)}</code>`;
|
||||||
|
}
|
||||||
|
titleStart(title) {
|
||||||
|
this.result += `<h${Math.min(title.level, 6)}>`;
|
||||||
|
}
|
||||||
|
titleEnd(title) {
|
||||||
|
this.result += `</h${Math.min(title.level, 6)}>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CollectKeywords extends Handler {
|
||||||
|
keywords = {};
|
||||||
|
keyword(keyword) {
|
||||||
|
this.keywords[keyword.key] = this.keywords[keyword.key] || [];
|
||||||
|
this.keywords[keyword.key].push(keyword.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handle = (org, handler) => {
|
||||||
|
if (typeof org === "string") {
|
||||||
|
org = Org.parse(org);
|
||||||
|
}
|
||||||
|
handle$1(org, handler);
|
||||||
|
};
|
||||||
|
const renderHtml = (org, handler = new HtmlHandler()) => {
|
||||||
|
handle(org, handler);
|
||||||
|
return handler.result;
|
||||||
|
};
|
||||||
|
const keywords = (org) => {
|
||||||
|
const handler = new CollectKeywords();
|
||||||
|
handle(org, handler);
|
||||||
|
return handler.keywords;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Handler, HtmlHandler, Org, escapeHtml, handle, init, keywords, renderHtml };
|
703
orgize.umd.js
Normal file
703
orgize.umd.js
Normal file
|
@ -0,0 +1,703 @@
|
||||||
|
(function (global, factory) {
|
||||||
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
||||||
|
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
||||||
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.orgize = {}));
|
||||||
|
}(this, (function (exports) { 'use strict';
|
||||||
|
|
||||||
|
let wasm;
|
||||||
|
|
||||||
|
const heap = new Array(32).fill(undefined);
|
||||||
|
|
||||||
|
heap.push(undefined, null, true, false);
|
||||||
|
|
||||||
|
function getObject(idx) { return heap[idx]; }
|
||||||
|
|
||||||
|
let heap_next = heap.length;
|
||||||
|
|
||||||
|
function dropObject(idx) {
|
||||||
|
if (idx < 36) return;
|
||||||
|
heap[idx] = heap_next;
|
||||||
|
heap_next = idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
function takeObject(idx) {
|
||||||
|
const ret = getObject(idx);
|
||||||
|
dropObject(idx);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
||||||
|
|
||||||
|
cachedTextDecoder.decode();
|
||||||
|
|
||||||
|
let cachegetUint8Memory0 = null;
|
||||||
|
function getUint8Memory0() {
|
||||||
|
if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
|
||||||
|
cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
|
||||||
|
}
|
||||||
|
return cachegetUint8Memory0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStringFromWasm0(ptr, len) {
|
||||||
|
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
||||||
|
}
|
||||||
|
|
||||||
|
function addHeapObject(obj) {
|
||||||
|
if (heap_next === heap.length) heap.push(heap.length + 1);
|
||||||
|
const idx = heap_next;
|
||||||
|
heap_next = heap[idx];
|
||||||
|
|
||||||
|
heap[idx] = obj;
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
function debugString(val) {
|
||||||
|
// primitive types
|
||||||
|
const type = typeof val;
|
||||||
|
if (type == 'number' || type == 'boolean' || val == null) {
|
||||||
|
return `${val}`;
|
||||||
|
}
|
||||||
|
if (type == 'string') {
|
||||||
|
return `"${val}"`;
|
||||||
|
}
|
||||||
|
if (type == 'symbol') {
|
||||||
|
const description = val.description;
|
||||||
|
if (description == null) {
|
||||||
|
return 'Symbol';
|
||||||
|
} else {
|
||||||
|
return `Symbol(${description})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type == 'function') {
|
||||||
|
const name = val.name;
|
||||||
|
if (typeof name == 'string' && name.length > 0) {
|
||||||
|
return `Function(${name})`;
|
||||||
|
} else {
|
||||||
|
return 'Function';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// objects
|
||||||
|
if (Array.isArray(val)) {
|
||||||
|
const length = val.length;
|
||||||
|
let debug = '[';
|
||||||
|
if (length > 0) {
|
||||||
|
debug += debugString(val[0]);
|
||||||
|
}
|
||||||
|
for(let i = 1; i < length; i++) {
|
||||||
|
debug += ', ' + debugString(val[i]);
|
||||||
|
}
|
||||||
|
debug += ']';
|
||||||
|
return debug;
|
||||||
|
}
|
||||||
|
// Test for built-in
|
||||||
|
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
|
||||||
|
let className;
|
||||||
|
if (builtInMatches.length > 1) {
|
||||||
|
className = builtInMatches[1];
|
||||||
|
} else {
|
||||||
|
// Failed to match the standard '[object ClassName]'
|
||||||
|
return toString.call(val);
|
||||||
|
}
|
||||||
|
if (className == 'Object') {
|
||||||
|
// we're a user defined class or Object
|
||||||
|
// JSON.stringify avoids problems with cycles, and is generally much
|
||||||
|
// easier than looping through ownProperties of `val`.
|
||||||
|
try {
|
||||||
|
return 'Object(' + JSON.stringify(val) + ')';
|
||||||
|
} catch (_) {
|
||||||
|
return 'Object';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// errors
|
||||||
|
if (val instanceof Error) {
|
||||||
|
return `${val.name}: ${val.message}\n${val.stack}`;
|
||||||
|
}
|
||||||
|
// TODO we could test for more things here, like `Set`s and `Map`s.
|
||||||
|
return className;
|
||||||
|
}
|
||||||
|
|
||||||
|
let WASM_VECTOR_LEN = 0;
|
||||||
|
|
||||||
|
let cachedTextEncoder = new TextEncoder('utf-8');
|
||||||
|
|
||||||
|
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
|
||||||
|
? function (arg, view) {
|
||||||
|
return cachedTextEncoder.encodeInto(arg, view);
|
||||||
|
}
|
||||||
|
: function (arg, view) {
|
||||||
|
const buf = cachedTextEncoder.encode(arg);
|
||||||
|
view.set(buf);
|
||||||
|
return {
|
||||||
|
read: arg.length,
|
||||||
|
written: buf.length
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function passStringToWasm0(arg, malloc, realloc) {
|
||||||
|
|
||||||
|
if (realloc === undefined) {
|
||||||
|
const buf = cachedTextEncoder.encode(arg);
|
||||||
|
const ptr = malloc(buf.length);
|
||||||
|
getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
|
||||||
|
WASM_VECTOR_LEN = buf.length;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = arg.length;
|
||||||
|
let ptr = malloc(len);
|
||||||
|
|
||||||
|
const mem = getUint8Memory0();
|
||||||
|
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
for (; offset < len; offset++) {
|
||||||
|
const code = arg.charCodeAt(offset);
|
||||||
|
if (code > 0x7F) break;
|
||||||
|
mem[ptr + offset] = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset !== len) {
|
||||||
|
if (offset !== 0) {
|
||||||
|
arg = arg.slice(offset);
|
||||||
|
}
|
||||||
|
ptr = realloc(ptr, len, len = offset + arg.length * 3);
|
||||||
|
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
|
||||||
|
const ret = encodeString(arg, view);
|
||||||
|
|
||||||
|
offset += ret.written;
|
||||||
|
}
|
||||||
|
|
||||||
|
WASM_VECTOR_LEN = offset;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachegetInt32Memory0 = null;
|
||||||
|
function getInt32Memory0() {
|
||||||
|
if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
|
||||||
|
cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
|
||||||
|
}
|
||||||
|
return cachegetInt32Memory0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _assertClass(instance, klass) {
|
||||||
|
if (!(instance instanceof klass)) {
|
||||||
|
throw new Error(`expected instance of ${klass.name}`);
|
||||||
|
}
|
||||||
|
return instance.ptr;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {Org} org
|
||||||
|
* @param {any} handler
|
||||||
|
*/
|
||||||
|
function handle$1(org, handler) {
|
||||||
|
_assertClass(org, Org);
|
||||||
|
wasm.handle(org.ptr, addHeapObject(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleError(f, args) {
|
||||||
|
try {
|
||||||
|
return f.apply(this, args);
|
||||||
|
} catch (e) {
|
||||||
|
wasm.__wbindgen_exn_store(addHeapObject(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
class Org {
|
||||||
|
|
||||||
|
static __wrap(ptr) {
|
||||||
|
const obj = Object.create(Org.prototype);
|
||||||
|
obj.ptr = ptr;
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
__destroy_into_raw() {
|
||||||
|
const ptr = this.ptr;
|
||||||
|
this.ptr = 0;
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
free() {
|
||||||
|
const ptr = this.__destroy_into_raw();
|
||||||
|
wasm.__wbg_org_free(ptr);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @returns {Org}
|
||||||
|
*/
|
||||||
|
static parse(input) {
|
||||||
|
var ptr0 = passStringToWasm0(input, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
|
var len0 = WASM_VECTOR_LEN;
|
||||||
|
var ret = wasm.org_parse(ptr0, len0);
|
||||||
|
return Org.__wrap(ret);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
toJson() {
|
||||||
|
var ret = wasm.org_toJson(this.ptr);
|
||||||
|
return takeObject(ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function load(module, imports) {
|
||||||
|
if (typeof Response === 'function' && module instanceof Response) {
|
||||||
|
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
||||||
|
try {
|
||||||
|
return await WebAssembly.instantiateStreaming(module, imports);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
if (module.headers.get('Content-Type') != 'application/wasm') {
|
||||||
|
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bytes = await module.arrayBuffer();
|
||||||
|
return await WebAssembly.instantiate(bytes, imports);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const instance = await WebAssembly.instantiate(module, imports);
|
||||||
|
|
||||||
|
if (instance instanceof WebAssembly.Instance) {
|
||||||
|
return { instance, module };
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init(input) {
|
||||||
|
if (typeof input === 'undefined') {
|
||||||
|
input = new URL('orgize_bg.wasm', (typeof document === 'undefined' && typeof location === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : typeof document === 'undefined' ? location.href : (document.currentScript && document.currentScript.src || new URL('orgize.umd.js', document.baseURI).href)));
|
||||||
|
}
|
||||||
|
const imports = {};
|
||||||
|
imports.wbg = {};
|
||||||
|
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
|
||||||
|
takeObject(arg0);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_text_9d7be88cb9d0e993 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).text(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_code_bb5df0e20028d1b4 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).code(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_cookie_c40e503c71995ccf = function(arg0, arg1) {
|
||||||
|
getObject(arg0).cookie(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_rule_c48ea58e05284f0a = function(arg0) {
|
||||||
|
getObject(arg0).rule();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_exampleBlock_79db13bf5ce41758 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).exampleBlock(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_exportBlock_f4da62ce962cdc72 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).exportBlock(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_sourceBlock_d66de695a8ba6a28 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).sourceBlock(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_inlineSrc_612eb06e9a588764 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).inlineSrc(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_link_aea6fc46604ae21a = function(arg0, arg1) {
|
||||||
|
getObject(arg0).link(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_snippet_051b7a269fe830d3 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).snippet(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_timestamp_0be7b46dee608dc7 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).timestamp(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_verbatim_5fa7a8bf6797d799 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).verbatim(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_fixedWidth_7cfbc61dd4893d59 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).fixedWidth(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_keyword_9880bffc52b1f72e = function(arg0, arg1) {
|
||||||
|
getObject(arg0).keyword(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_listStart_4fc5893ef927c6af = function(arg0, arg1) {
|
||||||
|
getObject(arg0).listStart(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_listEnd_56585dd32218ccac = function(arg0, arg1) {
|
||||||
|
getObject(arg0).listEnd(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_tableStart_ecb632f749e1843a = function(arg0, arg1) {
|
||||||
|
getObject(arg0).tableStart(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_tableRowStart_07c7f9e025d8d435 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).tableRowStart(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_tableCellStart_c60848b19baf111d = function(arg0, arg1) {
|
||||||
|
getObject(arg0).tableCellStart(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_titleStart_7ae650910466668c = function(arg0, arg1) {
|
||||||
|
getObject(arg0).titleStart(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_titleEnd_8e7c66a7cd410da6 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).titleEnd(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_boldStart_a570a541eee54a04 = function(arg0) {
|
||||||
|
getObject(arg0).boldStart();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_boldEnd_e9de72bd9d6d67aa = function(arg0) {
|
||||||
|
getObject(arg0).boldEnd();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_centerBlockStart_f3b607811bf7c15a = function(arg0, arg1) {
|
||||||
|
getObject(arg0).centerBlockStart(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_centerBlockEnd_c28d268ed13c8d99 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).centerBlockEnd(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_documentStart_ead3479098bdf3cd = function(arg0) {
|
||||||
|
getObject(arg0).documentStart();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_documentEnd_835258edd09a2073 = function(arg0) {
|
||||||
|
getObject(arg0).documentEnd();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_italicStart_098d845d576cfe4d = function(arg0) {
|
||||||
|
getObject(arg0).italicStart();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_italicEnd_a26a9f1a03504572 = function(arg0) {
|
||||||
|
getObject(arg0).italicEnd();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_listItemStart_f7e3e45d4064122a = function(arg0) {
|
||||||
|
getObject(arg0).listItemStart();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_listItemEnd_e5d5d202169afbc4 = function(arg0) {
|
||||||
|
getObject(arg0).listItemEnd();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_paragraphStart_0911d346fe2f0aa8 = function(arg0) {
|
||||||
|
getObject(arg0).paragraphStart();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_paragraphEnd_7e3be6d14519b8f9 = function(arg0) {
|
||||||
|
getObject(arg0).paragraphEnd();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_quoteBlockStart_a85d3fbaae8ddcab = function(arg0, arg1) {
|
||||||
|
getObject(arg0).quoteBlockStart(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_quoteBlockEnd_924f13342cf61145 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).quoteBlockEnd(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_sectionStart_21c356e55c1979de = function(arg0) {
|
||||||
|
getObject(arg0).sectionStart();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_sectionEnd_ebd85494219048b3 = function(arg0) {
|
||||||
|
getObject(arg0).sectionEnd();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_strikeStart_bb876a13c23ae4a0 = function(arg0) {
|
||||||
|
getObject(arg0).strikeStart();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_strikeEnd_b27d2e51423382b0 = function(arg0) {
|
||||||
|
getObject(arg0).strikeEnd();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_underlineStart_061a7650f74f999d = function(arg0) {
|
||||||
|
getObject(arg0).underlineStart();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_underlineEnd_9d755ffb65c1f633 = function(arg0) {
|
||||||
|
getObject(arg0).underlineEnd();
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_verseBlockStart_5810dffbfb567d87 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).verseBlockStart(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_verseBlockEnd_750d8dfa93c5b0ab = function(arg0, arg1) {
|
||||||
|
getObject(arg0).verseBlockEnd(takeObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
|
||||||
|
var ret = getStringFromWasm0(arg0, arg1);
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbindgen_number_new = function(arg0) {
|
||||||
|
var ret = arg0;
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
|
||||||
|
var ret = getObject(arg0);
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_set_f1a4ac8f3a605b11 = function(arg0, arg1, arg2) {
|
||||||
|
getObject(arg0)[takeObject(arg1)] = takeObject(arg2);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_new_949bbc1147195c4e = function() {
|
||||||
|
var ret = new Array();
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_new_ac32179a660db4bb = function() {
|
||||||
|
var ret = new Map();
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_new_0b83d3df67ecb33e = function() {
|
||||||
|
var ret = new Object();
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbindgen_is_string = function(arg0) {
|
||||||
|
var ret = typeof(getObject(arg0)) === 'string';
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_push_284486ca27c6aa8b = function(arg0, arg1) {
|
||||||
|
var ret = getObject(arg0).push(getObject(arg1));
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_new_342a24ca698edd87 = function(arg0, arg1) {
|
||||||
|
var ret = new Error(getStringFromWasm0(arg0, arg1));
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_set_a46091b120cc63e9 = function(arg0, arg1, arg2) {
|
||||||
|
var ret = getObject(arg0).set(getObject(arg1), getObject(arg2));
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_fromCodePoint_65bd58e0db7e8253 = function() { return handleError(function (arg0) {
|
||||||
|
var ret = String.fromCodePoint(arg0 >>> 0);
|
||||||
|
return addHeapObject(ret);
|
||||||
|
}, arguments) };
|
||||||
|
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
|
||||||
|
var ret = debugString(getObject(arg1));
|
||||||
|
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
|
var len0 = WASM_VECTOR_LEN;
|
||||||
|
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||||
|
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
|
||||||
|
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
|
||||||
|
input = fetch(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const { instance, module } = await load(await input, imports);
|
||||||
|
|
||||||
|
wasm = instance.exports;
|
||||||
|
init.__wbindgen_wasm_module = module;
|
||||||
|
|
||||||
|
return wasm;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Handler {
|
||||||
|
text(_text) { }
|
||||||
|
code(_item) { }
|
||||||
|
cookie(_item) { }
|
||||||
|
rule() { }
|
||||||
|
exampleBlock(_item) { }
|
||||||
|
exportBlock(_item) { }
|
||||||
|
sourceBlock(_item) { }
|
||||||
|
inlineSrc(_item) { }
|
||||||
|
link(_item) { }
|
||||||
|
snippet(_item) { }
|
||||||
|
timestamp(_item) { }
|
||||||
|
verbatim(_item) { }
|
||||||
|
fixedWidth(_item) { }
|
||||||
|
listStart(_item) { }
|
||||||
|
listEnd(_item) { }
|
||||||
|
tableStart(_item) { }
|
||||||
|
tableEnd(_item) { }
|
||||||
|
tableRowStart(_item) { }
|
||||||
|
tableRowEnd(_item) { }
|
||||||
|
tableCellStart(_item) { }
|
||||||
|
tableCellEnd(_item) { }
|
||||||
|
titleStart(_item) { }
|
||||||
|
titleEnd(_item) { }
|
||||||
|
boldStart() { }
|
||||||
|
boldEnd() { }
|
||||||
|
centerBlockStart(_item) { }
|
||||||
|
centerBlockEnd(_item) { }
|
||||||
|
documentStart() { }
|
||||||
|
documentEnd() { }
|
||||||
|
italicStart() { }
|
||||||
|
italicEnd() { }
|
||||||
|
listItemStart() { }
|
||||||
|
listItemEnd() { }
|
||||||
|
paragraphStart() { }
|
||||||
|
paragraphEnd() { }
|
||||||
|
quoteBlockStart(_item) { }
|
||||||
|
quoteBlockEnd(_item) { }
|
||||||
|
sectionStart() { }
|
||||||
|
sectionEnd() { }
|
||||||
|
strikeStart() { }
|
||||||
|
strikeEnd() { }
|
||||||
|
underlineStart() { }
|
||||||
|
underlineEnd() { }
|
||||||
|
verseBlockStart(_item) { }
|
||||||
|
verseBlockEnd(_item) { }
|
||||||
|
keyword(_item) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
const tags = {
|
||||||
|
"&": "&",
|
||||||
|
"<": "<",
|
||||||
|
">": ">",
|
||||||
|
'"': """,
|
||||||
|
"'": "'",
|
||||||
|
};
|
||||||
|
const replaceTags = (tag) => tags[tag];
|
||||||
|
const escapeHtml = (str) => str.replace(/[&<>"']/g, replaceTags);
|
||||||
|
class HtmlHandler extends Handler {
|
||||||
|
result;
|
||||||
|
constructor(result = "") {
|
||||||
|
super();
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
static escape() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
quoteBlockStart() {
|
||||||
|
this.result += "<blockquote>";
|
||||||
|
}
|
||||||
|
quoteBlockEnd() {
|
||||||
|
this.result += "</blockquote>";
|
||||||
|
}
|
||||||
|
centerBlockStart() {
|
||||||
|
this.result += '<div class="center">';
|
||||||
|
}
|
||||||
|
centerBlockEnd() {
|
||||||
|
this.result += "</div>";
|
||||||
|
}
|
||||||
|
verseBlockStart() {
|
||||||
|
this.result += '<p class="verse">';
|
||||||
|
}
|
||||||
|
verseBlockEnd() {
|
||||||
|
this.result += "</p>";
|
||||||
|
}
|
||||||
|
boldStart() {
|
||||||
|
this.result += "<b>";
|
||||||
|
}
|
||||||
|
boldEnd() {
|
||||||
|
this.result += "</b>";
|
||||||
|
}
|
||||||
|
documentStart() {
|
||||||
|
this.result += "<main>";
|
||||||
|
}
|
||||||
|
documentEnd() {
|
||||||
|
this.result += "</main>";
|
||||||
|
}
|
||||||
|
listStart(list) {
|
||||||
|
this.result += `<${list.ordered ? "o" : "u"}l>`;
|
||||||
|
}
|
||||||
|
listEnd(list) {
|
||||||
|
this.result += `</${list.ordered ? "o" : "u"}l>`;
|
||||||
|
}
|
||||||
|
italicStart() {
|
||||||
|
this.result += "<i>";
|
||||||
|
}
|
||||||
|
italicEnd() {
|
||||||
|
this.result += "</i>";
|
||||||
|
}
|
||||||
|
listItemStart() {
|
||||||
|
this.result += "<li>";
|
||||||
|
}
|
||||||
|
listItemEnd() {
|
||||||
|
this.result += "</li>";
|
||||||
|
}
|
||||||
|
paragraphStart() {
|
||||||
|
this.result += "<p>";
|
||||||
|
}
|
||||||
|
paragraphEnd() {
|
||||||
|
this.result += "</p>";
|
||||||
|
}
|
||||||
|
sectionStart() {
|
||||||
|
this.result += "<section>";
|
||||||
|
}
|
||||||
|
sectionEnd() {
|
||||||
|
this.result += "</section>";
|
||||||
|
}
|
||||||
|
strikeStart() {
|
||||||
|
this.result += "<s>";
|
||||||
|
}
|
||||||
|
strikeEnd() {
|
||||||
|
this.result += "</s>";
|
||||||
|
}
|
||||||
|
underlineStart() {
|
||||||
|
this.result += "<u>";
|
||||||
|
}
|
||||||
|
underlineEnd() {
|
||||||
|
this.result += "</u>";
|
||||||
|
}
|
||||||
|
exampleBlock(block) {
|
||||||
|
this.result += `<pre class="example">${escapeHtml(block.contents)}</pre>`;
|
||||||
|
}
|
||||||
|
sourceBlock(block) {
|
||||||
|
this.result += `<pre class="example">${escapeHtml(block.contents)}</pre>`;
|
||||||
|
}
|
||||||
|
inlineSrc(src) {
|
||||||
|
this.result += `<code class="src src-${src.lang}">${escapeHtml(src.body)}</code>`;
|
||||||
|
}
|
||||||
|
code(value) {
|
||||||
|
this.result += `<code>${escapeHtml(value)}</code>`;
|
||||||
|
}
|
||||||
|
link(link) {
|
||||||
|
this.result += `<a href="${link.path}">${escapeHtml(link.desc || link.path)}</a>`;
|
||||||
|
}
|
||||||
|
snippet(snippet) {
|
||||||
|
if (snippet.name.toLowerCase() === "html") {
|
||||||
|
this.result += snippet.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text(value) {
|
||||||
|
this.result += escapeHtml(value);
|
||||||
|
}
|
||||||
|
verbatim(value) {
|
||||||
|
this.result += `<code>${escapeHtml(value)}</code>`;
|
||||||
|
}
|
||||||
|
fixedWidth(item) {
|
||||||
|
this.result += `<pre class="example">${escapeHtml(item.value)}</pre>`;
|
||||||
|
}
|
||||||
|
rule() {
|
||||||
|
this.result += "<hr>";
|
||||||
|
}
|
||||||
|
cookie(cookie) {
|
||||||
|
this.result += `<code>${escapeHtml(cookie.value)}</code>`;
|
||||||
|
}
|
||||||
|
titleStart(title) {
|
||||||
|
this.result += `<h${Math.min(title.level, 6)}>`;
|
||||||
|
}
|
||||||
|
titleEnd(title) {
|
||||||
|
this.result += `</h${Math.min(title.level, 6)}>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CollectKeywords extends Handler {
|
||||||
|
keywords = {};
|
||||||
|
keyword(keyword) {
|
||||||
|
this.keywords[keyword.key] = this.keywords[keyword.key] || [];
|
||||||
|
this.keywords[keyword.key].push(keyword.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handle = (org, handler) => {
|
||||||
|
if (typeof org === "string") {
|
||||||
|
org = Org.parse(org);
|
||||||
|
}
|
||||||
|
handle$1(org, handler);
|
||||||
|
};
|
||||||
|
const renderHtml = (org, handler = new HtmlHandler()) => {
|
||||||
|
handle(org, handler);
|
||||||
|
return handler.result;
|
||||||
|
};
|
||||||
|
const keywords = (org) => {
|
||||||
|
const handler = new CollectKeywords();
|
||||||
|
handle(org, handler);
|
||||||
|
return handler.keywords;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Handler = Handler;
|
||||||
|
exports.HtmlHandler = HtmlHandler;
|
||||||
|
exports.Org = Org;
|
||||||
|
exports.escapeHtml = escapeHtml;
|
||||||
|
exports.handle = handle;
|
||||||
|
exports.init = init;
|
||||||
|
exports.keywords = keywords;
|
||||||
|
exports.renderHtml = renderHtml;
|
||||||
|
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
|
|
||||||
|
})));
|
BIN
orgize_bg.wasm
Normal file
BIN
orgize_bg.wasm
Normal file
Binary file not shown.
|
@ -1,18 +0,0 @@
|
||||||
/// Parse configuration
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct ParseConfig {
|
|
||||||
/// Headline's todo keywords
|
|
||||||
pub todo_keywords: (Vec<String>, Vec<String>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ParseConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
ParseConfig {
|
|
||||||
todo_keywords: (vec![String::from("TODO")], vec![String::from("DONE")]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
pub static ref DEFAULT_CONFIG: ParseConfig = ParseConfig::default();
|
|
||||||
}
|
|
|
@ -1,408 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom::{
|
|
||||||
bytes::complete::tag_no_case,
|
|
||||||
character::complete::{alpha1, space0},
|
|
||||||
sequence::preceded,
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::elements::Element;
|
|
||||||
use crate::parse::combinators::{blank_lines_count, line, lines_till};
|
|
||||||
|
|
||||||
/// Special Block Element
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
pub struct SpecialBlock<'a> {
|
|
||||||
/// Block parameters
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub parameters: Option<Cow<'a, str>>,
|
|
||||||
/// Block name
|
|
||||||
pub name: Cow<'a, str>,
|
|
||||||
/// Numbers of blank lines between first block's line and next non-blank
|
|
||||||
/// line
|
|
||||||
pub pre_blank: usize,
|
|
||||||
/// Numbers of blank lines between last block's line and next non-blank line
|
|
||||||
/// or buffer's end
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpecialBlock<'_> {
|
|
||||||
pub fn into_owned(self) -> SpecialBlock<'static> {
|
|
||||||
SpecialBlock {
|
|
||||||
name: self.name.into_owned().into(),
|
|
||||||
parameters: self.parameters.map(Into::into).map(Cow::Owned),
|
|
||||||
pre_blank: self.pre_blank,
|
|
||||||
post_blank: self.post_blank,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Quote Block Element
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
pub struct QuoteBlock<'a> {
|
|
||||||
/// Optional block parameters
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub parameters: Option<Cow<'a, str>>,
|
|
||||||
/// Numbers of blank lines between first block's line and next non-blank
|
|
||||||
/// line
|
|
||||||
pub pre_blank: usize,
|
|
||||||
/// Numbers of blank lines between last block's line and next non-blank line
|
|
||||||
/// or buffer's end
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl QuoteBlock<'_> {
|
|
||||||
pub fn into_owned(self) -> QuoteBlock<'static> {
|
|
||||||
QuoteBlock {
|
|
||||||
parameters: self.parameters.map(Into::into).map(Cow::Owned),
|
|
||||||
pre_blank: self.pre_blank,
|
|
||||||
post_blank: self.post_blank,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Center Block Element
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
pub struct CenterBlock<'a> {
|
|
||||||
/// Optional block parameters
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub parameters: Option<Cow<'a, str>>,
|
|
||||||
/// Numbers of blank lines between first block's line and next non-blank
|
|
||||||
/// line
|
|
||||||
pub pre_blank: usize,
|
|
||||||
/// Numbers of blank lines between last block's line and next non-blank line
|
|
||||||
/// or buffer's end
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CenterBlock<'_> {
|
|
||||||
pub fn into_owned(self) -> CenterBlock<'static> {
|
|
||||||
CenterBlock {
|
|
||||||
parameters: self.parameters.map(Into::into).map(Cow::Owned),
|
|
||||||
pre_blank: self.pre_blank,
|
|
||||||
post_blank: self.post_blank,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verse Block Element
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
pub struct VerseBlock<'a> {
|
|
||||||
/// Optional block parameters
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub parameters: Option<Cow<'a, str>>,
|
|
||||||
/// Numbers of blank lines between first block's line and next non-blank
|
|
||||||
/// line
|
|
||||||
pub pre_blank: usize,
|
|
||||||
/// Numbers of blank lines between last block's line and next non-blank line
|
|
||||||
/// or buffer's end
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VerseBlock<'_> {
|
|
||||||
pub fn into_owned(self) -> VerseBlock<'static> {
|
|
||||||
VerseBlock {
|
|
||||||
parameters: self.parameters.map(Into::into).map(Cow::Owned),
|
|
||||||
pre_blank: self.pre_blank,
|
|
||||||
post_blank: self.post_blank,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Comment Block Element
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
pub struct CommentBlock<'a> {
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub data: Option<Cow<'a, str>>,
|
|
||||||
/// Comment block contents
|
|
||||||
pub contents: Cow<'a, str>,
|
|
||||||
/// Numbers of blank lines between last block's line and next non-blank line
|
|
||||||
/// or buffer's end
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CommentBlock<'_> {
|
|
||||||
pub fn into_owned(self) -> CommentBlock<'static> {
|
|
||||||
CommentBlock {
|
|
||||||
data: self.data.map(Into::into).map(Cow::Owned),
|
|
||||||
contents: self.contents.into_owned().into(),
|
|
||||||
post_blank: self.post_blank,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Example Block Element
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
pub struct ExampleBlock<'a> {
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub data: Option<Cow<'a, str>>,
|
|
||||||
/// Block contents
|
|
||||||
pub contents: Cow<'a, str>,
|
|
||||||
/// Numbers of blank lines between last block's line and next non-blank line
|
|
||||||
/// or buffer's end
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExampleBlock<'_> {
|
|
||||||
pub fn into_owned(self) -> ExampleBlock<'static> {
|
|
||||||
ExampleBlock {
|
|
||||||
data: self.data.map(Into::into).map(Cow::Owned),
|
|
||||||
contents: self.contents.into_owned().into(),
|
|
||||||
post_blank: self.post_blank,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Export Block Element
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
pub struct ExportBlock<'a> {
|
|
||||||
pub data: Cow<'a, str>,
|
|
||||||
/// Block contents
|
|
||||||
pub contents: Cow<'a, str>,
|
|
||||||
/// Numbers of blank lines between last block's line and next non-blank line
|
|
||||||
/// or buffer's end
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExportBlock<'_> {
|
|
||||||
pub fn into_owned(self) -> ExportBlock<'static> {
|
|
||||||
ExportBlock {
|
|
||||||
data: self.data.into_owned().into(),
|
|
||||||
contents: self.contents.into_owned().into(),
|
|
||||||
post_blank: self.post_blank,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Src Block Element
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
pub struct SourceBlock<'a> {
|
|
||||||
/// Block contents
|
|
||||||
pub contents: Cow<'a, str>,
|
|
||||||
/// Language of the code in the block
|
|
||||||
pub language: Cow<'a, str>,
|
|
||||||
pub arguments: Cow<'a, str>,
|
|
||||||
/// Numbers of blank lines between last block's line and next non-blank line
|
|
||||||
/// or buffer's end
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SourceBlock<'_> {
|
|
||||||
pub fn into_owned(self) -> SourceBlock<'static> {
|
|
||||||
SourceBlock {
|
|
||||||
language: self.language.into_owned().into(),
|
|
||||||
arguments: self.arguments.into_owned().into(),
|
|
||||||
contents: self.contents.into_owned().into(),
|
|
||||||
post_blank: self.post_blank,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: fn number_lines() -> Some(New) | Some(Continued) | None { }
|
|
||||||
// TODO: fn preserve_indent() -> bool { }
|
|
||||||
// TODO: fn use_labels() -> bool { }
|
|
||||||
// TODO: fn label_fmt() -> Option<String> { }
|
|
||||||
// TODO: fn retain_labels() -> bool { }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
pub(crate) struct RawBlock<'a> {
|
|
||||||
pub name: &'a str,
|
|
||||||
pub arguments: &'a str,
|
|
||||||
|
|
||||||
pub pre_blank: usize,
|
|
||||||
pub contents: &'a str,
|
|
||||||
pub contents_without_blank_lines: &'a str,
|
|
||||||
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> RawBlock<'a> {
|
|
||||||
pub fn parse(input: &str) -> Option<(&str, RawBlock)> {
|
|
||||||
parse_internal(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_element(self) -> (Element<'a>, &'a str) {
|
|
||||||
let RawBlock {
|
|
||||||
name,
|
|
||||||
contents,
|
|
||||||
arguments,
|
|
||||||
pre_blank,
|
|
||||||
contents_without_blank_lines,
|
|
||||||
post_blank,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
let arguments: Option<Cow<'a, str>> = if arguments.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(arguments.into())
|
|
||||||
};
|
|
||||||
|
|
||||||
let element = match &*name.to_uppercase() {
|
|
||||||
"CENTER" => CenterBlock {
|
|
||||||
parameters: arguments,
|
|
||||||
pre_blank,
|
|
||||||
post_blank,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
"QUOTE" => QuoteBlock {
|
|
||||||
parameters: arguments,
|
|
||||||
pre_blank,
|
|
||||||
post_blank,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
"VERSE" => VerseBlock {
|
|
||||||
parameters: arguments,
|
|
||||||
pre_blank,
|
|
||||||
post_blank,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
"COMMENT" => CommentBlock {
|
|
||||||
data: arguments,
|
|
||||||
contents: contents.into(),
|
|
||||||
post_blank,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
"EXAMPLE" => ExampleBlock {
|
|
||||||
data: arguments,
|
|
||||||
contents: contents.into(),
|
|
||||||
post_blank,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
"EXPORT" => ExportBlock {
|
|
||||||
data: arguments.unwrap_or_default(),
|
|
||||||
contents: contents.into(),
|
|
||||||
post_blank,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
"SRC" => {
|
|
||||||
let (language, arguments) = match &arguments {
|
|
||||||
Some(Cow::Borrowed(args)) => {
|
|
||||||
let (language, arguments) =
|
|
||||||
args.split_at(args.find(' ').unwrap_or_else(|| args.len()));
|
|
||||||
(language.into(), arguments.into())
|
|
||||||
}
|
|
||||||
None => (Cow::Borrowed(""), Cow::Borrowed("")),
|
|
||||||
_ => unreachable!(
|
|
||||||
"`parse_block_element` returns `Some(Cow::Borrowed)` or `None`"
|
|
||||||
),
|
|
||||||
};
|
|
||||||
SourceBlock {
|
|
||||||
arguments,
|
|
||||||
language,
|
|
||||||
contents: contents.into(),
|
|
||||||
post_blank,
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
_ => SpecialBlock {
|
|
||||||
parameters: arguments,
|
|
||||||
name: name.into(),
|
|
||||||
pre_blank,
|
|
||||||
post_blank,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
(element, contents_without_blank_lines)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_internal(input: &str) -> IResult<&str, RawBlock, ()> {
|
|
||||||
let (input, _) = space0(input)?;
|
|
||||||
let (input, name) = preceded(tag_no_case("#+BEGIN_"), alpha1)(input)?;
|
|
||||||
let (input, arguments) = line(input)?;
|
|
||||||
let end_line = format!("#+END_{}", name);
|
|
||||||
let (input, contents) = lines_till(|line| line.trim().eq_ignore_ascii_case(&end_line))(input)?;
|
|
||||||
let (contents_without_blank_lines, pre_blank) = blank_lines_count(contents)?;
|
|
||||||
let (input, post_blank) = blank_lines_count(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
RawBlock {
|
|
||||||
name,
|
|
||||||
contents,
|
|
||||||
arguments: arguments.trim(),
|
|
||||||
pre_blank,
|
|
||||||
contents_without_blank_lines,
|
|
||||||
post_blank,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(
|
|
||||||
RawBlock::parse(
|
|
||||||
r#"#+BEGIN_SRC
|
|
||||||
#+END_SRC"#
|
|
||||||
),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
RawBlock {
|
|
||||||
contents: "",
|
|
||||||
contents_without_blank_lines: "",
|
|
||||||
pre_blank: 0,
|
|
||||||
post_blank: 0,
|
|
||||||
name: "SRC".into(),
|
|
||||||
arguments: ""
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
RawBlock::parse(
|
|
||||||
r#"#+begin_src
|
|
||||||
#+end_src"#
|
|
||||||
),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
RawBlock {
|
|
||||||
contents: "",
|
|
||||||
contents_without_blank_lines: "",
|
|
||||||
pre_blank: 0,
|
|
||||||
post_blank: 0,
|
|
||||||
name: "src".into(),
|
|
||||||
arguments: ""
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
RawBlock::parse(
|
|
||||||
r#"#+BEGIN_SRC javascript
|
|
||||||
console.log('Hello World!');
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
"#
|
|
||||||
),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
RawBlock {
|
|
||||||
contents: "console.log('Hello World!');\n",
|
|
||||||
contents_without_blank_lines: "console.log('Hello World!');\n",
|
|
||||||
pre_blank: 0,
|
|
||||||
post_blank: 1,
|
|
||||||
name: "SRC".into(),
|
|
||||||
arguments: "javascript"
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
// TODO: more testing
|
|
||||||
}
|
|
|
@ -1,242 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom::{
|
|
||||||
bytes::complete::tag,
|
|
||||||
character::complete::{char, digit1, space0},
|
|
||||||
combinator::recognize,
|
|
||||||
sequence::separated_pair,
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::elements::timestamp::{parse_inactive, Datetime, Timestamp};
|
|
||||||
use crate::parse::combinators::{blank_lines_count, eol};
|
|
||||||
|
|
||||||
/// Clock Element
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[cfg_attr(feature = "ser", serde(untagged))]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Clock<'a> {
|
|
||||||
/// Closed Clock
|
|
||||||
Closed {
|
|
||||||
/// Time start
|
|
||||||
start: Datetime<'a>,
|
|
||||||
/// Time end
|
|
||||||
end: Datetime<'a>,
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
repeater: Option<Cow<'a, str>>,
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
delay: Option<Cow<'a, str>>,
|
|
||||||
/// Clock duration
|
|
||||||
duration: Cow<'a, str>,
|
|
||||||
/// Numbers of blank lines between the clock line and next non-blank
|
|
||||||
/// line or buffer's end
|
|
||||||
post_blank: usize,
|
|
||||||
},
|
|
||||||
/// Running Clock
|
|
||||||
Running {
|
|
||||||
/// Time start
|
|
||||||
start: Datetime<'a>,
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
repeater: Option<Cow<'a, str>>,
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
delay: Option<Cow<'a, str>>,
|
|
||||||
/// Numbers of blank lines between the clock line and next non-blank
|
|
||||||
/// line or buffer's end
|
|
||||||
post_blank: usize,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clock<'_> {
|
|
||||||
pub(crate) fn parse(input: &str) -> Option<(&str, Clock)> {
|
|
||||||
parse_internal(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_onwed(self) -> Clock<'static> {
|
|
||||||
match self {
|
|
||||||
Clock::Closed {
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
repeater,
|
|
||||||
delay,
|
|
||||||
duration,
|
|
||||||
post_blank,
|
|
||||||
} => Clock::Closed {
|
|
||||||
start: start.into_owned(),
|
|
||||||
end: end.into_owned(),
|
|
||||||
repeater: repeater.map(Into::into).map(Cow::Owned),
|
|
||||||
delay: delay.map(Into::into).map(Cow::Owned),
|
|
||||||
duration: duration.into_owned().into(),
|
|
||||||
post_blank,
|
|
||||||
},
|
|
||||||
Clock::Running {
|
|
||||||
start,
|
|
||||||
repeater,
|
|
||||||
delay,
|
|
||||||
post_blank,
|
|
||||||
} => Clock::Running {
|
|
||||||
start: start.into_owned(),
|
|
||||||
repeater: repeater.map(Into::into).map(Cow::Owned),
|
|
||||||
delay: delay.map(Into::into).map(Cow::Owned),
|
|
||||||
post_blank,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if the clock is running.
|
|
||||||
pub fn is_running(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Clock::Closed { .. } => false,
|
|
||||||
Clock::Running { .. } => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if the clock is closed.
|
|
||||||
pub fn is_closed(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Clock::Closed { .. } => true,
|
|
||||||
Clock::Running { .. } => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns clock duration, or `None` if it's running.
|
|
||||||
pub fn duration(&self) -> Option<&str> {
|
|
||||||
match self {
|
|
||||||
Clock::Closed { duration, .. } => Some(duration),
|
|
||||||
Clock::Running { .. } => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Constructs a timestamp from the clock.
|
|
||||||
pub fn value(&self) -> Timestamp {
|
|
||||||
match &*self {
|
|
||||||
Clock::Closed {
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
repeater,
|
|
||||||
delay,
|
|
||||||
..
|
|
||||||
} => Timestamp::InactiveRange {
|
|
||||||
start: start.clone(),
|
|
||||||
end: end.clone(),
|
|
||||||
repeater: repeater.clone(),
|
|
||||||
delay: delay.clone(),
|
|
||||||
},
|
|
||||||
Clock::Running {
|
|
||||||
start,
|
|
||||||
repeater,
|
|
||||||
delay,
|
|
||||||
..
|
|
||||||
} => Timestamp::Inactive {
|
|
||||||
start: start.clone(),
|
|
||||||
repeater: repeater.clone(),
|
|
||||||
delay: delay.clone(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_internal(input: &str) -> IResult<&str, Clock, ()> {
|
|
||||||
let (input, _) = space0(input)?;
|
|
||||||
let (input, _) = tag("CLOCK:")(input)?;
|
|
||||||
let (input, _) = space0(input)?;
|
|
||||||
let (input, timestamp) = parse_inactive(input)?;
|
|
||||||
|
|
||||||
match timestamp {
|
|
||||||
Timestamp::InactiveRange {
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
repeater,
|
|
||||||
delay,
|
|
||||||
} => {
|
|
||||||
let (input, _) = space0(input)?;
|
|
||||||
let (input, _) = tag("=>")(input)?;
|
|
||||||
let (input, _) = space0(input)?;
|
|
||||||
let (input, duration) = recognize(separated_pair(digit1, char(':'), digit1))(input)?;
|
|
||||||
let (input, _) = eol(input)?;
|
|
||||||
let (input, blank) = blank_lines_count(input)?;
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
Clock::Closed {
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
repeater,
|
|
||||||
delay,
|
|
||||||
duration: duration.into(),
|
|
||||||
post_blank: blank,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
Timestamp::Inactive {
|
|
||||||
start,
|
|
||||||
repeater,
|
|
||||||
delay,
|
|
||||||
} => {
|
|
||||||
let (input, _) = eol(input)?;
|
|
||||||
let (input, blank) = blank_lines_count(input)?;
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
Clock::Running {
|
|
||||||
start,
|
|
||||||
repeater,
|
|
||||||
delay,
|
|
||||||
post_blank: blank,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
_ => unreachable!(
|
|
||||||
"`parse_inactive` only returns `Timestamp::InactiveRange` or `Timestamp::Inactive`."
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(
|
|
||||||
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Clock::Running {
|
|
||||||
start: Datetime {
|
|
||||||
year: 2003,
|
|
||||||
month: 9,
|
|
||||||
day: 16,
|
|
||||||
dayname: "Tue".into(),
|
|
||||||
hour: Some(9),
|
|
||||||
minute: Some(39)
|
|
||||||
},
|
|
||||||
repeater: None,
|
|
||||||
delay: None,
|
|
||||||
post_blank: 0,
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00\n\n"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Clock::Closed {
|
|
||||||
start: Datetime {
|
|
||||||
year: 2003,
|
|
||||||
month: 9,
|
|
||||||
day: 16,
|
|
||||||
dayname: "Tue".into(),
|
|
||||||
hour: Some(9),
|
|
||||||
minute: Some(39)
|
|
||||||
},
|
|
||||||
end: Datetime {
|
|
||||||
year: 2003,
|
|
||||||
month: 9,
|
|
||||||
day: 16,
|
|
||||||
dayname: "Tue".into(),
|
|
||||||
hour: Some(10),
|
|
||||||
minute: Some(39)
|
|
||||||
},
|
|
||||||
repeater: None,
|
|
||||||
delay: None,
|
|
||||||
duration: "1:00".into(),
|
|
||||||
post_blank: 1,
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom::{
|
|
||||||
error::{make_error, ErrorKind},
|
|
||||||
Err, IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::parse::combinators::{blank_lines_count, lines_while};
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
pub struct Comment<'a> {
|
|
||||||
/// Comments value, with pound signs
|
|
||||||
pub value: Cow<'a, str>,
|
|
||||||
/// Numbers of blank lines between last comment's line and next non-blank
|
|
||||||
/// line or buffer's end
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Comment<'_> {
|
|
||||||
pub(crate) fn parse(input: &str) -> Option<(&str, Comment)> {
|
|
||||||
parse_internal(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> Comment<'static> {
|
|
||||||
Comment {
|
|
||||||
value: self.value.into_owned().into(),
|
|
||||||
post_blank: self.post_blank,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_internal(input: &str) -> IResult<&str, Comment, ()> {
|
|
||||||
let (input, value) = lines_while(|line| {
|
|
||||||
let line = line.trim_start();
|
|
||||||
line == "#" || line.starts_with("# ")
|
|
||||||
})(input)?;
|
|
||||||
|
|
||||||
if value.is_empty() {
|
|
||||||
// TODO: better error kind
|
|
||||||
return Err(Err::Error(make_error(input, ErrorKind::Many0)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (input, post_blank) = blank_lines_count(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
Comment {
|
|
||||||
value: value.into(),
|
|
||||||
post_blank,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
|
@ -1,122 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom::{
|
|
||||||
branch::alt,
|
|
||||||
bytes::complete::tag,
|
|
||||||
character::complete::digit0,
|
|
||||||
combinator::recognize,
|
|
||||||
sequence::{delimited, pair, separated_pair},
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Statistics Cookie Object
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Cookie<'a> {
|
|
||||||
/// Full cookie value
|
|
||||||
pub value: Cow<'a, str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cookie<'_> {
|
|
||||||
pub(crate) fn parse(input: &str) -> Option<(&str, Cookie)> {
|
|
||||||
parse_internal(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> Cookie<'static> {
|
|
||||||
Cookie {
|
|
||||||
value: self.value.into_owned().into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn parse_internal(input: &str) -> IResult<&str, Cookie, ()> {
|
|
||||||
let (input, value) = recognize(delimited(
|
|
||||||
tag("["),
|
|
||||||
alt((
|
|
||||||
separated_pair(digit0, tag("/"), digit0),
|
|
||||||
pair(digit0, tag("%")),
|
|
||||||
)),
|
|
||||||
tag("]"),
|
|
||||||
))(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
Cookie {
|
|
||||||
value: value.into(),
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(
|
|
||||||
Cookie::parse("[1/10]"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Cookie {
|
|
||||||
value: "[1/10]".into()
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Cookie::parse("[1/1000]"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Cookie {
|
|
||||||
value: "[1/1000]".into()
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Cookie::parse("[10%]"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Cookie {
|
|
||||||
value: "[10%]".into()
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Cookie::parse("[%]"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Cookie {
|
|
||||||
value: "[%]".into()
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Cookie::parse("[/]"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Cookie {
|
|
||||||
value: "[/]".into()
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Cookie::parse("[100/]"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Cookie {
|
|
||||||
value: "[100/]".into()
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Cookie::parse("[/100]"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Cookie {
|
|
||||||
value: "[/100]".into()
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(Cookie::parse("[10% ]").is_none());
|
|
||||||
assert!(Cookie::parse("[1//100]").is_none());
|
|
||||||
assert!(Cookie::parse("[1\\100]").is_none());
|
|
||||||
assert!(Cookie::parse("[10%%]").is_none());
|
|
||||||
}
|
|
|
@ -1,121 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom::{
|
|
||||||
bytes::complete::{tag, take_while1},
|
|
||||||
character::complete::space0,
|
|
||||||
sequence::delimited,
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::parse::combinators::{blank_lines_count, eol, lines_till};
|
|
||||||
|
|
||||||
/// Drawer Element
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
pub struct Drawer<'a> {
|
|
||||||
/// Drawer name
|
|
||||||
pub name: Cow<'a, str>,
|
|
||||||
/// Numbers of blank lines between first drawer's line and next non-blank
|
|
||||||
/// line
|
|
||||||
pub pre_blank: usize,
|
|
||||||
/// Numbers of blank lines between last drawer's line and next non-blank
|
|
||||||
/// line or buffer's end
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drawer<'_> {
|
|
||||||
pub(crate) fn parse(input: &str) -> Option<(&str, (Drawer, &str))> {
|
|
||||||
parse_drawer(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> Drawer<'static> {
|
|
||||||
Drawer {
|
|
||||||
name: self.name.into_owned().into(),
|
|
||||||
pre_blank: self.pre_blank,
|
|
||||||
post_blank: self.post_blank,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn parse_drawer(input: &str) -> IResult<&str, (Drawer, &str), ()> {
|
|
||||||
let (input, (mut drawer, content)) = parse_drawer_without_blank(input)?;
|
|
||||||
|
|
||||||
let (content, blank) = blank_lines_count(content)?;
|
|
||||||
drawer.pre_blank = blank;
|
|
||||||
|
|
||||||
let (input, blank) = blank_lines_count(input)?;
|
|
||||||
drawer.post_blank = blank;
|
|
||||||
|
|
||||||
Ok((input, (drawer, content)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_drawer_without_blank(input: &str) -> IResult<&str, (Drawer, &str), ()> {
|
|
||||||
let (input, _) = space0(input)?;
|
|
||||||
let (input, name) = delimited(
|
|
||||||
tag(":"),
|
|
||||||
take_while1(|c: char| c.is_ascii_alphabetic() || c == '-' || c == '_'),
|
|
||||||
tag(":"),
|
|
||||||
)(input)?;
|
|
||||||
let (input, _) = eol(input)?;
|
|
||||||
let (input, contents) = lines_till(|line| line.trim().eq_ignore_ascii_case(":END:"))(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
(
|
|
||||||
Drawer {
|
|
||||||
name: name.into(),
|
|
||||||
pre_blank: 0,
|
|
||||||
post_blank: 0,
|
|
||||||
},
|
|
||||||
contents,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(
|
|
||||||
parse_drawer(
|
|
||||||
r#":PROPERTIES:
|
|
||||||
:CUSTOM_ID: id
|
|
||||||
:END:"#
|
|
||||||
),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
Drawer {
|
|
||||||
name: "PROPERTIES".into(),
|
|
||||||
pre_blank: 0,
|
|
||||||
post_blank: 0
|
|
||||||
},
|
|
||||||
" :CUSTOM_ID: id\n"
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_drawer(
|
|
||||||
r#":PROPERTIES:
|
|
||||||
|
|
||||||
|
|
||||||
:END:
|
|
||||||
|
|
||||||
"#
|
|
||||||
),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
Drawer {
|
|
||||||
name: "PROPERTIES".into(),
|
|
||||||
pre_blank: 2,
|
|
||||||
post_blank: 1,
|
|
||||||
},
|
|
||||||
""
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
// https://github.com/PoiScript/orgize/issues/9
|
|
||||||
assert!(parse_drawer(":SPAGHETTI:\n").is_err());
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom::{
|
|
||||||
bytes::complete::tag_no_case,
|
|
||||||
character::complete::{alpha1, space0, space1},
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::parse::combinators::{blank_lines_count, line, lines_till};
|
|
||||||
|
|
||||||
/// Dynamic Block Element
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
pub struct DynBlock<'a> {
|
|
||||||
/// Block name
|
|
||||||
pub block_name: Cow<'a, str>,
|
|
||||||
/// Block argument
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub arguments: Option<Cow<'a, str>>,
|
|
||||||
/// Numbers of blank lines between first block's line and next non-blank
|
|
||||||
/// line
|
|
||||||
pub pre_blank: usize,
|
|
||||||
/// Numbers of blank lines between last drawer's line and next non-blank
|
|
||||||
/// line or buffer's end
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DynBlock<'_> {
|
|
||||||
pub(crate) fn parse(input: &str) -> Option<(&str, (DynBlock, &str))> {
|
|
||||||
parse_internal(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> DynBlock<'static> {
|
|
||||||
DynBlock {
|
|
||||||
block_name: self.block_name.into_owned().into(),
|
|
||||||
arguments: self.arguments.map(Into::into).map(Cow::Owned),
|
|
||||||
pre_blank: self.pre_blank,
|
|
||||||
post_blank: self.post_blank,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn parse_internal(input: &str) -> IResult<&str, (DynBlock, &str), ()> {
|
|
||||||
let (input, _) = space0(input)?;
|
|
||||||
let (input, _) = tag_no_case("#+BEGIN:")(input)?;
|
|
||||||
let (input, _) = space1(input)?;
|
|
||||||
let (input, name) = alpha1(input)?;
|
|
||||||
let (input, args) = line(input)?;
|
|
||||||
let (input, contents) = lines_till(|line| line.trim().eq_ignore_ascii_case("#+END:"))(input)?;
|
|
||||||
let (contents, pre_blank) = blank_lines_count(contents)?;
|
|
||||||
let (input, post_blank) = blank_lines_count(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
(
|
|
||||||
DynBlock {
|
|
||||||
block_name: name.into(),
|
|
||||||
arguments: if args.trim().is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(args.trim().into())
|
|
||||||
},
|
|
||||||
pre_blank,
|
|
||||||
post_blank,
|
|
||||||
},
|
|
||||||
contents,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
// TODO: testing
|
|
||||||
assert_eq!(
|
|
||||||
DynBlock::parse(
|
|
||||||
r#"#+BEGIN: clocktable :scope file
|
|
||||||
|
|
||||||
|
|
||||||
CONTENTS
|
|
||||||
#+END:
|
|
||||||
|
|
||||||
"#
|
|
||||||
),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
DynBlock {
|
|
||||||
block_name: "clocktable".into(),
|
|
||||||
arguments: Some(":scope file".into()),
|
|
||||||
pre_blank: 2,
|
|
||||||
post_blank: 1,
|
|
||||||
},
|
|
||||||
"CONTENTS\n"
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
use bytecount::count;
|
|
||||||
use memchr::memchr_iter;
|
|
||||||
|
|
||||||
use crate::elements::Element;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
pub(crate) struct Emphasis<'a> {
|
|
||||||
marker: u8,
|
|
||||||
contents: &'a str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Emphasis<'a> {
|
|
||||||
pub fn parse(text: &str, marker: u8) -> Option<(&str, Emphasis)> {
|
|
||||||
if text.len() < 3 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let bytes = text.as_bytes();
|
|
||||||
|
|
||||||
if bytes[1].is_ascii_whitespace() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in memchr_iter(marker, bytes).skip(1) {
|
|
||||||
// contains at least one character
|
|
||||||
if i == 1 {
|
|
||||||
continue;
|
|
||||||
} else if count(&bytes[1..i], b'\n') >= 2 {
|
|
||||||
break;
|
|
||||||
} else if validate_marker(i, text) {
|
|
||||||
return Some((
|
|
||||||
&text[i + 1..],
|
|
||||||
Emphasis {
|
|
||||||
marker,
|
|
||||||
contents: &text[1..i],
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_element(self) -> (Element<'a>, &'a str) {
|
|
||||||
let Emphasis { marker, contents } = self;
|
|
||||||
let element = match marker {
|
|
||||||
b'*' => Element::Bold,
|
|
||||||
b'+' => Element::Strike,
|
|
||||||
b'/' => Element::Italic,
|
|
||||||
b'_' => Element::Underline,
|
|
||||||
b'=' => Element::Verbatim {
|
|
||||||
value: contents.into(),
|
|
||||||
},
|
|
||||||
b'~' => Element::Code {
|
|
||||||
value: contents.into(),
|
|
||||||
},
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
(element, contents)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_marker(pos: usize, text: &str) -> bool {
|
|
||||||
if text.as_bytes()[pos - 1].is_ascii_whitespace() {
|
|
||||||
false
|
|
||||||
} else if let Some(&post) = text.as_bytes().get(pos + 1) {
|
|
||||||
match post {
|
|
||||||
b' ' | b'-' | b'.' | b',' | b':' | b'!' | b'?' | b'\'' | b'\n' | b')' | b'}' => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(
|
|
||||||
Emphasis::parse("*bold*", b'*'),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Emphasis {
|
|
||||||
contents: "bold",
|
|
||||||
marker: b'*'
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Emphasis::parse("*bo*ld*", b'*'),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Emphasis {
|
|
||||||
contents: "bo*ld",
|
|
||||||
marker: b'*'
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Emphasis::parse("*bo\nld*", b'*'),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Emphasis {
|
|
||||||
contents: "bo\nld",
|
|
||||||
marker: b'*'
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(Emphasis::parse("*bold*a", b'*'), None);
|
|
||||||
assert_eq!(Emphasis::parse("*bold*", b'/'), None);
|
|
||||||
assert_eq!(Emphasis::parse("*bold *", b'*'), None);
|
|
||||||
assert_eq!(Emphasis::parse("* bold*", b'*'), None);
|
|
||||||
assert_eq!(Emphasis::parse("*b\nol\nd*", b'*'), None);
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom::{
|
|
||||||
error::{make_error, ErrorKind},
|
|
||||||
Err, IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::parse::combinators::{blank_lines_count, lines_while};
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
pub struct FixedWidth<'a> {
|
|
||||||
/// Fixed width value
|
|
||||||
pub value: Cow<'a, str>,
|
|
||||||
/// Numbers of blank lines between last fixed width's line and next
|
|
||||||
/// non-blank line or buffer's end
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FixedWidth<'_> {
|
|
||||||
pub(crate) fn parse(input: &str) -> Option<(&str, FixedWidth)> {
|
|
||||||
parse_internal(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> FixedWidth<'static> {
|
|
||||||
FixedWidth {
|
|
||||||
value: self.value.into_owned().into(),
|
|
||||||
post_blank: self.post_blank,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_internal(input: &str) -> IResult<&str, FixedWidth, ()> {
|
|
||||||
let (input, value) = lines_while(|line| {
|
|
||||||
let line = line.trim_start();
|
|
||||||
line == ":" || line.starts_with(": ")
|
|
||||||
})(input)?;
|
|
||||||
|
|
||||||
if value.is_empty() {
|
|
||||||
// TODO: better error kind
|
|
||||||
return Err(Err::Error(make_error(input, ErrorKind::Many0)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (input, post_blank) = blank_lines_count(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
FixedWidth {
|
|
||||||
value: value.into(),
|
|
||||||
post_blank,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(
|
|
||||||
FixedWidth::parse(
|
|
||||||
r#": A
|
|
||||||
:
|
|
||||||
: B
|
|
||||||
: C
|
|
||||||
|
|
||||||
"#
|
|
||||||
),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
FixedWidth {
|
|
||||||
value: r#": A
|
|
||||||
:
|
|
||||||
: B
|
|
||||||
: C
|
|
||||||
"#
|
|
||||||
.into(),
|
|
||||||
post_blank: 1
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,117 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom::{
|
|
||||||
bytes::complete::{tag, take_while1},
|
|
||||||
sequence::delimited,
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::parse::combinators::{blank_lines_count, line};
|
|
||||||
|
|
||||||
/// Footnote Definition Element
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
pub struct FnDef<'a> {
|
|
||||||
/// Footnote label, used for reference
|
|
||||||
pub label: Cow<'a, str>,
|
|
||||||
/// Numbers of blank lines between last footnote definition's line and next
|
|
||||||
/// non-blank line or buffer's end
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FnDef<'_> {
|
|
||||||
pub(crate) fn parse(input: &str) -> Option<(&str, (FnDef, &str))> {
|
|
||||||
parse_internal(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> FnDef<'static> {
|
|
||||||
FnDef {
|
|
||||||
label: self.label.into_owned().into(),
|
|
||||||
post_blank: self.post_blank,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_internal(input: &str) -> IResult<&str, (FnDef, &str), ()> {
|
|
||||||
let (input, label) = delimited(
|
|
||||||
tag("[fn:"),
|
|
||||||
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
|
|
||||||
tag("]"),
|
|
||||||
)(input)?;
|
|
||||||
|
|
||||||
let (input, content) = line(input)?;
|
|
||||||
|
|
||||||
let (input, post_blank) = blank_lines_count(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
(
|
|
||||||
FnDef {
|
|
||||||
label: label.into(),
|
|
||||||
post_blank,
|
|
||||||
},
|
|
||||||
content,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(
|
|
||||||
FnDef::parse("[fn:1] https://orgmode.org"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
FnDef {
|
|
||||||
label: "1".into(),
|
|
||||||
post_blank: 0
|
|
||||||
},
|
|
||||||
" https://orgmode.org"
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
FnDef::parse("[fn:word_1] https://orgmode.org"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
FnDef {
|
|
||||||
label: "word_1".into(),
|
|
||||||
post_blank: 0,
|
|
||||||
},
|
|
||||||
" https://orgmode.org"
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
FnDef::parse("[fn:WORD-1] https://orgmode.org"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
FnDef {
|
|
||||||
label: "WORD-1".into(),
|
|
||||||
post_blank: 0,
|
|
||||||
},
|
|
||||||
" https://orgmode.org"
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
FnDef::parse("[fn:WORD]"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
FnDef {
|
|
||||||
label: "WORD".into(),
|
|
||||||
post_blank: 0,
|
|
||||||
},
|
|
||||||
""
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(FnDef::parse("[fn:] https://orgmode.org").is_none());
|
|
||||||
assert!(FnDef::parse("[fn:wor d] https://orgmode.org").is_none());
|
|
||||||
assert!(FnDef::parse("[fn:WORD https://orgmode.org").is_none());
|
|
||||||
}
|
|
|
@ -1,111 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use memchr::memchr2_iter;
|
|
||||||
use nom::{
|
|
||||||
bytes::complete::{tag, take_while},
|
|
||||||
combinator::opt,
|
|
||||||
error::{make_error, ErrorKind},
|
|
||||||
sequence::preceded,
|
|
||||||
Err, IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Footnote Reference Element
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct FnRef<'a> {
|
|
||||||
/// Footnote label
|
|
||||||
pub label: Cow<'a, str>,
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub definition: Option<Cow<'a, str>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FnRef<'_> {
|
|
||||||
pub(crate) fn parse(input: &str) -> Option<(&str, FnRef)> {
|
|
||||||
parse_internal(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> FnRef<'static> {
|
|
||||||
FnRef {
|
|
||||||
label: self.label.into_owned().into(),
|
|
||||||
definition: self.definition.map(Into::into).map(Cow::Owned),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn parse_internal(input: &str) -> IResult<&str, FnRef, ()> {
|
|
||||||
let (input, _) = tag("[fn:")(input)?;
|
|
||||||
let (input, label) =
|
|
||||||
take_while(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_')(input)?;
|
|
||||||
let (input, definition) = opt(preceded(tag(":"), balanced_brackets))(input)?;
|
|
||||||
let (input, _) = tag("]")(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
FnRef {
|
|
||||||
label: label.into(),
|
|
||||||
definition: definition.map(Into::into),
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn balanced_brackets(input: &str) -> IResult<&str, &str, ()> {
|
|
||||||
let mut pairs = 1;
|
|
||||||
for i in memchr2_iter(b'[', b']', input.as_bytes()) {
|
|
||||||
if input.as_bytes()[i] == b'[' {
|
|
||||||
pairs += 1;
|
|
||||||
} else if pairs != 1 {
|
|
||||||
pairs -= 1;
|
|
||||||
} else {
|
|
||||||
return Ok((&input[i..], &input[0..i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(Err::Error(make_error(input, ErrorKind::Tag)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(
|
|
||||||
FnRef::parse("[fn:1]"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
FnRef {
|
|
||||||
label: "1".into(),
|
|
||||||
definition: None
|
|
||||||
},
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
FnRef::parse("[fn:1:2]"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
FnRef {
|
|
||||||
label: "1".into(),
|
|
||||||
definition: Some("2".into())
|
|
||||||
},
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
FnRef::parse("[fn::2]"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
FnRef {
|
|
||||||
label: "".into(),
|
|
||||||
definition: Some("2".into())
|
|
||||||
},
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
FnRef::parse("[fn::[]]"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
FnRef {
|
|
||||||
label: "".into(),
|
|
||||||
definition: Some("[]".into())
|
|
||||||
},
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(FnRef::parse("[fn::[]").is_none());
|
|
||||||
}
|
|
|
@ -1,122 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom::{
|
|
||||||
bytes::complete::{tag, take_till},
|
|
||||||
combinator::opt,
|
|
||||||
sequence::{delimited, preceded},
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Inline Babel Call Object
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
pub struct InlineCall<'a> {
|
|
||||||
/// Called code block name
|
|
||||||
pub name: Cow<'a, str>,
|
|
||||||
/// Header arguments applied to the code block
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub inside_header: Option<Cow<'a, str>>,
|
|
||||||
/// Argument passed to the code block
|
|
||||||
pub arguments: Cow<'a, str>,
|
|
||||||
/// Header arguments applied to the calling instance
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub end_header: Option<Cow<'a, str>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InlineCall<'_> {
|
|
||||||
pub(crate) fn parse(input: &str) -> Option<(&str, InlineCall)> {
|
|
||||||
parse_internal(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> InlineCall<'static> {
|
|
||||||
InlineCall {
|
|
||||||
name: self.name.into_owned().into(),
|
|
||||||
arguments: self.arguments.into_owned().into(),
|
|
||||||
inside_header: self.inside_header.map(Into::into).map(Cow::Owned),
|
|
||||||
end_header: self.end_header.map(Into::into).map(Cow::Owned),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn parse_internal(input: &str) -> IResult<&str, InlineCall, ()> {
|
|
||||||
let (input, name) = preceded(
|
|
||||||
tag("call_"),
|
|
||||||
take_till(|c| c == '[' || c == '\n' || c == '(' || c == ')'),
|
|
||||||
)(input)?;
|
|
||||||
let (input, inside_header) = opt(delimited(
|
|
||||||
tag("["),
|
|
||||||
take_till(|c| c == ']' || c == '\n'),
|
|
||||||
tag("]"),
|
|
||||||
))(input)?;
|
|
||||||
let (input, arguments) =
|
|
||||||
delimited(tag("("), take_till(|c| c == ')' || c == '\n'), tag(")"))(input)?;
|
|
||||||
let (input, end_header) = opt(delimited(
|
|
||||||
tag("["),
|
|
||||||
take_till(|c| c == ']' || c == '\n'),
|
|
||||||
tag("]"),
|
|
||||||
))(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
InlineCall {
|
|
||||||
name: name.into(),
|
|
||||||
arguments: arguments.into(),
|
|
||||||
inside_header: inside_header.map(Into::into),
|
|
||||||
end_header: end_header.map(Into::into),
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(
|
|
||||||
InlineCall::parse("call_square(4)"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
InlineCall {
|
|
||||||
name: "square".into(),
|
|
||||||
arguments: "4".into(),
|
|
||||||
inside_header: None,
|
|
||||||
end_header: None,
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
InlineCall::parse("call_square[:results output](4)"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
InlineCall {
|
|
||||||
name: "square".into(),
|
|
||||||
arguments: "4".into(),
|
|
||||||
inside_header: Some(":results output".into()),
|
|
||||||
end_header: None,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
InlineCall::parse("call_square(4)[:results html]"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
InlineCall {
|
|
||||||
name: "square".into(),
|
|
||||||
arguments: "4".into(),
|
|
||||||
inside_header: None,
|
|
||||||
end_header: Some(":results html".into()),
|
|
||||||
},
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
InlineCall::parse("call_square[:results output](4)[:results html]"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
InlineCall {
|
|
||||||
name: "square".into(),
|
|
||||||
arguments: "4".into(),
|
|
||||||
inside_header: Some(":results output".into()),
|
|
||||||
end_header: Some(":results html".into()),
|
|
||||||
},
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom::{
|
|
||||||
bytes::complete::{tag, take_till, take_while1},
|
|
||||||
combinator::opt,
|
|
||||||
sequence::delimited,
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Inline Src Block Object
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct InlineSrc<'a> {
|
|
||||||
/// Language of the code
|
|
||||||
pub lang: Cow<'a, str>,
|
|
||||||
/// Optional header arguments
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub options: Option<Cow<'a, str>>,
|
|
||||||
/// Source code
|
|
||||||
pub body: Cow<'a, str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InlineSrc<'_> {
|
|
||||||
pub(crate) fn parse(input: &str) -> Option<(&str, InlineSrc)> {
|
|
||||||
parse_internal(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> InlineSrc<'static> {
|
|
||||||
InlineSrc {
|
|
||||||
lang: self.lang.into_owned().into(),
|
|
||||||
options: self.options.map(Into::into).map(Cow::Owned),
|
|
||||||
body: self.body.into_owned().into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn parse_internal(input: &str) -> IResult<&str, InlineSrc, ()> {
|
|
||||||
let (input, _) = tag("src_")(input)?;
|
|
||||||
let (input, lang) =
|
|
||||||
take_while1(|c: char| !c.is_ascii_whitespace() && c != '[' && c != '{')(input)?;
|
|
||||||
let (input, options) = opt(delimited(
|
|
||||||
tag("["),
|
|
||||||
take_till(|c| c == '\n' || c == ']'),
|
|
||||||
tag("]"),
|
|
||||||
))(input)?;
|
|
||||||
let (input, body) = delimited(tag("{"), take_till(|c| c == '\n' || c == '}'), tag("}"))(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
InlineSrc {
|
|
||||||
lang: lang.into(),
|
|
||||||
options: options.map(Into::into),
|
|
||||||
body: body.into(),
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(
|
|
||||||
InlineSrc::parse("src_C{int a = 0;}"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
InlineSrc {
|
|
||||||
lang: "C".into(),
|
|
||||||
options: None,
|
|
||||||
body: "int a = 0;".into()
|
|
||||||
},
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
InlineSrc::parse("src_xml[:exports code]{<tag>text</tag>}"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
InlineSrc {
|
|
||||||
lang: "xml".into(),
|
|
||||||
options: Some(":exports code".into()),
|
|
||||||
body: "<tag>text</tag>".into(),
|
|
||||||
},
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(InlineSrc::parse("src_xml[:exports code]{<tag>text</tag>").is_none());
|
|
||||||
assert!(InlineSrc::parse("src_[:exports code]{<tag>text</tag>}").is_none());
|
|
||||||
assert!(InlineSrc::parse("src_xml[:exports code]").is_none());
|
|
||||||
}
|
|
|
@ -1,230 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom::{
|
|
||||||
bytes::complete::{tag, take_till},
|
|
||||||
character::complete::space0,
|
|
||||||
combinator::opt,
|
|
||||||
sequence::delimited,
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::elements::Element;
|
|
||||||
use crate::parse::combinators::{blank_lines_count, line};
|
|
||||||
|
|
||||||
/// Keyword Element
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Keyword<'a> {
|
|
||||||
/// Keyword name
|
|
||||||
pub key: Cow<'a, str>,
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub optional: Option<Cow<'a, str>>,
|
|
||||||
/// Keyword value
|
|
||||||
pub value: Cow<'a, str>,
|
|
||||||
/// Numbers of blank lines between keyword line and next non-blank line or
|
|
||||||
/// buffer's end
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Keyword<'_> {
|
|
||||||
pub fn into_owned(self) -> Keyword<'static> {
|
|
||||||
Keyword {
|
|
||||||
key: self.key.into_owned().into(),
|
|
||||||
optional: self.optional.map(Into::into).map(Cow::Owned),
|
|
||||||
value: self.value.into_owned().into(),
|
|
||||||
post_blank: self.post_blank,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Babel Call Element
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
pub struct BabelCall<'a> {
|
|
||||||
/// Babel call value
|
|
||||||
pub value: Cow<'a, str>,
|
|
||||||
/// Numbers of blank lines between babel call line and next non-blank line
|
|
||||||
/// or buffer's end
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BabelCall<'_> {
|
|
||||||
pub fn into_owned(self) -> BabelCall<'static> {
|
|
||||||
BabelCall {
|
|
||||||
value: self.value.into_owned().into(),
|
|
||||||
post_blank: self.post_blank,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
pub(crate) struct RawKeyword<'a> {
|
|
||||||
pub key: &'a str,
|
|
||||||
pub value: &'a str,
|
|
||||||
pub optional: Option<&'a str>,
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> RawKeyword<'a> {
|
|
||||||
pub fn parse(input: &str) -> Option<(&str, RawKeyword)> {
|
|
||||||
parse_internal(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_element(self) -> Element<'a> {
|
|
||||||
let RawKeyword {
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
optional,
|
|
||||||
post_blank,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
if (&*key).eq_ignore_ascii_case("CALL") {
|
|
||||||
BabelCall {
|
|
||||||
value: value.into(),
|
|
||||||
post_blank,
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
} else {
|
|
||||||
Keyword {
|
|
||||||
key: key.into(),
|
|
||||||
optional: optional.map(Into::into),
|
|
||||||
value: value.into(),
|
|
||||||
post_blank,
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_internal(input: &str) -> IResult<&str, RawKeyword, ()> {
|
|
||||||
let (input, _) = space0(input)?;
|
|
||||||
let (input, _) = tag("#+")(input)?;
|
|
||||||
let (input, key) = take_till(|c: char| c.is_ascii_whitespace() || c == ':' || c == '[')(input)?;
|
|
||||||
let (input, optional) = opt(delimited(
|
|
||||||
tag("["),
|
|
||||||
take_till(|c| c == ']' || c == '\n'),
|
|
||||||
tag("]"),
|
|
||||||
))(input)?;
|
|
||||||
let (input, _) = tag(":")(input)?;
|
|
||||||
let (input, value) = line(input)?;
|
|
||||||
let (input, post_blank) = blank_lines_count(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
RawKeyword {
|
|
||||||
key,
|
|
||||||
optional,
|
|
||||||
value: value.trim(),
|
|
||||||
post_blank,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(
|
|
||||||
RawKeyword::parse("#+KEY:"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
RawKeyword {
|
|
||||||
key: "KEY",
|
|
||||||
optional: None,
|
|
||||||
value: "",
|
|
||||||
post_blank: 0
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
RawKeyword::parse("#+KEY: VALUE"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
RawKeyword {
|
|
||||||
key: "KEY",
|
|
||||||
optional: None,
|
|
||||||
value: "VALUE",
|
|
||||||
post_blank: 0
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
RawKeyword::parse("#+K_E_Y: VALUE"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
RawKeyword {
|
|
||||||
key: "K_E_Y",
|
|
||||||
optional: None,
|
|
||||||
value: "VALUE",
|
|
||||||
post_blank: 0
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
RawKeyword::parse("#+KEY:VALUE\n"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
RawKeyword {
|
|
||||||
key: "KEY",
|
|
||||||
optional: None,
|
|
||||||
value: "VALUE",
|
|
||||||
post_blank: 0
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert!(RawKeyword::parse("#+KE Y: VALUE").is_none());
|
|
||||||
assert!(RawKeyword::parse("#+ KEY: VALUE").is_none());
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
RawKeyword::parse("#+RESULTS:"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
RawKeyword {
|
|
||||||
key: "RESULTS",
|
|
||||||
optional: None,
|
|
||||||
value: "",
|
|
||||||
post_blank: 0
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
RawKeyword::parse("#+ATTR_LATEX: :width 5cm\n"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
RawKeyword {
|
|
||||||
key: "ATTR_LATEX",
|
|
||||||
optional: None,
|
|
||||||
value: ":width 5cm",
|
|
||||||
post_blank: 0
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
RawKeyword::parse("#+CALL: double(n=4)"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
RawKeyword {
|
|
||||||
key: "CALL",
|
|
||||||
optional: None,
|
|
||||||
value: "double(n=4)",
|
|
||||||
post_blank: 0
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
RawKeyword::parse("#+CAPTION[Short caption]: Longer caption."),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
RawKeyword {
|
|
||||||
key: "CAPTION",
|
|
||||||
optional: Some("Short caption"),
|
|
||||||
value: "Longer caption.",
|
|
||||||
post_blank: 0
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom::{
|
|
||||||
bytes::complete::{tag, take_while},
|
|
||||||
combinator::opt,
|
|
||||||
sequence::delimited,
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Link Object
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Link<'a> {
|
|
||||||
/// Link destination
|
|
||||||
pub path: Cow<'a, str>,
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub desc: Option<Cow<'a, str>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Link<'_> {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn parse(input: &str) -> Option<(&str, Link)> {
|
|
||||||
parse_internal(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> Link<'static> {
|
|
||||||
Link {
|
|
||||||
path: self.path.into_owned().into(),
|
|
||||||
desc: self.desc.map(Into::into).map(Cow::Owned),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn parse_internal(input: &str) -> IResult<&str, Link, ()> {
|
|
||||||
let (input, path) = delimited(
|
|
||||||
tag("[["),
|
|
||||||
take_while(|c: char| c != '<' && c != '>' && c != '\n' && c != ']'),
|
|
||||||
tag("]"),
|
|
||||||
)(input)?;
|
|
||||||
let (input, desc) = opt(delimited(
|
|
||||||
tag("["),
|
|
||||||
take_while(|c: char| c != '[' && c != ']'),
|
|
||||||
tag("]"),
|
|
||||||
))(input)?;
|
|
||||||
let (input, _) = tag("]")(input)?;
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
Link {
|
|
||||||
path: path.into(),
|
|
||||||
desc: desc.map(Into::into),
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(
|
|
||||||
Link::parse("[[#id]]"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Link {
|
|
||||||
path: "#id".into(),
|
|
||||||
desc: None
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Link::parse("[[#id][desc]]"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Link {
|
|
||||||
path: "#id".into(),
|
|
||||||
desc: Some("desc".into())
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert!(Link::parse("[[#id][desc]").is_none());
|
|
||||||
}
|
|
|
@ -1,316 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::iter::once;
|
|
||||||
|
|
||||||
use memchr::{memchr, memchr_iter};
|
|
||||||
use nom::{
|
|
||||||
branch::alt,
|
|
||||||
bytes::complete::tag,
|
|
||||||
character::complete::{digit1, space0},
|
|
||||||
combinator::{map, recognize},
|
|
||||||
sequence::terminated,
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Plain List Element
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct List {
|
|
||||||
/// List indent, number of whitespaces
|
|
||||||
pub indent: usize,
|
|
||||||
/// List's type, determined by the first item of this list
|
|
||||||
pub ordered: bool,
|
|
||||||
/// Numbers of blank lines between last list's line and next non-blank line
|
|
||||||
/// or buffer's end
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List Item Element
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ListItem<'a> {
|
|
||||||
/// List item bullet
|
|
||||||
pub bullet: Cow<'a, str>,
|
|
||||||
/// List item indent, number of whitespaces
|
|
||||||
pub indent: usize,
|
|
||||||
/// List item type
|
|
||||||
pub ordered: bool,
|
|
||||||
// TODO checkbox
|
|
||||||
// TODO counter
|
|
||||||
// TODO tag
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ListItem<'_> {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn parse(input: &str) -> Option<(&str, (ListItem, &str))> {
|
|
||||||
list_item(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> ListItem<'static> {
|
|
||||||
ListItem {
|
|
||||||
bullet: self.bullet.into_owned().into(),
|
|
||||||
indent: self.indent,
|
|
||||||
ordered: self.ordered,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_item(input: &str) -> IResult<&str, (ListItem, &str), ()> {
|
|
||||||
let (input, indent) = map(space0, |s: &str| s.len())(input)?;
|
|
||||||
let (input, bullet) = recognize(alt((
|
|
||||||
tag("+ "),
|
|
||||||
tag("* "),
|
|
||||||
tag("- "),
|
|
||||||
terminated(digit1, tag(". ")),
|
|
||||||
)))(input)?;
|
|
||||||
let (input, contents) = list_item_contents(input, indent);
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
(
|
|
||||||
ListItem {
|
|
||||||
bullet: bullet.into(),
|
|
||||||
indent,
|
|
||||||
ordered: bullet.starts_with(|c: char| c.is_ascii_digit()),
|
|
||||||
},
|
|
||||||
contents,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_item_contents(input: &str, indent: usize) -> (&str, &str) {
|
|
||||||
let mut last_end = memchr(b'\n', input.as_bytes())
|
|
||||||
.map(|i| i + 1)
|
|
||||||
.unwrap_or_else(|| input.len());
|
|
||||||
|
|
||||||
for i in memchr_iter(b'\n', input.as_bytes())
|
|
||||||
.map(|i| i + 1)
|
|
||||||
.chain(once(input.len()))
|
|
||||||
.skip(1)
|
|
||||||
{
|
|
||||||
if input[last_end..i]
|
|
||||||
.as_bytes()
|
|
||||||
.iter()
|
|
||||||
.all(u8::is_ascii_whitespace)
|
|
||||||
{
|
|
||||||
let x = memchr(b'\n', &input[i..].as_bytes())
|
|
||||||
.map(|ii| i + ii + 1)
|
|
||||||
.unwrap_or_else(|| input.len());
|
|
||||||
|
|
||||||
// two consecutive empty lines
|
|
||||||
if input[i..x].as_bytes().iter().all(u8::is_ascii_whitespace) {
|
|
||||||
return (&input[x..], &input[0..x]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// line less or equally indented than the starting line
|
|
||||||
if input[last_end..i]
|
|
||||||
.as_bytes()
|
|
||||||
.iter()
|
|
||||||
.take(indent + 1)
|
|
||||||
.any(|c| !c.is_ascii_whitespace())
|
|
||||||
{
|
|
||||||
return (&input[last_end..], &input[0..last_end]);
|
|
||||||
}
|
|
||||||
|
|
||||||
last_end = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
("", input)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(
|
|
||||||
list_item(
|
|
||||||
r#"+ item1
|
|
||||||
+ item2"#
|
|
||||||
),
|
|
||||||
Ok((
|
|
||||||
"+ item2",
|
|
||||||
(
|
|
||||||
ListItem {
|
|
||||||
bullet: "+ ".into(),
|
|
||||||
indent: 0,
|
|
||||||
ordered: false,
|
|
||||||
},
|
|
||||||
r#"item1
|
|
||||||
"#
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
list_item(
|
|
||||||
r#"* item1
|
|
||||||
|
|
||||||
* item2"#
|
|
||||||
),
|
|
||||||
Ok((
|
|
||||||
"* item2",
|
|
||||||
(
|
|
||||||
ListItem {
|
|
||||||
bullet: "* ".into(),
|
|
||||||
indent: 0,
|
|
||||||
ordered: false,
|
|
||||||
},
|
|
||||||
r#"item1
|
|
||||||
|
|
||||||
"#
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
list_item(
|
|
||||||
r#"* item1
|
|
||||||
|
|
||||||
|
|
||||||
* item2"#
|
|
||||||
),
|
|
||||||
Ok((
|
|
||||||
"* item2",
|
|
||||||
(
|
|
||||||
ListItem {
|
|
||||||
bullet: "* ".into(),
|
|
||||||
indent: 0,
|
|
||||||
ordered: false,
|
|
||||||
},
|
|
||||||
r#"item1
|
|
||||||
|
|
||||||
|
|
||||||
"#
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
list_item(
|
|
||||||
r#"* item1
|
|
||||||
|
|
||||||
"#
|
|
||||||
),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
ListItem {
|
|
||||||
bullet: "* ".into(),
|
|
||||||
indent: 0,
|
|
||||||
ordered: false,
|
|
||||||
},
|
|
||||||
r#"item1
|
|
||||||
|
|
||||||
"#
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
list_item(
|
|
||||||
r#"+ item1
|
|
||||||
+ item2
|
|
||||||
"#
|
|
||||||
),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
ListItem {
|
|
||||||
bullet: "+ ".into(),
|
|
||||||
indent: 0,
|
|
||||||
ordered: false,
|
|
||||||
},
|
|
||||||
r#"item1
|
|
||||||
+ item2
|
|
||||||
"#
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
list_item(
|
|
||||||
r#"+ item1
|
|
||||||
|
|
||||||
+ item2
|
|
||||||
|
|
||||||
+ item 3"#
|
|
||||||
),
|
|
||||||
Ok((
|
|
||||||
"+ item 3",
|
|
||||||
(
|
|
||||||
ListItem {
|
|
||||||
bullet: "+ ".into(),
|
|
||||||
indent: 0,
|
|
||||||
ordered: false,
|
|
||||||
},
|
|
||||||
r#"item1
|
|
||||||
|
|
||||||
+ item2
|
|
||||||
|
|
||||||
"#
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
list_item(
|
|
||||||
r#" + item1
|
|
||||||
|
|
||||||
+ item2"#
|
|
||||||
),
|
|
||||||
Ok((
|
|
||||||
" + item2",
|
|
||||||
(
|
|
||||||
ListItem {
|
|
||||||
bullet: "+ ".into(),
|
|
||||||
indent: 2,
|
|
||||||
ordered: false,
|
|
||||||
},
|
|
||||||
r#"item1
|
|
||||||
|
|
||||||
"#
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
list_item(
|
|
||||||
r#" 1. item1
|
|
||||||
2. item2
|
|
||||||
3. item3"#
|
|
||||||
),
|
|
||||||
Ok((
|
|
||||||
r#"2. item2
|
|
||||||
3. item3"#,
|
|
||||||
(
|
|
||||||
ListItem {
|
|
||||||
bullet: "1. ".into(),
|
|
||||||
indent: 2,
|
|
||||||
ordered: true,
|
|
||||||
},
|
|
||||||
r#"item1
|
|
||||||
"#
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
list_item(
|
|
||||||
r#"+ 1
|
|
||||||
|
|
||||||
- 2
|
|
||||||
|
|
||||||
- 3
|
|
||||||
|
|
||||||
+ 4"#
|
|
||||||
),
|
|
||||||
Ok((
|
|
||||||
"+ 4",
|
|
||||||
(
|
|
||||||
ListItem {
|
|
||||||
bullet: "+ ".into(),
|
|
||||||
indent: 0,
|
|
||||||
ordered: false,
|
|
||||||
},
|
|
||||||
r#"1
|
|
||||||
|
|
||||||
- 2
|
|
||||||
|
|
||||||
- 3
|
|
||||||
|
|
||||||
"#
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom::{
|
|
||||||
bytes::complete::{tag, take, take_until, take_while1},
|
|
||||||
combinator::{opt, verify},
|
|
||||||
sequence::delimited,
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Macro Object
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Macros<'a> {
|
|
||||||
/// Macro name
|
|
||||||
pub name: Cow<'a, str>,
|
|
||||||
/// Arguments passed to the macro
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub arguments: Option<Cow<'a, str>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Macros<'_> {
|
|
||||||
pub(crate) fn parse(input: &str) -> Option<(&str, Macros)> {
|
|
||||||
parse_internal(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> Macros<'static> {
|
|
||||||
Macros {
|
|
||||||
name: self.name.into_owned().into(),
|
|
||||||
arguments: self.arguments.map(Into::into).map(Cow::Owned),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn parse_internal(input: &str) -> IResult<&str, Macros, ()> {
|
|
||||||
let (input, _) = tag("{{{")(input)?;
|
|
||||||
let (input, name) = verify(
|
|
||||||
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
|
|
||||||
|s: &str| s.starts_with(|c: char| c.is_ascii_alphabetic()),
|
|
||||||
)(input)?;
|
|
||||||
let (input, arguments) = opt(delimited(tag("("), take_until(")}}}"), take(1usize)))(input)?;
|
|
||||||
let (input, _) = tag("}}}")(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
Macros {
|
|
||||||
name: name.into(),
|
|
||||||
arguments: arguments.map(Into::into),
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test() {
|
|
||||||
assert_eq!(
|
|
||||||
Macros::parse("{{{poem(red,blue)}}}"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Macros {
|
|
||||||
name: "poem".into(),
|
|
||||||
arguments: Some("red,blue".into())
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Macros::parse("{{{poem())}}}"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Macros {
|
|
||||||
name: "poem".into(),
|
|
||||||
arguments: Some(")".into())
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Macros::parse("{{{author}}}"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Macros {
|
|
||||||
name: "author".into(),
|
|
||||||
arguments: None
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(Macros::parse("{{{0uthor}}}").is_none());
|
|
||||||
assert!(Macros::parse("{{{author}}").is_none());
|
|
||||||
assert!(Macros::parse("{{{poem(}}}").is_none());
|
|
||||||
assert!(Macros::parse("{{{poem)}}}").is_none());
|
|
||||||
}
|
|
|
@ -1,245 +0,0 @@
|
||||||
//! Org-mode elements
|
|
||||||
|
|
||||||
pub(crate) mod block;
|
|
||||||
pub(crate) mod clock;
|
|
||||||
pub(crate) mod comment;
|
|
||||||
pub(crate) mod cookie;
|
|
||||||
pub(crate) mod drawer;
|
|
||||||
pub(crate) mod dyn_block;
|
|
||||||
pub(crate) mod emphasis;
|
|
||||||
pub(crate) mod fixed_width;
|
|
||||||
pub(crate) mod fn_def;
|
|
||||||
pub(crate) mod fn_ref;
|
|
||||||
pub(crate) mod inline_call;
|
|
||||||
pub(crate) mod inline_src;
|
|
||||||
pub(crate) mod keyword;
|
|
||||||
pub(crate) mod link;
|
|
||||||
pub(crate) mod list;
|
|
||||||
pub(crate) mod macros;
|
|
||||||
pub(crate) mod planning;
|
|
||||||
pub(crate) mod radio_target;
|
|
||||||
pub(crate) mod rule;
|
|
||||||
pub(crate) mod snippet;
|
|
||||||
pub(crate) mod table;
|
|
||||||
pub(crate) mod target;
|
|
||||||
pub(crate) mod timestamp;
|
|
||||||
pub(crate) mod title;
|
|
||||||
|
|
||||||
pub use self::{
|
|
||||||
block::{
|
|
||||||
CenterBlock, CommentBlock, ExampleBlock, ExportBlock, QuoteBlock, SourceBlock,
|
|
||||||
SpecialBlock, VerseBlock,
|
|
||||||
},
|
|
||||||
clock::Clock,
|
|
||||||
comment::Comment,
|
|
||||||
cookie::Cookie,
|
|
||||||
drawer::Drawer,
|
|
||||||
dyn_block::DynBlock,
|
|
||||||
fixed_width::FixedWidth,
|
|
||||||
fn_def::FnDef,
|
|
||||||
fn_ref::FnRef,
|
|
||||||
inline_call::InlineCall,
|
|
||||||
inline_src::InlineSrc,
|
|
||||||
keyword::{BabelCall, Keyword},
|
|
||||||
link::Link,
|
|
||||||
list::{List, ListItem},
|
|
||||||
macros::Macros,
|
|
||||||
planning::Planning,
|
|
||||||
rule::Rule,
|
|
||||||
snippet::Snippet,
|
|
||||||
table::{Table, TableCell, TableRow},
|
|
||||||
target::Target,
|
|
||||||
timestamp::{Datetime, Timestamp},
|
|
||||||
title::{PropertiesMap, Title},
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
/// Element Enum
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[cfg_attr(feature = "ser", serde(tag = "type", rename_all = "kebab-case"))]
|
|
||||||
pub enum Element<'a> {
|
|
||||||
SpecialBlock(SpecialBlock<'a>),
|
|
||||||
QuoteBlock(QuoteBlock<'a>),
|
|
||||||
CenterBlock(CenterBlock<'a>),
|
|
||||||
VerseBlock(VerseBlock<'a>),
|
|
||||||
CommentBlock(CommentBlock<'a>),
|
|
||||||
ExampleBlock(ExampleBlock<'a>),
|
|
||||||
ExportBlock(ExportBlock<'a>),
|
|
||||||
SourceBlock(SourceBlock<'a>),
|
|
||||||
BabelCall(BabelCall<'a>),
|
|
||||||
Section,
|
|
||||||
Clock(Clock<'a>),
|
|
||||||
Cookie(Cookie<'a>),
|
|
||||||
RadioTarget,
|
|
||||||
Drawer(Drawer<'a>),
|
|
||||||
Document { pre_blank: usize },
|
|
||||||
DynBlock(DynBlock<'a>),
|
|
||||||
FnDef(FnDef<'a>),
|
|
||||||
FnRef(FnRef<'a>),
|
|
||||||
Headline { level: usize },
|
|
||||||
InlineCall(InlineCall<'a>),
|
|
||||||
InlineSrc(InlineSrc<'a>),
|
|
||||||
Keyword(Keyword<'a>),
|
|
||||||
Link(Link<'a>),
|
|
||||||
List(List),
|
|
||||||
ListItem(ListItem<'a>),
|
|
||||||
Macros(Macros<'a>),
|
|
||||||
Snippet(Snippet<'a>),
|
|
||||||
Text { value: Cow<'a, str> },
|
|
||||||
Paragraph { post_blank: usize },
|
|
||||||
Rule(Rule),
|
|
||||||
Timestamp(Timestamp<'a>),
|
|
||||||
Target(Target<'a>),
|
|
||||||
Bold,
|
|
||||||
Strike,
|
|
||||||
Italic,
|
|
||||||
Underline,
|
|
||||||
Verbatim { value: Cow<'a, str> },
|
|
||||||
Code { value: Cow<'a, str> },
|
|
||||||
Comment(Comment<'a>),
|
|
||||||
FixedWidth(FixedWidth<'a>),
|
|
||||||
Title(Title<'a>),
|
|
||||||
Table(Table<'a>),
|
|
||||||
TableRow(TableRow),
|
|
||||||
TableCell(TableCell),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Element<'_> {
|
|
||||||
pub fn is_container(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Element::SpecialBlock(_)
|
|
||||||
| Element::QuoteBlock(_)
|
|
||||||
| Element::CenterBlock(_)
|
|
||||||
| Element::VerseBlock(_)
|
|
||||||
| Element::Bold
|
|
||||||
| Element::Document { .. }
|
|
||||||
| Element::DynBlock(_)
|
|
||||||
| Element::Headline { .. }
|
|
||||||
| Element::Italic
|
|
||||||
| Element::List(_)
|
|
||||||
| Element::ListItem(_)
|
|
||||||
| Element::Paragraph { .. }
|
|
||||||
| Element::Section
|
|
||||||
| Element::Strike
|
|
||||||
| Element::Underline
|
|
||||||
| Element::Title(_)
|
|
||||||
| Element::Table(_)
|
|
||||||
| Element::TableRow(TableRow::Header)
|
|
||||||
| Element::TableRow(TableRow::Body)
|
|
||||||
| Element::TableCell(_) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> Element<'static> {
|
|
||||||
use Element::*;
|
|
||||||
|
|
||||||
match self {
|
|
||||||
SpecialBlock(e) => SpecialBlock(e.into_owned()),
|
|
||||||
QuoteBlock(e) => QuoteBlock(e.into_owned()),
|
|
||||||
CenterBlock(e) => CenterBlock(e.into_owned()),
|
|
||||||
VerseBlock(e) => VerseBlock(e.into_owned()),
|
|
||||||
CommentBlock(e) => CommentBlock(e.into_owned()),
|
|
||||||
ExampleBlock(e) => ExampleBlock(e.into_owned()),
|
|
||||||
ExportBlock(e) => ExportBlock(e.into_owned()),
|
|
||||||
SourceBlock(e) => SourceBlock(e.into_owned()),
|
|
||||||
BabelCall(e) => BabelCall(e.into_owned()),
|
|
||||||
Section => Section,
|
|
||||||
Clock(e) => Clock(e.into_onwed()),
|
|
||||||
Cookie(e) => Cookie(e.into_owned()),
|
|
||||||
RadioTarget => RadioTarget,
|
|
||||||
Drawer(e) => Drawer(e.into_owned()),
|
|
||||||
Document { pre_blank } => Document { pre_blank },
|
|
||||||
DynBlock(e) => DynBlock(e.into_owned()),
|
|
||||||
FnDef(e) => FnDef(e.into_owned()),
|
|
||||||
FnRef(e) => FnRef(e.into_owned()),
|
|
||||||
Headline { level } => Headline { level },
|
|
||||||
InlineCall(e) => InlineCall(e.into_owned()),
|
|
||||||
InlineSrc(e) => InlineSrc(e.into_owned()),
|
|
||||||
Keyword(e) => Keyword(e.into_owned()),
|
|
||||||
Link(e) => Link(e.into_owned()),
|
|
||||||
List(e) => List(e),
|
|
||||||
ListItem(e) => ListItem(e.into_owned()),
|
|
||||||
Macros(e) => Macros(e.into_owned()),
|
|
||||||
Snippet(e) => Snippet(e.into_owned()),
|
|
||||||
Text { value } => Text {
|
|
||||||
value: value.into_owned().into(),
|
|
||||||
},
|
|
||||||
Paragraph { post_blank } => Paragraph { post_blank },
|
|
||||||
Rule(e) => Rule(e),
|
|
||||||
Timestamp(e) => Timestamp(e.into_owned()),
|
|
||||||
Target(e) => Target(e.into_owned()),
|
|
||||||
Bold => Bold,
|
|
||||||
Strike => Strike,
|
|
||||||
Italic => Italic,
|
|
||||||
Underline => Underline,
|
|
||||||
Verbatim { value } => Verbatim {
|
|
||||||
value: value.into_owned().into(),
|
|
||||||
},
|
|
||||||
Code { value } => Code {
|
|
||||||
value: value.into_owned().into(),
|
|
||||||
},
|
|
||||||
Comment(e) => Comment(e.into_owned()),
|
|
||||||
FixedWidth(e) => FixedWidth(e.into_owned()),
|
|
||||||
Title(e) => Title(e.into_owned()),
|
|
||||||
Table(e) => Table(e.into_owned()),
|
|
||||||
TableRow(e) => TableRow(e),
|
|
||||||
TableCell(e) => TableCell(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_from {
|
|
||||||
($($ele0:ident),*; $($ele1:ident),*) => {
|
|
||||||
$(
|
|
||||||
impl<'a> From<$ele0<'a>> for Element<'a> {
|
|
||||||
fn from(ele: $ele0<'a>) -> Element<'a> {
|
|
||||||
Element::$ele0(ele)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
$(
|
|
||||||
impl<'a> From<$ele1> for Element<'a> {
|
|
||||||
fn from(ele: $ele1) -> Element<'a> {
|
|
||||||
Element::$ele1(ele)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_from!(
|
|
||||||
BabelCall,
|
|
||||||
CenterBlock,
|
|
||||||
Clock,
|
|
||||||
Comment,
|
|
||||||
CommentBlock,
|
|
||||||
Cookie,
|
|
||||||
Drawer,
|
|
||||||
DynBlock,
|
|
||||||
ExampleBlock,
|
|
||||||
ExportBlock,
|
|
||||||
FixedWidth,
|
|
||||||
FnDef,
|
|
||||||
FnRef,
|
|
||||||
InlineCall,
|
|
||||||
InlineSrc,
|
|
||||||
Keyword,
|
|
||||||
Link,
|
|
||||||
ListItem,
|
|
||||||
Macros,
|
|
||||||
QuoteBlock,
|
|
||||||
Snippet,
|
|
||||||
SourceBlock,
|
|
||||||
SpecialBlock,
|
|
||||||
Table,
|
|
||||||
Target,
|
|
||||||
Timestamp,
|
|
||||||
Title,
|
|
||||||
VerseBlock;
|
|
||||||
List,
|
|
||||||
Rule,
|
|
||||||
TableRow
|
|
||||||
);
|
|
|
@ -1,98 +0,0 @@
|
||||||
use memchr::memchr;
|
|
||||||
|
|
||||||
use crate::elements::Timestamp;
|
|
||||||
|
|
||||||
/// Planning element
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Planning<'a> {
|
|
||||||
/// Timestamp associated to deadline keyword
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub deadline: Option<Timestamp<'a>>,
|
|
||||||
/// Timestamp associated to scheduled keyword
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub scheduled: Option<Timestamp<'a>>,
|
|
||||||
/// Timestamp associated to closed keyword
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub closed: Option<Timestamp<'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Planning<'_> {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn parse(text: &str) -> Option<(&str, Planning)> {
|
|
||||||
let (mut deadline, mut scheduled, mut closed) = (None, None, None);
|
|
||||||
let (mut tail, off) = memchr(b'\n', text.as_bytes())
|
|
||||||
.map(|i| (text[..i].trim(), i + 1))
|
|
||||||
.unwrap_or_else(|| (text.trim(), text.len()));
|
|
||||||
|
|
||||||
while let Some(i) = memchr(b' ', tail.as_bytes()) {
|
|
||||||
let next = &tail[i + 1..].trim_start();
|
|
||||||
|
|
||||||
macro_rules! set_timestamp {
|
|
||||||
($timestamp:expr) => {{
|
|
||||||
let (new_tail, timestamp) =
|
|
||||||
Timestamp::parse_active(next).or(Timestamp::parse_inactive(next))?;
|
|
||||||
$timestamp = Some(timestamp);
|
|
||||||
tail = new_tail.trim_start();
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
match &tail[..i] {
|
|
||||||
"DEADLINE:" if deadline.is_none() => set_timestamp!(deadline),
|
|
||||||
"SCHEDULED:" if scheduled.is_none() => set_timestamp!(scheduled),
|
|
||||||
"CLOSED:" if closed.is_none() => set_timestamp!(closed),
|
|
||||||
_ => return None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if deadline.is_none() && scheduled.is_none() && closed.is_none() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some((
|
|
||||||
&text[off..],
|
|
||||||
Planning {
|
|
||||||
deadline,
|
|
||||||
scheduled,
|
|
||||||
closed,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> Planning<'static> {
|
|
||||||
Planning {
|
|
||||||
deadline: self.deadline.map(|x| x.into_owned()),
|
|
||||||
scheduled: self.scheduled.map(|x| x.into_owned()),
|
|
||||||
closed: self.closed.map(|x| x.into_owned()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn prase() {
|
|
||||||
use crate::elements::Datetime;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Planning::parse("SCHEDULED: <2019-04-08 Mon>\n"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Planning {
|
|
||||||
scheduled: Some(Timestamp::Active {
|
|
||||||
start: Datetime {
|
|
||||||
year: 2019,
|
|
||||||
month: 4,
|
|
||||||
day: 8,
|
|
||||||
dayname: "Mon".into(),
|
|
||||||
hour: None,
|
|
||||||
minute: None
|
|
||||||
},
|
|
||||||
repeater: None,
|
|
||||||
delay: None
|
|
||||||
}),
|
|
||||||
deadline: None,
|
|
||||||
closed: None,
|
|
||||||
}
|
|
||||||
))
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
use nom::{
|
|
||||||
bytes::complete::{tag, take_while},
|
|
||||||
combinator::verify,
|
|
||||||
sequence::delimited,
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: text-markup, entities, latex-fragments, subscript and superscript
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn parse_radio_target(input: &str) -> Option<(&str, &str)> {
|
|
||||||
parse_internal(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn parse_internal(input: &str) -> IResult<&str, &str, ()> {
|
|
||||||
let (input, contents) = delimited(
|
|
||||||
tag("<<<"),
|
|
||||||
verify(
|
|
||||||
take_while(|c: char| c != '<' && c != '\n' && c != '>'),
|
|
||||||
|s: &str| s.starts_with(|c| c != ' ') && s.ends_with(|c| c != ' '),
|
|
||||||
),
|
|
||||||
tag(">>>"),
|
|
||||||
)(input)?;
|
|
||||||
|
|
||||||
Ok((input, contents))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(parse_radio_target("<<<target>>>"), Some(("", "target")));
|
|
||||||
assert_eq!(parse_radio_target("<<<tar get>>>"), Some(("", "tar get")));
|
|
||||||
|
|
||||||
assert!(parse_radio_target("<<<target >>>").is_none());
|
|
||||||
assert!(parse_radio_target("<<< target>>>").is_none());
|
|
||||||
assert!(parse_radio_target("<<<ta<get>>>").is_none());
|
|
||||||
assert!(parse_radio_target("<<<ta>get>>>").is_none());
|
|
||||||
assert!(parse_radio_target("<<<ta\nget>>>").is_none());
|
|
||||||
assert!(parse_radio_target("<<<target>>").is_none());
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
use nom::{bytes::complete::take_while_m_n, character::complete::space0, IResult};
|
|
||||||
|
|
||||||
use crate::parse::combinators::{blank_lines_count, eol};
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
pub struct Rule {
|
|
||||||
/// Numbers of blank lines between rule line and next non-blank line or
|
|
||||||
/// buffer's end
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Rule {
|
|
||||||
pub(crate) fn parse(input: &str) -> Option<(&str, Rule)> {
|
|
||||||
parse_internal(input).ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_internal(input: &str) -> IResult<&str, Rule, ()> {
|
|
||||||
let (input, _) = space0(input)?;
|
|
||||||
let (input, _) = take_while_m_n(5, usize::max_value(), |c| c == '-')(input)?;
|
|
||||||
let (input, _) = eol(input)?;
|
|
||||||
let (input, post_blank) = blank_lines_count(input)?;
|
|
||||||
Ok((input, Rule { post_blank }))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(Rule::parse("-----"), Some(("", Rule { post_blank: 0 })));
|
|
||||||
assert_eq!(Rule::parse("--------"), Some(("", Rule { post_blank: 0 })));
|
|
||||||
assert_eq!(
|
|
||||||
Rule::parse("-----\n\n\n"),
|
|
||||||
Some(("", Rule { post_blank: 2 }))
|
|
||||||
);
|
|
||||||
assert_eq!(Rule::parse("----- \n"), Some(("", Rule { post_blank: 0 })));
|
|
||||||
|
|
||||||
assert!(Rule::parse("").is_none());
|
|
||||||
assert!(Rule::parse("----").is_none());
|
|
||||||
assert!(Rule::parse("----").is_none());
|
|
||||||
assert!(Rule::parse("None----").is_none());
|
|
||||||
assert!(Rule::parse("None ----").is_none());
|
|
||||||
assert!(Rule::parse("None------").is_none());
|
|
||||||
assert!(Rule::parse("----None----").is_none());
|
|
||||||
assert!(Rule::parse("\t\t----").is_none());
|
|
||||||
assert!(Rule::parse("------None").is_none());
|
|
||||||
assert!(Rule::parse("----- None").is_none());
|
|
||||||
}
|
|
|
@ -1,100 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom::{
|
|
||||||
bytes::complete::{tag, take, take_until, take_while1},
|
|
||||||
sequence::{delimited, separated_pair},
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Export Snippet Object
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Snippet<'a> {
|
|
||||||
/// Back-end name
|
|
||||||
pub name: Cow<'a, str>,
|
|
||||||
/// Export code
|
|
||||||
pub value: Cow<'a, str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Snippet<'_> {
|
|
||||||
pub(crate) fn parse(input: &str) -> Option<(&str, Snippet)> {
|
|
||||||
parse_internal(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> Snippet<'static> {
|
|
||||||
Snippet {
|
|
||||||
name: self.name.into_owned().into(),
|
|
||||||
value: self.value.into_owned().into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn parse_internal(input: &str) -> IResult<&str, Snippet, ()> {
|
|
||||||
let (input, (name, value)) = delimited(
|
|
||||||
tag("@@"),
|
|
||||||
separated_pair(
|
|
||||||
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-'),
|
|
||||||
tag(":"),
|
|
||||||
take_until("@@"),
|
|
||||||
),
|
|
||||||
take(2usize),
|
|
||||||
)(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
Snippet {
|
|
||||||
name: name.into(),
|
|
||||||
value: value.into(),
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(
|
|
||||||
Snippet::parse("@@html:<b>@@"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Snippet {
|
|
||||||
name: "html".into(),
|
|
||||||
value: "<b>".into()
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Snippet::parse("@@latex:any arbitrary LaTeX code@@"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Snippet {
|
|
||||||
name: "latex".into(),
|
|
||||||
value: "any arbitrary LaTeX code".into(),
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Snippet::parse("@@html:@@"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Snippet {
|
|
||||||
name: "html".into(),
|
|
||||||
value: "".into(),
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Snippet::parse("@@html:<p>@</p>@@"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Snippet {
|
|
||||||
name: "html".into(),
|
|
||||||
value: "<p>@</p>".into(),
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(Snippet::parse("@@html:<b>@").is_none());
|
|
||||||
assert!(Snippet::parse("@@html<b>@@").is_none());
|
|
||||||
assert!(Snippet::parse("@@:<b>@@").is_none());
|
|
||||||
}
|
|
|
@ -1,169 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom::{
|
|
||||||
error::{make_error, ErrorKind},
|
|
||||||
Err, IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::parse::combinators::{blank_lines_count, line, lines_while};
|
|
||||||
|
|
||||||
/// Table Element
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[cfg_attr(feature = "ser", serde(tag = "table_type"))]
|
|
||||||
pub enum Table<'a> {
|
|
||||||
/// "org" type table
|
|
||||||
#[cfg_attr(feature = "ser", serde(rename = "org"))]
|
|
||||||
Org {
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
tblfm: Option<Cow<'a, str>>,
|
|
||||||
/// Numbers of blank lines between last table's line and next non-blank
|
|
||||||
/// line or buffer's end
|
|
||||||
post_blank: usize,
|
|
||||||
has_header: bool,
|
|
||||||
},
|
|
||||||
/// "table.el" type table
|
|
||||||
#[cfg_attr(feature = "ser", serde(rename = "table.el"))]
|
|
||||||
TableEl {
|
|
||||||
value: Cow<'a, str>,
|
|
||||||
/// Numbers of blank lines between last table's line and next non-blank
|
|
||||||
/// line or buffer's end
|
|
||||||
post_blank: usize,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Table<'_> {
|
|
||||||
pub fn parse_table_el(input: &str) -> Option<(&str, Table)> {
|
|
||||||
Self::parse_table_el_internal(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_table_el_internal(input: &str) -> IResult<&str, Table, ()> {
|
|
||||||
let (_, first_line) = line(input)?;
|
|
||||||
|
|
||||||
let first_line = first_line.trim();
|
|
||||||
|
|
||||||
// Table.el tables start at lines beginning with "+-" string and followed by plus or minus signs
|
|
||||||
if !first_line.starts_with("+-")
|
|
||||||
|| first_line
|
|
||||||
.as_bytes()
|
|
||||||
.iter()
|
|
||||||
.any(|&c| c != b'+' && c != b'-')
|
|
||||||
{
|
|
||||||
// TODO: better error kind
|
|
||||||
return Err(Err::Error(make_error(input, ErrorKind::Many0)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Table.el tables end at the first line not starting with either a vertical line or a plus sign.
|
|
||||||
let (input, content) = lines_while(|line| {
|
|
||||||
let line = line.trim_start();
|
|
||||||
line.starts_with('|') || line.starts_with('+')
|
|
||||||
})(input)?;
|
|
||||||
|
|
||||||
let (input, post_blank) = blank_lines_count(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
Table::TableEl {
|
|
||||||
value: content.into(),
|
|
||||||
post_blank,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> Table<'static> {
|
|
||||||
match self {
|
|
||||||
Table::Org {
|
|
||||||
tblfm,
|
|
||||||
post_blank,
|
|
||||||
has_header,
|
|
||||||
} => Table::Org {
|
|
||||||
tblfm: tblfm.map(Into::into).map(Cow::Owned),
|
|
||||||
post_blank,
|
|
||||||
has_header,
|
|
||||||
},
|
|
||||||
Table::TableEl { value, post_blank } => Table::TableEl {
|
|
||||||
value: value.into_owned().into(),
|
|
||||||
post_blank,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Table Row Element
|
|
||||||
///
|
|
||||||
/// # Syntax
|
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// | 0 | 1 | 2 | <- TableRow::Body
|
|
||||||
/// | 0 | 1 | 2 | <- TableRow::Body
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// |-----+-----+-----| <- ignores
|
|
||||||
/// | 0 | 1 | 2 | <- TableRow::Header
|
|
||||||
/// | 0 | 1 | 2 | <- TableRow::Header
|
|
||||||
/// |-----+-----+-----| <- TableRow::HeaderRule
|
|
||||||
/// | 0 | 1 | 2 | <- TableRow::Body
|
|
||||||
/// |-----+-----+-----| <- TableRow::BodyRule
|
|
||||||
/// | 0 | 1 | 2 | <- TableRow::Body
|
|
||||||
/// |-----+-----+-----| <- TableRow::BodyRule
|
|
||||||
/// |-----+-----+-----| <- TableRow::BodyRule
|
|
||||||
/// | 0 | 1 | 2 | <- TableRow::Body
|
|
||||||
/// |-----+-----+-----| <- ignores
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[cfg_attr(feature = "ser", serde(tag = "table_row_type"))]
|
|
||||||
#[cfg_attr(feature = "ser", serde(rename_all = "kebab-case"))]
|
|
||||||
pub enum TableRow {
|
|
||||||
/// This row is part of table header
|
|
||||||
Header,
|
|
||||||
/// This row is part of table body
|
|
||||||
Body,
|
|
||||||
/// This row is between table header and body
|
|
||||||
HeaderRule,
|
|
||||||
/// This row is between table body and next body
|
|
||||||
BodyRule,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Table Cell Element
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[cfg_attr(feature = "ser", serde(tag = "table_cell_type"))]
|
|
||||||
#[cfg_attr(feature = "ser", serde(rename_all = "kebab-case"))]
|
|
||||||
pub enum TableCell {
|
|
||||||
/// Header cell
|
|
||||||
Header,
|
|
||||||
/// Body cell, or standard cell
|
|
||||||
Body,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_table_el_() {
|
|
||||||
assert_eq!(
|
|
||||||
Table::parse_table_el(
|
|
||||||
r#" +---+
|
|
||||||
| |
|
|
||||||
+---+
|
|
||||||
|
|
||||||
"#
|
|
||||||
),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Table::TableEl {
|
|
||||||
value: r#" +---+
|
|
||||||
| |
|
|
||||||
+---+
|
|
||||||
"#
|
|
||||||
.into(),
|
|
||||||
post_blank: 1
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert!(Table::parse_table_el("").is_none());
|
|
||||||
assert!(Table::parse_table_el("+----|---").is_none());
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom::{
|
|
||||||
bytes::complete::{tag, take_while},
|
|
||||||
combinator::verify,
|
|
||||||
sequence::delimited,
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Target Object
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Target<'a> {
|
|
||||||
/// Target ID
|
|
||||||
pub target: Cow<'a, str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Target<'_> {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn parse(input: &str) -> Option<(&str, Target)> {
|
|
||||||
parse_internal(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> Target<'static> {
|
|
||||||
Target {
|
|
||||||
target: self.target.into_owned().into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn parse_internal(input: &str) -> IResult<&str, Target, ()> {
|
|
||||||
let (input, target) = delimited(
|
|
||||||
tag("<<"),
|
|
||||||
verify(
|
|
||||||
take_while(|c: char| c != '<' && c != '\n' && c != '>'),
|
|
||||||
|s: &str| s.starts_with(|c| c != ' ') && s.ends_with(|c| c != ' '),
|
|
||||||
),
|
|
||||||
tag(">>"),
|
|
||||||
)(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
Target {
|
|
||||||
target: target.into(),
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(
|
|
||||||
Target::parse("<<target>>"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Target {
|
|
||||||
target: "target".into()
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Target::parse("<<tar get>>"),
|
|
||||||
Some((
|
|
||||||
"",
|
|
||||||
Target {
|
|
||||||
target: "tar get".into()
|
|
||||||
}
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(Target::parse("<<target >>").is_none());
|
|
||||||
assert!(Target::parse("<< target>>").is_none());
|
|
||||||
assert!(Target::parse("<<ta<get>>").is_none());
|
|
||||||
assert!(Target::parse("<<ta>get>>").is_none());
|
|
||||||
assert!(Target::parse("<<ta\nget>>").is_none());
|
|
||||||
assert!(Target::parse("<<target>").is_none());
|
|
||||||
}
|
|
|
@ -1,482 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom::{
|
|
||||||
bytes::complete::{tag, take, take_till, take_while, take_while_m_n},
|
|
||||||
character::complete::{space0, space1},
|
|
||||||
combinator::{map, map_res, opt},
|
|
||||||
sequence::preceded,
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Datetime Struct
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Datetime<'a> {
|
|
||||||
pub year: u16,
|
|
||||||
pub month: u8,
|
|
||||||
pub day: u8,
|
|
||||||
pub dayname: Cow<'a, str>,
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub hour: Option<u8>,
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub minute: Option<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Datetime<'_> {
|
|
||||||
pub fn into_owned(self) -> Datetime<'static> {
|
|
||||||
Datetime {
|
|
||||||
year: self.year,
|
|
||||||
month: self.month,
|
|
||||||
day: self.day,
|
|
||||||
dayname: self.dayname.into_owned().into(),
|
|
||||||
hour: self.hour,
|
|
||||||
minute: self.minute,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "chrono")]
|
|
||||||
mod chrono {
|
|
||||||
use super::Datetime;
|
|
||||||
use chrono::*;
|
|
||||||
|
|
||||||
impl Into<NaiveDate> for Datetime<'_> {
|
|
||||||
fn into(self) -> NaiveDate {
|
|
||||||
(&self).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<NaiveTime> for Datetime<'_> {
|
|
||||||
fn into(self) -> NaiveTime {
|
|
||||||
(&self).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<NaiveDateTime> for Datetime<'_> {
|
|
||||||
fn into(self) -> NaiveDateTime {
|
|
||||||
(&self).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<DateTime<Utc>> for Datetime<'_> {
|
|
||||||
fn into(self) -> DateTime<Utc> {
|
|
||||||
(&self).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<NaiveDate> for &Datetime<'_> {
|
|
||||||
fn into(self) -> NaiveDate {
|
|
||||||
NaiveDate::from_ymd(self.year.into(), self.month.into(), self.day.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<NaiveTime> for &Datetime<'_> {
|
|
||||||
fn into(self) -> NaiveTime {
|
|
||||||
NaiveTime::from_hms(
|
|
||||||
self.hour.unwrap_or_default().into(),
|
|
||||||
self.minute.unwrap_or_default().into(),
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<NaiveDateTime> for &Datetime<'_> {
|
|
||||||
fn into(self) -> NaiveDateTime {
|
|
||||||
NaiveDateTime::new(self.into(), self.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<DateTime<Utc>> for &Datetime<'_> {
|
|
||||||
fn into(self) -> DateTime<Utc> {
|
|
||||||
DateTime::from_utc(self.into(), Utc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Timestamp Object
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[cfg_attr(feature = "ser", serde(rename_all = "kebab-case"))]
|
|
||||||
#[cfg_attr(feature = "ser", serde(tag = "timestamp_type"))]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Timestamp<'a> {
|
|
||||||
Active {
|
|
||||||
start: Datetime<'a>,
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
repeater: Option<Cow<'a, str>>,
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
delay: Option<Cow<'a, str>>,
|
|
||||||
},
|
|
||||||
Inactive {
|
|
||||||
start: Datetime<'a>,
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
repeater: Option<Cow<'a, str>>,
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
delay: Option<Cow<'a, str>>,
|
|
||||||
},
|
|
||||||
ActiveRange {
|
|
||||||
start: Datetime<'a>,
|
|
||||||
end: Datetime<'a>,
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
repeater: Option<Cow<'a, str>>,
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
delay: Option<Cow<'a, str>>,
|
|
||||||
},
|
|
||||||
InactiveRange {
|
|
||||||
start: Datetime<'a>,
|
|
||||||
end: Datetime<'a>,
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
repeater: Option<Cow<'a, str>>,
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
delay: Option<Cow<'a, str>>,
|
|
||||||
},
|
|
||||||
Diary {
|
|
||||||
value: Cow<'a, str>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Timestamp<'_> {
|
|
||||||
pub(crate) fn parse_active(input: &str) -> Option<(&str, Timestamp)> {
|
|
||||||
parse_active(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn parse_inactive(input: &str) -> Option<(&str, Timestamp)> {
|
|
||||||
parse_inactive(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn parse_diary(input: &str) -> Option<(&str, Timestamp)> {
|
|
||||||
parse_diary(input).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> Timestamp<'static> {
|
|
||||||
match self {
|
|
||||||
Timestamp::Active {
|
|
||||||
start,
|
|
||||||
repeater,
|
|
||||||
delay,
|
|
||||||
} => Timestamp::Active {
|
|
||||||
start: start.into_owned(),
|
|
||||||
repeater: repeater.map(Into::into).map(Cow::Owned),
|
|
||||||
delay: delay.map(Into::into).map(Cow::Owned),
|
|
||||||
},
|
|
||||||
Timestamp::Inactive {
|
|
||||||
start,
|
|
||||||
repeater,
|
|
||||||
delay,
|
|
||||||
} => Timestamp::Inactive {
|
|
||||||
start: start.into_owned(),
|
|
||||||
repeater: repeater.map(Into::into).map(Cow::Owned),
|
|
||||||
delay: delay.map(Into::into).map(Cow::Owned),
|
|
||||||
},
|
|
||||||
Timestamp::ActiveRange {
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
repeater,
|
|
||||||
delay,
|
|
||||||
} => Timestamp::ActiveRange {
|
|
||||||
start: start.into_owned(),
|
|
||||||
end: end.into_owned(),
|
|
||||||
repeater: repeater.map(Into::into).map(Cow::Owned),
|
|
||||||
delay: delay.map(Into::into).map(Cow::Owned),
|
|
||||||
},
|
|
||||||
Timestamp::InactiveRange {
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
repeater,
|
|
||||||
delay,
|
|
||||||
} => Timestamp::InactiveRange {
|
|
||||||
start: start.into_owned(),
|
|
||||||
end: end.into_owned(),
|
|
||||||
repeater: repeater.map(Into::into).map(Cow::Owned),
|
|
||||||
delay: delay.map(Into::into).map(Cow::Owned),
|
|
||||||
},
|
|
||||||
Timestamp::Diary { value } => Timestamp::Diary {
|
|
||||||
value: value.into_owned().into(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_active(input: &str) -> IResult<&str, Timestamp, ()> {
|
|
||||||
let (input, _) = tag("<")(input)?;
|
|
||||||
let (input, start) = parse_datetime(input)?;
|
|
||||||
|
|
||||||
if input.starts_with('-') {
|
|
||||||
let (input, (hour, minute)) = parse_time(&input[1..])?;
|
|
||||||
let (input, _) = space0(input)?;
|
|
||||||
// TODO: delay-or-repeater
|
|
||||||
let (input, _) = tag(">")(input)?;
|
|
||||||
let mut end = start.clone();
|
|
||||||
end.hour = Some(hour);
|
|
||||||
end.minute = Some(minute);
|
|
||||||
return Ok((
|
|
||||||
input,
|
|
||||||
Timestamp::ActiveRange {
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
repeater: None,
|
|
||||||
delay: None,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (input, _) = space0(input)?;
|
|
||||||
// TODO: delay-or-repeater
|
|
||||||
let (input, _) = tag(">")(input)?;
|
|
||||||
|
|
||||||
if input.starts_with("--<") {
|
|
||||||
let (input, end) = parse_datetime(&input["--<".len()..])?;
|
|
||||||
let (input, _) = space0(input)?;
|
|
||||||
// TODO: delay-or-repeater
|
|
||||||
let (input, _) = tag(">")(input)?;
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
Timestamp::ActiveRange {
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
repeater: None,
|
|
||||||
delay: None,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
Timestamp::Active {
|
|
||||||
start,
|
|
||||||
repeater: None,
|
|
||||||
delay: None,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_inactive(input: &str) -> IResult<&str, Timestamp, ()> {
|
|
||||||
let (input, _) = tag("[")(input)?;
|
|
||||||
let (input, start) = parse_datetime(input)?;
|
|
||||||
|
|
||||||
if input.starts_with('-') {
|
|
||||||
let (input, (hour, minute)) = parse_time(&input[1..])?;
|
|
||||||
let (input, _) = space0(input)?;
|
|
||||||
// TODO: delay-or-repeater
|
|
||||||
let (input, _) = tag("]")(input)?;
|
|
||||||
let mut end = start.clone();
|
|
||||||
end.hour = Some(hour);
|
|
||||||
end.minute = Some(minute);
|
|
||||||
return Ok((
|
|
||||||
input,
|
|
||||||
Timestamp::InactiveRange {
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
repeater: None,
|
|
||||||
delay: None,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (input, _) = space0(input)?;
|
|
||||||
// TODO: delay-or-repeater
|
|
||||||
let (input, _) = tag("]")(input)?;
|
|
||||||
|
|
||||||
if input.starts_with("--[") {
|
|
||||||
let (input, end) = parse_datetime(&input["--[".len()..])?;
|
|
||||||
let (input, _) = space0(input)?;
|
|
||||||
// TODO: delay-or-repeater
|
|
||||||
let (input, _) = tag("]")(input)?;
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
Timestamp::InactiveRange {
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
repeater: None,
|
|
||||||
delay: None,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
Timestamp::Inactive {
|
|
||||||
start,
|
|
||||||
repeater: None,
|
|
||||||
delay: None,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_diary(input: &str) -> IResult<&str, Timestamp, ()> {
|
|
||||||
let (input, _) = tag("<%%(")(input)?;
|
|
||||||
let (input, value) = take_till(|c| c == ')' || c == '>' || c == '\n')(input)?;
|
|
||||||
let (input, _) = tag(")>")(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
Timestamp::Diary {
|
|
||||||
value: value.into(),
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_time(input: &str) -> IResult<&str, (u8, u8), ()> {
|
|
||||||
let (input, hour) = map_res(take_while_m_n(1, 2, |c: char| c.is_ascii_digit()), |num| {
|
|
||||||
u8::from_str_radix(num, 10)
|
|
||||||
})(input)?;
|
|
||||||
let (input, _) = tag(":")(input)?;
|
|
||||||
let (input, minute) = map_res(take(2usize), |num| u8::from_str_radix(num, 10))(input)?;
|
|
||||||
Ok((input, (hour, minute)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_datetime(input: &str) -> IResult<&str, Datetime, ()> {
|
|
||||||
let parse_u8 = |num| u8::from_str_radix(num, 10);
|
|
||||||
|
|
||||||
let (input, year) = map_res(take(4usize), |num| u16::from_str_radix(num, 10))(input)?;
|
|
||||||
let (input, _) = tag("-")(input)?;
|
|
||||||
let (input, month) = map_res(take(2usize), parse_u8)(input)?;
|
|
||||||
let (input, _) = tag("-")(input)?;
|
|
||||||
let (input, day) = map_res(take(2usize), parse_u8)(input)?;
|
|
||||||
let (input, _) = space1(input)?;
|
|
||||||
let (input, dayname) = take_while(|c: char| {
|
|
||||||
!c.is_ascii_whitespace()
|
|
||||||
&& !c.is_ascii_digit()
|
|
||||||
&& c != '+'
|
|
||||||
&& c != '-'
|
|
||||||
&& c != ']'
|
|
||||||
&& c != '>'
|
|
||||||
})(input)?;
|
|
||||||
let (input, (hour, minute)) = map(opt(preceded(space1, parse_time)), |time| {
|
|
||||||
(time.map(|t| t.0), time.map(|t| t.1))
|
|
||||||
})(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
Datetime {
|
|
||||||
year,
|
|
||||||
month,
|
|
||||||
day,
|
|
||||||
dayname: dayname.into(),
|
|
||||||
hour,
|
|
||||||
minute,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
// #[cfg_attr(test, derive(PartialEq))]
|
|
||||||
// #[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
// #[derive(Debug, Copy, Clone)]
|
|
||||||
// pub enum RepeaterType {
|
|
||||||
// Cumulate,
|
|
||||||
// CatchUp,
|
|
||||||
// Restart,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[cfg_attr(test, derive(PartialEq))]
|
|
||||||
// #[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
// #[derive(Debug, Copy, Clone)]
|
|
||||||
// pub enum DelayType {
|
|
||||||
// All,
|
|
||||||
// First,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[cfg_attr(test, derive(PartialEq))]
|
|
||||||
// #[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
// #[derive(Debug, Copy, Clone)]
|
|
||||||
// pub enum TimeUnit {
|
|
||||||
// Hour,
|
|
||||||
// Day,
|
|
||||||
// Week,
|
|
||||||
// Month,
|
|
||||||
// Year,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[cfg_attr(test, derive(PartialEq))]
|
|
||||||
// #[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
// #[derive(Debug, Copy, Clone)]
|
|
||||||
// pub struct Repeater {
|
|
||||||
// pub ty: RepeaterType,
|
|
||||||
// pub value: usize,
|
|
||||||
// pub unit: TimeUnit,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[cfg_attr(test, derive(PartialEq))]
|
|
||||||
// #[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
// #[derive(Debug, Copy, Clone)]
|
|
||||||
// pub struct Delay {
|
|
||||||
// pub ty: DelayType,
|
|
||||||
// pub value: usize,
|
|
||||||
// pub unit: TimeUnit,
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(
|
|
||||||
parse_inactive("[2003-09-16 Tue]"),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
Timestamp::Inactive {
|
|
||||||
start: Datetime {
|
|
||||||
year: 2003,
|
|
||||||
month: 9,
|
|
||||||
day: 16,
|
|
||||||
dayname: "Tue".into(),
|
|
||||||
hour: None,
|
|
||||||
minute: None
|
|
||||||
},
|
|
||||||
repeater: None,
|
|
||||||
delay: None,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_inactive("[2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39]"),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
Timestamp::InactiveRange {
|
|
||||||
start: Datetime {
|
|
||||||
year: 2003,
|
|
||||||
month: 9,
|
|
||||||
day: 16,
|
|
||||||
dayname: "Tue".into(),
|
|
||||||
hour: Some(9),
|
|
||||||
minute: Some(39)
|
|
||||||
},
|
|
||||||
end: Datetime {
|
|
||||||
year: 2003,
|
|
||||||
month: 9,
|
|
||||||
day: 16,
|
|
||||||
dayname: "Tue".into(),
|
|
||||||
hour: Some(10),
|
|
||||||
minute: Some(39),
|
|
||||||
},
|
|
||||||
repeater: None,
|
|
||||||
delay: None
|
|
||||||
},
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_active("<2003-09-16 Tue 09:39-10:39>"),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
Timestamp::ActiveRange {
|
|
||||||
start: Datetime {
|
|
||||||
year: 2003,
|
|
||||||
month: 9,
|
|
||||||
day: 16,
|
|
||||||
dayname: "Tue".into(),
|
|
||||||
hour: Some(9),
|
|
||||||
minute: Some(39),
|
|
||||||
},
|
|
||||||
end: Datetime {
|
|
||||||
year: 2003,
|
|
||||||
month: 9,
|
|
||||||
day: 16,
|
|
||||||
dayname: "Tue".into(),
|
|
||||||
hour: Some(10),
|
|
||||||
minute: Some(39),
|
|
||||||
},
|
|
||||||
repeater: None,
|
|
||||||
delay: None
|
|
||||||
},
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,551 +0,0 @@
|
||||||
//! Headline Title
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::{borrow::Cow, iter::FromIterator};
|
|
||||||
|
|
||||||
use memchr::memrchr2;
|
|
||||||
use nom::{
|
|
||||||
branch::alt,
|
|
||||||
bytes::complete::{tag, take_until, take_while},
|
|
||||||
character::complete::{anychar, line_ending, space1},
|
|
||||||
combinator::{map, opt, verify},
|
|
||||||
error::{make_error, ErrorKind},
|
|
||||||
multi::fold_many0,
|
|
||||||
sequence::{delimited, preceded},
|
|
||||||
Err, IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
config::ParseConfig,
|
|
||||||
elements::{drawer::parse_drawer_without_blank, Planning, Timestamp},
|
|
||||||
parse::combinators::{blank_lines_count, line, one_word},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Title Element
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Title<'a> {
|
|
||||||
/// Headline level, number of stars
|
|
||||||
pub level: usize,
|
|
||||||
/// Headline priority cookie
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub priority: Option<char>,
|
|
||||||
/// Headline title tags
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Vec::is_empty"))]
|
|
||||||
pub tags: Vec<Cow<'a, str>>,
|
|
||||||
/// Headline todo keyword
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub keyword: Option<Cow<'a, str>>,
|
|
||||||
/// Raw headline's text, without the stars and the tags
|
|
||||||
pub raw: Cow<'a, str>,
|
|
||||||
/// Planning element associated to this headline
|
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
|
||||||
pub planning: Option<Box<Planning<'a>>>,
|
|
||||||
/// Property drawer associated to this headline
|
|
||||||
#[cfg_attr(
|
|
||||||
feature = "ser",
|
|
||||||
serde(skip_serializing_if = "PropertiesMap::is_empty")
|
|
||||||
)]
|
|
||||||
pub properties: PropertiesMap<'a>,
|
|
||||||
/// Numbers of blank lines between last title's line and next non-blank line
|
|
||||||
/// or buffer's end
|
|
||||||
pub post_blank: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Title<'_> {
|
|
||||||
pub(crate) fn parse<'a>(
|
|
||||||
input: &'a str,
|
|
||||||
config: &ParseConfig,
|
|
||||||
) -> Option<(&'a str, (Title<'a>, &'a str))> {
|
|
||||||
parse_title(input, config).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: fn is_quoted(&self) -> bool { }
|
|
||||||
// TODO: fn is_footnote_section(&self) -> bool { }
|
|
||||||
|
|
||||||
/// Returns this headline's closed timestamp, or `None` if not set.
|
|
||||||
pub fn closed(&self) -> Option<&Timestamp> {
|
|
||||||
self.planning.as_ref().and_then(|p| p.closed.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns this headline's scheduled timestamp, or `None` if not set.
|
|
||||||
pub fn scheduled(&self) -> Option<&Timestamp> {
|
|
||||||
self.planning.as_ref().and_then(|p| p.scheduled.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns this headline's deadline timestamp, or `None` if not set.
|
|
||||||
pub fn deadline(&self) -> Option<&Timestamp> {
|
|
||||||
self.planning.as_ref().and_then(|p| p.deadline.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if this headline is archived
|
|
||||||
pub fn is_archived(&self) -> bool {
|
|
||||||
self.tags.iter().any(|tag| tag == "ARCHIVE")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if this headline is commented
|
|
||||||
pub fn is_commented(&self) -> bool {
|
|
||||||
self.raw.starts_with("COMMENT")
|
|
||||||
&& (self.raw.len() == 7 || self.raw[7..].starts_with(char::is_whitespace))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> Title<'static> {
|
|
||||||
Title {
|
|
||||||
level: self.level,
|
|
||||||
priority: self.priority,
|
|
||||||
tags: self
|
|
||||||
.tags
|
|
||||||
.into_iter()
|
|
||||||
.map(|s| s.into_owned().into())
|
|
||||||
.collect(),
|
|
||||||
keyword: self.keyword.map(Into::into).map(Cow::Owned),
|
|
||||||
raw: self.raw.into_owned().into(),
|
|
||||||
planning: self.planning.map(|p| Box::new(p.into_owned())),
|
|
||||||
properties: self.properties.into_owned(),
|
|
||||||
post_blank: self.post_blank,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Title<'_> {
|
|
||||||
fn default() -> Title<'static> {
|
|
||||||
Title {
|
|
||||||
level: 1,
|
|
||||||
priority: None,
|
|
||||||
tags: Vec::new(),
|
|
||||||
keyword: None,
|
|
||||||
raw: Cow::Borrowed(""),
|
|
||||||
planning: None,
|
|
||||||
properties: PropertiesMap::new(),
|
|
||||||
post_blank: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Properties
|
|
||||||
#[derive(Default, Debug, Clone)]
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
|
||||||
pub struct PropertiesMap<'a> {
|
|
||||||
pub pairs: Vec<(Cow<'a, str>, Cow<'a, str>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> PropertiesMap<'a> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
PropertiesMap { pairs: Vec::new() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.pairs.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &(Cow<'a, str>, Cow<'a, str>)> {
|
|
||||||
self.pairs.iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut (Cow<'a, str>, Cow<'a, str>)> {
|
|
||||||
self.pairs.iter_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_iter(self) -> impl Iterator<Item = (Cow<'a, str>, Cow<'a, str>)> {
|
|
||||||
self.pairs.into_iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_hash_map(self) -> HashMap<Cow<'a, str>, Cow<'a, str>> {
|
|
||||||
self.pairs.into_iter().collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "indexmap")]
|
|
||||||
pub fn into_index_map(self) -> indexmap::IndexMap<Cow<'a, str>, Cow<'a, str>> {
|
|
||||||
self.pairs.into_iter().collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_owned(self) -> PropertiesMap<'static> {
|
|
||||||
self.pairs
|
|
||||||
.into_iter()
|
|
||||||
.map(|(k, v)| (k.into_owned().into(), v.into_owned().into()))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> FromIterator<(Cow<'a, str>, Cow<'a, str>)> for PropertiesMap<'a> {
|
|
||||||
fn from_iter<T: IntoIterator<Item = (Cow<'a, str>, Cow<'a, str>)>>(iter: T) -> Self {
|
|
||||||
let mut map = PropertiesMap::new();
|
|
||||||
map.pairs.extend(iter);
|
|
||||||
map
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn white_spaces_or_eol(input: &str) -> IResult<&str, &str, ()> {
|
|
||||||
alt((space1, line_ending))(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn parse_title<'a>(
|
|
||||||
input: &'a str,
|
|
||||||
config: &ParseConfig,
|
|
||||||
) -> IResult<&'a str, (Title<'a>, &'a str), ()> {
|
|
||||||
let (input, level) = map(take_while(|c: char| c == '*'), |s: &str| s.len())(input)?;
|
|
||||||
|
|
||||||
debug_assert!(level > 0);
|
|
||||||
|
|
||||||
let (input, keyword) = opt(preceded(
|
|
||||||
space1,
|
|
||||||
verify(one_word, |s: &str| {
|
|
||||||
config.todo_keywords.0.iter().any(|x| x == s)
|
|
||||||
|| config.todo_keywords.1.iter().any(|x| x == s)
|
|
||||||
}),
|
|
||||||
))(input)?;
|
|
||||||
|
|
||||||
let (input, priority) = opt(delimited(
|
|
||||||
space1,
|
|
||||||
delimited(
|
|
||||||
tag("[#"),
|
|
||||||
verify(anychar, |c: &char| c.is_ascii_uppercase()),
|
|
||||||
tag("]"),
|
|
||||||
),
|
|
||||||
white_spaces_or_eol,
|
|
||||||
))(input)?;
|
|
||||||
let (input, tail) = line(input)?;
|
|
||||||
let tail = tail.trim();
|
|
||||||
|
|
||||||
// tags can be separated by space or \t
|
|
||||||
let (raw, tags) = memrchr2(b' ', b'\t', tail.as_bytes())
|
|
||||||
.map(|i| (tail[0..i].trim(), &tail[i + 1..]))
|
|
||||||
.filter(|(_, x)| is_tag_line(x))
|
|
||||||
.unwrap_or((tail, ""));
|
|
||||||
|
|
||||||
let tags = tags
|
|
||||||
.split(':')
|
|
||||||
.filter(|s| !s.is_empty())
|
|
||||||
.map(Into::into)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let (input, planning) = Planning::parse(input)
|
|
||||||
.map(|(input, planning)| (input, Some(Box::new(planning))))
|
|
||||||
.unwrap_or((input, None));
|
|
||||||
|
|
||||||
let (input, properties) = opt(parse_properties_drawer)(input)?;
|
|
||||||
let (input, post_blank) = blank_lines_count(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
(
|
|
||||||
Title {
|
|
||||||
properties: properties.unwrap_or_default(),
|
|
||||||
level,
|
|
||||||
keyword: keyword.map(Into::into),
|
|
||||||
priority,
|
|
||||||
tags,
|
|
||||||
raw: raw.into(),
|
|
||||||
planning,
|
|
||||||
post_blank,
|
|
||||||
},
|
|
||||||
raw,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_tag_line(input: &str) -> bool {
|
|
||||||
input.len() > 2
|
|
||||||
&& input.starts_with(':')
|
|
||||||
&& input.ends_with(':')
|
|
||||||
&& input.chars().all(|ch| {
|
|
||||||
ch.is_alphanumeric() || ch == '_' || ch == '@' || ch == '#' || ch == '%' || ch == ':'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn parse_properties_drawer(input: &str) -> IResult<&str, PropertiesMap<'_>, ()> {
|
|
||||||
let (input, (drawer, content)) = parse_drawer_without_blank(input.trim_start())?;
|
|
||||||
if drawer.name != "PROPERTIES" {
|
|
||||||
return Err(Err::Error(make_error(input, ErrorKind::Tag)));
|
|
||||||
}
|
|
||||||
let (_, map) = fold_many0(
|
|
||||||
parse_node_property,
|
|
||||||
PropertiesMap::new,
|
|
||||||
|mut acc: PropertiesMap, (name, value)| {
|
|
||||||
acc.pairs.push((name.into(), value.into()));
|
|
||||||
acc
|
|
||||||
},
|
|
||||||
)(content)?;
|
|
||||||
Ok((input, map))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn parse_node_property(input: &str) -> IResult<&str, (&str, &str), ()> {
|
|
||||||
let (input, _) = blank_lines_count(input)?;
|
|
||||||
let input = input.trim_start();
|
|
||||||
let (input, name) = map(delimited(tag(":"), take_until(":"), tag(":")), |s: &str| {
|
|
||||||
s.trim_end_matches('+')
|
|
||||||
})(input)?;
|
|
||||||
let (input, value) = line(input)?;
|
|
||||||
Ok((input, (name, value.trim())))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_title_() {
|
|
||||||
use crate::config::DEFAULT_CONFIG;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
parse_title("**** DONE [#A] COMMENT Title :tag:a2%:", &DEFAULT_CONFIG),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
Title {
|
|
||||||
level: 4,
|
|
||||||
keyword: Some("DONE".into()),
|
|
||||||
priority: Some('A'),
|
|
||||||
raw: "COMMENT Title".into(),
|
|
||||||
tags: vec!["tag".into(), "a2%".into()],
|
|
||||||
planning: None,
|
|
||||||
properties: PropertiesMap::new(),
|
|
||||||
post_blank: 0,
|
|
||||||
},
|
|
||||||
"COMMENT Title"
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_title("**** ToDO [#A] COMMENT Title", &DEFAULT_CONFIG),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
Title {
|
|
||||||
level: 4,
|
|
||||||
keyword: None,
|
|
||||||
priority: None,
|
|
||||||
raw: "ToDO [#A] COMMENT Title".into(),
|
|
||||||
tags: vec![],
|
|
||||||
planning: None,
|
|
||||||
properties: PropertiesMap::new(),
|
|
||||||
post_blank: 0,
|
|
||||||
},
|
|
||||||
"ToDO [#A] COMMENT Title"
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_title("**** T0DO [#A] COMMENT Title", &DEFAULT_CONFIG),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
Title {
|
|
||||||
level: 4,
|
|
||||||
keyword: None,
|
|
||||||
priority: None,
|
|
||||||
raw: "T0DO [#A] COMMENT Title".into(),
|
|
||||||
tags: vec![],
|
|
||||||
planning: None,
|
|
||||||
properties: PropertiesMap::new(),
|
|
||||||
post_blank: 0,
|
|
||||||
},
|
|
||||||
"T0DO [#A] COMMENT Title"
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_title("**** DONE [#1] COMMENT Title", &DEFAULT_CONFIG),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
Title {
|
|
||||||
level: 4,
|
|
||||||
keyword: Some("DONE".into()),
|
|
||||||
priority: None,
|
|
||||||
raw: "[#1] COMMENT Title".into(),
|
|
||||||
tags: vec![],
|
|
||||||
planning: None,
|
|
||||||
properties: PropertiesMap::new(),
|
|
||||||
post_blank: 0,
|
|
||||||
},
|
|
||||||
"[#1] COMMENT Title"
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_title("**** DONE [#a] COMMENT Title", &DEFAULT_CONFIG),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
Title {
|
|
||||||
level: 4,
|
|
||||||
keyword: Some("DONE".into()),
|
|
||||||
priority: None,
|
|
||||||
raw: "[#a] COMMENT Title".into(),
|
|
||||||
tags: vec![],
|
|
||||||
planning: None,
|
|
||||||
properties: PropertiesMap::new(),
|
|
||||||
post_blank: 0,
|
|
||||||
},
|
|
||||||
"[#a] COMMENT Title"
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
// https://github.com/PoiScript/orgize/issues/20
|
|
||||||
assert_eq!(
|
|
||||||
parse_title("** DONE [#B]::", &DEFAULT_CONFIG),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
Title {
|
|
||||||
level: 2,
|
|
||||||
keyword: Some("DONE".into()),
|
|
||||||
priority: None,
|
|
||||||
raw: "[#B]::".into(),
|
|
||||||
tags: vec![],
|
|
||||||
planning: None,
|
|
||||||
properties: PropertiesMap::new(),
|
|
||||||
post_blank: 0,
|
|
||||||
},
|
|
||||||
"[#B]::"
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
parse_title("**** Title :tag:a2%", &DEFAULT_CONFIG),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
Title {
|
|
||||||
level: 4,
|
|
||||||
keyword: None,
|
|
||||||
priority: None,
|
|
||||||
raw: "Title :tag:a2%".into(),
|
|
||||||
tags: vec![],
|
|
||||||
planning: None,
|
|
||||||
properties: PropertiesMap::new(),
|
|
||||||
post_blank: 0,
|
|
||||||
},
|
|
||||||
"Title :tag:a2%"
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_title("**** Title tag:a2%:", &DEFAULT_CONFIG),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
Title {
|
|
||||||
level: 4,
|
|
||||||
keyword: None,
|
|
||||||
priority: None,
|
|
||||||
raw: "Title tag:a2%:".into(),
|
|
||||||
tags: vec![],
|
|
||||||
planning: None,
|
|
||||||
properties: PropertiesMap::new(),
|
|
||||||
post_blank: 0,
|
|
||||||
},
|
|
||||||
"Title tag:a2%:"
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
parse_title(
|
|
||||||
"**** DONE Title",
|
|
||||||
&ParseConfig {
|
|
||||||
todo_keywords: (vec![], vec![]),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
Title {
|
|
||||||
level: 4,
|
|
||||||
keyword: None,
|
|
||||||
priority: None,
|
|
||||||
raw: "DONE Title".into(),
|
|
||||||
tags: vec![],
|
|
||||||
planning: None,
|
|
||||||
properties: PropertiesMap::new(),
|
|
||||||
post_blank: 0,
|
|
||||||
},
|
|
||||||
"DONE Title"
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse_title(
|
|
||||||
"**** TASK [#A] Title",
|
|
||||||
&ParseConfig {
|
|
||||||
todo_keywords: (vec!["TASK".to_string()], vec![]),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
Title {
|
|
||||||
level: 4,
|
|
||||||
keyword: Some("TASK".into()),
|
|
||||||
priority: Some('A'),
|
|
||||||
raw: "Title".into(),
|
|
||||||
tags: vec![],
|
|
||||||
planning: None,
|
|
||||||
properties: PropertiesMap::new(),
|
|
||||||
post_blank: 0,
|
|
||||||
},
|
|
||||||
"Title"
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_properties_drawer_() {
|
|
||||||
assert_eq!(
|
|
||||||
parse_properties_drawer(" :PROPERTIES:\n :CUSTOM_ID: id\n :END:"),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
vec![("CUSTOM_ID".into(), "id".into())]
|
|
||||||
.into_iter()
|
|
||||||
.collect::<PropertiesMap>()
|
|
||||||
))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "indexmap")]
|
|
||||||
fn preserve_properties_drawer_order() {
|
|
||||||
let mut vec = Vec::default();
|
|
||||||
// Use a large number of properties to reduce false pass rate, since HashMap
|
|
||||||
// is non-deterministic. There are roughly 10^18 possible derangements of this sequence.
|
|
||||||
for i in 0..20 {
|
|
||||||
// Avoid alphabetic or numeric order.
|
|
||||||
let j = (i + 7) % 20;
|
|
||||||
vec.push((
|
|
||||||
Cow::Owned(format!(
|
|
||||||
"{}{}",
|
|
||||||
if i % 3 == 0 {
|
|
||||||
"FOO"
|
|
||||||
} else if i % 3 == 1 {
|
|
||||||
"QUX"
|
|
||||||
} else {
|
|
||||||
"BAR"
|
|
||||||
},
|
|
||||||
j
|
|
||||||
)),
|
|
||||||
Cow::Owned(i.to_string()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut s = String::default();
|
|
||||||
|
|
||||||
for (k, v) in &vec {
|
|
||||||
s += &format!(" :{}: {}\n", k, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
let drawer = format!(" :PROPERTIES:\n{}:END:\n", &s);
|
|
||||||
|
|
||||||
let map = parse_properties_drawer(&drawer).unwrap().1.into_index_map();
|
|
||||||
|
|
||||||
// indexmap should be in the same order as vector
|
|
||||||
for (left, right) in vec.iter().zip(map) {
|
|
||||||
assert_eq!(left, &right);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,397 +0,0 @@
|
||||||
use std::fmt;
|
|
||||||
use std::io::{Error, Result as IOResult, Write};
|
|
||||||
|
|
||||||
use jetscii::{bytes, BytesConst};
|
|
||||||
|
|
||||||
use crate::elements::{Element, Table, TableCell, TableRow, Timestamp};
|
|
||||||
use crate::export::write_datetime;
|
|
||||||
|
|
||||||
/// A wrapper for escaping sensitive characters in html.
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use orgize::export::HtmlEscape as Escape;
|
|
||||||
///
|
|
||||||
/// assert_eq!(format!("{}", Escape("< < <")), "< < <");
|
|
||||||
/// assert_eq!(
|
|
||||||
/// format!("{}", Escape("<script>alert('Hello XSS')</script>")),
|
|
||||||
/// "<script>alert('Hello XSS')</script>"
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
pub struct HtmlEscape<S: AsRef<str>>(pub S);
|
|
||||||
|
|
||||||
impl<S: AsRef<str>> fmt::Display for HtmlEscape<S> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
let mut pos = 0;
|
|
||||||
|
|
||||||
let content = self.0.as_ref();
|
|
||||||
let bytes = content.as_bytes();
|
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
static ref ESCAPE_BYTES: BytesConst = bytes!(b'<', b'>', b'&', b'\'', b'"');
|
|
||||||
}
|
|
||||||
|
|
||||||
while let Some(off) = ESCAPE_BYTES.find(&bytes[pos..]) {
|
|
||||||
write!(f, "{}", &content[pos..pos + off])?;
|
|
||||||
|
|
||||||
pos += off + 1;
|
|
||||||
|
|
||||||
match bytes[pos - 1] {
|
|
||||||
b'<' => write!(f, "<")?,
|
|
||||||
b'>' => write!(f, ">")?,
|
|
||||||
b'&' => write!(f, "&")?,
|
|
||||||
b'\'' => write!(f, "'")?,
|
|
||||||
b'"' => write!(f, """)?,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(f, "{}", &content[pos..])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HtmlHandler<E: From<Error>>: Default {
|
|
||||||
fn start<W: Write>(&mut self, w: W, element: &Element) -> Result<(), E>;
|
|
||||||
fn end<W: Write>(&mut self, w: W, element: &Element) -> Result<(), E>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Default Html Handler
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct DefaultHtmlHandler;
|
|
||||||
|
|
||||||
impl HtmlHandler<Error> for DefaultHtmlHandler {
|
|
||||||
fn start<W: Write>(&mut self, mut w: W, element: &Element) -> IOResult<()> {
|
|
||||||
match element {
|
|
||||||
// container elements
|
|
||||||
Element::SpecialBlock(_) => (),
|
|
||||||
Element::QuoteBlock(_) => write!(w, "<blockquote>")?,
|
|
||||||
Element::CenterBlock(_) => write!(w, "<div class=\"center\">")?,
|
|
||||||
Element::VerseBlock(_) => write!(w, "<p class=\"verse\">")?,
|
|
||||||
Element::Bold => write!(w, "<b>")?,
|
|
||||||
Element::Document { .. } => write!(w, "<main>")?,
|
|
||||||
Element::DynBlock(_dyn_block) => (),
|
|
||||||
Element::Headline { .. } => (),
|
|
||||||
Element::List(list) => {
|
|
||||||
if list.ordered {
|
|
||||||
write!(w, "<ol>")?;
|
|
||||||
} else {
|
|
||||||
write!(w, "<ul>")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::Italic => write!(w, "<i>")?,
|
|
||||||
Element::ListItem(_) => write!(w, "<li>")?,
|
|
||||||
Element::Paragraph { .. } => write!(w, "<p>")?,
|
|
||||||
Element::Section => write!(w, "<section>")?,
|
|
||||||
Element::Strike => write!(w, "<s>")?,
|
|
||||||
Element::Underline => write!(w, "<u>")?,
|
|
||||||
// non-container elements
|
|
||||||
Element::CommentBlock(_) => (),
|
|
||||||
Element::ExampleBlock(block) => write!(
|
|
||||||
w,
|
|
||||||
"<pre class=\"example\">{}</pre>",
|
|
||||||
HtmlEscape(&block.contents)
|
|
||||||
)?,
|
|
||||||
Element::ExportBlock(block) => {
|
|
||||||
if block.data.eq_ignore_ascii_case("HTML") {
|
|
||||||
write!(w, "{}", block.contents)?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::SourceBlock(block) => {
|
|
||||||
if block.language.is_empty() {
|
|
||||||
write!(
|
|
||||||
w,
|
|
||||||
"<pre class=\"example\">{}</pre>",
|
|
||||||
HtmlEscape(&block.contents)
|
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
write!(
|
|
||||||
w,
|
|
||||||
"<div class=\"org-src-container\"><pre class=\"src src-{}\">{}</pre></div>",
|
|
||||||
block.language,
|
|
||||||
HtmlEscape(&block.contents)
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::BabelCall(_) => (),
|
|
||||||
Element::InlineSrc(inline_src) => write!(
|
|
||||||
w,
|
|
||||||
"<code class=\"src src-{}\">{}</code>",
|
|
||||||
inline_src.lang,
|
|
||||||
HtmlEscape(&inline_src.body)
|
|
||||||
)?,
|
|
||||||
Element::Code { value } => write!(w, "<code>{}</code>", HtmlEscape(value))?,
|
|
||||||
Element::FnRef(_fn_ref) => (),
|
|
||||||
Element::InlineCall(_) => (),
|
|
||||||
Element::Link(link) => write!(
|
|
||||||
w,
|
|
||||||
"<a href=\"{}\">{}</a>",
|
|
||||||
HtmlEscape(&link.path),
|
|
||||||
HtmlEscape(link.desc.as_ref().unwrap_or(&link.path)),
|
|
||||||
)?,
|
|
||||||
Element::Macros(_macros) => (),
|
|
||||||
Element::RadioTarget => (),
|
|
||||||
Element::Snippet(snippet) => {
|
|
||||||
if snippet.name.eq_ignore_ascii_case("HTML") {
|
|
||||||
write!(w, "{}", snippet.value)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::Target(_target) => (),
|
|
||||||
Element::Text { value } => write!(w, "{}", HtmlEscape(value))?,
|
|
||||||
Element::Timestamp(timestamp) => {
|
|
||||||
write!(
|
|
||||||
&mut w,
|
|
||||||
"<span class=\"timestamp-wrapper\"><span class=\"timestamp\">"
|
|
||||||
)?;
|
|
||||||
|
|
||||||
match timestamp {
|
|
||||||
Timestamp::Active { start, .. } => {
|
|
||||||
write_datetime(&mut w, "<", start, ">")?;
|
|
||||||
}
|
|
||||||
Timestamp::Inactive { start, .. } => {
|
|
||||||
write_datetime(&mut w, "[", start, "]")?;
|
|
||||||
}
|
|
||||||
Timestamp::ActiveRange { start, end, .. } => {
|
|
||||||
write_datetime(&mut w, "<", start, ">–")?;
|
|
||||||
write_datetime(&mut w, "<", end, ">")?;
|
|
||||||
}
|
|
||||||
Timestamp::InactiveRange { start, end, .. } => {
|
|
||||||
write_datetime(&mut w, "[", start, "]–")?;
|
|
||||||
write_datetime(&mut w, "[", end, "]")?;
|
|
||||||
}
|
|
||||||
Timestamp::Diary { value } => {
|
|
||||||
write!(&mut w, "<%%({})>", HtmlEscape(value))?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(&mut w, "</span></span>")?;
|
|
||||||
}
|
|
||||||
Element::Verbatim { value } => write!(&mut w, "<code>{}</code>", HtmlEscape(value))?,
|
|
||||||
Element::FnDef(_fn_def) => (),
|
|
||||||
Element::Clock(_clock) => (),
|
|
||||||
Element::Comment(_) => (),
|
|
||||||
Element::FixedWidth(fixed_width) => write!(
|
|
||||||
w,
|
|
||||||
"<pre class=\"example\">{}</pre>",
|
|
||||||
HtmlEscape(&fixed_width.value)
|
|
||||||
)?,
|
|
||||||
Element::Keyword(_keyword) => (),
|
|
||||||
Element::Drawer(_drawer) => (),
|
|
||||||
Element::Rule(_) => write!(w, "<hr>")?,
|
|
||||||
Element::Cookie(cookie) => write!(w, "<code>{}</code>", cookie.value)?,
|
|
||||||
Element::Title(title) => {
|
|
||||||
write!(w, "<h{}>", if title.level <= 6 { title.level } else { 6 })?;
|
|
||||||
}
|
|
||||||
Element::Table(Table::TableEl { .. }) => (),
|
|
||||||
Element::Table(Table::Org { has_header, .. }) => {
|
|
||||||
write!(w, "<table>")?;
|
|
||||||
if *has_header {
|
|
||||||
write!(w, "<thead>")?;
|
|
||||||
} else {
|
|
||||||
write!(w, "<tbody>")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::TableRow(row) => match row {
|
|
||||||
TableRow::Body => write!(w, "<tr>")?,
|
|
||||||
TableRow::BodyRule => write!(w, "</tbody><tbody>")?,
|
|
||||||
TableRow::Header => write!(w, "<tr>")?,
|
|
||||||
TableRow::HeaderRule => write!(w, "</thead><tbody>")?,
|
|
||||||
},
|
|
||||||
Element::TableCell(cell) => match cell {
|
|
||||||
TableCell::Body => write!(w, "<td>")?,
|
|
||||||
TableCell::Header => write!(w, "<th>")?,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn end<W: Write>(&mut self, mut w: W, element: &Element) -> IOResult<()> {
|
|
||||||
match element {
|
|
||||||
// container elements
|
|
||||||
Element::SpecialBlock(_) => (),
|
|
||||||
Element::QuoteBlock(_) => write!(w, "</blockquote>")?,
|
|
||||||
Element::CenterBlock(_) => write!(w, "</div>")?,
|
|
||||||
Element::VerseBlock(_) => write!(w, "</p>")?,
|
|
||||||
Element::Bold => write!(w, "</b>")?,
|
|
||||||
Element::Document { .. } => write!(w, "</main>")?,
|
|
||||||
Element::DynBlock(_dyn_block) => (),
|
|
||||||
Element::Headline { .. } => (),
|
|
||||||
Element::List(list) => {
|
|
||||||
if list.ordered {
|
|
||||||
write!(w, "</ol>")?;
|
|
||||||
} else {
|
|
||||||
write!(w, "</ul>")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::Italic => write!(w, "</i>")?,
|
|
||||||
Element::ListItem(_) => write!(w, "</li>")?,
|
|
||||||
Element::Paragraph { .. } => write!(w, "</p>")?,
|
|
||||||
Element::Section => write!(w, "</section>")?,
|
|
||||||
Element::Strike => write!(w, "</s>")?,
|
|
||||||
Element::Underline => write!(w, "</u>")?,
|
|
||||||
Element::Title(title) => {
|
|
||||||
write!(w, "</h{}>", if title.level <= 6 { title.level } else { 6 })?
|
|
||||||
}
|
|
||||||
Element::Table(Table::TableEl { .. }) => (),
|
|
||||||
Element::Table(Table::Org { .. }) => {
|
|
||||||
write!(w, "</tbody></table>")?;
|
|
||||||
}
|
|
||||||
Element::TableRow(TableRow::Body) | Element::TableRow(TableRow::Header) => {
|
|
||||||
write!(w, "</tr>")?;
|
|
||||||
}
|
|
||||||
Element::TableCell(cell) => match cell {
|
|
||||||
TableCell::Body => write!(w, "</td>")?,
|
|
||||||
TableCell::Header => write!(w, "</th>")?,
|
|
||||||
},
|
|
||||||
// non-container elements
|
|
||||||
_ => debug_assert!(!element.is_container()),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "syntect")]
|
|
||||||
mod syntect_handler {
|
|
||||||
use super::*;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
use syntect::{
|
|
||||||
easy::HighlightLines,
|
|
||||||
highlighting::ThemeSet,
|
|
||||||
html::{styled_line_to_highlighted_html, IncludeBackground},
|
|
||||||
parsing::SyntaxSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Syntect Html Handler
|
|
||||||
///
|
|
||||||
/// Simple Usage:
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use orgize::Org;
|
|
||||||
/// use orgize::export::{DefaultHtmlHandler, SyntectHtmlHandler};
|
|
||||||
///
|
|
||||||
/// let mut handler = SyntectHtmlHandler::new(DefaultHtmlHandler);
|
|
||||||
/// let org = Org::parse("src_rust{println!(\"Hello\")}");
|
|
||||||
///
|
|
||||||
/// let mut vec = vec![];
|
|
||||||
///
|
|
||||||
/// org.write_html_custom(&mut vec, &mut handler).unwrap();
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Customize:
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// // orgize has re-exported the whole syntect crate
|
|
||||||
/// use orgize::syntect::parsing::SyntaxSet;
|
|
||||||
/// use orgize::export::{DefaultHtmlHandler, SyntectHtmlHandler};
|
|
||||||
///
|
|
||||||
/// let mut handler = SyntectHtmlHandler {
|
|
||||||
/// syntax_set: {
|
|
||||||
/// let set = SyntaxSet::load_defaults_newlines();
|
|
||||||
/// let mut builder = set.into_builder();
|
|
||||||
/// // add extra language syntax
|
|
||||||
/// builder.add_from_folder("path/to/syntax/dir", true).unwrap();
|
|
||||||
/// builder.build()
|
|
||||||
/// },
|
|
||||||
/// // specify theme
|
|
||||||
/// theme: String::from("Solarized (dark)"),
|
|
||||||
/// inner: DefaultHtmlHandler,
|
|
||||||
/// ..Default::default()
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// // Make sure to check if theme presents or it will panic at runtime
|
|
||||||
/// if handler.theme_set.themes.contains_key("dont-exists") {
|
|
||||||
///
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub struct SyntectHtmlHandler<E: From<Error>, H: HtmlHandler<E>> {
|
|
||||||
/// syntax set, default is `SyntaxSet::load_defaults_newlines()`
|
|
||||||
pub syntax_set: SyntaxSet,
|
|
||||||
/// theme set, default is `ThemeSet::load_defaults()`
|
|
||||||
pub theme_set: ThemeSet,
|
|
||||||
/// theme used for highlighting, default is `"InspiredGitHub"`
|
|
||||||
pub theme: String,
|
|
||||||
/// inner html handler
|
|
||||||
pub inner: H,
|
|
||||||
/// background color, default is `IncludeBackground::No`
|
|
||||||
pub background: IncludeBackground,
|
|
||||||
/// handler error type
|
|
||||||
pub error_type: PhantomData<E>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: From<Error>, H: HtmlHandler<E>> SyntectHtmlHandler<E, H> {
|
|
||||||
pub fn new(inner: H) -> Self {
|
|
||||||
SyntectHtmlHandler {
|
|
||||||
inner,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn highlight(&self, language: Option<&str>, content: &str) -> String {
|
|
||||||
let mut highlighter = HighlightLines::new(
|
|
||||||
language
|
|
||||||
.and_then(|lang| self.syntax_set.find_syntax_by_token(lang))
|
|
||||||
.unwrap_or_else(|| self.syntax_set.find_syntax_plain_text()),
|
|
||||||
&self.theme_set.themes[&self.theme],
|
|
||||||
);
|
|
||||||
let regions = highlighter.highlight(content, &self.syntax_set);
|
|
||||||
styled_line_to_highlighted_html(®ions[..], self.background)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: From<Error>, H: HtmlHandler<E>> Default for SyntectHtmlHandler<E, H> {
|
|
||||||
fn default() -> Self {
|
|
||||||
SyntectHtmlHandler {
|
|
||||||
syntax_set: SyntaxSet::load_defaults_newlines(),
|
|
||||||
theme_set: ThemeSet::load_defaults(),
|
|
||||||
theme: String::from("InspiredGitHub"),
|
|
||||||
inner: H::default(),
|
|
||||||
background: IncludeBackground::No,
|
|
||||||
error_type: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: From<Error>, H: HtmlHandler<E>> HtmlHandler<E> for SyntectHtmlHandler<E, H> {
|
|
||||||
fn start<W: Write>(&mut self, mut w: W, element: &Element) -> Result<(), E> {
|
|
||||||
match element {
|
|
||||||
Element::InlineSrc(inline_src) => write!(
|
|
||||||
w,
|
|
||||||
"<code>{}</code>",
|
|
||||||
self.highlight(Some(&inline_src.lang), &inline_src.body)
|
|
||||||
)?,
|
|
||||||
Element::SourceBlock(block) => {
|
|
||||||
if block.language.is_empty() {
|
|
||||||
write!(w, "<pre class=\"example\">{}</pre>", block.contents)?;
|
|
||||||
} else {
|
|
||||||
write!(
|
|
||||||
w,
|
|
||||||
"<div class=\"org-src-container\"><pre class=\"src src-{}\">{}</pre></div>",
|
|
||||||
block.language,
|
|
||||||
self.highlight(Some(&block.language), &block.contents)
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::FixedWidth(fixed_width) => write!(
|
|
||||||
w,
|
|
||||||
"<pre class=\"example\">{}</pre>",
|
|
||||||
self.highlight(None, &fixed_width.value)
|
|
||||||
)?,
|
|
||||||
Element::ExampleBlock(block) => write!(
|
|
||||||
w,
|
|
||||||
"<pre class=\"example\">{}</pre>",
|
|
||||||
self.highlight(None, &block.contents)
|
|
||||||
)?,
|
|
||||||
_ => self.inner.start(w, element)?,
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn end<W: Write>(&mut self, w: W, element: &Element) -> Result<(), E> {
|
|
||||||
self.inner.end(w, element)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "syntect")]
|
|
||||||
pub use syntect_handler::SyntectHtmlHandler;
|
|
|
@ -1,31 +0,0 @@
|
||||||
//! Export `Org` struct to various formats.
|
|
||||||
|
|
||||||
mod html;
|
|
||||||
mod org;
|
|
||||||
|
|
||||||
#[cfg(feature = "syntect")]
|
|
||||||
pub use html::SyntectHtmlHandler;
|
|
||||||
pub use html::{DefaultHtmlHandler, HtmlEscape, HtmlHandler};
|
|
||||||
pub use org::{DefaultOrgHandler, OrgHandler};
|
|
||||||
|
|
||||||
use std::io::{Error, Write};
|
|
||||||
|
|
||||||
use crate::elements::Datetime;
|
|
||||||
|
|
||||||
pub(crate) fn write_datetime<W: Write>(
|
|
||||||
mut w: W,
|
|
||||||
start: &str,
|
|
||||||
datetime: &Datetime,
|
|
||||||
end: &str,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
write!(w, "{}", start)?;
|
|
||||||
write!(
|
|
||||||
w,
|
|
||||||
"{}-{:02}-{:02} {}",
|
|
||||||
datetime.year, datetime.month, datetime.day, datetime.dayname
|
|
||||||
)?;
|
|
||||||
if let (Some(hour), Some(minute)) = (datetime.hour, datetime.minute) {
|
|
||||||
write!(w, " {:02}:{:02}", hour, minute)?;
|
|
||||||
}
|
|
||||||
write!(w, "{}", end)
|
|
||||||
}
|
|
|
@ -1,321 +0,0 @@
|
||||||
use std::io::{Error, Result as IOResult, Write};
|
|
||||||
|
|
||||||
use crate::elements::{Clock, Element, Table, Timestamp};
|
|
||||||
use crate::export::write_datetime;
|
|
||||||
|
|
||||||
pub trait OrgHandler<E: From<Error>>: Default {
|
|
||||||
fn start<W: Write>(&mut self, w: W, element: &Element) -> Result<(), E>;
|
|
||||||
fn end<W: Write>(&mut self, w: W, element: &Element) -> Result<(), E>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct DefaultOrgHandler;
|
|
||||||
|
|
||||||
impl OrgHandler<Error> for DefaultOrgHandler {
|
|
||||||
fn start<W: Write>(&mut self, mut w: W, element: &Element) -> IOResult<()> {
|
|
||||||
match element {
|
|
||||||
// container elements
|
|
||||||
Element::SpecialBlock(block) => {
|
|
||||||
writeln!(w, "#+BEGIN_{}", block.name)?;
|
|
||||||
write_blank_lines(&mut w, block.pre_blank)?;
|
|
||||||
}
|
|
||||||
Element::QuoteBlock(block) => {
|
|
||||||
writeln!(&mut w, "#+BEGIN_QUOTE")?;
|
|
||||||
write_blank_lines(&mut w, block.pre_blank)?;
|
|
||||||
}
|
|
||||||
Element::CenterBlock(block) => {
|
|
||||||
writeln!(&mut w, "#+BEGIN_CENTER")?;
|
|
||||||
write_blank_lines(&mut w, block.pre_blank)?;
|
|
||||||
}
|
|
||||||
Element::VerseBlock(block) => {
|
|
||||||
writeln!(&mut w, "#+BEGIN_VERSE")?;
|
|
||||||
write_blank_lines(&mut w, block.pre_blank)?;
|
|
||||||
}
|
|
||||||
Element::Bold => write!(w, "*")?,
|
|
||||||
Element::Document { pre_blank } => {
|
|
||||||
write_blank_lines(w, *pre_blank)?;
|
|
||||||
}
|
|
||||||
Element::DynBlock(dyn_block) => {
|
|
||||||
write!(&mut w, "#+BEGIN: {}", dyn_block.block_name)?;
|
|
||||||
if let Some(parameters) = &dyn_block.arguments {
|
|
||||||
write!(&mut w, " {}", parameters)?;
|
|
||||||
}
|
|
||||||
write_blank_lines(&mut w, dyn_block.pre_blank + 1)?;
|
|
||||||
}
|
|
||||||
Element::Headline { .. } => (),
|
|
||||||
Element::List(_list) => (),
|
|
||||||
Element::Italic => write!(w, "/")?,
|
|
||||||
Element::ListItem(list_item) => {
|
|
||||||
for _ in 0..list_item.indent {
|
|
||||||
write!(&mut w, " ")?;
|
|
||||||
}
|
|
||||||
write!(&mut w, "{}", list_item.bullet)?;
|
|
||||||
}
|
|
||||||
Element::Paragraph { .. } => (),
|
|
||||||
Element::Section => (),
|
|
||||||
Element::Strike => write!(w, "+")?,
|
|
||||||
Element::Underline => write!(w, "_")?,
|
|
||||||
Element::Drawer(drawer) => {
|
|
||||||
writeln!(&mut w, ":{}:", drawer.name)?;
|
|
||||||
write_blank_lines(&mut w, drawer.pre_blank)?;
|
|
||||||
}
|
|
||||||
// non-container elements
|
|
||||||
Element::CommentBlock(block) => {
|
|
||||||
writeln!(&mut w, "#+BEGIN_COMMENT")?;
|
|
||||||
write!(&mut w, "{}", block.contents)?;
|
|
||||||
writeln!(&mut w, "#+END_COMMENT")?;
|
|
||||||
write_blank_lines(&mut w, block.post_blank)?;
|
|
||||||
}
|
|
||||||
Element::ExampleBlock(block) => {
|
|
||||||
writeln!(&mut w, "#+BEGIN_EXAMPLE")?;
|
|
||||||
write!(&mut w, "{}", block.contents)?;
|
|
||||||
writeln!(&mut w, "#+END_EXAMPLE")?;
|
|
||||||
write_blank_lines(&mut w, block.post_blank)?;
|
|
||||||
}
|
|
||||||
Element::ExportBlock(block) => {
|
|
||||||
writeln!(&mut w, "#+BEGIN_EXPORT {}", block.data)?;
|
|
||||||
write!(&mut w, "{}", block.contents)?;
|
|
||||||
writeln!(&mut w, "#+END_EXPORT")?;
|
|
||||||
write_blank_lines(&mut w, block.post_blank)?;
|
|
||||||
}
|
|
||||||
Element::SourceBlock(block) => {
|
|
||||||
writeln!(&mut w, "#+BEGIN_SRC {}", block.language)?;
|
|
||||||
write!(&mut w, "{}", block.contents)?;
|
|
||||||
writeln!(&mut w, "#+END_SRC")?;
|
|
||||||
write_blank_lines(&mut w, block.post_blank)?;
|
|
||||||
}
|
|
||||||
Element::BabelCall(call) => {
|
|
||||||
writeln!(&mut w, "#+CALL: {}", call.value)?;
|
|
||||||
write_blank_lines(w, call.post_blank)?;
|
|
||||||
}
|
|
||||||
Element::InlineSrc(inline_src) => {
|
|
||||||
write!(&mut w, "src_{}", inline_src.lang)?;
|
|
||||||
if let Some(options) = &inline_src.options {
|
|
||||||
write!(&mut w, "[{}]", options)?;
|
|
||||||
}
|
|
||||||
write!(&mut w, "{{{}}}", inline_src.body)?;
|
|
||||||
}
|
|
||||||
Element::Code { value } => write!(w, "~{}~", value)?,
|
|
||||||
Element::FnRef(fn_ref) => {
|
|
||||||
write!(&mut w, "[fn:{}", fn_ref.label)?;
|
|
||||||
if let Some(definition) = &fn_ref.definition {
|
|
||||||
write!(&mut w, ":{}", definition)?;
|
|
||||||
}
|
|
||||||
write!(&mut w, "]")?;
|
|
||||||
}
|
|
||||||
Element::InlineCall(inline_call) => {
|
|
||||||
write!(&mut w, "call_{}", inline_call.name)?;
|
|
||||||
if let Some(header) = &inline_call.inside_header {
|
|
||||||
write!(&mut w, "[{}]", header)?;
|
|
||||||
}
|
|
||||||
write!(&mut w, "({})", inline_call.arguments)?;
|
|
||||||
if let Some(header) = &inline_call.end_header {
|
|
||||||
write!(&mut w, "[{}]", header)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::Link(link) => {
|
|
||||||
write!(&mut w, "[[{}]", link.path)?;
|
|
||||||
if let Some(desc) = &link.desc {
|
|
||||||
write!(&mut w, "[{}]", desc)?;
|
|
||||||
}
|
|
||||||
write!(&mut w, "]")?;
|
|
||||||
}
|
|
||||||
Element::Macros(_macros) => (),
|
|
||||||
Element::RadioTarget => (),
|
|
||||||
Element::Snippet(snippet) => write!(w, "@@{}:{}@@", snippet.name, snippet.value)?,
|
|
||||||
Element::Target(_target) => (),
|
|
||||||
Element::Text { value } => write!(w, "{}", value)?,
|
|
||||||
Element::Timestamp(timestamp) => {
|
|
||||||
write_timestamp(&mut w, ×tamp)?;
|
|
||||||
}
|
|
||||||
Element::Verbatim { value } => write!(w, "={}=", value)?,
|
|
||||||
Element::FnDef(fn_def) => {
|
|
||||||
write_blank_lines(w, fn_def.post_blank)?;
|
|
||||||
}
|
|
||||||
Element::Clock(clock) => {
|
|
||||||
write!(w, "CLOCK: ")?;
|
|
||||||
|
|
||||||
match clock {
|
|
||||||
Clock::Closed {
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
duration,
|
|
||||||
post_blank,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
write_datetime(&mut w, "[", &start, "]--")?;
|
|
||||||
write_datetime(&mut w, "[", &end, "]")?;
|
|
||||||
writeln!(&mut w, " => {}", duration)?;
|
|
||||||
write_blank_lines(&mut w, *post_blank)?;
|
|
||||||
}
|
|
||||||
Clock::Running {
|
|
||||||
start, post_blank, ..
|
|
||||||
} => {
|
|
||||||
write_datetime(&mut w, "[", &start, "]\n")?;
|
|
||||||
write_blank_lines(&mut w, *post_blank)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::Comment(comment) => {
|
|
||||||
write!(w, "{}", comment.value)?;
|
|
||||||
write_blank_lines(&mut w, comment.post_blank)?;
|
|
||||||
}
|
|
||||||
Element::FixedWidth(fixed_width) => {
|
|
||||||
write!(&mut w, "{}", fixed_width.value)?;
|
|
||||||
write_blank_lines(&mut w, fixed_width.post_blank)?;
|
|
||||||
}
|
|
||||||
Element::Keyword(keyword) => {
|
|
||||||
write!(&mut w, "#+{}", keyword.key)?;
|
|
||||||
if let Some(optional) = &keyword.optional {
|
|
||||||
write!(&mut w, "[{}]", optional)?;
|
|
||||||
}
|
|
||||||
writeln!(&mut w, ": {}", keyword.value)?;
|
|
||||||
write_blank_lines(&mut w, keyword.post_blank)?;
|
|
||||||
}
|
|
||||||
Element::Rule(rule) => {
|
|
||||||
writeln!(w, "-----")?;
|
|
||||||
write_blank_lines(&mut w, rule.post_blank)?;
|
|
||||||
}
|
|
||||||
Element::Cookie(_cookie) => (),
|
|
||||||
Element::Title(title) => {
|
|
||||||
for _ in 0..title.level {
|
|
||||||
write!(&mut w, "*")?;
|
|
||||||
}
|
|
||||||
if let Some(keyword) = &title.keyword {
|
|
||||||
write!(&mut w, " {}", keyword)?;
|
|
||||||
}
|
|
||||||
if let Some(priority) = title.priority {
|
|
||||||
write!(&mut w, " [#{}]", priority)?;
|
|
||||||
}
|
|
||||||
write!(&mut w, " ")?;
|
|
||||||
}
|
|
||||||
Element::Table(_) => (),
|
|
||||||
Element::TableRow(_) => (),
|
|
||||||
Element::TableCell(_) => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn end<W: Write>(&mut self, mut w: W, element: &Element) -> IOResult<()> {
|
|
||||||
match element {
|
|
||||||
// container elements
|
|
||||||
Element::SpecialBlock(block) => {
|
|
||||||
writeln!(&mut w, "#+END_{}", block.name)?;
|
|
||||||
write_blank_lines(&mut w, block.post_blank)?;
|
|
||||||
}
|
|
||||||
Element::QuoteBlock(block) => {
|
|
||||||
writeln!(&mut w, "#+END_QUOTE")?;
|
|
||||||
write_blank_lines(&mut w, block.post_blank)?;
|
|
||||||
}
|
|
||||||
Element::CenterBlock(block) => {
|
|
||||||
writeln!(&mut w, "#+END_CENTER")?;
|
|
||||||
write_blank_lines(&mut w, block.post_blank)?;
|
|
||||||
}
|
|
||||||
Element::VerseBlock(block) => {
|
|
||||||
writeln!(&mut w, "#+END_VERSE")?;
|
|
||||||
write_blank_lines(&mut w, block.post_blank)?;
|
|
||||||
}
|
|
||||||
Element::Bold => write!(w, "*")?,
|
|
||||||
Element::Document { .. } => (),
|
|
||||||
Element::DynBlock(dyn_block) => {
|
|
||||||
writeln!(w, "#+END:")?;
|
|
||||||
write_blank_lines(w, dyn_block.post_blank)?;
|
|
||||||
}
|
|
||||||
Element::Headline { .. } => (),
|
|
||||||
Element::List(list) => {
|
|
||||||
write_blank_lines(w, list.post_blank)?;
|
|
||||||
}
|
|
||||||
Element::Italic => write!(w, "/")?,
|
|
||||||
Element::ListItem(_) => (),
|
|
||||||
Element::Paragraph { post_blank } => {
|
|
||||||
write_blank_lines(w, post_blank + 1)?;
|
|
||||||
}
|
|
||||||
Element::Section => (),
|
|
||||||
Element::Strike => write!(w, "+")?,
|
|
||||||
Element::Underline => write!(w, "_")?,
|
|
||||||
Element::Drawer(drawer) => {
|
|
||||||
writeln!(&mut w, ":END:")?;
|
|
||||||
write_blank_lines(&mut w, drawer.post_blank)?;
|
|
||||||
}
|
|
||||||
Element::Title(title) => {
|
|
||||||
if !title.tags.is_empty() {
|
|
||||||
write!(&mut w, " :")?;
|
|
||||||
for tag in &title.tags {
|
|
||||||
write!(&mut w, "{}:", tag)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeln!(&mut w)?;
|
|
||||||
if let Some(planning) = &title.planning {
|
|
||||||
if let Some(scheduled) = &planning.scheduled {
|
|
||||||
write!(&mut w, "SCHEDULED: ")?;
|
|
||||||
write_timestamp(&mut w, &scheduled)?;
|
|
||||||
}
|
|
||||||
if let Some(deadline) = &planning.deadline {
|
|
||||||
if planning.scheduled.is_some() {
|
|
||||||
write!(&mut w, " ")?;
|
|
||||||
}
|
|
||||||
write!(&mut w, "DEADLINE: ")?;
|
|
||||||
write_timestamp(&mut w, &deadline)?;
|
|
||||||
}
|
|
||||||
if let Some(closed) = &planning.closed {
|
|
||||||
if planning.deadline.is_some() {
|
|
||||||
write!(&mut w, " ")?;
|
|
||||||
}
|
|
||||||
write!(&mut w, "CLOSED: ")?;
|
|
||||||
write_timestamp(&mut w, &closed)?;
|
|
||||||
}
|
|
||||||
writeln!(&mut w)?;
|
|
||||||
}
|
|
||||||
if !title.properties.is_empty() {
|
|
||||||
writeln!(&mut w, ":PROPERTIES:")?;
|
|
||||||
for (key, value) in title.properties.iter() {
|
|
||||||
writeln!(&mut w, ":{}: {}", key, value)?;
|
|
||||||
}
|
|
||||||
writeln!(&mut w, ":END:")?;
|
|
||||||
}
|
|
||||||
write_blank_lines(&mut w, title.post_blank)?;
|
|
||||||
}
|
|
||||||
Element::Table(Table::Org { post_blank, .. }) => {
|
|
||||||
write_blank_lines(w, *post_blank)?;
|
|
||||||
}
|
|
||||||
Element::Table(Table::TableEl { post_blank, .. }) => {
|
|
||||||
write_blank_lines(w, *post_blank)?;
|
|
||||||
}
|
|
||||||
Element::TableRow(_) => (),
|
|
||||||
Element::TableCell(_) => (),
|
|
||||||
// non-container elements
|
|
||||||
_ => debug_assert!(!element.is_container()),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_blank_lines<W: Write>(mut w: W, count: usize) -> Result<(), Error> {
|
|
||||||
for _ in 0..count {
|
|
||||||
writeln!(w)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_timestamp<W: Write>(mut w: W, timestamp: &Timestamp) -> Result<(), Error> {
|
|
||||||
match timestamp {
|
|
||||||
Timestamp::Active { start, .. } => {
|
|
||||||
write_datetime(w, "<", start, ">")?;
|
|
||||||
}
|
|
||||||
Timestamp::Inactive { start, .. } => {
|
|
||||||
write_datetime(w, "[", start, "]")?;
|
|
||||||
}
|
|
||||||
Timestamp::ActiveRange { start, end, .. } => {
|
|
||||||
write_datetime(&mut w, "<", start, ">--")?;
|
|
||||||
write_datetime(&mut w, "<", end, ">")?;
|
|
||||||
}
|
|
||||||
Timestamp::InactiveRange { start, end, .. } => {
|
|
||||||
write_datetime(&mut w, "[", start, "]--")?;
|
|
||||||
write_datetime(&mut w, "[", end, "]")?;
|
|
||||||
}
|
|
||||||
Timestamp::Diary { value } => write!(w, "<%%({})>", value)?,
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
1219
src/headline.rs
1219
src/headline.rs
File diff suppressed because it is too large
Load diff
245
src/lib.rs
245
src/lib.rs
|
@ -1,245 +0,0 @@
|
||||||
//! A Rust library for parsing orgmode files.
|
|
||||||
//!
|
|
||||||
//! [Live demo](https://orgize.herokuapp.com/)
|
|
||||||
//!
|
|
||||||
//! # Parse
|
|
||||||
//!
|
|
||||||
//! To parse a orgmode string, simply invoking the [`Org::parse`] function:
|
|
||||||
//!
|
|
||||||
//! [`Org::parse`]: struct.Org.html#method.parse
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! use orgize::Org;
|
|
||||||
//!
|
|
||||||
//! Org::parse("* DONE Title :tag:");
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! or [`Org::parse_custom`]:
|
|
||||||
//!
|
|
||||||
//! [`Org::parse_custom`]: struct.Org.html#method.parse_custom
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! use orgize::{Org, ParseConfig};
|
|
||||||
//!
|
|
||||||
//! Org::parse_custom(
|
|
||||||
//! "* TASK Title 1",
|
|
||||||
//! &ParseConfig {
|
|
||||||
//! // custom todo keywords
|
|
||||||
//! todo_keywords: (vec!["TASK".to_string()], vec![]),
|
|
||||||
//! ..Default::default()
|
|
||||||
//! },
|
|
||||||
//! );
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! # Iter
|
|
||||||
//!
|
|
||||||
//! [`Org::iter`] function will returns an iterator of [`Event`]s, which is
|
|
||||||
//! a simple wrapper of [`Element`].
|
|
||||||
//!
|
|
||||||
//! [`Org::iter`]: struct.Org.html#method.iter
|
|
||||||
//! [`Event`]: enum.Event.html
|
|
||||||
//! [`Element`]: elements/enum.Element.html
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! use orgize::Org;
|
|
||||||
//!
|
|
||||||
//! for event in Org::parse("* DONE Title :tag:").iter() {
|
|
||||||
//! // handling the event
|
|
||||||
//! }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! **Note**: whether an element is container or not, it will appears twice in one loop.
|
|
||||||
//! One as [`Event::Start(element)`], one as [`Event::End(element)`].
|
|
||||||
//!
|
|
||||||
//! [`Event::Start(element)`]: enum.Event.html#variant.Start
|
|
||||||
//! [`Event::End(element)`]: enum.Event.html#variant.End
|
|
||||||
//!
|
|
||||||
//! # Render html
|
|
||||||
//!
|
|
||||||
//! You can call the [`Org::write_html`] function to generate html directly, which
|
|
||||||
//! uses the [`DefaultHtmlHandler`] internally:
|
|
||||||
//!
|
|
||||||
//! [`Org::write_html`]: struct.Org.html#method.write_html
|
|
||||||
//! [`DefaultHtmlHandler`]: export/struct.DefaultHtmlHandler.html
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! use orgize::Org;
|
|
||||||
//!
|
|
||||||
//! let mut writer = Vec::new();
|
|
||||||
//! Org::parse("* title\n*section*").write_html(&mut writer).unwrap();
|
|
||||||
//!
|
|
||||||
//! assert_eq!(
|
|
||||||
//! String::from_utf8(writer).unwrap(),
|
|
||||||
//! "<main><h1>title</h1><section><p><b>section</b></p></section></main>"
|
|
||||||
//! );
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! # Render html with custom `HtmlHandler`
|
|
||||||
//!
|
|
||||||
//! To customize html rendering, simply implementing [`HtmlHandler`] trait and passing
|
|
||||||
//! it to the [`Org::write_html_custom`] function.
|
|
||||||
//!
|
|
||||||
//! [`HtmlHandler`]: export/trait.HtmlHandler.html
|
|
||||||
//! [`Org::write_html_custom`]: struct.Org.html#method.write_html_custom
|
|
||||||
//!
|
|
||||||
//! The following code demonstrates how to add a id for every headline and return
|
|
||||||
//! own error type while rendering.
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! use std::convert::From;
|
|
||||||
//! use std::io::{Error as IOError, Write};
|
|
||||||
//! use std::string::FromUtf8Error;
|
|
||||||
//!
|
|
||||||
//! use orgize::export::{DefaultHtmlHandler, HtmlHandler};
|
|
||||||
//! use orgize::{Element, Org};
|
|
||||||
//! use slugify::slugify;
|
|
||||||
//!
|
|
||||||
//! #[derive(Debug)]
|
|
||||||
//! enum MyError {
|
|
||||||
//! IO(IOError),
|
|
||||||
//! Heading,
|
|
||||||
//! Utf8(FromUtf8Error),
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! // From<std::io::Error> trait is required for custom error type
|
|
||||||
//! impl From<IOError> for MyError {
|
|
||||||
//! fn from(err: IOError) -> Self {
|
|
||||||
//! MyError::IO(err)
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! impl From<FromUtf8Error> for MyError {
|
|
||||||
//! fn from(err: FromUtf8Error) -> Self {
|
|
||||||
//! MyError::Utf8(err)
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! #[derive(Default)]
|
|
||||||
//! struct MyHtmlHandler(DefaultHtmlHandler);
|
|
||||||
//!
|
|
||||||
//! impl HtmlHandler<MyError> for MyHtmlHandler {
|
|
||||||
//! fn start<W: Write>(&mut self, mut w: W, element: &Element) -> Result<(), MyError> {
|
|
||||||
//! if let Element::Title(title) = element {
|
|
||||||
//! if title.level > 6 {
|
|
||||||
//! return Err(MyError::Heading);
|
|
||||||
//! } else {
|
|
||||||
//! write!(
|
|
||||||
//! w,
|
|
||||||
//! "<h{0}><a id=\"{1}\" href=\"#{1}\">",
|
|
||||||
//! title.level,
|
|
||||||
//! slugify!(&title.raw),
|
|
||||||
//! )?;
|
|
||||||
//! }
|
|
||||||
//! } else {
|
|
||||||
//! // fallthrough to default handler
|
|
||||||
//! self.0.start(w, element)?;
|
|
||||||
//! }
|
|
||||||
//! Ok(())
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! fn end<W: Write>(&mut self, mut w: W, element: &Element) -> Result<(), MyError> {
|
|
||||||
//! if let Element::Title(title) = element {
|
|
||||||
//! write!(w, "</a></h{}>", title.level)?;
|
|
||||||
//! } else {
|
|
||||||
//! self.0.end(w, element)?;
|
|
||||||
//! }
|
|
||||||
//! Ok(())
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! fn main() -> Result<(), MyError> {
|
|
||||||
//! let mut writer = Vec::new();
|
|
||||||
//! let mut handler = MyHtmlHandler::default();
|
|
||||||
//! Org::parse("* title\n*section*").write_html_custom(&mut writer, &mut handler)?;
|
|
||||||
//!
|
|
||||||
//! assert_eq!(
|
|
||||||
//! String::from_utf8(writer)?,
|
|
||||||
//! "<main><h1><a id=\"title\" href=\"#title\">title</a></h1>\
|
|
||||||
//! <section><p><b>section</b></p></section></main>"
|
|
||||||
//! );
|
|
||||||
//!
|
|
||||||
//! Ok(())
|
|
||||||
//! }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! **Note**: as I mentioned above, each element will appears two times while iterating.
|
|
||||||
//! And handler will silently ignores all end events from non-container elements.
|
|
||||||
//!
|
|
||||||
//! So if you want to change how a non-container element renders, just redefine the `start`
|
|
||||||
//! function and leave the `end` function unchanged.
|
|
||||||
//!
|
|
||||||
//! # Serde
|
|
||||||
//!
|
|
||||||
//! `Org` struct have already implemented serde's `Serialize` trait. It means you can
|
|
||||||
//! serialize it into any format supported by serde, such as json:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! use orgize::Org;
|
|
||||||
//! use serde_json::{json, to_string};
|
|
||||||
//!
|
|
||||||
//! let org = Org::parse("I 'm *bold*.");
|
|
||||||
//! #[cfg(feature = "ser")]
|
|
||||||
//! println!("{}", to_string(&org).unwrap());
|
|
||||||
//!
|
|
||||||
//! // {
|
|
||||||
//! // "type": "document",
|
|
||||||
//! // "children": [{
|
|
||||||
//! // "type": "section",
|
|
||||||
//! // "children": [{
|
|
||||||
//! // "type": "paragraph",
|
|
||||||
//! // "children":[{
|
|
||||||
//! // "type": "text",
|
|
||||||
//! // "value":"I 'm "
|
|
||||||
//! // }, {
|
|
||||||
//! // "type": "bold",
|
|
||||||
//! // "children":[{
|
|
||||||
//! // "type": "text",
|
|
||||||
//! // "value": "bold"
|
|
||||||
//! // }]
|
|
||||||
//! // }, {
|
|
||||||
//! // "type":"text",
|
|
||||||
//! // "value":"."
|
|
||||||
//! // }]
|
|
||||||
//! // }]
|
|
||||||
//! // }]
|
|
||||||
//! // }
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! # Features
|
|
||||||
//!
|
|
||||||
//! By now, orgize provides three features:
|
|
||||||
//!
|
|
||||||
//! + `ser`: adds the ability to serialize `Org` and other elements using `serde`, enabled by default.
|
|
||||||
//!
|
|
||||||
//! + `chrono`: adds the ability to convert `Datetime` into `chrono` structs, disabled by default.
|
|
||||||
//!
|
|
||||||
//! + `syntect`: provides [`SyntectHtmlHandler`] for highlighting code block, disabled by default.
|
|
||||||
//!
|
|
||||||
//! [`SyntectHtmlHandler`]: export/struct.SyntectHtmlHandler.html
|
|
||||||
//!
|
|
||||||
//! # License
|
|
||||||
//!
|
|
||||||
//! MIT
|
|
||||||
|
|
||||||
mod config;
|
|
||||||
pub mod elements;
|
|
||||||
pub mod export;
|
|
||||||
mod headline;
|
|
||||||
mod org;
|
|
||||||
mod parse;
|
|
||||||
mod parsers;
|
|
||||||
mod validate;
|
|
||||||
|
|
||||||
// Re-export of the indextree crate.
|
|
||||||
pub use indextree;
|
|
||||||
#[cfg(feature = "syntect")]
|
|
||||||
pub use syntect;
|
|
||||||
|
|
||||||
pub use config::ParseConfig;
|
|
||||||
pub use elements::Element;
|
|
||||||
pub use headline::{Document, Headline};
|
|
||||||
pub use org::{Event, Org};
|
|
||||||
pub use validate::ValidationError;
|
|
||||||
|
|
||||||
#[cfg(feature = "wasm")]
|
|
||||||
mod wasm;
|
|
193
src/org.rs
193
src/org.rs
|
@ -1,193 +0,0 @@
|
||||||
use indextree::{Arena, NodeEdge, NodeId};
|
|
||||||
use std::io::{Error, Write};
|
|
||||||
use std::ops::{Index, IndexMut};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
config::{ParseConfig, DEFAULT_CONFIG},
|
|
||||||
elements::{Element, Keyword},
|
|
||||||
export::{DefaultHtmlHandler, DefaultOrgHandler, HtmlHandler, OrgHandler},
|
|
||||||
parsers::{blank_lines_count, parse_container, Container, OwnedArena},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Org<'a> {
|
|
||||||
pub(crate) arena: Arena<Element<'a>>,
|
|
||||||
pub(crate) root: NodeId,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Event<'a, 'b> {
|
|
||||||
Start(&'b Element<'a>),
|
|
||||||
End(&'b Element<'a>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Org<'a> {
|
|
||||||
/// Creates a new empty `Org` struct.
|
|
||||||
pub fn new() -> Org<'static> {
|
|
||||||
let mut arena = Arena::new();
|
|
||||||
let root = arena.new_node(Element::Document { pre_blank: 0 });
|
|
||||||
Org { arena, root }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses string `text` into `Org` struct.
|
|
||||||
pub fn parse(text: &'a str) -> Org<'a> {
|
|
||||||
Org::parse_custom(text, &DEFAULT_CONFIG)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Likes `parse`, but accepts `String`.
|
|
||||||
pub fn parse_string(text: String) -> Org<'static> {
|
|
||||||
Org::parse_string_custom(text, &DEFAULT_CONFIG)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses string `text` into `Org` struct with custom `ParseConfig`.
|
|
||||||
pub fn parse_custom(text: &'a str, config: &ParseConfig) -> Org<'a> {
|
|
||||||
let mut arena = Arena::new();
|
|
||||||
let (text, pre_blank) = blank_lines_count(text);
|
|
||||||
let root = arena.new_node(Element::Document { pre_blank });
|
|
||||||
let mut org = Org { arena, root };
|
|
||||||
|
|
||||||
parse_container(
|
|
||||||
&mut org.arena,
|
|
||||||
Container::Document {
|
|
||||||
content: text,
|
|
||||||
node: org.root,
|
|
||||||
},
|
|
||||||
config,
|
|
||||||
);
|
|
||||||
|
|
||||||
org.debug_validate();
|
|
||||||
|
|
||||||
org
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Likes `parse_custom`, but accepts `String`.
|
|
||||||
pub fn parse_string_custom(text: String, config: &ParseConfig) -> Org<'static> {
|
|
||||||
let mut arena = Arena::new();
|
|
||||||
let (text, pre_blank) = blank_lines_count(&text);
|
|
||||||
let root = arena.new_node(Element::Document { pre_blank });
|
|
||||||
let mut org = Org { arena, root };
|
|
||||||
|
|
||||||
parse_container(
|
|
||||||
&mut OwnedArena::new(&mut org.arena),
|
|
||||||
Container::Document {
|
|
||||||
content: text,
|
|
||||||
node: org.root,
|
|
||||||
},
|
|
||||||
config,
|
|
||||||
);
|
|
||||||
|
|
||||||
org.debug_validate();
|
|
||||||
|
|
||||||
org
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference to the underlay arena.
|
|
||||||
pub fn arena(&self) -> &Arena<Element<'a>> {
|
|
||||||
&self.arena
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a mutual reference to the underlay arena.
|
|
||||||
pub fn arena_mut(&mut self) -> &mut Arena<Element<'a>> {
|
|
||||||
&mut self.arena
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an iterator of `Event`s.
|
|
||||||
pub fn iter<'b>(&'b self) -> impl Iterator<Item = Event<'a, 'b>> + 'b {
|
|
||||||
self.root.traverse(&self.arena).map(move |edge| match edge {
|
|
||||||
NodeEdge::Start(node) => Event::Start(&self[node]),
|
|
||||||
NodeEdge::End(node) => Event::End(&self[node]),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an iterator of `Keyword`s.
|
|
||||||
pub fn keywords(&self) -> impl Iterator<Item = &Keyword<'_>> {
|
|
||||||
self.root
|
|
||||||
.descendants(&self.arena)
|
|
||||||
.skip(1)
|
|
||||||
.filter_map(move |node| match &self[node] {
|
|
||||||
Element::Keyword(kw) => Some(kw),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes an `Org` struct as html format.
|
|
||||||
pub fn write_html<W>(&self, writer: W) -> Result<(), Error>
|
|
||||||
where
|
|
||||||
W: Write,
|
|
||||||
{
|
|
||||||
self.write_html_custom(writer, &mut DefaultHtmlHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes an `Org` struct as html format with custom `HtmlHandler`.
|
|
||||||
pub fn write_html_custom<W, H, E>(&self, mut writer: W, handler: &mut H) -> Result<(), E>
|
|
||||||
where
|
|
||||||
W: Write,
|
|
||||||
E: From<Error>,
|
|
||||||
H: HtmlHandler<E>,
|
|
||||||
{
|
|
||||||
for event in self.iter() {
|
|
||||||
match event {
|
|
||||||
Event::Start(element) => handler.start(&mut writer, element)?,
|
|
||||||
Event::End(element) => handler.end(&mut writer, element)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes an `Org` struct as org format.
|
|
||||||
pub fn write_org<W>(&self, writer: W) -> Result<(), Error>
|
|
||||||
where
|
|
||||||
W: Write,
|
|
||||||
{
|
|
||||||
self.write_org_custom(writer, &mut DefaultOrgHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes an `Org` struct as org format with custom `OrgHandler`.
|
|
||||||
pub fn write_org_custom<W, H, E>(&self, mut writer: W, handler: &mut H) -> Result<(), E>
|
|
||||||
where
|
|
||||||
W: Write,
|
|
||||||
E: From<Error>,
|
|
||||||
H: OrgHandler<E>,
|
|
||||||
{
|
|
||||||
for event in self.iter() {
|
|
||||||
match event {
|
|
||||||
Event::Start(element) => handler.start(&mut writer, element)?,
|
|
||||||
Event::End(element) => handler.end(&mut writer, element)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Org<'static> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Org::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Index<NodeId> for Org<'a> {
|
|
||||||
type Output = Element<'a>;
|
|
||||||
|
|
||||||
fn index(&self, node_id: NodeId) -> &Self::Output {
|
|
||||||
self.arena[node_id].get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IndexMut<NodeId> for Org<'a> {
|
|
||||||
fn index_mut(&mut self, node_id: NodeId) -> &mut Self::Output {
|
|
||||||
self.arena[node_id].get_mut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ser")]
|
|
||||||
use serde::{ser::Serializer, Serialize};
|
|
||||||
|
|
||||||
#[cfg(feature = "ser")]
|
|
||||||
impl Serialize for Org<'_> {
|
|
||||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
|
||||||
use serde_indextree::Node;
|
|
||||||
|
|
||||||
serializer.serialize_newtype_struct("Org", &Node::new(self.root, &self.arena))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,136 +0,0 @@
|
||||||
//! Parsers combinators
|
|
||||||
|
|
||||||
use memchr::memchr;
|
|
||||||
use nom::{
|
|
||||||
bytes::complete::take_while1,
|
|
||||||
combinator::verify,
|
|
||||||
error::{make_error, ErrorKind},
|
|
||||||
Err, IResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
// read until the first line_ending, if line_ending is not present, return the input directly
|
|
||||||
pub fn line(input: &str) -> IResult<&str, &str, ()> {
|
|
||||||
if let Some(i) = memchr(b'\n', input.as_bytes()) {
|
|
||||||
if i > 0 && input.as_bytes()[i - 1] == b'\r' {
|
|
||||||
Ok((&input[i + 1..], &input[0..i - 1]))
|
|
||||||
} else {
|
|
||||||
Ok((&input[i + 1..], &input[0..i]))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(("", input))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lines_till<F>(predicate: F) -> impl Fn(&str) -> IResult<&str, &str, ()>
|
|
||||||
where
|
|
||||||
F: Fn(&str) -> bool,
|
|
||||||
{
|
|
||||||
move |i| {
|
|
||||||
let mut input = i;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// TODO: better error kind
|
|
||||||
if input.is_empty() {
|
|
||||||
return Err(Err::Error(make_error(input, ErrorKind::Many0)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (input_, line_) = line(input)?;
|
|
||||||
|
|
||||||
debug_assert_ne!(input, input_);
|
|
||||||
|
|
||||||
if predicate(line_) {
|
|
||||||
let offset = i.len() - input.len();
|
|
||||||
return Ok((input_, &i[0..offset]));
|
|
||||||
}
|
|
||||||
|
|
||||||
input = input_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lines_while<F>(predicate: F) -> impl Fn(&str) -> IResult<&str, &str, ()>
|
|
||||||
where
|
|
||||||
F: Fn(&str) -> bool,
|
|
||||||
{
|
|
||||||
move |i| {
|
|
||||||
let mut input = i;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// unlike lines_till, line_while won't return error
|
|
||||||
if input.is_empty() {
|
|
||||||
return Ok(("", i));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (input_, line_) = line(input)?;
|
|
||||||
|
|
||||||
debug_assert_ne!(input, input_);
|
|
||||||
|
|
||||||
if !predicate(line_) {
|
|
||||||
let offset = i.len() - input.len();
|
|
||||||
return Ok((input, &i[0..offset]));
|
|
||||||
}
|
|
||||||
|
|
||||||
input = input_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_lines_while() {
|
|
||||||
assert_eq!(lines_while(|line| line == "foo")("foo"), Ok(("", "foo")));
|
|
||||||
assert_eq!(lines_while(|line| line == "foo")("bar"), Ok(("bar", "")));
|
|
||||||
assert_eq!(
|
|
||||||
lines_while(|line| line == "foo")("foo\n\n"),
|
|
||||||
Ok(("\n", "foo\n"))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
lines_while(|line| line.trim().is_empty())("\n\n\n"),
|
|
||||||
Ok(("", "\n\n\n"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eol(input: &str) -> IResult<&str, &str, ()> {
|
|
||||||
verify(line, |s: &str| {
|
|
||||||
s.as_bytes().iter().all(u8::is_ascii_whitespace)
|
|
||||||
})(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn one_word(input: &str) -> IResult<&str, &str, ()> {
|
|
||||||
take_while1(|c: char| !c.is_ascii_whitespace())(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn blank_lines_count(input: &str) -> IResult<&str, usize, ()> {
|
|
||||||
let mut count = 0;
|
|
||||||
let mut input = input;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if input.is_empty() {
|
|
||||||
return Ok(("", count));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (input_, line_) = line(input)?;
|
|
||||||
|
|
||||||
debug_assert_ne!(input, input_);
|
|
||||||
|
|
||||||
if !line_.chars().all(char::is_whitespace) {
|
|
||||||
return Ok((input, count));
|
|
||||||
}
|
|
||||||
|
|
||||||
count += 1;
|
|
||||||
|
|
||||||
input = input_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_blank_lines_count() {
|
|
||||||
assert_eq!(blank_lines_count("foo"), Ok(("foo", 0)));
|
|
||||||
assert_eq!(blank_lines_count(" foo"), Ok((" foo", 0)));
|
|
||||||
assert_eq!(blank_lines_count(" \t\nfoo\n"), Ok(("foo\n", 1)));
|
|
||||||
assert_eq!(blank_lines_count("\n \r\n\nfoo\n"), Ok(("foo\n", 3)));
|
|
||||||
assert_eq!(
|
|
||||||
blank_lines_count("\r\n \n \r\n foo\n"),
|
|
||||||
Ok((" foo\n", 3))
|
|
||||||
);
|
|
||||||
assert_eq!(blank_lines_count("\r\n \n \r\n \n"), Ok(("", 4)));
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
pub mod combinators;
|
|
657
src/parsers.rs
657
src/parsers.rs
|
@ -1,657 +0,0 @@
|
||||||
use std::iter::once;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
use indextree::{Arena, NodeId};
|
|
||||||
use jetscii::{bytes, BytesConst};
|
|
||||||
use memchr::{memchr, memchr_iter};
|
|
||||||
use nom::bytes::complete::take_while1;
|
|
||||||
|
|
||||||
use crate::config::ParseConfig;
|
|
||||||
use crate::elements::{
|
|
||||||
block::RawBlock, emphasis::Emphasis, keyword::RawKeyword, radio_target::parse_radio_target,
|
|
||||||
Clock, Comment, Cookie, Drawer, DynBlock, Element, FixedWidth, FnDef, FnRef, InlineCall,
|
|
||||||
InlineSrc, Link, List, ListItem, Macros, Rule, Snippet, Table, TableCell, TableRow, Target,
|
|
||||||
Timestamp, Title,
|
|
||||||
};
|
|
||||||
use crate::parse::combinators::lines_while;
|
|
||||||
|
|
||||||
pub trait ElementArena<'a> {
|
|
||||||
fn append<T>(&mut self, element: T, parent: NodeId) -> NodeId
|
|
||||||
where
|
|
||||||
T: Into<Element<'a>>;
|
|
||||||
fn insert_before_last_child<T>(&mut self, element: T, parent: NodeId) -> NodeId
|
|
||||||
where
|
|
||||||
T: Into<Element<'a>>;
|
|
||||||
fn set<T>(&mut self, node: NodeId, element: T)
|
|
||||||
where
|
|
||||||
T: Into<Element<'a>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type BorrowedArena<'a> = Arena<Element<'a>>;
|
|
||||||
|
|
||||||
impl<'a> ElementArena<'a> for BorrowedArena<'a> {
|
|
||||||
fn append<T>(&mut self, element: T, parent: NodeId) -> NodeId
|
|
||||||
where
|
|
||||||
T: Into<Element<'a>>,
|
|
||||||
{
|
|
||||||
let node = self.new_node(element.into());
|
|
||||||
parent.append(node, self);
|
|
||||||
node
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert_before_last_child<T>(&mut self, element: T, parent: NodeId) -> NodeId
|
|
||||||
where
|
|
||||||
T: Into<Element<'a>>,
|
|
||||||
{
|
|
||||||
if let Some(child) = self[parent].last_child() {
|
|
||||||
let node = self.new_node(element.into());
|
|
||||||
child.insert_before(node, self);
|
|
||||||
node
|
|
||||||
} else {
|
|
||||||
self.append(element, parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set<T>(&mut self, node: NodeId, element: T)
|
|
||||||
where
|
|
||||||
T: Into<Element<'a>>,
|
|
||||||
{
|
|
||||||
*self[node].get_mut() = element.into();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct OwnedArena<'a, 'b, 'c> {
|
|
||||||
arena: &'b mut Arena<Element<'c>>,
|
|
||||||
phantom: PhantomData<&'a ()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b, 'c> OwnedArena<'a, 'b, 'c> {
|
|
||||||
pub fn new(arena: &'b mut Arena<Element<'c>>) -> OwnedArena<'a, 'b, 'c> {
|
|
||||||
OwnedArena {
|
|
||||||
arena,
|
|
||||||
phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ElementArena<'a> for OwnedArena<'a, '_, '_> {
|
|
||||||
fn append<T>(&mut self, element: T, parent: NodeId) -> NodeId
|
|
||||||
where
|
|
||||||
T: Into<Element<'a>>,
|
|
||||||
{
|
|
||||||
self.arena.append(element.into().into_owned(), parent)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert_before_last_child<T>(&mut self, element: T, parent: NodeId) -> NodeId
|
|
||||||
where
|
|
||||||
T: Into<Element<'a>>,
|
|
||||||
{
|
|
||||||
self.arena
|
|
||||||
.insert_before_last_child(element.into().into_owned(), parent)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set<T>(&mut self, node: NodeId, element: T)
|
|
||||||
where
|
|
||||||
T: Into<Element<'a>>,
|
|
||||||
{
|
|
||||||
self.arena.set(node, element.into().into_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Container<'a> {
|
|
||||||
// Block, List Item
|
|
||||||
Block { content: &'a str, node: NodeId },
|
|
||||||
// Paragraph, Inline Markup
|
|
||||||
Inline { content: &'a str, node: NodeId },
|
|
||||||
// Headline
|
|
||||||
Headline { content: &'a str, node: NodeId },
|
|
||||||
// Document
|
|
||||||
Document { content: &'a str, node: NodeId },
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_container<'a, T: ElementArena<'a>>(
|
|
||||||
arena: &mut T,
|
|
||||||
container: Container<'a>,
|
|
||||||
config: &ParseConfig,
|
|
||||||
) {
|
|
||||||
let containers = &mut vec![container];
|
|
||||||
|
|
||||||
while let Some(container) = containers.pop() {
|
|
||||||
match container {
|
|
||||||
Container::Document { content, node } => {
|
|
||||||
parse_section_and_headlines(arena, content, node, containers);
|
|
||||||
}
|
|
||||||
Container::Headline { content, node } => {
|
|
||||||
parse_headline_content(arena, content, node, containers, config);
|
|
||||||
}
|
|
||||||
Container::Block { content, node } => {
|
|
||||||
parse_blocks(arena, content, node, containers);
|
|
||||||
}
|
|
||||||
Container::Inline { content, node } => {
|
|
||||||
parse_inlines(arena, content, node, containers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_headline_content<'a, T: ElementArena<'a>>(
|
|
||||||
arena: &mut T,
|
|
||||||
content: &'a str,
|
|
||||||
parent: NodeId,
|
|
||||||
containers: &mut Vec<Container<'a>>,
|
|
||||||
config: &ParseConfig,
|
|
||||||
) {
|
|
||||||
let (tail, (title, content)) = Title::parse(content, config).unwrap();
|
|
||||||
let node = arena.append(title, parent);
|
|
||||||
containers.push(Container::Inline { content, node });
|
|
||||||
parse_section_and_headlines(arena, tail, parent, containers);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_section_and_headlines<'a, T: ElementArena<'a>>(
|
|
||||||
arena: &mut T,
|
|
||||||
content: &'a str,
|
|
||||||
parent: NodeId,
|
|
||||||
containers: &mut Vec<Container<'a>>,
|
|
||||||
) {
|
|
||||||
let content = blank_lines_count(content).0;
|
|
||||||
|
|
||||||
if content.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut last_end = 0;
|
|
||||||
for i in memchr_iter(b'\n', content.as_bytes()).chain(once(content.len())) {
|
|
||||||
if let Some((mut tail, (headline_content, level))) = parse_headline(&content[last_end..]) {
|
|
||||||
if last_end != 0 {
|
|
||||||
let node = arena.append(Element::Section, parent);
|
|
||||||
let content = &content[0..last_end];
|
|
||||||
containers.push(Container::Block { content, node });
|
|
||||||
}
|
|
||||||
|
|
||||||
let node = arena.append(Element::Headline { level }, parent);
|
|
||||||
containers.push(Container::Headline {
|
|
||||||
content: headline_content,
|
|
||||||
node,
|
|
||||||
});
|
|
||||||
|
|
||||||
while let Some((new_tail, (content, level))) = parse_headline(tail) {
|
|
||||||
debug_assert_ne!(tail, new_tail);
|
|
||||||
let node = arena.append(Element::Headline { level }, parent);
|
|
||||||
containers.push(Container::Headline { content, node });
|
|
||||||
tail = new_tail;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
last_end = i + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let node = arena.append(Element::Section, parent);
|
|
||||||
containers.push(Container::Block { content, node });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_blocks<'a, T: ElementArena<'a>>(
|
|
||||||
arena: &mut T,
|
|
||||||
content: &'a str,
|
|
||||||
parent: NodeId,
|
|
||||||
containers: &mut Vec<Container<'a>>,
|
|
||||||
) {
|
|
||||||
let mut tail = blank_lines_count(content).0;
|
|
||||||
|
|
||||||
if let Some(new_tail) = parse_block(content, arena, parent, containers) {
|
|
||||||
tail = blank_lines_count(new_tail).0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut text = tail;
|
|
||||||
let mut pos = 0;
|
|
||||||
|
|
||||||
while !tail.is_empty() {
|
|
||||||
let i = memchr(b'\n', tail.as_bytes())
|
|
||||||
.map(|i| i + 1)
|
|
||||||
.unwrap_or_else(|| tail.len());
|
|
||||||
if tail.as_bytes()[0..i].iter().all(u8::is_ascii_whitespace) {
|
|
||||||
let (tail_, blank) = blank_lines_count(&tail[i..]);
|
|
||||||
debug_assert_ne!(tail, tail_);
|
|
||||||
tail = tail_;
|
|
||||||
|
|
||||||
let node = arena.append(
|
|
||||||
Element::Paragraph {
|
|
||||||
// including the current line (&tail[0..i])
|
|
||||||
post_blank: blank + 1,
|
|
||||||
},
|
|
||||||
parent,
|
|
||||||
);
|
|
||||||
|
|
||||||
containers.push(Container::Inline {
|
|
||||||
content: &text[0..pos].trim_end(),
|
|
||||||
node,
|
|
||||||
});
|
|
||||||
|
|
||||||
pos = 0;
|
|
||||||
text = tail;
|
|
||||||
} else if let Some(new_tail) = parse_block(tail, arena, parent, containers) {
|
|
||||||
if pos != 0 {
|
|
||||||
let node =
|
|
||||||
arena.insert_before_last_child(Element::Paragraph { post_blank: 0 }, parent);
|
|
||||||
|
|
||||||
containers.push(Container::Inline {
|
|
||||||
content: &text[0..pos].trim_end(),
|
|
||||||
node,
|
|
||||||
});
|
|
||||||
|
|
||||||
pos = 0;
|
|
||||||
}
|
|
||||||
debug_assert_ne!(tail, blank_lines_count(new_tail).0);
|
|
||||||
tail = blank_lines_count(new_tail).0;
|
|
||||||
text = tail;
|
|
||||||
} else {
|
|
||||||
debug_assert_ne!(tail, &tail[i..]);
|
|
||||||
tail = &tail[i..];
|
|
||||||
pos += i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !text.is_empty() {
|
|
||||||
let node = arena.append(Element::Paragraph { post_blank: 0 }, parent);
|
|
||||||
|
|
||||||
containers.push(Container::Inline {
|
|
||||||
content: &text[0..pos].trim_end(),
|
|
||||||
node,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_block<'a, T: ElementArena<'a>>(
|
|
||||||
contents: &'a str,
|
|
||||||
arena: &mut T,
|
|
||||||
parent: NodeId,
|
|
||||||
containers: &mut Vec<Container<'a>>,
|
|
||||||
) -> Option<&'a str> {
|
|
||||||
match contents
|
|
||||||
.as_bytes()
|
|
||||||
.iter()
|
|
||||||
.find(|c| !c.is_ascii_whitespace())?
|
|
||||||
{
|
|
||||||
b'[' => {
|
|
||||||
let (tail, (fn_def, content)) = FnDef::parse(contents)?;
|
|
||||||
let node = arena.append(fn_def, parent);
|
|
||||||
containers.push(Container::Block { content, node });
|
|
||||||
Some(tail)
|
|
||||||
}
|
|
||||||
b'0'..=b'9' | b'*' => {
|
|
||||||
let tail = parse_list(arena, contents, parent, containers)?;
|
|
||||||
Some(tail)
|
|
||||||
}
|
|
||||||
b'C' => {
|
|
||||||
let (tail, clock) = Clock::parse(contents)?;
|
|
||||||
arena.append(clock, parent);
|
|
||||||
Some(tail)
|
|
||||||
}
|
|
||||||
b'\'' => {
|
|
||||||
// TODO: LaTeX environment
|
|
||||||
None
|
|
||||||
}
|
|
||||||
b'-' => {
|
|
||||||
if let Some((tail, rule)) = Rule::parse(contents) {
|
|
||||||
arena.append(rule, parent);
|
|
||||||
Some(tail)
|
|
||||||
} else {
|
|
||||||
let tail = parse_list(arena, contents, parent, containers)?;
|
|
||||||
Some(tail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b':' => {
|
|
||||||
if let Some((tail, (drawer, content))) = Drawer::parse(contents) {
|
|
||||||
let node = arena.append(drawer, parent);
|
|
||||||
containers.push(Container::Block { content, node });
|
|
||||||
Some(tail)
|
|
||||||
} else {
|
|
||||||
let (tail, fixed_width) = FixedWidth::parse(contents)?;
|
|
||||||
arena.append(fixed_width, parent);
|
|
||||||
Some(tail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b'|' => {
|
|
||||||
let tail = parse_org_table(arena, contents, containers, parent);
|
|
||||||
Some(tail)
|
|
||||||
}
|
|
||||||
b'+' => {
|
|
||||||
if let Some((tail, table)) = Table::parse_table_el(contents) {
|
|
||||||
arena.append(table, parent);
|
|
||||||
Some(tail)
|
|
||||||
} else {
|
|
||||||
let tail = parse_list(arena, contents, parent, containers)?;
|
|
||||||
Some(tail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b'#' => {
|
|
||||||
if let Some((tail, block)) = RawBlock::parse(contents) {
|
|
||||||
let (element, content) = block.into_element();
|
|
||||||
// avoid use after free
|
|
||||||
let is_block_container = match element {
|
|
||||||
Element::CenterBlock(_)
|
|
||||||
| Element::QuoteBlock(_)
|
|
||||||
| Element::VerseBlock(_)
|
|
||||||
| Element::SpecialBlock(_) => true,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
let node = arena.append(element, parent);
|
|
||||||
if is_block_container {
|
|
||||||
containers.push(Container::Block { content, node });
|
|
||||||
}
|
|
||||||
Some(tail)
|
|
||||||
} else if let Some((tail, (dyn_block, content))) = DynBlock::parse(contents) {
|
|
||||||
let node = arena.append(dyn_block, parent);
|
|
||||||
containers.push(Container::Block { content, node });
|
|
||||||
Some(tail)
|
|
||||||
} else if let Some((tail, keyword)) = RawKeyword::parse(contents) {
|
|
||||||
arena.append(keyword.into_element(), parent);
|
|
||||||
Some(tail)
|
|
||||||
} else {
|
|
||||||
let (tail, comment) = Comment::parse(contents)?;
|
|
||||||
arena.append(comment, parent);
|
|
||||||
Some(tail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct InlinePositions<'a> {
|
|
||||||
bytes: &'a [u8],
|
|
||||||
pos: usize,
|
|
||||||
next: Option<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InlinePositions<'_> {
|
|
||||||
fn new(bytes: &[u8]) -> InlinePositions {
|
|
||||||
InlinePositions {
|
|
||||||
bytes,
|
|
||||||
pos: 0,
|
|
||||||
next: Some(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for InlinePositions<'_> {
|
|
||||||
type Item = usize;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
static ref PRE_BYTES: BytesConst =
|
|
||||||
bytes!(b'@', b'<', b'[', b' ', b'(', b'{', b'\'', b'"', b'\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
self.next.take().or_else(|| {
|
|
||||||
PRE_BYTES.find(&self.bytes[self.pos..]).map(|i| {
|
|
||||||
self.pos += i + 1;
|
|
||||||
|
|
||||||
match self.bytes[self.pos - 1] {
|
|
||||||
b'{' => {
|
|
||||||
self.next = Some(self.pos);
|
|
||||||
self.pos - 1
|
|
||||||
}
|
|
||||||
b' ' | b'(' | b'\'' | b'"' | b'\n' => self.pos,
|
|
||||||
_ => self.pos - 1,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_inlines<'a, T: ElementArena<'a>>(
|
|
||||||
arena: &mut T,
|
|
||||||
content: &'a str,
|
|
||||||
parent: NodeId,
|
|
||||||
containers: &mut Vec<Container<'a>>,
|
|
||||||
) {
|
|
||||||
let mut tail = content;
|
|
||||||
|
|
||||||
if let Some(tail_) = parse_inline(tail, arena, containers, parent) {
|
|
||||||
tail = tail_;
|
|
||||||
}
|
|
||||||
|
|
||||||
while let Some((tail_, i)) = InlinePositions::new(tail.as_bytes())
|
|
||||||
.filter_map(|i| parse_inline(&tail[i..], arena, containers, parent).map(|tail| (tail, i)))
|
|
||||||
.next()
|
|
||||||
{
|
|
||||||
if i != 0 {
|
|
||||||
arena.insert_before_last_child(
|
|
||||||
Element::Text {
|
|
||||||
value: tail[0..i].into(),
|
|
||||||
},
|
|
||||||
parent,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
tail = tail_;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !tail.is_empty() {
|
|
||||||
arena.append(Element::Text { value: tail.into() }, parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_inline<'a, T: ElementArena<'a>>(
|
|
||||||
contents: &'a str,
|
|
||||||
arena: &mut T,
|
|
||||||
containers: &mut Vec<Container<'a>>,
|
|
||||||
parent: NodeId,
|
|
||||||
) -> Option<&'a str> {
|
|
||||||
if contents.len() < 3 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let byte = contents.as_bytes()[0];
|
|
||||||
|
|
||||||
match byte {
|
|
||||||
b'@' => {
|
|
||||||
let (tail, snippet) = Snippet::parse(contents)?;
|
|
||||||
arena.append(snippet, parent);
|
|
||||||
Some(tail)
|
|
||||||
}
|
|
||||||
b'{' => {
|
|
||||||
let (tail, macros) = Macros::parse(contents)?;
|
|
||||||
arena.append(macros, parent);
|
|
||||||
Some(tail)
|
|
||||||
}
|
|
||||||
b'<' => {
|
|
||||||
if let Some((tail, _content)) = parse_radio_target(contents) {
|
|
||||||
arena.append(Element::RadioTarget, parent);
|
|
||||||
Some(tail)
|
|
||||||
} else if let Some((tail, target)) = Target::parse(contents) {
|
|
||||||
arena.append(target, parent);
|
|
||||||
Some(tail)
|
|
||||||
} else if let Some((tail, timestamp)) = Timestamp::parse_active(contents) {
|
|
||||||
arena.append(timestamp, parent);
|
|
||||||
Some(tail)
|
|
||||||
} else {
|
|
||||||
let (tail, timestamp) = Timestamp::parse_diary(contents)?;
|
|
||||||
arena.append(timestamp, parent);
|
|
||||||
Some(tail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b'[' => {
|
|
||||||
if let Some((tail, fn_ref)) = FnRef::parse(contents) {
|
|
||||||
arena.append(fn_ref, parent);
|
|
||||||
Some(tail)
|
|
||||||
} else if let Some((tail, link)) = Link::parse(contents) {
|
|
||||||
arena.append(link, parent);
|
|
||||||
Some(tail)
|
|
||||||
} else if let Some((tail, cookie)) = Cookie::parse(contents) {
|
|
||||||
arena.append(cookie, parent);
|
|
||||||
Some(tail)
|
|
||||||
} else {
|
|
||||||
let (tail, timestamp) = Timestamp::parse_inactive(contents)?;
|
|
||||||
arena.append(timestamp, parent);
|
|
||||||
Some(tail)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b'*' | b'+' | b'/' | b'_' | b'=' | b'~' => {
|
|
||||||
let (tail, emphasis) = Emphasis::parse(contents, byte)?;
|
|
||||||
let (element, content) = emphasis.into_element();
|
|
||||||
let is_inline_container = match element {
|
|
||||||
Element::Bold | Element::Strike | Element::Italic | Element::Underline => true,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
let node = arena.append(element, parent);
|
|
||||||
if is_inline_container {
|
|
||||||
containers.push(Container::Inline { content, node });
|
|
||||||
}
|
|
||||||
Some(tail)
|
|
||||||
}
|
|
||||||
b's' => {
|
|
||||||
let (tail, inline_src) = InlineSrc::parse(contents)?;
|
|
||||||
arena.append(inline_src, parent);
|
|
||||||
Some(tail)
|
|
||||||
}
|
|
||||||
b'c' => {
|
|
||||||
let (tail, inline_call) = InlineCall::parse(contents)?;
|
|
||||||
arena.append(inline_call, parent);
|
|
||||||
Some(tail)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_list<'a, T: ElementArena<'a>>(
|
|
||||||
arena: &mut T,
|
|
||||||
contents: &'a str,
|
|
||||||
parent: NodeId,
|
|
||||||
containers: &mut Vec<Container<'a>>,
|
|
||||||
) -> Option<&'a str> {
|
|
||||||
let (mut tail, (first_item, content)) = ListItem::parse(contents)?;
|
|
||||||
let first_item_indent = first_item.indent;
|
|
||||||
let first_item_ordered = first_item.ordered;
|
|
||||||
|
|
||||||
let parent = arena.append(Element::Document { pre_blank: 0 }, parent); // placeholder
|
|
||||||
|
|
||||||
let node = arena.append(first_item, parent);
|
|
||||||
containers.push(Container::Block { content, node });
|
|
||||||
|
|
||||||
while let Some((tail_, (item, content))) = ListItem::parse(tail) {
|
|
||||||
if item.indent == first_item_indent {
|
|
||||||
let node = arena.append(item, parent);
|
|
||||||
containers.push(Container::Block { content, node });
|
|
||||||
debug_assert_ne!(tail, tail_);
|
|
||||||
tail = tail_;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let (tail, post_blank) = blank_lines_count(tail);
|
|
||||||
|
|
||||||
arena.set(
|
|
||||||
parent,
|
|
||||||
List {
|
|
||||||
indent: first_item_indent,
|
|
||||||
ordered: first_item_ordered,
|
|
||||||
post_blank,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Some(tail)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_org_table<'a, T: ElementArena<'a>>(
|
|
||||||
arena: &mut T,
|
|
||||||
contents: &'a str,
|
|
||||||
containers: &mut Vec<Container<'a>>,
|
|
||||||
parent: NodeId,
|
|
||||||
) -> &'a str {
|
|
||||||
let (tail, contents) =
|
|
||||||
lines_while(|line| line.trim_start().starts_with('|'))(contents).unwrap_or((contents, ""));
|
|
||||||
let (tail, post_blank) = blank_lines_count(tail);
|
|
||||||
|
|
||||||
let mut iter = contents.trim_end().lines().peekable();
|
|
||||||
|
|
||||||
let mut lines = vec![];
|
|
||||||
|
|
||||||
let mut has_header = false;
|
|
||||||
|
|
||||||
// TODO: merge contiguous rules
|
|
||||||
|
|
||||||
if let Some(line) = iter.next() {
|
|
||||||
let line = line.trim_start();
|
|
||||||
if !line.starts_with("|-") {
|
|
||||||
lines.push(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while let Some(line) = iter.next() {
|
|
||||||
let line = line.trim_start();
|
|
||||||
if iter.peek().is_none() && line.starts_with("|-") {
|
|
||||||
break;
|
|
||||||
} else if line.starts_with("|-") {
|
|
||||||
has_header = true;
|
|
||||||
}
|
|
||||||
lines.push(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
let parent = arena.append(
|
|
||||||
Table::Org {
|
|
||||||
tblfm: None,
|
|
||||||
post_blank,
|
|
||||||
has_header,
|
|
||||||
},
|
|
||||||
parent,
|
|
||||||
);
|
|
||||||
|
|
||||||
for line in lines {
|
|
||||||
if line.starts_with("|-") {
|
|
||||||
if has_header {
|
|
||||||
arena.append(Element::TableRow(TableRow::HeaderRule), parent);
|
|
||||||
has_header = false;
|
|
||||||
} else {
|
|
||||||
arena.append(Element::TableRow(TableRow::BodyRule), parent);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if has_header {
|
|
||||||
let parent = arena.append(Element::TableRow(TableRow::Header), parent);
|
|
||||||
for content in line.split_terminator('|').skip(1) {
|
|
||||||
let node = arena.append(Element::TableCell(TableCell::Header), parent);
|
|
||||||
containers.push(Container::Inline {
|
|
||||||
content: content.trim(),
|
|
||||||
node,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let parent = arena.append(Element::TableRow(TableRow::Body), parent);
|
|
||||||
for content in line.split_terminator('|').skip(1) {
|
|
||||||
let node = arena.append(Element::TableCell(TableCell::Body), parent);
|
|
||||||
containers.push(Container::Inline {
|
|
||||||
content: content.trim(),
|
|
||||||
node,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tail
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn blank_lines_count(input: &str) -> (&str, usize) {
|
|
||||||
crate::parse::combinators::blank_lines_count(input).unwrap_or((input, 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_headline(input: &str) -> Option<(&str, (&str, usize))> {
|
|
||||||
let (input_, level) = parse_headline_level(input)?;
|
|
||||||
let (input_, content) = lines_while(move |line| {
|
|
||||||
parse_headline_level(line)
|
|
||||||
.map(|(_, l)| l > level)
|
|
||||||
.unwrap_or(true)
|
|
||||||
})(input_)
|
|
||||||
.unwrap_or((input_, ""));
|
|
||||||
Some((input_, (&input[0..level + content.len()], level)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_headline_level(input: &str) -> Option<(&str, usize)> {
|
|
||||||
let (input, stars) = take_while1::<_, _, ()>(|c: char| c == '*')(input).ok()?;
|
|
||||||
|
|
||||||
if input.starts_with(' ') || input.starts_with('\n') || input.is_empty() {
|
|
||||||
Some((input, stars.len()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
209
src/validate.rs
209
src/validate.rs
|
@ -1,209 +0,0 @@
|
||||||
use indextree::NodeId;
|
|
||||||
use std::ops::RangeInclusive;
|
|
||||||
|
|
||||||
use crate::elements::{Element, Table, TableCell, TableRow};
|
|
||||||
use crate::Org;
|
|
||||||
|
|
||||||
/// Validation Error
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum ValidationError {
|
|
||||||
/// Expected at least one child
|
|
||||||
ExpectedChildren {
|
|
||||||
at: NodeId,
|
|
||||||
},
|
|
||||||
/// Expected no children
|
|
||||||
UnexpectedChildren {
|
|
||||||
at: NodeId,
|
|
||||||
},
|
|
||||||
UnexpectedElement {
|
|
||||||
expected: &'static str,
|
|
||||||
at: NodeId,
|
|
||||||
},
|
|
||||||
/// Expected a detached element
|
|
||||||
ExpectedDetached {
|
|
||||||
at: NodeId,
|
|
||||||
},
|
|
||||||
/// Expected headline level in specify range
|
|
||||||
HeadlineLevelMismatch {
|
|
||||||
range: RangeInclusive<usize>,
|
|
||||||
at: NodeId,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ValidationError {
|
|
||||||
pub fn element<'a, 'b>(&self, org: &'a Org<'b>) -> &'a Element<'b> {
|
|
||||||
match self {
|
|
||||||
ValidationError::ExpectedChildren { at }
|
|
||||||
| ValidationError::UnexpectedChildren { at }
|
|
||||||
| ValidationError::UnexpectedElement { at, .. }
|
|
||||||
| ValidationError::ExpectedDetached { at }
|
|
||||||
| ValidationError::HeadlineLevelMismatch { at, .. } => &org[*at],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type ValidationResult<T> = Result<T, ValidationError>;
|
|
||||||
|
|
||||||
impl Org<'_> {
|
|
||||||
/// Validates an `Org` struct.
|
|
||||||
pub fn validate(&self) -> Vec<ValidationError> {
|
|
||||||
let mut errors = Vec::new();
|
|
||||||
|
|
||||||
macro_rules! expect_element {
|
|
||||||
($node:ident, $expect:expr, $($pattern:pat)|+) => {
|
|
||||||
match self[$node] {
|
|
||||||
$($pattern)|+ => (),
|
|
||||||
_ => errors.push(ValidationError::UnexpectedElement {
|
|
||||||
expected: $expect,
|
|
||||||
at: $node
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! expect_children {
|
|
||||||
($node:ident) => {
|
|
||||||
if self.arena[$node].first_child().is_none() {
|
|
||||||
errors.push(ValidationError::ExpectedChildren { at: $node });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
for node_id in self.root.descendants(&self.arena) {
|
|
||||||
let node = &self.arena[node_id];
|
|
||||||
match node.get() {
|
|
||||||
Element::Document { .. } => {
|
|
||||||
let mut children = node_id.children(&self.arena);
|
|
||||||
if let Some(child) = children.next() {
|
|
||||||
expect_element!(
|
|
||||||
child,
|
|
||||||
"Headline|Section",
|
|
||||||
Element::Headline { .. } | Element::Section
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for child in children {
|
|
||||||
expect_element!(child, "Headline", Element::Headline { .. });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::Headline { .. } => {
|
|
||||||
expect_children!(node_id);
|
|
||||||
|
|
||||||
let mut children = node_id.children(&self.arena);
|
|
||||||
if let Some(child) = children.next() {
|
|
||||||
expect_element!(child, "Title", Element::Title(_));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(child) = children.next() {
|
|
||||||
expect_element!(
|
|
||||||
child,
|
|
||||||
"Headline|Section",
|
|
||||||
Element::Headline { .. } | Element::Section
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for child in children {
|
|
||||||
expect_element!(child, "Headline", Element::Headline { .. });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::Title(title) => {
|
|
||||||
if !title.raw.is_empty() && node.first_child().is_none() {
|
|
||||||
errors.push(ValidationError::ExpectedChildren { at: node_id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::List(_) => {
|
|
||||||
expect_children!(node_id);
|
|
||||||
for child in node_id.children(&self.arena) {
|
|
||||||
expect_element!(child, "ListItem", Element::ListItem(_));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::Table(Table::Org { .. }) => {
|
|
||||||
for child in node_id.children(&self.arena) {
|
|
||||||
expect_element!(child, "TableRow", Element::TableRow(_));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::TableRow(TableRow::Header) => {
|
|
||||||
for child in node_id.children(&self.arena) {
|
|
||||||
expect_element!(
|
|
||||||
child,
|
|
||||||
"TableCell::Header",
|
|
||||||
Element::TableCell(TableCell::Header)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::TableRow(TableRow::Body) => {
|
|
||||||
for child in node_id.children(&self.arena) {
|
|
||||||
expect_element!(
|
|
||||||
child,
|
|
||||||
"TableCell::Body",
|
|
||||||
Element::TableCell(TableCell::Body)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::CommentBlock(_)
|
|
||||||
| Element::ExampleBlock(_)
|
|
||||||
| Element::ExportBlock(_)
|
|
||||||
| Element::SourceBlock(_)
|
|
||||||
| Element::BabelCall(_)
|
|
||||||
| Element::InlineSrc(_)
|
|
||||||
| Element::Code { .. }
|
|
||||||
| Element::FnRef(_)
|
|
||||||
| Element::InlineCall(_)
|
|
||||||
| Element::Link(_)
|
|
||||||
| Element::Macros(_)
|
|
||||||
| Element::RadioTarget
|
|
||||||
| Element::Snippet(_)
|
|
||||||
| Element::Target(_)
|
|
||||||
| Element::Text { .. }
|
|
||||||
| Element::Timestamp(_)
|
|
||||||
| Element::Verbatim { .. }
|
|
||||||
| Element::FnDef(_)
|
|
||||||
| Element::Clock(_)
|
|
||||||
| Element::Comment { .. }
|
|
||||||
| Element::FixedWidth { .. }
|
|
||||||
| Element::Keyword(_)
|
|
||||||
| Element::Rule(_)
|
|
||||||
| Element::Cookie(_)
|
|
||||||
| Element::TableRow(TableRow::BodyRule)
|
|
||||||
| Element::TableRow(TableRow::HeaderRule) => {
|
|
||||||
if node.first_child().is_some() {
|
|
||||||
errors.push(ValidationError::UnexpectedChildren { at: node_id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element::SpecialBlock(_)
|
|
||||||
| Element::QuoteBlock(_)
|
|
||||||
| Element::CenterBlock(_)
|
|
||||||
| Element::VerseBlock(_)
|
|
||||||
| Element::Paragraph { .. }
|
|
||||||
| Element::Section
|
|
||||||
| Element::Bold
|
|
||||||
| Element::Italic
|
|
||||||
| Element::Underline
|
|
||||||
| Element::Strike
|
|
||||||
| Element::DynBlock(_) => {
|
|
||||||
expect_children!(node_id);
|
|
||||||
}
|
|
||||||
Element::ListItem(_)
|
|
||||||
| Element::Drawer(_)
|
|
||||||
| Element::TableCell(_)
|
|
||||||
| Element::Table(_) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
errors
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn debug_validate(&self) {
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
let errors = self.validate();
|
|
||||||
if !errors.is_empty() {
|
|
||||||
eprintln!("Org validation failed. {} error(s) found:", errors.len());
|
|
||||||
for err in errors {
|
|
||||||
eprintln!("{:?} at {:?}", err, err.element(self));
|
|
||||||
}
|
|
||||||
panic!(
|
|
||||||
"Looks like there's a bug in orgize! Please report it with your org-mode content at https://github.com/PoiScript/orgize/issues."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
189
src/wasm/mod.rs
189
src/wasm/mod.rs
|
@ -1,189 +0,0 @@
|
||||||
#[global_allocator]
|
|
||||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
|
||||||
|
|
||||||
use serde::Serialize;
|
|
||||||
use serde_wasm_bindgen::Serializer;
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
|
|
||||||
use crate::{Element, Event};
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub struct Org(crate::Org<'static>);
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
impl Org {
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn parse(input: String) -> Self {
|
|
||||||
Org(crate::Org::parse_string(input))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = toJson)]
|
|
||||||
pub fn to_json(&self) -> JsValue {
|
|
||||||
to_value(&self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
extern "C" {
|
|
||||||
#[wasm_bindgen(skip_typescript)]
|
|
||||||
pub type Handler;
|
|
||||||
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn text(this: &Handler, text: JsValue);
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn code(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn cookie(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn rule(this: &Handler);
|
|
||||||
#[wasm_bindgen(method, js_name = exampleBlock)]
|
|
||||||
pub fn example_block(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method, js_name = exportBlock)]
|
|
||||||
pub fn export_block(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method, js_name = sourceBlock)]
|
|
||||||
pub fn source_block(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method, js_name = inlineSrc)]
|
|
||||||
pub fn inline_src(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn link(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn snippet(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn timestamp(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn verbatim(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn fixedWidth(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method)]
|
|
||||||
pub fn keyword(this: &Handler, item: JsValue);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method, js_name = listStart)]
|
|
||||||
pub fn list_start(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method, js_name = listEnd)]
|
|
||||||
pub fn list_end(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method, js_name = tableStart)]
|
|
||||||
pub fn table_start(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method, js_name = tableEnd)]
|
|
||||||
pub fn table_end(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method, js_name = tableRowStart)]
|
|
||||||
pub fn table_row_start(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method, js_name = tableRowEnd)]
|
|
||||||
pub fn table_row_end(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method, js_name = tableCellStart)]
|
|
||||||
pub fn table_cell_start(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method, js_name = tableCellEnd)]
|
|
||||||
pub fn table_cell_end(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method, js_name = titleStart)]
|
|
||||||
pub fn title_start(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method, js_name = titleEnd)]
|
|
||||||
pub fn title_end(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method, js_name = boldStart)]
|
|
||||||
pub fn bold_start(this: &Handler);
|
|
||||||
#[wasm_bindgen(method, js_name = boldEnd)]
|
|
||||||
pub fn bold_end(this: &Handler);
|
|
||||||
#[wasm_bindgen(method, js_name = centerBlockStart)]
|
|
||||||
pub fn center_block_start(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method, js_name = centerBlockEnd)]
|
|
||||||
pub fn center_block_end(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method, js_name = documentStart)]
|
|
||||||
pub fn document_start(this: &Handler);
|
|
||||||
#[wasm_bindgen(method, js_name = documentEnd)]
|
|
||||||
pub fn document_end(this: &Handler);
|
|
||||||
#[wasm_bindgen(method, js_name = italicStart)]
|
|
||||||
pub fn italic_start(this: &Handler);
|
|
||||||
#[wasm_bindgen(method, js_name = italicEnd)]
|
|
||||||
pub fn italic_end(this: &Handler);
|
|
||||||
#[wasm_bindgen(method, js_name = listItemStart)]
|
|
||||||
pub fn list_item_start(this: &Handler);
|
|
||||||
#[wasm_bindgen(method, js_name = listItemEnd)]
|
|
||||||
pub fn list_item_end(this: &Handler);
|
|
||||||
#[wasm_bindgen(method, js_name = paragraphStart)]
|
|
||||||
pub fn paragraph_start(this: &Handler);
|
|
||||||
#[wasm_bindgen(method, js_name = paragraphEnd)]
|
|
||||||
pub fn paragraph_end(this: &Handler);
|
|
||||||
#[wasm_bindgen(method, js_name = quoteBlockStart)]
|
|
||||||
pub fn quote_block_start(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method, js_name = quoteBlockEnd)]
|
|
||||||
pub fn quote_block_end(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method, js_name = sectionStart)]
|
|
||||||
pub fn section_start(this: &Handler);
|
|
||||||
#[wasm_bindgen(method, js_name = sectionEnd)]
|
|
||||||
pub fn section_end(this: &Handler);
|
|
||||||
#[wasm_bindgen(method, js_name = strikeStart)]
|
|
||||||
pub fn strike_start(this: &Handler);
|
|
||||||
#[wasm_bindgen(method, js_name = strikeEnd)]
|
|
||||||
pub fn strike_end(this: &Handler);
|
|
||||||
#[wasm_bindgen(method, js_name = underlineStart)]
|
|
||||||
pub fn underline_start(this: &Handler);
|
|
||||||
#[wasm_bindgen(method, js_name = underlineEnd)]
|
|
||||||
pub fn underline_end(this: &Handler);
|
|
||||||
#[wasm_bindgen(method, js_name = verseBlockStart)]
|
|
||||||
pub fn verse_block_start(this: &Handler, item: JsValue);
|
|
||||||
#[wasm_bindgen(method, js_name = verseBlockEnd)]
|
|
||||||
pub fn verse_block_end(this: &Handler, item: JsValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn handle(org: &Org, handler: Handler) {
|
|
||||||
for event in org.0.iter() {
|
|
||||||
use Element::*;
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::Start(Text { value }) => handler.text(JsValue::from_str(value)),
|
|
||||||
Event::Start(ExampleBlock(block)) => handler.example_block(to_value(block)),
|
|
||||||
Event::Start(ExportBlock(block)) => handler.export_block(to_value(block)),
|
|
||||||
Event::Start(SourceBlock(block)) => handler.source_block(to_value(block)),
|
|
||||||
Event::Start(InlineSrc(src)) => handler.inline_src(to_value(src)),
|
|
||||||
Event::Start(Code { value }) => handler.code(JsValue::from_str(value)),
|
|
||||||
Event::Start(Link(link)) => handler.link(to_value(link)),
|
|
||||||
Event::Start(Snippet(snippet)) => handler.snippet(to_value(snippet)),
|
|
||||||
Event::Start(Timestamp(timestamp)) => handler.timestamp(to_value(timestamp)),
|
|
||||||
Event::Start(Verbatim { value }) => handler.verbatim(JsValue::from_str(value)),
|
|
||||||
Event::Start(FixedWidth(fixed_width)) => handler.fixedWidth(to_value(fixed_width)),
|
|
||||||
Event::Start(Rule(_)) => handler.rule(),
|
|
||||||
Event::Start(Cookie(cookie)) => handler.cookie(to_value(cookie)),
|
|
||||||
Event::Start(Keyword(keyword)) => handler.keyword(to_value(keyword)),
|
|
||||||
|
|
||||||
Event::Start(Table(table)) => handler.table_start(to_value(table)),
|
|
||||||
Event::End(Table(table)) => handler.table_start(to_value(table)),
|
|
||||||
Event::Start(TableRow(row)) => handler.table_row_start(to_value(row)),
|
|
||||||
Event::End(TableRow(row)) => handler.table_row_start(to_value(row)),
|
|
||||||
Event::Start(TableCell(cell)) => handler.table_cell_start(to_value(cell)),
|
|
||||||
Event::End(TableCell(cell)) => handler.table_cell_start(to_value(cell)),
|
|
||||||
Event::Start(Title(title)) => handler.title_start(to_value(title)),
|
|
||||||
Event::End(Title(title)) => handler.title_end(to_value(title)),
|
|
||||||
Event::Start(QuoteBlock(block)) => handler.quote_block_start(to_value(block)),
|
|
||||||
Event::End(QuoteBlock(block)) => handler.quote_block_end(to_value(block)),
|
|
||||||
Event::Start(CenterBlock(block)) => handler.center_block_start(to_value(block)),
|
|
||||||
Event::End(CenterBlock(block)) => handler.center_block_end(to_value(block)),
|
|
||||||
Event::Start(VerseBlock(block)) => handler.verse_block_start(to_value(block)),
|
|
||||||
Event::End(VerseBlock(block)) => handler.verse_block_end(to_value(block)),
|
|
||||||
Event::Start(Bold) => handler.bold_start(),
|
|
||||||
Event::End(Bold) => handler.bold_end(),
|
|
||||||
Event::Start(Document { .. }) => handler.document_start(),
|
|
||||||
Event::End(Document { .. }) => handler.document_end(),
|
|
||||||
Event::Start(List(list)) => handler.list_start(to_value(list)),
|
|
||||||
Event::End(List(list)) => handler.list_end(to_value(list)),
|
|
||||||
Event::Start(Italic) => handler.italic_start(),
|
|
||||||
Event::End(Italic) => handler.italic_end(),
|
|
||||||
Event::Start(ListItem(_)) => handler.list_item_start(),
|
|
||||||
Event::End(ListItem(_)) => handler.list_item_end(),
|
|
||||||
Event::Start(Paragraph { .. }) => handler.paragraph_start(),
|
|
||||||
Event::End(Paragraph { .. }) => handler.paragraph_end(),
|
|
||||||
Event::Start(Section) => handler.section_start(),
|
|
||||||
Event::End(Section) => handler.section_end(),
|
|
||||||
Event::Start(Strike) => handler.strike_start(),
|
|
||||||
Event::End(Strike) => handler.strike_end(),
|
|
||||||
Event::Start(Underline) => handler.underline_start(),
|
|
||||||
Event::End(Underline) => handler.underline_end(),
|
|
||||||
|
|
||||||
_ => continue,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_value<T: Serialize + ?Sized>(value: &T) -> JsValue {
|
|
||||||
value
|
|
||||||
.serialize(&Serializer::new().serialize_maps_as_objects(true))
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
use orgize::Org;
|
|
||||||
|
|
||||||
const ORG_STR: &str = r#"
|
|
||||||
|
|
||||||
#+TITLE: org
|
|
||||||
|
|
||||||
#+BEGIN_QUOTE
|
|
||||||
|
|
||||||
CONTENTS
|
|
||||||
|
|
||||||
#+END_QUOTE
|
|
||||||
|
|
||||||
* Headline 1
|
|
||||||
SCHEDULED: <2019-10-28 Mon>
|
|
||||||
:PROPERTIES:
|
|
||||||
:ID: headline-1
|
|
||||||
:END:
|
|
||||||
|
|
||||||
:LOGBOOK:
|
|
||||||
|
|
||||||
CLOCK: [2019-10-28 Mon 08:53]
|
|
||||||
|
|
||||||
CLOCK: [2019-10-28 Mon 08:53]--[2019-10-28 Mon 08:53] => 0:00
|
|
||||||
|
|
||||||
:END:
|
|
||||||
|
|
||||||
-----
|
|
||||||
|
|
||||||
#+CALL: VALUE
|
|
||||||
|
|
||||||
#
|
|
||||||
# Comment
|
|
||||||
#
|
|
||||||
|
|
||||||
#+BEGIN: NAME PARAMETERS
|
|
||||||
|
|
||||||
CONTENTS
|
|
||||||
|
|
||||||
#+END:
|
|
||||||
|
|
||||||
:
|
|
||||||
: Fixed width
|
|
||||||
:
|
|
||||||
|
|
||||||
#+BEGIN_COMMENT
|
|
||||||
|
|
||||||
COMMENT
|
|
||||||
|
|
||||||
#+END_COMMENT
|
|
||||||
|
|
||||||
#+BEGIN_EXAMPLE
|
|
||||||
#+END_EXAMPLE
|
|
||||||
|
|
||||||
1. 1
|
|
||||||
|
|
||||||
2. 2
|
|
||||||
|
|
||||||
3. 3
|
|
||||||
|
|
||||||
+ 1
|
|
||||||
|
|
||||||
+ 2
|
|
||||||
|
|
||||||
- 3
|
|
||||||
|
|
||||||
- 4
|
|
||||||
|
|
||||||
+ 5
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"#;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn blank() {
|
|
||||||
let org = Org::parse(ORG_STR);
|
|
||||||
|
|
||||||
let mut writer = Vec::new();
|
|
||||||
org.write_org(&mut writer).unwrap();
|
|
||||||
|
|
||||||
// eprintln!("{}", serde_json::to_string_pretty(&org).unwrap());
|
|
||||||
|
|
||||||
assert_eq!(String::from_utf8(writer).unwrap(), ORG_STR);
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
use orgize::Org;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_handle_empty_emphasis() {
|
|
||||||
let cases = &[
|
|
||||||
"* / // a",
|
|
||||||
"\"* / // a\"",
|
|
||||||
"* * ** a",
|
|
||||||
"* 2020\n** December\n*** Experiment\nType A is marked with * and type B is marked with **.\n",
|
|
||||||
"* 2020\n:DRAWER:\n* ** a\n:END:",
|
|
||||||
"* * ** :a:",
|
|
||||||
"* * ** "
|
|
||||||
];
|
|
||||||
|
|
||||||
for case in cases {
|
|
||||||
let _ = Org::parse(case);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
use orgize::Org;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_handle_empty_list_item() {
|
|
||||||
let cases = &[
|
|
||||||
"0. ",
|
|
||||||
"* \n0. ",
|
|
||||||
" * ",
|
|
||||||
" 0. ",
|
|
||||||
"\t* ",
|
|
||||||
"- ",
|
|
||||||
"- hello\n- ",
|
|
||||||
"- \n- hello",
|
|
||||||
"- hello\n- \n- world",
|
|
||||||
"* world\n- ",
|
|
||||||
];
|
|
||||||
|
|
||||||
for case in cases {
|
|
||||||
let _ = Org::parse(case);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
use orgize::Org;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn bad_headline_tags() {
|
|
||||||
contains_no_tag(Org::parse("* a ::"));
|
|
||||||
|
|
||||||
contains_no_tag(Org::parse("* a :(:"));
|
|
||||||
|
|
||||||
contains_one_tag(Org::parse("* a \t:_:"), "_");
|
|
||||||
|
|
||||||
contains_one_tag(Org::parse("* a \t :@:"), "@");
|
|
||||||
|
|
||||||
contains_one_tag(Org::parse("* a :#:"), "#");
|
|
||||||
|
|
||||||
contains_one_tag(Org::parse("* a\t :%:"), "%");
|
|
||||||
|
|
||||||
contains_one_tag(Org::parse("* a :余:"), "余");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn contains_no_tag(org: Org) {
|
|
||||||
assert!(org.headlines().next().unwrap().title(&org).tags.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn contains_one_tag(org: Org, tag: &str) {
|
|
||||||
assert_eq!(vec![tag], org.headlines().next().unwrap().title(&org).tags);
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
use orgize::Org;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn whitespaces() {
|
|
||||||
let org = Org::parse(" ");
|
|
||||||
|
|
||||||
assert(&org);
|
|
||||||
|
|
||||||
let org = Org::parse("\t \t \n \t \t \n \t");
|
|
||||||
|
|
||||||
assert(&org);
|
|
||||||
|
|
||||||
let org = Org::parse("\u{000b}\u{0085}\u{00a0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200a}\u{2028}\u{2029}\u{202f}\u{205f}\u{3000}");
|
|
||||||
|
|
||||||
assert(&org);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert(org: &Org) {
|
|
||||||
assert_eq!(
|
|
||||||
org.iter().count(),
|
|
||||||
2,
|
|
||||||
"should contains only one element - document"
|
|
||||||
);
|
|
||||||
}
|
|
172
tests/parse.rs
172
tests/parse.rs
|
@ -1,172 +0,0 @@
|
||||||
use orgize::Org;
|
|
||||||
use pretty_assertions::assert_eq;
|
|
||||||
|
|
||||||
macro_rules! test_suite {
|
|
||||||
($name:ident, $content:expr, $expected:expr) => {
|
|
||||||
#[test]
|
|
||||||
fn $name() {
|
|
||||||
let mut writer = Vec::new();
|
|
||||||
let org = Org::parse($content);
|
|
||||||
org.write_html(&mut writer).unwrap();
|
|
||||||
let string = String::from_utf8(writer).unwrap();
|
|
||||||
assert_eq!(string, $expected);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test_suite!(
|
|
||||||
emphasis,
|
|
||||||
"*bold*, /italic/,\n_underlined_, =verbatim= and ~code~",
|
|
||||||
"<main><section><p><b>bold</b>, <i>italic</i>,\n<u>underlined</u>, \
|
|
||||||
<code>verbatim</code> and <code>code</code></p></section></main>"
|
|
||||||
);
|
|
||||||
|
|
||||||
test_suite!(
|
|
||||||
link,
|
|
||||||
"Visit[[http://example.com][link1]]or[[http://example.com][link1]].",
|
|
||||||
r#"<main><section><p>Visit<a href="http://example.com">link1</a>or<a href="http://example.com">link1</a>.</p></section></main>"#
|
|
||||||
);
|
|
||||||
|
|
||||||
test_suite!(
|
|
||||||
section_and_headline,
|
|
||||||
r#"
|
|
||||||
* title 1
|
|
||||||
section 1
|
|
||||||
** title 2
|
|
||||||
section 2
|
|
||||||
* title 3
|
|
||||||
section 3
|
|
||||||
* title 4
|
|
||||||
section 4
|
|
||||||
"#,
|
|
||||||
"<main><h1>title 1</h1><section><p>section 1</p></section>\
|
|
||||||
<h2>title 2</h2><section><p>section 2</p></section>\
|
|
||||||
<h1>title 3</h1><section><p>section 3</p></section>\
|
|
||||||
<h1>title 4</h1><section><p>section 4</p></section></main>"
|
|
||||||
);
|
|
||||||
|
|
||||||
test_suite!(
|
|
||||||
list,
|
|
||||||
r#"
|
|
||||||
+ 1
|
|
||||||
|
|
||||||
+ 2
|
|
||||||
|
|
||||||
- 3
|
|
||||||
|
|
||||||
- 4
|
|
||||||
|
|
||||||
+ 5
|
|
||||||
"#,
|
|
||||||
"<main><section><ul>\
|
|
||||||
<li><p>1</p></li>\
|
|
||||||
<li><p>2</p><ul><li><p>3</p></li><li><p>4</p></li></ul></li>\
|
|
||||||
<li><p>5</p></li>\
|
|
||||||
</ul></section></main>"
|
|
||||||
);
|
|
||||||
|
|
||||||
test_suite!(
|
|
||||||
snippet,
|
|
||||||
"@@html:<del>@@delete this@@html:</del>@@",
|
|
||||||
"<main><section><p><del>delete this</del></p></section></main>"
|
|
||||||
);
|
|
||||||
|
|
||||||
test_suite!(
|
|
||||||
paragraphs,
|
|
||||||
r#"
|
|
||||||
* title
|
|
||||||
|
|
||||||
paragraph 1
|
|
||||||
|
|
||||||
paragraph 2
|
|
||||||
|
|
||||||
paragraph 3
|
|
||||||
|
|
||||||
paragraph 4
|
|
||||||
"#,
|
|
||||||
"<main><h1>title</h1><section>\
|
|
||||||
<p>paragraph 1</p><p>paragraph 2</p>\
|
|
||||||
<p>paragraph 3</p><p>paragraph 4</p>\
|
|
||||||
</section></main>"
|
|
||||||
);
|
|
||||||
|
|
||||||
test_suite!(
|
|
||||||
table1,
|
|
||||||
r#"
|
|
||||||
|-----+-----+-----|
|
|
||||||
| 0 | 1 | 2 |
|
|
||||||
|-----+-----+-----|
|
|
||||||
| 4 | 5 | 6 |
|
|
||||||
"#,
|
|
||||||
"<main><section><table>\
|
|
||||||
<thead><tr><th>0</th><th>1</th><th>2</th></tr></thead>\
|
|
||||||
<tbody><tr><td>4</td><td>5</td><td>6</td></tr></tbody>\
|
|
||||||
</table></section></main>"
|
|
||||||
);
|
|
||||||
|
|
||||||
test_suite!(
|
|
||||||
table2,
|
|
||||||
r#"
|
|
||||||
|-----+-----+-----|
|
|
||||||
| 0 | 1 | 2 |
|
|
||||||
| 4 | 5 | 6 |
|
|
||||||
|-----+-----+-----|
|
|
||||||
"#,
|
|
||||||
"<main><section><table>\
|
|
||||||
<tbody><tr><td>0</td><td>1</td><td>2</td></tr>\
|
|
||||||
<tr><td>4</td><td>5</td><td>6</td></tr></tbody>\
|
|
||||||
</table></section></main>"
|
|
||||||
);
|
|
||||||
|
|
||||||
test_suite!(
|
|
||||||
table3,
|
|
||||||
r#"
|
|
||||||
|-----+-----+-----|
|
|
||||||
|-----+-----+-----|
|
|
||||||
| 0 | 1 | 2 |
|
|
||||||
| 4 | 5 | 6 |
|
|
||||||
"#,
|
|
||||||
"<main><section><table><thead></thead>\
|
|
||||||
<tbody><tr><td>0</td><td>1</td><td>2</td></tr>\
|
|
||||||
<tr><td>4</td><td>5</td><td>6</td></tr></tbody>\
|
|
||||||
</table></section></main>"
|
|
||||||
);
|
|
||||||
|
|
||||||
test_suite!(
|
|
||||||
table4,
|
|
||||||
r#"
|
|
||||||
| 0 | 1 | 2 |
|
|
||||||
| 4 | 5 | 6 |
|
|
||||||
|-----+-----+-----|
|
|
||||||
|-----+-----+-----|
|
|
||||||
"#,
|
|
||||||
"<main><section><table>\
|
|
||||||
<thead><tr><th>0</th><th>1</th><th>2</th></tr>\
|
|
||||||
<tr><th>4</th><th>5</th><th>6</th></tr></thead>\
|
|
||||||
<tbody></tbody></table></section></main>"
|
|
||||||
);
|
|
||||||
|
|
||||||
test_suite!(
|
|
||||||
table5,
|
|
||||||
r#"
|
|
||||||
|-----+-----+-----|
|
|
||||||
|-----+-----+-----|
|
|
||||||
"#,
|
|
||||||
"<main><section><table><tbody></tbody></table></section></main>"
|
|
||||||
);
|
|
||||||
|
|
||||||
test_suite!(
|
|
||||||
table6,
|
|
||||||
r#"
|
|
||||||
|
|
|
||||||
|-
|
|
||||||
|
|
|
||||||
|-
|
|
||||||
|
|
|
||||||
"#,
|
|
||||||
"<main><section><table>\
|
|
||||||
<thead><tr></tr></thead>\
|
|
||||||
<tbody><tr></tr></tbody>\
|
|
||||||
<tbody><tr></tr></tbody>\
|
|
||||||
</table></section></main>"
|
|
||||||
);
|
|
6
wasm/.gitignore
vendored
6
wasm/.gitignore
vendored
|
@ -1,6 +0,0 @@
|
||||||
/lib
|
|
||||||
/node_modules
|
|
||||||
/out-tsc
|
|
||||||
/package
|
|
||||||
/pkg
|
|
||||||
*.tgz
|
|
|
@ -1,41 +0,0 @@
|
||||||
# orgize
|
|
||||||
|
|
||||||
![npm](https://img.shields.io/npm/v/orgize)
|
|
||||||
|
|
||||||
## Quick start
|
|
||||||
|
|
||||||
Install the package:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install orgize
|
|
||||||
yarn add orgize
|
|
||||||
```
|
|
||||||
|
|
||||||
Load the wasm module and init:
|
|
||||||
|
|
||||||
### Browser
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { init, renderHtml } from "orgize";
|
|
||||||
|
|
||||||
init().then(() => {
|
|
||||||
console.log(renderHtml("* Hello, /world/!"));
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Node.js
|
|
||||||
|
|
||||||
```js
|
|
||||||
const { init, renderHtml } = require("orgize");
|
|
||||||
const { readFile } = require("fs/promises");
|
|
||||||
|
|
||||||
readFile(require.resolve("orgize/lib/orgize_bg.wasm"))
|
|
||||||
.then((bytes) => init(new WebAssembly.Module(bytes)))
|
|
||||||
.then(() => {
|
|
||||||
console.log(renderHtml("* Hello, /world/!"));
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT
|
|
|
@ -1,27 +0,0 @@
|
||||||
{
|
|
||||||
"name": "orgize",
|
|
||||||
"version": "0.0.3",
|
|
||||||
"license": "MIT",
|
|
||||||
"author": "PoiScript <poiscript@gmail.com>",
|
|
||||||
"main": "lib/orgize.umd.js",
|
|
||||||
"module": "lib/orgize.es.js",
|
|
||||||
"typings": "lib/orgize.d.ts",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/PoiScript/orgize"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"prebuild": "rm -rf out-tsc/ lib/",
|
|
||||||
"build": "tsc && rollup -c rollup.js && cp pkg/orgize_bg.wasm lib/"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"rollup": "^2.56.3",
|
|
||||||
"rollup-plugin-copy": "^3.4.0",
|
|
||||||
"rollup-plugin-dts": "^4.0.0",
|
|
||||||
"typescript": "^4.4.2"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"lib",
|
|
||||||
"README.md"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
import dts from "rollup-plugin-dts";
|
|
||||||
import copy from "rollup-plugin-copy";
|
|
||||||
|
|
||||||
export default [
|
|
||||||
{
|
|
||||||
input: "./out-tsc/index.d.ts",
|
|
||||||
output: {
|
|
||||||
file: "./lib/orgize.d.ts",
|
|
||||||
},
|
|
||||||
plugins: [dts()],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "./out-tsc/index.js",
|
|
||||||
output: [
|
|
||||||
{
|
|
||||||
file: "./lib/orgize.es.js",
|
|
||||||
format: "es",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "orgize",
|
|
||||||
file: "./lib/orgize.umd.js",
|
|
||||||
format: "umd",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
plugins: [
|
|
||||||
copy({
|
|
||||||
targets: [{ src: "index.html", dest: "lib" }],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
|
@ -1,102 +0,0 @@
|
||||||
export class Handler {
|
|
||||||
text(_text: string) {}
|
|
||||||
code(_item: string) {}
|
|
||||||
cookie(_item: Cookie) {}
|
|
||||||
rule() {}
|
|
||||||
exampleBlock(_item: Block) {}
|
|
||||||
exportBlock(_item: Block) {}
|
|
||||||
sourceBlock(_item: SourceBlock) {}
|
|
||||||
inlineSrc(_item: InlineSrc) {}
|
|
||||||
link(_item: Link) {}
|
|
||||||
snippet(_item: Snippet) {}
|
|
||||||
timestamp(_item: any) {}
|
|
||||||
verbatim(_item: string) {}
|
|
||||||
fixedWidth(_item: FixedWidth) {}
|
|
||||||
listStart(_item: List) {}
|
|
||||||
listEnd(_item: List) {}
|
|
||||||
tableStart(_item: any) {}
|
|
||||||
tableEnd(_item: any) {}
|
|
||||||
tableRowStart(_item: any) {}
|
|
||||||
tableRowEnd(_item: any) {}
|
|
||||||
tableCellStart(_item: any) {}
|
|
||||||
tableCellEnd(_item: any) {}
|
|
||||||
titleStart(_item: Title) {}
|
|
||||||
titleEnd(_item: Title) {}
|
|
||||||
boldStart() {}
|
|
||||||
boldEnd() {}
|
|
||||||
centerBlockStart(_item: any) {}
|
|
||||||
centerBlockEnd(_item: any) {}
|
|
||||||
documentStart() {}
|
|
||||||
documentEnd() {}
|
|
||||||
italicStart() {}
|
|
||||||
italicEnd() {}
|
|
||||||
listItemStart() {}
|
|
||||||
listItemEnd() {}
|
|
||||||
paragraphStart() {}
|
|
||||||
paragraphEnd() {}
|
|
||||||
quoteBlockStart(_item: any) {}
|
|
||||||
quoteBlockEnd(_item: any) {}
|
|
||||||
sectionStart() {}
|
|
||||||
sectionEnd() {}
|
|
||||||
strikeStart() {}
|
|
||||||
strikeEnd() {}
|
|
||||||
underlineStart() {}
|
|
||||||
underlineEnd() {}
|
|
||||||
verseBlockStart(_item: any) {}
|
|
||||||
verseBlockEnd(_item: any) {}
|
|
||||||
keyword(_item: Keyword) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Title = {
|
|
||||||
level: number;
|
|
||||||
priority?: string;
|
|
||||||
tags?: string[];
|
|
||||||
keyword?: string;
|
|
||||||
raw: string;
|
|
||||||
properties?: { [key: string]: string };
|
|
||||||
post_blank: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type List = {
|
|
||||||
ordered: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Block = {
|
|
||||||
contents: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type InlineSrc = {
|
|
||||||
lang: string;
|
|
||||||
body: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Link = {
|
|
||||||
path: string;
|
|
||||||
desc?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type FixedWidth = {
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Cookie = {
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SourceBlock = {
|
|
||||||
contents: string;
|
|
||||||
language: string;
|
|
||||||
arguments: string;
|
|
||||||
post_blank: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Keyword = {
|
|
||||||
key: string;
|
|
||||||
optional?: string;
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Snippet = {
|
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
};
|
|
160
wasm/src/html.ts
160
wasm/src/html.ts
|
@ -1,160 +0,0 @@
|
||||||
import {
|
|
||||||
Block,
|
|
||||||
Cookie,
|
|
||||||
FixedWidth,
|
|
||||||
Handler,
|
|
||||||
InlineSrc,
|
|
||||||
Link,
|
|
||||||
List,
|
|
||||||
Snippet,
|
|
||||||
Title,
|
|
||||||
} from "./handler";
|
|
||||||
|
|
||||||
const tags: { [tag: string]: string } = {
|
|
||||||
"&": "&",
|
|
||||||
"<": "<",
|
|
||||||
">": ">",
|
|
||||||
'"': """,
|
|
||||||
"'": "'",
|
|
||||||
};
|
|
||||||
|
|
||||||
const replaceTags = (tag: string): string => tags[tag];
|
|
||||||
|
|
||||||
export const escapeHtml = (str: string): string =>
|
|
||||||
str.replace(/[&<>"']/g, replaceTags);
|
|
||||||
|
|
||||||
export class HtmlHandler extends Handler {
|
|
||||||
result: string;
|
|
||||||
|
|
||||||
constructor(result: string = "") {
|
|
||||||
super();
|
|
||||||
this.result = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static escape(): string {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
quoteBlockStart() {
|
|
||||||
this.result += "<blockquote>";
|
|
||||||
}
|
|
||||||
quoteBlockEnd() {
|
|
||||||
this.result += "</blockquote>";
|
|
||||||
}
|
|
||||||
centerBlockStart() {
|
|
||||||
this.result += '<div class="center">';
|
|
||||||
}
|
|
||||||
centerBlockEnd() {
|
|
||||||
this.result += "</div>";
|
|
||||||
}
|
|
||||||
verseBlockStart() {
|
|
||||||
this.result += '<p class="verse">';
|
|
||||||
}
|
|
||||||
verseBlockEnd() {
|
|
||||||
this.result += "</p>";
|
|
||||||
}
|
|
||||||
boldStart() {
|
|
||||||
this.result += "<b>";
|
|
||||||
}
|
|
||||||
boldEnd() {
|
|
||||||
this.result += "</b>";
|
|
||||||
}
|
|
||||||
documentStart() {
|
|
||||||
this.result += "<main>";
|
|
||||||
}
|
|
||||||
documentEnd() {
|
|
||||||
this.result += "</main>";
|
|
||||||
}
|
|
||||||
|
|
||||||
listStart(list: List) {
|
|
||||||
this.result += `<${list.ordered ? "o" : "u"}l>`;
|
|
||||||
}
|
|
||||||
listEnd(list: List) {
|
|
||||||
this.result += `</${list.ordered ? "o" : "u"}l>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
italicStart() {
|
|
||||||
this.result += "<i>";
|
|
||||||
}
|
|
||||||
italicEnd() {
|
|
||||||
this.result += "</i>";
|
|
||||||
}
|
|
||||||
listItemStart() {
|
|
||||||
this.result += "<li>";
|
|
||||||
}
|
|
||||||
listItemEnd() {
|
|
||||||
this.result += "</li>";
|
|
||||||
}
|
|
||||||
paragraphStart() {
|
|
||||||
this.result += "<p>";
|
|
||||||
}
|
|
||||||
paragraphEnd() {
|
|
||||||
this.result += "</p>";
|
|
||||||
}
|
|
||||||
sectionStart() {
|
|
||||||
this.result += "<section>";
|
|
||||||
}
|
|
||||||
sectionEnd() {
|
|
||||||
this.result += "</section>";
|
|
||||||
}
|
|
||||||
strikeStart() {
|
|
||||||
this.result += "<s>";
|
|
||||||
}
|
|
||||||
strikeEnd() {
|
|
||||||
this.result += "</s>";
|
|
||||||
}
|
|
||||||
underlineStart() {
|
|
||||||
this.result += "<u>";
|
|
||||||
}
|
|
||||||
underlineEnd() {
|
|
||||||
this.result += "</u>";
|
|
||||||
}
|
|
||||||
|
|
||||||
exampleBlock(block: Block) {
|
|
||||||
this.result += `<pre class="example">${escapeHtml(block.contents)}</pre>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceBlock(block: Block) {
|
|
||||||
this.result += `<pre class="example">${escapeHtml(block.contents)}</pre>`;
|
|
||||||
}
|
|
||||||
inlineSrc(src: InlineSrc) {
|
|
||||||
this.result += `<code class="src src-${src.lang}">${escapeHtml(
|
|
||||||
src.body
|
|
||||||
)}</code>`;
|
|
||||||
}
|
|
||||||
code(value: string) {
|
|
||||||
this.result += `<code>${escapeHtml(value)}</code>`;
|
|
||||||
}
|
|
||||||
link(link: Link) {
|
|
||||||
this.result += `<a href="${link.path}">${escapeHtml(
|
|
||||||
link.desc || link.path
|
|
||||||
)}</a>`;
|
|
||||||
}
|
|
||||||
snippet(snippet: Snippet) {
|
|
||||||
if (snippet.name.toLowerCase() === "html") {
|
|
||||||
this.result += snippet.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
text(value: string) {
|
|
||||||
this.result += escapeHtml(value);
|
|
||||||
}
|
|
||||||
verbatim(value: string) {
|
|
||||||
this.result += `<code>${escapeHtml(value)}</code>`;
|
|
||||||
}
|
|
||||||
fixedWidth(item: FixedWidth) {
|
|
||||||
this.result += `<pre class="example">${escapeHtml(item.value)}</pre>`;
|
|
||||||
}
|
|
||||||
rule() {
|
|
||||||
this.result += "<hr>";
|
|
||||||
}
|
|
||||||
cookie(cookie: Cookie) {
|
|
||||||
this.result += `<code>${escapeHtml(cookie.value)}</code>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
titleStart(title: Title) {
|
|
||||||
this.result += `<h${Math.min(title.level, 6)}>`;
|
|
||||||
}
|
|
||||||
titleEnd(title: Title) {
|
|
||||||
this.result += `</h${Math.min(title.level, 6)}>`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
import init, {
|
|
||||||
handle as internalHandle,
|
|
||||||
InitInput,
|
|
||||||
InitOutput,
|
|
||||||
Org,
|
|
||||||
} from "../pkg/orgize";
|
|
||||||
import { Handler } from "./handler";
|
|
||||||
import { HtmlHandler } from "./html";
|
|
||||||
import { CollectKeywords } from "./keyword";
|
|
||||||
|
|
||||||
export const handle = (org: Org | string, handler: Handler) => {
|
|
||||||
if (typeof org === "string") {
|
|
||||||
org = Org.parse(org);
|
|
||||||
}
|
|
||||||
internalHandle(org, handler);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const renderHtml = (
|
|
||||||
org: Org | string,
|
|
||||||
handler: HtmlHandler = new HtmlHandler()
|
|
||||||
): string => {
|
|
||||||
handle(org, handler);
|
|
||||||
return handler.result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const keywords = (org: Org | string): { [key: string]: string[] } => {
|
|
||||||
const handler = new CollectKeywords();
|
|
||||||
handle(org, handler);
|
|
||||||
return handler.keywords;
|
|
||||||
};
|
|
||||||
|
|
||||||
export * from "./handler";
|
|
||||||
export * from "./html";
|
|
||||||
export { Org, init, InitInput, InitOutput };
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { Handler, Keyword } from "./handler";
|
|
||||||
|
|
||||||
export class CollectKeywords extends Handler {
|
|
||||||
keywords: { [key: string]: string[] } = {};
|
|
||||||
|
|
||||||
keyword(keyword: Keyword) {
|
|
||||||
this.keywords[keyword.key] = this.keywords[keyword.key] || [];
|
|
||||||
this.keywords[keyword.key].push(keyword.value);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
const { readFile } = require("fs/promises");
|
|
||||||
const { resolve } = require("path");
|
|
||||||
const { strictEqual } = require("assert");
|
|
||||||
|
|
||||||
const { init, renderHtml } = require("../lib/orgize.umd");
|
|
||||||
|
|
||||||
const assert = (org, html) => strictEqual(renderHtml(org), html);
|
|
||||||
|
|
||||||
readFile(resolve(__dirname, "../lib/orgize_bg.wasm"))
|
|
||||||
.then((bytes) => new WebAssembly.Module(bytes))
|
|
||||||
.then((module) => init(module))
|
|
||||||
.then(() => {
|
|
||||||
assert(
|
|
||||||
"*bold*, /italic/,\n_underlined_, =verbatim= and ~code~",
|
|
||||||
"<main><section><p><b>bold</b>, <i>italic</i>,\n<u>underlined</u>, " +
|
|
||||||
"<code>verbatim</code> and <code>code</code></p></section></main>"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert(
|
|
||||||
"Visit[[http://example.com][link1]]or[[http://example.com][link1]].",
|
|
||||||
`<main><section><p>Visit<a href="http://example.com">link1</a>or<a href="http://example.com">link1</a>.</p></section></main>`
|
|
||||||
);
|
|
||||||
|
|
||||||
assert(
|
|
||||||
`
|
|
||||||
* title 1
|
|
||||||
section 1
|
|
||||||
** title 2
|
|
||||||
section 2
|
|
||||||
* title 3
|
|
||||||
section 3
|
|
||||||
* title 4
|
|
||||||
section 4
|
|
||||||
`,
|
|
||||||
"<main><h1>title 1</h1><section><p>section 1</p></section>" +
|
|
||||||
"<h2>title 2</h2><section><p>section 2</p></section>" +
|
|
||||||
"<h1>title 3</h1><section><p>section 3</p></section>" +
|
|
||||||
"<h1>title 4</h1><section><p>section 4</p></section></main>"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert(
|
|
||||||
`
|
|
||||||
+ 1
|
|
||||||
|
|
||||||
+ 2
|
|
||||||
|
|
||||||
- 3
|
|
||||||
|
|
||||||
- 4
|
|
||||||
|
|
||||||
+ 5
|
|
||||||
`,
|
|
||||||
"<main><section><ul>" +
|
|
||||||
"<li><p>1</p></li>" +
|
|
||||||
"<li><p>2</p><ul><li><p>3</p></li><li><p>4</p></li></ul></li>" +
|
|
||||||
"<li><p>5</p></li>" +
|
|
||||||
"</ul></section></main>"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert(
|
|
||||||
"@@html:<del>@@delete this@@html:</del>@@",
|
|
||||||
"<main><section><p><del>delete this</del></p></section></main>"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert(
|
|
||||||
`
|
|
||||||
* title
|
|
||||||
|
|
||||||
paragraph 1
|
|
||||||
|
|
||||||
paragraph 2
|
|
||||||
|
|
||||||
paragraph 3
|
|
||||||
|
|
||||||
paragraph 4
|
|
||||||
`,
|
|
||||||
"<main><h1>title</h1><section>" +
|
|
||||||
"<p>paragraph 1</p><p>paragraph 2</p>" +
|
|
||||||
"<p>paragraph 3</p><p>paragraph 4</p>" +
|
|
||||||
"</section></main>"
|
|
||||||
);
|
|
||||||
});
|
|
|
@ -1,17 +0,0 @@
|
||||||
const { readFile } = require("fs/promises");
|
|
||||||
const { resolve } = require("path");
|
|
||||||
const { deepStrictEqual } = require("assert");
|
|
||||||
|
|
||||||
const { init, keywords } = require("../lib/orgize.umd");
|
|
||||||
|
|
||||||
const assert = (org, kw) => deepStrictEqual(keywords(org), kw);
|
|
||||||
|
|
||||||
readFile(resolve(__dirname, "../lib/orgize_bg.wasm"))
|
|
||||||
.then((bytes) => new WebAssembly.Module(bytes))
|
|
||||||
.then((module) => init(module))
|
|
||||||
.then(() => {
|
|
||||||
assert("#+TITLE: orgize test cases\n#+FOO: bar", {
|
|
||||||
TITLE: ["orgize test cases"],
|
|
||||||
FOO: ["bar"],
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ESNext",
|
|
||||||
"module": "ESNext",
|
|
||||||
"lib": ["ESNext", "WebWorker"],
|
|
||||||
"strict": true,
|
|
||||||
"outDir": "out-tsc",
|
|
||||||
"rootDir": "./src",
|
|
||||||
"declaration": true
|
|
||||||
},
|
|
||||||
"include": ["./src"]
|
|
||||||
}
|
|
416
wasm/yarn.lock
416
wasm/yarn.lock
|
@ -1,416 +0,0 @@
|
||||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
|
||||||
# yarn lockfile v1
|
|
||||||
|
|
||||||
|
|
||||||
"@babel/code-frame@^7.14.5":
|
|
||||||
version "7.14.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb"
|
|
||||||
integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==
|
|
||||||
dependencies:
|
|
||||||
"@babel/highlight" "^7.14.5"
|
|
||||||
|
|
||||||
"@babel/helper-validator-identifier@^7.14.5":
|
|
||||||
version "7.14.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48"
|
|
||||||
integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==
|
|
||||||
|
|
||||||
"@babel/highlight@^7.14.5":
|
|
||||||
version "7.14.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9"
|
|
||||||
integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==
|
|
||||||
dependencies:
|
|
||||||
"@babel/helper-validator-identifier" "^7.14.5"
|
|
||||||
chalk "^2.0.0"
|
|
||||||
js-tokens "^4.0.0"
|
|
||||||
|
|
||||||
"@nodelib/fs.scandir@2.1.5":
|
|
||||||
version "2.1.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
|
||||||
integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
|
|
||||||
dependencies:
|
|
||||||
"@nodelib/fs.stat" "2.0.5"
|
|
||||||
run-parallel "^1.1.9"
|
|
||||||
|
|
||||||
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
|
|
||||||
version "2.0.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
|
|
||||||
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
|
||||||
|
|
||||||
"@nodelib/fs.walk@^1.2.3":
|
|
||||||
version "1.2.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
|
|
||||||
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
|
|
||||||
dependencies:
|
|
||||||
"@nodelib/fs.scandir" "2.1.5"
|
|
||||||
fastq "^1.6.0"
|
|
||||||
|
|
||||||
"@types/fs-extra@^8.0.1":
|
|
||||||
version "8.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.2.tgz#7125cc2e4bdd9bd2fc83005ffdb1d0ba00cca61f"
|
|
||||||
integrity sha512-SvSrYXfWSc7R4eqnOzbQF4TZmfpNSM9FrSWLU3EUnWBuyZqNBOrv1B1JA3byUDPUl9z4Ab3jeZG2eDdySlgNMg==
|
|
||||||
dependencies:
|
|
||||||
"@types/node" "*"
|
|
||||||
|
|
||||||
"@types/glob@^7.1.1":
|
|
||||||
version "7.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb"
|
|
||||||
integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==
|
|
||||||
dependencies:
|
|
||||||
"@types/minimatch" "*"
|
|
||||||
"@types/node" "*"
|
|
||||||
|
|
||||||
"@types/minimatch@*":
|
|
||||||
version "3.0.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40"
|
|
||||||
integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==
|
|
||||||
|
|
||||||
"@types/node@*":
|
|
||||||
version "16.11.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae"
|
|
||||||
integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==
|
|
||||||
|
|
||||||
ansi-styles@^3.2.1:
|
|
||||||
version "3.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
|
||||||
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
|
|
||||||
dependencies:
|
|
||||||
color-convert "^1.9.0"
|
|
||||||
|
|
||||||
array-union@^2.1.0:
|
|
||||||
version "2.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
|
|
||||||
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
|
|
||||||
|
|
||||||
balanced-match@^1.0.0:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
|
||||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
|
||||||
|
|
||||||
brace-expansion@^1.1.7:
|
|
||||||
version "1.1.11"
|
|
||||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
|
||||||
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
|
|
||||||
dependencies:
|
|
||||||
balanced-match "^1.0.0"
|
|
||||||
concat-map "0.0.1"
|
|
||||||
|
|
||||||
braces@^3.0.1:
|
|
||||||
version "3.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
|
||||||
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
|
|
||||||
dependencies:
|
|
||||||
fill-range "^7.0.1"
|
|
||||||
|
|
||||||
chalk@^2.0.0:
|
|
||||||
version "2.4.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
|
||||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
|
||||||
dependencies:
|
|
||||||
ansi-styles "^3.2.1"
|
|
||||||
escape-string-regexp "^1.0.5"
|
|
||||||
supports-color "^5.3.0"
|
|
||||||
|
|
||||||
color-convert@^1.9.0:
|
|
||||||
version "1.9.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
|
||||||
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
|
|
||||||
dependencies:
|
|
||||||
color-name "1.1.3"
|
|
||||||
|
|
||||||
color-name@1.1.3:
|
|
||||||
version "1.1.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
|
||||||
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
|
|
||||||
|
|
||||||
colorette@^1.1.0:
|
|
||||||
version "1.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40"
|
|
||||||
integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==
|
|
||||||
|
|
||||||
concat-map@0.0.1:
|
|
||||||
version "0.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
|
||||||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
|
||||||
|
|
||||||
dir-glob@^3.0.1:
|
|
||||||
version "3.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
|
|
||||||
integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==
|
|
||||||
dependencies:
|
|
||||||
path-type "^4.0.0"
|
|
||||||
|
|
||||||
escape-string-regexp@^1.0.5:
|
|
||||||
version "1.0.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
|
||||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
|
||||||
|
|
||||||
fast-glob@^3.0.3:
|
|
||||||
version "3.2.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1"
|
|
||||||
integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==
|
|
||||||
dependencies:
|
|
||||||
"@nodelib/fs.stat" "^2.0.2"
|
|
||||||
"@nodelib/fs.walk" "^1.2.3"
|
|
||||||
glob-parent "^5.1.2"
|
|
||||||
merge2 "^1.3.0"
|
|
||||||
micromatch "^4.0.4"
|
|
||||||
|
|
||||||
fastq@^1.6.0:
|
|
||||||
version "1.13.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
|
|
||||||
integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==
|
|
||||||
dependencies:
|
|
||||||
reusify "^1.0.4"
|
|
||||||
|
|
||||||
fill-range@^7.0.1:
|
|
||||||
version "7.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
|
|
||||||
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
|
|
||||||
dependencies:
|
|
||||||
to-regex-range "^5.0.1"
|
|
||||||
|
|
||||||
fs-extra@^8.1.0:
|
|
||||||
version "8.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
|
|
||||||
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
|
|
||||||
dependencies:
|
|
||||||
graceful-fs "^4.2.0"
|
|
||||||
jsonfile "^4.0.0"
|
|
||||||
universalify "^0.1.0"
|
|
||||||
|
|
||||||
fs.realpath@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
|
||||||
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
|
||||||
|
|
||||||
fsevents@~2.3.2:
|
|
||||||
version "2.3.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
|
||||||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
|
||||||
|
|
||||||
glob-parent@^5.1.2:
|
|
||||||
version "5.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
|
||||||
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
|
||||||
dependencies:
|
|
||||||
is-glob "^4.0.1"
|
|
||||||
|
|
||||||
glob@^7.1.3:
|
|
||||||
version "7.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
|
|
||||||
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
|
|
||||||
dependencies:
|
|
||||||
fs.realpath "^1.0.0"
|
|
||||||
inflight "^1.0.4"
|
|
||||||
inherits "2"
|
|
||||||
minimatch "^3.0.4"
|
|
||||||
once "^1.3.0"
|
|
||||||
path-is-absolute "^1.0.0"
|
|
||||||
|
|
||||||
globby@10.0.1:
|
|
||||||
version "10.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.1.tgz#4782c34cb75dd683351335c5829cc3420e606b22"
|
|
||||||
integrity sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==
|
|
||||||
dependencies:
|
|
||||||
"@types/glob" "^7.1.1"
|
|
||||||
array-union "^2.1.0"
|
|
||||||
dir-glob "^3.0.1"
|
|
||||||
fast-glob "^3.0.3"
|
|
||||||
glob "^7.1.3"
|
|
||||||
ignore "^5.1.1"
|
|
||||||
merge2 "^1.2.3"
|
|
||||||
slash "^3.0.0"
|
|
||||||
|
|
||||||
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
|
||||||
version "4.2.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
|
|
||||||
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
|
|
||||||
|
|
||||||
has-flag@^3.0.0:
|
|
||||||
version "3.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
|
||||||
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
|
|
||||||
|
|
||||||
ignore@^5.1.1:
|
|
||||||
version "5.1.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb"
|
|
||||||
integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==
|
|
||||||
|
|
||||||
inflight@^1.0.4:
|
|
||||||
version "1.0.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
|
||||||
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
|
|
||||||
dependencies:
|
|
||||||
once "^1.3.0"
|
|
||||||
wrappy "1"
|
|
||||||
|
|
||||||
inherits@2:
|
|
||||||
version "2.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
|
||||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
|
||||||
|
|
||||||
is-extglob@^2.1.1:
|
|
||||||
version "2.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
|
||||||
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
|
|
||||||
|
|
||||||
is-glob@^4.0.1:
|
|
||||||
version "4.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
|
||||||
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
|
|
||||||
dependencies:
|
|
||||||
is-extglob "^2.1.1"
|
|
||||||
|
|
||||||
is-number@^7.0.0:
|
|
||||||
version "7.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
|
||||||
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
|
||||||
|
|
||||||
is-plain-object@^3.0.0:
|
|
||||||
version "3.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b"
|
|
||||||
integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==
|
|
||||||
|
|
||||||
js-tokens@^4.0.0:
|
|
||||||
version "4.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
|
||||||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
|
||||||
|
|
||||||
jsonfile@^4.0.0:
|
|
||||||
version "4.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
|
||||||
integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
|
|
||||||
optionalDependencies:
|
|
||||||
graceful-fs "^4.1.6"
|
|
||||||
|
|
||||||
magic-string@^0.25.7:
|
|
||||||
version "0.25.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
|
|
||||||
integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
|
|
||||||
dependencies:
|
|
||||||
sourcemap-codec "^1.4.4"
|
|
||||||
|
|
||||||
merge2@^1.2.3, merge2@^1.3.0:
|
|
||||||
version "1.4.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
|
||||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
|
||||||
|
|
||||||
micromatch@^4.0.4:
|
|
||||||
version "4.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9"
|
|
||||||
integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==
|
|
||||||
dependencies:
|
|
||||||
braces "^3.0.1"
|
|
||||||
picomatch "^2.2.3"
|
|
||||||
|
|
||||||
minimatch@^3.0.4:
|
|
||||||
version "3.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
|
||||||
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
|
|
||||||
dependencies:
|
|
||||||
brace-expansion "^1.1.7"
|
|
||||||
|
|
||||||
once@^1.3.0:
|
|
||||||
version "1.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
|
||||||
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
|
||||||
dependencies:
|
|
||||||
wrappy "1"
|
|
||||||
|
|
||||||
path-is-absolute@^1.0.0:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
|
||||||
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
|
||||||
|
|
||||||
path-type@^4.0.0:
|
|
||||||
version "4.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
|
|
||||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
|
||||||
|
|
||||||
picomatch@^2.2.3:
|
|
||||||
version "2.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
|
|
||||||
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
|
|
||||||
|
|
||||||
queue-microtask@^1.2.2:
|
|
||||||
version "1.2.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
|
||||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
|
||||||
|
|
||||||
reusify@^1.0.4:
|
|
||||||
version "1.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
|
|
||||||
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
|
|
||||||
|
|
||||||
rollup-plugin-copy@^3.4.0:
|
|
||||||
version "3.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/rollup-plugin-copy/-/rollup-plugin-copy-3.4.0.tgz#f1228a3ffb66ffad8606e2f3fb7ff23141ed3286"
|
|
||||||
integrity sha512-rGUmYYsYsceRJRqLVlE9FivJMxJ7X6jDlP79fmFkL8sJs7VVMSVyA2yfyL+PGyO/vJs4A87hwhgVfz61njI+uQ==
|
|
||||||
dependencies:
|
|
||||||
"@types/fs-extra" "^8.0.1"
|
|
||||||
colorette "^1.1.0"
|
|
||||||
fs-extra "^8.1.0"
|
|
||||||
globby "10.0.1"
|
|
||||||
is-plain-object "^3.0.0"
|
|
||||||
|
|
||||||
rollup-plugin-dts@^4.0.0:
|
|
||||||
version "4.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/rollup-plugin-dts/-/rollup-plugin-dts-4.0.0.tgz#7645280183b7624e77375a548a11297f9916f6d8"
|
|
||||||
integrity sha512-tgUC8CxVgtlLDVloUEA9uACVaxjJHuYxlDSTp1LdCexA0bJx+RuMi45RjdLG9RTCgZlV5YBh3O7P2u6dS1KlnA==
|
|
||||||
dependencies:
|
|
||||||
magic-string "^0.25.7"
|
|
||||||
optionalDependencies:
|
|
||||||
"@babel/code-frame" "^7.14.5"
|
|
||||||
|
|
||||||
rollup@^2.56.3:
|
|
||||||
version "2.56.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.56.3.tgz#b63edadd9851b0d618a6d0e6af8201955a77aeff"
|
|
||||||
integrity sha512-Au92NuznFklgQCUcV96iXlxUbHuB1vQMaH76DHl5M11TotjOHwqk9CwcrT78+Tnv4FN9uTBxq6p4EJoYkpyekg==
|
|
||||||
optionalDependencies:
|
|
||||||
fsevents "~2.3.2"
|
|
||||||
|
|
||||||
run-parallel@^1.1.9:
|
|
||||||
version "1.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
|
|
||||||
integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
|
|
||||||
dependencies:
|
|
||||||
queue-microtask "^1.2.2"
|
|
||||||
|
|
||||||
slash@^3.0.0:
|
|
||||||
version "3.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
|
||||||
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
|
|
||||||
|
|
||||||
sourcemap-codec@^1.4.4:
|
|
||||||
version "1.4.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
|
|
||||||
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
|
|
||||||
|
|
||||||
supports-color@^5.3.0:
|
|
||||||
version "5.5.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
|
||||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
|
||||||
dependencies:
|
|
||||||
has-flag "^3.0.0"
|
|
||||||
|
|
||||||
to-regex-range@^5.0.1:
|
|
||||||
version "5.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
|
|
||||||
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
|
|
||||||
dependencies:
|
|
||||||
is-number "^7.0.0"
|
|
||||||
|
|
||||||
typescript@^4.4.2:
|
|
||||||
version "4.4.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.2.tgz#6d618640d430e3569a1dfb44f7d7e600ced3ee86"
|
|
||||||
integrity sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==
|
|
||||||
|
|
||||||
universalify@^0.1.0:
|
|
||||||
version "0.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
|
||||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
|
||||||
|
|
||||||
wrappy@1:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
|
||||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
|
Loading…
Reference in a new issue