From 33173bcc298a2b4462a13bad87d2ede957de4a84 Mon Sep 17 00:00:00 2001 From: Levi Neely <141506390+lneely@users.noreply.github.com> Date: Wed, 30 Sep 2026 22:27:50 +0200 Subject: [PATCH] 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). --- AGENTS.md | 22 ++-- Cargo.lock | 37 ------- Cargo.toml | 1 - README.md | 54 +++++----- crates/orkmode-core/Cargo.toml | 20 ---- crates/orkmode-core/src/ffi.rs | 185 --------------------------------- crates/orkmode-core/src/lib.rs | 96 ----------------- 7 files changed, 38 insertions(+), 377 deletions(-) delete mode 100644 crates/orkmode-core/Cargo.toml delete mode 100644 crates/orkmode-core/src/ffi.rs delete mode 100644 crates/orkmode-core/src/lib.rs diff --git a/AGENTS.md b/AGENTS.md index 470a097..387a809 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,8 +14,8 @@ Current state: **v0.1** — parser and CLI complete, no UI yet. crates/ ├── org-ast/ # AST types (Document, Section, Element, etc.) ├── org-parser/ # Tree-sitter parser, AST conversion -├── orkmode-core/ # High-level API, C FFI -└── ork-cli/ # CLI tool (bin name: ork) +├── ork-server/ # 9P server exposing org files (bin: ork-server) +└── ork-cli/ # CLI tool, a 9P client (bin name: ork) ``` ## 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`* - Inline objects (bold, links) are partially implemented -### FFI +### 9P server -- C-compatible FFI in `orkmode-core/src/ffi.rs` -- Functions prefixed `ork_*` -- Caller owns returned strings (free with `ork_string_free`) +- `ork-server` exposes org files as a 9P namespace (docs, sections, fields, agenda, query) +- Structural ops via the `ctl` file: rm, move, promote, demote, refile, archive, lint +- `ork` is a pure 9P client; all org logic lives in the server ## 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 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: 1. Set up Qt6/QML project in `ui/` -2. Link orkmode-core via C FFI -3. Implement document model (QAbstractItemModel) +2. Connect to `ork-server` over 9P (no FFI) +3. Implement document model (QAbstractItemModel) from the 9P namespace 4. Basic headline rendering with folding -5. Syntax highlighting +5. Structural styling (keywords, priorities, tags, timestamps) ## Testing Changes diff --git a/Cargo.lock b/Cargo.lock index b319a62..956f25e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -346,12 +346,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - [[package]] name = "js-sys" version = "0.3.106" @@ -571,18 +565,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "orkmode-core" -version = "0.1.0" -dependencies = [ - "org-ast", - "org-parser", - "pretty_assertions", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "parking_lot" version = "0.12.5" @@ -754,19 +736,6 @@ dependencies = [ "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]] name = "sharded-slab" version = "0.1.7" @@ -1197,9 +1166,3 @@ name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - -[[package]] -name = "zmij" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index 96e8392..21a32ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ resolver = "2" members = [ "crates/org-parser", "crates/org-ast", - "crates/orkmode-core", "crates/ork-cli", "crates/ork-server", ] diff --git a/README.md b/README.md index 667b37c..7af5e63 100644 --- a/README.md +++ b/README.md @@ -25,14 +25,17 @@ orkmode/ │ │ ├── convert.rs # Tree-sitter → AST conversion │ │ ├── ts.rs # Tree-sitter language bindings │ │ └── error.rs # Error types -│ ├── orkmode-core/ # Core library with FFI +│ ├── ork-cli/ # Command-line interface (9P client) │ │ └── src/ -│ │ ├── lib.rs # High-level API -│ │ └── ffi.rs # C FFI for Qt integration -│ └── ork-cli/ # Command-line interface +│ │ ├── main.rs # CLI commands (base + friendly verbs) +│ │ └── client.rs # 9P2000 client +│ └── ork-server/ # 9P server exposing org files │ └── src/ -│ ├── main.rs # CLI entry point -│ └── commands.rs # Command implementations +│ ├── bin/ork-server.rs # Server entry point +│ ├── 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 ``` @@ -189,31 +192,28 @@ for section in doc.all_sections() { } ``` -### C/C++ FFI +### 9P server & client -```c -#include "orkmode.h" +Serve a directory of org files over 9P, then act on it with `ork`: -// Parse a document -OrkDocument* doc = ork_parse("* Hello World"); -if (doc) { - // Get as JSON - char* json = ork_document_to_json(doc); - printf("%s\n", json); - ork_string_free(json); - - // Get title - char* title = ork_document_title(doc); - if (title) { - printf("Title: %s\n", title); - ork_string_free(title); - } - - // Cleanup - ork_document_free(doc); -} +```sh +# Start the server (unix socket or tcp) +ork-server -a unix:///tmp/ork.sock -d ~/org & + +# Point the client at it (or pass -a on each call) +export ORK_ADDR=unix:///tmp/ork.sock + +ork docs # list documents +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 ### Parser Design diff --git a/crates/orkmode-core/Cargo.toml b/crates/orkmode-core/Cargo.toml deleted file mode 100644 index 59e5efe..0000000 --- a/crates/orkmode-core/Cargo.toml +++ /dev/null @@ -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 } diff --git a/crates/orkmode-core/src/ffi.rs b/crates/orkmode-core/src/ffi.rs deleted file mode 100644 index b144cc2..0000000 --- a/crates/orkmode-core/src/ffi.rs +++ /dev/null @@ -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 -} diff --git a/crates/orkmode-core/src/lib.rs b/crates/orkmode-core/src/lib.rs deleted file mode 100644 index c9d895e..0000000 --- a/crates/orkmode-core/src/lib.rs +++ /dev/null @@ -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 { - 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 { - 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 { - 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()); - } -}