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:
Levi Neely 2026-09-30 22:27:50 +02:00
parent 7ee65a4a2d
commit 33173bcc29
7 changed files with 38 additions and 377 deletions

View File

@ -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

37
Cargo.lock generated
View File

@ -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"

View File

@ -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",
] ]

View File

@ -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); # Point the client at it (or pass -a on each call)
printf("%s\n", json); export ORK_ADDR=unix:///tmp/ork.sock
ork_string_free(json);
ork docs # list documents
// Get title ork tree work # outline of work.org
char* title = ork_document_title(doc); ork todos # TODO items across all docs
if (title) { ork set work my-task keyword DONE
printf("Title: %s\n", title); ork refile work my-task archive --parent done-items
ork_string_free(title); ork query 'tag:work+todo:TODO'
}
// Cleanup
ork_document_free(doc);
}
``` ```
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

View File

@ -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 }

View File

@ -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
}

View File

@ -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());
}
}