Remove dead orkmode-core (FFI) crate
Dropping the C FFI layer left orkmode-core unused: nothing in the workspace depends on it, and the GUI will speak 9P instead. Delete the crate, drop it from the workspace, and scrub FFI references from the docs (README structure/usage, AGENTS structure/FFI/next-steps).
This commit is contained in:
parent
7ee65a4a2d
commit
33173bcc29
22
AGENTS.md
22
AGENTS.md
|
|
@ -14,8 +14,8 @@ Current state: **v0.1** — parser and CLI complete, no UI yet.
|
||||||
crates/
|
crates/
|
||||||
├── org-ast/ # AST types (Document, Section, Element, etc.)
|
├── org-ast/ # AST types (Document, Section, Element, etc.)
|
||||||
├── org-parser/ # Tree-sitter parser, AST conversion
|
├── org-parser/ # Tree-sitter parser, AST conversion
|
||||||
├── orkmode-core/ # High-level API, C FFI
|
├── ork-server/ # 9P server exposing org files (bin: ork-server)
|
||||||
└── ork-cli/ # CLI tool (bin name: ork)
|
└── ork-cli/ # CLI tool, a 9P client (bin name: ork)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Working with the Code
|
## Working with the Code
|
||||||
|
|
@ -64,11 +64,11 @@ Use `-j/--json` for machine-readable output. Use `-n/--dry-run` for mutations.
|
||||||
- `Document` → `Section`* → `Element`* / `Section`*
|
- `Document` → `Section`* → `Element`* / `Section`*
|
||||||
- Inline objects (bold, links) are partially implemented
|
- Inline objects (bold, links) are partially implemented
|
||||||
|
|
||||||
### FFI
|
### 9P server
|
||||||
|
|
||||||
- C-compatible FFI in `orkmode-core/src/ffi.rs`
|
- `ork-server` exposes org files as a 9P namespace (docs, sections, fields, agenda, query)
|
||||||
- Functions prefixed `ork_*`
|
- Structural ops via the `ctl` file: rm, move, promote, demote, refile, archive, lint
|
||||||
- Caller owns returned strings (free with `ork_string_free`)
|
- `ork` is a pure 9P client; all org logic lives in the server
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
|
|
@ -100,16 +100,16 @@ Use `-j/--json` for machine-readable output. Use `-n/--dry-run` for mutations.
|
||||||
3. **Timestamp text** — displays "set" placeholder, not actual date
|
3. **Timestamp text** — displays "set" placeholder, not actual date
|
||||||
4. **No incremental parsing** — full reparse on every call
|
4. **No incremental parsing** — full reparse on every call
|
||||||
|
|
||||||
## Next Steps (v0.2)
|
## Next Steps (v0.3)
|
||||||
|
|
||||||
Priority: Qt/QML read-only document viewer.
|
Priority: Qt/QML read-only document viewer, over 9P.
|
||||||
|
|
||||||
Tasks:
|
Tasks:
|
||||||
1. Set up Qt6/QML project in `ui/`
|
1. Set up Qt6/QML project in `ui/`
|
||||||
2. Link orkmode-core via C FFI
|
2. Connect to `ork-server` over 9P (no FFI)
|
||||||
3. Implement document model (QAbstractItemModel)
|
3. Implement document model (QAbstractItemModel) from the 9P namespace
|
||||||
4. Basic headline rendering with folding
|
4. Basic headline rendering with folding
|
||||||
5. Syntax highlighting
|
5. Structural styling (keywords, priorities, tags, timestamps)
|
||||||
|
|
||||||
## Testing Changes
|
## Testing Changes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -346,12 +346,6 @@ version = "1.70.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itoa"
|
|
||||||
version = "1.0.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.106"
|
version = "0.3.106"
|
||||||
|
|
@ -571,18 +565,6 @@ dependencies = [
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "orkmode-core"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"org-ast",
|
|
||||||
"org-parser",
|
|
||||||
"pretty_assertions",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.5"
|
version = "0.12.5"
|
||||||
|
|
@ -754,19 +736,6 @@ dependencies = [
|
||||||
"syn 3.0.6",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_json"
|
|
||||||
version = "1.0.151"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
|
||||||
dependencies = [
|
|
||||||
"itoa",
|
|
||||||
"memchr",
|
|
||||||
"serde",
|
|
||||||
"serde_core",
|
|
||||||
"zmij",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sharded-slab"
|
name = "sharded-slab"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
|
|
@ -1197,9 +1166,3 @@ name = "yansi"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zmij"
|
|
||||||
version = "1.0.23"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"crates/org-parser",
|
"crates/org-parser",
|
||||||
"crates/org-ast",
|
"crates/org-ast",
|
||||||
"crates/orkmode-core",
|
|
||||||
"crates/ork-cli",
|
"crates/ork-cli",
|
||||||
"crates/ork-server",
|
"crates/ork-server",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
50
README.md
50
README.md
|
|
@ -25,14 +25,17 @@ orkmode/
|
||||||
│ │ ├── convert.rs # Tree-sitter → AST conversion
|
│ │ ├── convert.rs # Tree-sitter → AST conversion
|
||||||
│ │ ├── ts.rs # Tree-sitter language bindings
|
│ │ ├── ts.rs # Tree-sitter language bindings
|
||||||
│ │ └── error.rs # Error types
|
│ │ └── error.rs # Error types
|
||||||
│ ├── orkmode-core/ # Core library with FFI
|
│ ├── ork-cli/ # Command-line interface (9P client)
|
||||||
│ │ └── src/
|
│ │ └── src/
|
||||||
│ │ ├── lib.rs # High-level API
|
│ │ ├── main.rs # CLI commands (base + friendly verbs)
|
||||||
│ │ └── ffi.rs # C FFI for Qt integration
|
│ │ └── client.rs # 9P2000 client
|
||||||
│ └── ork-cli/ # Command-line interface
|
│ └── ork-server/ # 9P server exposing org files
|
||||||
│ └── src/
|
│ └── src/
|
||||||
│ ├── main.rs # CLI entry point
|
│ ├── bin/ork-server.rs # Server entry point
|
||||||
│ └── commands.rs # Command implementations
|
│ ├── state.rs # Document state, section ops, ctl verbs
|
||||||
|
│ ├── namespace.rs # 9P namespace (docs, sections, agenda, query)
|
||||||
|
│ ├── p9.rs # 9P protocol handling
|
||||||
|
│ └── virtfs.rs # Virtual filesystem tree
|
||||||
└── ui/ # (future) Qt/QML UI
|
└── ui/ # (future) Qt/QML UI
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -189,31 +192,28 @@ for section in doc.all_sections() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### C/C++ FFI
|
### 9P server & client
|
||||||
|
|
||||||
```c
|
Serve a directory of org files over 9P, then act on it with `ork`:
|
||||||
#include "orkmode.h"
|
|
||||||
|
|
||||||
// Parse a document
|
```sh
|
||||||
OrkDocument* doc = ork_parse("* Hello World");
|
# Start the server (unix socket or tcp)
|
||||||
if (doc) {
|
ork-server -a unix:///tmp/ork.sock -d ~/org &
|
||||||
// Get as JSON
|
|
||||||
char* json = ork_document_to_json(doc);
|
|
||||||
printf("%s\n", json);
|
|
||||||
ork_string_free(json);
|
|
||||||
|
|
||||||
// Get title
|
# Point the client at it (or pass -a on each call)
|
||||||
char* title = ork_document_title(doc);
|
export ORK_ADDR=unix:///tmp/ork.sock
|
||||||
if (title) {
|
|
||||||
printf("Title: %s\n", title);
|
|
||||||
ork_string_free(title);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup
|
ork docs # list documents
|
||||||
ork_document_free(doc);
|
ork tree work # outline of work.org
|
||||||
}
|
ork todos # TODO items across all docs
|
||||||
|
ork set work my-task keyword DONE
|
||||||
|
ork refile work my-task archive --parent done-items
|
||||||
|
ork query 'tag:work+todo:TODO'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Any 9P client works too (e.g. plan9port `9p`); the server owns all org
|
||||||
|
logic and reads/writes the real `.org` files.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### Parser Design
|
### Parser Design
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "orkmode-core"
|
|
||||||
version.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
authors.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
description = "Core library for orkmode - org-mode implementation"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["cdylib", "rlib"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
org-ast = { path = "../org-ast" }
|
|
||||||
org-parser = { path = "../org-parser" }
|
|
||||||
serde = { workspace = true }
|
|
||||||
serde_json = { workspace = true }
|
|
||||||
thiserror = { workspace = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
pretty_assertions = { workspace = true }
|
|
||||||
|
|
@ -1,185 +0,0 @@
|
||||||
//! FFI bindings for C/C++ integration.
|
|
||||||
//!
|
|
||||||
//! This module provides a C-compatible API for the orkmode parser,
|
|
||||||
//! enabling integration with Qt/C++ applications.
|
|
||||||
|
|
||||||
use std::ffi::{CStr, CString};
|
|
||||||
use std::os::raw::c_char;
|
|
||||||
use std::ptr;
|
|
||||||
|
|
||||||
use crate::{parse, parse_to_json_pretty};
|
|
||||||
|
|
||||||
/// Opaque handle to a parsed document.
|
|
||||||
pub struct OrkDocument {
|
|
||||||
doc: org_ast::Document,
|
|
||||||
_source: String, // Keep source alive for spans
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse an org document from a C string.
|
|
||||||
///
|
|
||||||
/// Returns a handle to the parsed document, or null on error.
|
|
||||||
/// The caller must free the document with `ork_document_free`.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// - `source` must be a valid null-terminated UTF-8 string.
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn ork_parse(source: *const c_char) -> *mut OrkDocument {
|
|
||||||
if source.is_null() {
|
|
||||||
return ptr::null_mut();
|
|
||||||
}
|
|
||||||
|
|
||||||
let source_str = match CStr::from_ptr(source).to_str() {
|
|
||||||
Ok(s) => s.to_string(),
|
|
||||||
Err(_) => return ptr::null_mut(),
|
|
||||||
};
|
|
||||||
|
|
||||||
match parse(&source_str) {
|
|
||||||
Ok(doc) => Box::into_raw(Box::new(OrkDocument {
|
|
||||||
doc,
|
|
||||||
_source: source_str,
|
|
||||||
})),
|
|
||||||
Err(_) => ptr::null_mut(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Free a parsed document.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// - `doc` must be a valid pointer returned by `ork_parse`, or null.
|
|
||||||
/// - After this call, `doc` must not be used.
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn ork_document_free(doc: *mut OrkDocument) {
|
|
||||||
if !doc.is_null() {
|
|
||||||
drop(Box::from_raw(doc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the document as JSON.
|
|
||||||
///
|
|
||||||
/// Returns a newly allocated string that must be freed with `ork_string_free`.
|
|
||||||
/// Returns null on error.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// - `doc` must be a valid pointer returned by `ork_parse`.
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn ork_document_to_json(doc: *const OrkDocument) -> *mut c_char {
|
|
||||||
if doc.is_null() {
|
|
||||||
return ptr::null_mut();
|
|
||||||
}
|
|
||||||
|
|
||||||
let doc = &(*doc).doc;
|
|
||||||
match serde_json::to_string_pretty(doc) {
|
|
||||||
Ok(json) => match CString::new(json) {
|
|
||||||
Ok(cstr) => cstr.into_raw(),
|
|
||||||
Err(_) => ptr::null_mut(),
|
|
||||||
},
|
|
||||||
Err(_) => ptr::null_mut(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse an org document and return JSON directly.
|
|
||||||
///
|
|
||||||
/// This is a convenience function that combines parsing and JSON serialization.
|
|
||||||
/// Returns a newly allocated string that must be freed with `ork_string_free`.
|
|
||||||
/// Returns null on error.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// - `source` must be a valid null-terminated UTF-8 string.
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn ork_parse_to_json(source: *const c_char) -> *mut c_char {
|
|
||||||
if source.is_null() {
|
|
||||||
return ptr::null_mut();
|
|
||||||
}
|
|
||||||
|
|
||||||
let source_str = match CStr::from_ptr(source).to_str() {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => return ptr::null_mut(),
|
|
||||||
};
|
|
||||||
|
|
||||||
match parse_to_json_pretty(source_str) {
|
|
||||||
Ok(json) => match CString::new(json) {
|
|
||||||
Ok(cstr) => cstr.into_raw(),
|
|
||||||
Err(_) => ptr::null_mut(),
|
|
||||||
},
|
|
||||||
Err(_) => ptr::null_mut(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Free a string returned by orkmode functions.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// - `s` must be a valid pointer returned by an orkmode function, or null.
|
|
||||||
/// - After this call, `s` must not be used.
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn ork_string_free(s: *mut c_char) {
|
|
||||||
if !s.is_null() {
|
|
||||||
drop(CString::from_raw(s));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the document title.
|
|
||||||
///
|
|
||||||
/// Returns a newly allocated string that must be freed with `ork_string_free`.
|
|
||||||
/// Returns null if no title is set.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// - `doc` must be a valid pointer returned by `ork_parse`.
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn ork_document_title(doc: *const OrkDocument) -> *mut c_char {
|
|
||||||
if doc.is_null() {
|
|
||||||
return ptr::null_mut();
|
|
||||||
}
|
|
||||||
|
|
||||||
match (*doc).doc.title() {
|
|
||||||
Some(title) => match CString::new(title) {
|
|
||||||
Ok(cstr) => cstr.into_raw(),
|
|
||||||
Err(_) => ptr::null_mut(),
|
|
||||||
},
|
|
||||||
None => ptr::null_mut(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the number of top-level sections.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// - `doc` must be a valid pointer returned by `ork_parse`.
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn ork_document_section_count(doc: *const OrkDocument) -> usize {
|
|
||||||
if doc.is_null() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
(*doc).doc.sections.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the total number of sections (including nested).
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// - `doc` must be a valid pointer returned by `ork_parse`.
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn ork_document_total_section_count(doc: *const OrkDocument) -> usize {
|
|
||||||
if doc.is_null() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
(*doc).doc.all_sections().count()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the number of TODO items.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// - `doc` must be a valid pointer returned by `ork_parse`.
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn ork_document_todo_count(doc: *const OrkDocument) -> usize {
|
|
||||||
if doc.is_null() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
(*doc).doc.todos().count()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Version info
|
|
||||||
/// Get the library version as a string.
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn ork_version() -> *const c_char {
|
|
||||||
// This is a static string, so it doesn't need to be freed.
|
|
||||||
static VERSION: &[u8] = b"0.1.0\0";
|
|
||||||
VERSION.as_ptr() as *const c_char
|
|
||||||
}
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
//! Orkmode Core Library
|
|
||||||
//!
|
|
||||||
//! This crate provides the core functionality for orkmode, including:
|
|
||||||
//! - Document parsing and AST manipulation
|
|
||||||
//! - FFI bindings for C++/Qt integration
|
|
||||||
//! - Serialization for data exchange
|
|
||||||
|
|
||||||
pub use org_ast;
|
|
||||||
pub use org_parser;
|
|
||||||
|
|
||||||
pub mod ffi;
|
|
||||||
|
|
||||||
use org_ast::Document;
|
|
||||||
use org_parser::{Parser, ParseError};
|
|
||||||
|
|
||||||
/// Parse an org document from a string.
|
|
||||||
pub fn parse(source: &str) -> Result<Document, ParseError> {
|
|
||||||
let mut parser = Parser::new();
|
|
||||||
parser.parse(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse an org document and return it as JSON.
|
|
||||||
pub fn parse_to_json(source: &str) -> Result<String, String> {
|
|
||||||
let doc = parse(source).map_err(|e| e.to_string())?;
|
|
||||||
serde_json::to_string(&doc).map_err(|e| e.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse an org document and return it as pretty-printed JSON.
|
|
||||||
pub fn parse_to_json_pretty(source: &str) -> Result<String, String> {
|
|
||||||
let doc = parse(source).map_err(|e| e.to_string())?;
|
|
||||||
serde_json::to_string_pretty(&doc).map_err(|e| e.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_simple() {
|
|
||||||
let doc = parse("* Hello World\nSome content").unwrap();
|
|
||||||
assert_eq!(doc.sections.len(), 1);
|
|
||||||
assert_eq!(doc.sections[0].headline.title_text(), "Hello World");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_to_json() {
|
|
||||||
let json = parse_to_json("* Test").unwrap();
|
|
||||||
assert!(json.contains("\"sections\""));
|
|
||||||
assert!(json.contains("Test"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_complex_document() {
|
|
||||||
let source = r#"#+TITLE: Test Document
|
|
||||||
#+TODO: TODO WAITING | DONE CANCELLED
|
|
||||||
|
|
||||||
* TODO [#A] First Task :work:urgent:
|
|
||||||
DEADLINE: <2025-01-15 Wed 14:00>
|
|
||||||
:PROPERTIES:
|
|
||||||
:CUSTOM_ID: task-1
|
|
||||||
:END:
|
|
||||||
|
|
||||||
Some description here.
|
|
||||||
|
|
||||||
** Subtask
|
|
||||||
- [ ] Item 1
|
|
||||||
- [X] Item 2
|
|
||||||
|
|
||||||
* DONE Second Task
|
|
||||||
CLOSED: [2025-01-14 Tue 10:00]
|
|
||||||
|
|
||||||
#+BEGIN_SRC python
|
|
||||||
def hello():
|
|
||||||
print("Hello, World!")
|
|
||||||
#+END_SRC
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let doc = parse(source).unwrap();
|
|
||||||
assert_eq!(doc.settings.title, Some("Test Document".to_string()));
|
|
||||||
assert_eq!(doc.sections.len(), 2);
|
|
||||||
|
|
||||||
// First section
|
|
||||||
let first = &doc.sections[0];
|
|
||||||
assert!(first.is_todo());
|
|
||||||
assert!(first.has_tag("work"));
|
|
||||||
assert!(first.has_tag("urgent"));
|
|
||||||
assert_eq!(first.headline.priority, Some(org_ast::Priority::A));
|
|
||||||
|
|
||||||
// Check children
|
|
||||||
assert_eq!(first.children.len(), 1);
|
|
||||||
|
|
||||||
// Second section
|
|
||||||
let second = &doc.sections[1];
|
|
||||||
assert!(second.is_done());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue