Clean up docs and fix set_planning_field
- README: update CLI docs to reflect 9P client architecture - README: update roadmap to reflect v0.3 progress (server, UI) - AGENTS.md: update CLI examples and current limitations - state.rs: rewrite set_planning_field to properly parse/rebuild planning lines (removes dead code, fixes replace logic)
This commit is contained in:
parent
e129f9ee94
commit
ed73ae5cca
33
AGENTS.md
33
AGENTS.md
|
|
@ -39,18 +39,20 @@ cargo test
|
|||
|
||||
### CLI
|
||||
|
||||
The `ork` CLI operates on directories by default:
|
||||
The `ork` CLI is a 9P client that talks to `ork-server`. Start a server first, then:
|
||||
|
||||
```bash
|
||||
ork info . # Aggregate info for all .org files
|
||||
ork sections --todos-only # List TODOs across files
|
||||
ork query "tag:work" -R # Recursive search
|
||||
ork get "section-id" -j # JSON output
|
||||
ork toggle "task-id" -n # Dry run
|
||||
ork add "New Task" file.org --todo TODO
|
||||
export ORK_ADDR=unix:///tmp/ork.sock
|
||||
|
||||
ork docs # List documents
|
||||
ork tree work # Outline of work.org
|
||||
ork todos # All TODOs
|
||||
ork get work my-task # Section fields
|
||||
ork set work my-task keyword DONE
|
||||
ork add work "* TODO New task :work:"
|
||||
```
|
||||
|
||||
Use `-j/--json` for machine-readable output. Use `-n/--dry-run` for mutations.
|
||||
Base 9P operations are available as `ork ls`, `ork read`, `ork write`, `ork rdwr`.
|
||||
|
||||
## Architecture Decisions
|
||||
|
||||
|
|
@ -99,9 +101,8 @@ Use `-j/--json` for machine-readable output. Use `-n/--dry-run` for mutations.
|
|||
## Current Limitations
|
||||
|
||||
1. **Inline objects incomplete** — tree-sitter-org doesn't fully parse bold/italic/links
|
||||
2. **Property/tag write commands** — stubs only, not implemented
|
||||
3. **Timestamp text** — displays "set" placeholder, not actual date
|
||||
4. **No incremental parsing** — full reparse on every call
|
||||
2. **No incremental parsing** — full reparse on every write
|
||||
3. **Section ID instability** — slug-derived IDs change when titles change (UUID IDs via `:ID:` are stable)
|
||||
|
||||
## Next Steps (v0.4)
|
||||
|
||||
|
|
@ -126,8 +127,14 @@ Before committing:
|
|||
```bash
|
||||
cargo build --release
|
||||
cargo test
|
||||
./target/release/ork check .
|
||||
./target/release/ork info test.org -j
|
||||
|
||||
# Integration test (start server, run CLI, stop server)
|
||||
ork-server -a unix:///tmp/ork-test.sock -d /tmp/test-org &
|
||||
SERVER_PID=$!
|
||||
export ORK_ADDR=unix:///tmp/ork-test.sock
|
||||
ork docs
|
||||
ork lint
|
||||
kill $SERVER_PID
|
||||
```
|
||||
|
||||
## Questions?
|
||||
|
|
|
|||
209
README.md
209
README.md
|
|
@ -116,126 +116,91 @@ cmake --build ui/build -j
|
|||
|
||||
## Usage
|
||||
|
||||
### CLI (ork)
|
||||
### Server
|
||||
|
||||
The `ork` command operates on org directories or individual files. All commands default to
|
||||
the current directory (`.`) when no path is specified. Use `-R` for recursive subdirectory scanning.
|
||||
Start the 9P server to expose an org directory:
|
||||
|
||||
```bash
|
||||
# List org files
|
||||
ork files # Current directory
|
||||
ork files ~/org # Specific directory
|
||||
ork files -R . # Recursive
|
||||
# Unix socket (preferred for local use)
|
||||
ork-server -a unix:///tmp/ork.sock -d ~/org
|
||||
|
||||
# Document info (aggregated across all files)
|
||||
ork info # Current directory
|
||||
ork info ~/org -R # Recursive
|
||||
ork info file.org # Single file
|
||||
ork info --json # JSON output
|
||||
|
||||
# List sections
|
||||
ork sections # All sections in cwd
|
||||
ork sections --todos-only # Only TODOs
|
||||
ork sections --tag work # Filter by tag
|
||||
ork sections --depth 2 # Max depth
|
||||
ork sections -R -j # Recursive, JSON
|
||||
|
||||
# Query sections
|
||||
ork query "tag:work" # By tag
|
||||
ork query "todo:TODO" # By TODO state
|
||||
ork query "priority:A" # By priority
|
||||
ork query "title:pattern" # By title
|
||||
ork query "file:notes" # By filename
|
||||
ork query "done:false" -j # JSON output
|
||||
|
||||
# Get specific section by ID or title
|
||||
ork get "my-task-id" # Searches all files
|
||||
ork get "My Task" -j # JSON output
|
||||
ork get "intro" ~/org -R # In specific path
|
||||
|
||||
# List tags with counts
|
||||
ork tags # All tags in cwd
|
||||
ork tags -R --json # Recursive, JSON
|
||||
|
||||
# Export as JSON
|
||||
ork export --pretty # All documents
|
||||
ork export --section my-id # Specific section
|
||||
|
||||
# Add section
|
||||
ork add "New Task" file.org # To specific file
|
||||
ork add "New Task" --file notes.org # When path is directory
|
||||
ork add "New Task" file.org --todo TODO --priority A --tags "work,urgent"
|
||||
ork add "New Task" file.org -n # Dry run
|
||||
|
||||
# Toggle TODO state
|
||||
ork toggle "task-id" # Searches all files
|
||||
ork toggle "task-id" -n # Dry run
|
||||
|
||||
# Agenda view
|
||||
ork agenda # Current directory
|
||||
ork agenda ~/org -R # Recursive
|
||||
ork agenda --days 14 --json # 14 days, JSON
|
||||
|
||||
# Validate syntax
|
||||
ork check # Current directory
|
||||
ork check -R --json # Recursive, JSON
|
||||
# TCP socket
|
||||
ork-server -a tcp://localhost:5640 -d ~/org
|
||||
```
|
||||
|
||||
### Rust API
|
||||
### CLI (ork)
|
||||
|
||||
```rust
|
||||
use org_parser::Parser;
|
||||
`ork` is a 9P client that talks to `ork-server`. All org-mode intelligence lives in the server.
|
||||
|
||||
let mut parser = Parser::new();
|
||||
let doc = parser.parse(r#"
|
||||
#+TITLE: My Document
|
||||
#+TODO: TODO WAITING | DONE
|
||||
|
||||
* TODO [#A] First Task :work:
|
||||
DEADLINE: <2025-01-15 Wed 14:00>
|
||||
|
||||
Some description.
|
||||
|
||||
** Subtask
|
||||
- [ ] Item 1
|
||||
- [X] Item 2
|
||||
"#).unwrap();
|
||||
|
||||
// Access document properties
|
||||
println!("Title: {:?}", doc.title());
|
||||
println!("Sections: {}", doc.sections.len());
|
||||
println!("TODOs: {}", doc.todos().count());
|
||||
|
||||
// Iterate sections
|
||||
for section in doc.all_sections() {
|
||||
println!("{} {}",
|
||||
"*".repeat(section.level() as usize),
|
||||
section.headline.title_text()
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 9P server & client
|
||||
|
||||
Serve a directory of org files over 9P, then act on it with `ork`:
|
||||
|
||||
```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)
|
||||
```bash
|
||||
# Set the server address (or use -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'
|
||||
# Base 9P operations
|
||||
ork ls / # List root namespace
|
||||
ork read /idx # Read a file
|
||||
ork write /work/task/keyword DONE # Write a value
|
||||
|
||||
# Friendly verbs (compositions of 9P ops)
|
||||
ork docs # List documents (name + title)
|
||||
ork sections work # Top-level sections in work.org
|
||||
ork tree work # Full section outline
|
||||
ork todos # All TODO items across docs
|
||||
ork todos work # TODOs in work.org only
|
||||
|
||||
# Section details
|
||||
ork get work my-task # Show section fields
|
||||
ork set work my-task keyword DONE # Set a field
|
||||
ork set work my-task tags work:urgent # Set tags (colon-separated)
|
||||
|
||||
# Create sections
|
||||
ork mkdoc notes # Create notes.org
|
||||
ork add work "* TODO New task :work:" # Add top-level section
|
||||
ork add work "* Subtask" -p parent-id # Add child section
|
||||
|
||||
# TODO management
|
||||
ork todo work my-task # Cycle to next state
|
||||
ork todo work my-task DONE # Set explicit state
|
||||
ork tag work my-task urgent # Add tag
|
||||
ork tag work my-task urgent -r # Remove tag
|
||||
|
||||
# Structural operations
|
||||
ork mv work my-task up # Reorder among siblings
|
||||
ork promote work my-task # Decrease level
|
||||
ork demote work my-task # Increase level
|
||||
ork rm work my-task # Delete section and subtree
|
||||
ork refile work my-task archive # Move to another doc
|
||||
ork refile work my-task archive -p done # As child of 'done' section
|
||||
ork archive work my-task # Move to work.org_archive
|
||||
|
||||
# Properties
|
||||
ork prop work my-task CUSTOM_ID # Get property
|
||||
ork prop work my-task EFFORT 2h # Set property
|
||||
ork prop work my-task EFFORT -d # Delete property
|
||||
|
||||
# Agenda views
|
||||
ork agenda todos # All TODOs
|
||||
ork agenda today # Items scheduled/due today
|
||||
ork agenda week # Next 7 days
|
||||
|
||||
# Query
|
||||
ork query 'tag:work' # Match sections by tag
|
||||
ork query 'tag:work+todo:TODO' # Compound query
|
||||
ork query 'priority:A' # By priority
|
||||
|
||||
# Maintenance
|
||||
ork reload # Rescan files from disk
|
||||
ork lint # Validate all documents
|
||||
ork lint work # Validate one document
|
||||
```
|
||||
|
||||
Any 9P client works too (e.g. plan9port `9p`); the server owns all org
|
||||
logic and reads/writes the real `.org` files.
|
||||
Any 9P client works (e.g., plan9port `9p`):
|
||||
|
||||
```bash
|
||||
9p -a 'unix!/tmp/ork.sock' ls /
|
||||
9p -a 'unix!/tmp/ork.sock' read /idx
|
||||
9p -a 'unix!/tmp/ork.sock' write /work/task/keyword DONE
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
|
|
@ -263,30 +228,36 @@ All nodes include source span information for editor integration.
|
|||
|
||||
## Roadmap
|
||||
|
||||
### Phase 1: Parser (Current)
|
||||
### Phase 1: Parser ✓
|
||||
- [x] Tree-sitter integration
|
||||
- [x] Core AST types
|
||||
- [x] Headline/section parsing
|
||||
- [x] Block element parsing
|
||||
- [ ] Complete inline object parsing
|
||||
|
||||
### Phase 2: Qt UI
|
||||
### Phase 2: 9P Server ✓
|
||||
- [x] Document state with locking
|
||||
- [x] Read/write section fields (keyword, priority, title, tags, timestamps, body)
|
||||
- [x] Create documents and sections
|
||||
- [x] Structural ops (delete, move, promote, demote, refile, archive)
|
||||
- [x] Agenda views (todos, today, week)
|
||||
- [x] Query interface (tag, todo, priority)
|
||||
- [x] ctl verbs (reload, lint)
|
||||
|
||||
### Phase 3: Qt UI (Current)
|
||||
- [x] Document rendering (read-only)
|
||||
- [x] Structural styling (keywords, priorities, tags, timestamps)
|
||||
- [x] Folding/cycling
|
||||
- [x] Folding/cycling (TreeView)
|
||||
- [x] Navigation
|
||||
- [ ] Syntax highlighting for section bodies
|
||||
|
||||
### Phase 3: Editing
|
||||
- [ ] Inline editing
|
||||
- [ ] TODO state cycling
|
||||
- [ ] Tag editing
|
||||
- [ ] Property editing
|
||||
- [ ] TODO state cycling (click to toggle)
|
||||
- [ ] Tag/property editing
|
||||
- [ ] Syntax highlighting for bodies
|
||||
|
||||
### Phase 4: Agenda
|
||||
- [ ] Basic agenda view
|
||||
- [ ] Dedicated agenda view panel
|
||||
- [ ] Day/week/month views
|
||||
- [ ] Filtering
|
||||
- [ ] Filtering and sorting
|
||||
- [ ] Custom views
|
||||
|
||||
### Phase 5: Advanced Features
|
||||
|
|
|
|||
|
|
@ -594,42 +594,57 @@ impl OrkState {
|
|||
let planning_end = planning.span.end.offset;
|
||||
let planning_text = &content[planning_start..planning_end];
|
||||
|
||||
// Parse existing planning fields
|
||||
let mut fields: HashMap<&str, &str> = HashMap::new();
|
||||
for part in planning_text.split_whitespace() {
|
||||
if part.ends_with(':') {
|
||||
let key = &part[..part.len()-1];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild planning line
|
||||
let new_planning = if timestamp.is_empty() {
|
||||
// Remove this field
|
||||
let filtered: String = planning_text
|
||||
.replace(&format!("{}: ", field), "")
|
||||
.split_whitespace()
|
||||
.filter(|s| !s.starts_with('<') || !planning_text.contains(&format!("{}: {}", field, s)))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
if filtered.trim().is_empty() {
|
||||
// Remove this field: strip "FIELD: <timestamp>" pattern
|
||||
let mut result = String::new();
|
||||
let mut skip_next_ts = false;
|
||||
for part in planning_text.split_whitespace() {
|
||||
if part == &format!("{}:", field) {
|
||||
skip_next_ts = true;
|
||||
continue;
|
||||
}
|
||||
if skip_next_ts && part.starts_with('<') {
|
||||
skip_next_ts = false;
|
||||
continue;
|
||||
}
|
||||
skip_next_ts = false;
|
||||
if !result.is_empty() { result.push(' '); }
|
||||
result.push_str(part);
|
||||
}
|
||||
if result.trim().is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("{}\n", filtered)
|
||||
format!("{}\n", result.trim())
|
||||
}
|
||||
} else {
|
||||
// Add/update this field
|
||||
let ts = if timestamp.starts_with('<') { timestamp.to_string() } else { format!("<{}>", timestamp) };
|
||||
if planning_text.contains(&format!("{}:", field)) {
|
||||
// Replace existing
|
||||
let re_pattern = format!(r"{}: <[^>]+>", field);
|
||||
planning_text.replace(&planning_text
|
||||
.split_whitespace()
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "), &format!("{}: {}", field, ts))
|
||||
// Replace existing: rebuild with new timestamp for this field
|
||||
let mut result = String::new();
|
||||
let mut replace_next_ts = false;
|
||||
for part in planning_text.split_whitespace() {
|
||||
if part == &format!("{}:", field) {
|
||||
replace_next_ts = true;
|
||||
if !result.is_empty() { result.push(' '); }
|
||||
result.push_str(part);
|
||||
continue;
|
||||
}
|
||||
if replace_next_ts && part.starts_with('<') {
|
||||
result.push(' ');
|
||||
result.push_str(&ts);
|
||||
replace_next_ts = false;
|
||||
continue;
|
||||
}
|
||||
replace_next_ts = false;
|
||||
if !result.is_empty() { result.push(' '); }
|
||||
result.push_str(part);
|
||||
}
|
||||
format!("{}\n", result.trim())
|
||||
} else {
|
||||
// Add new
|
||||
format!("{}{}: {}\n", planning_text.trim_end(), field, ts)
|
||||
// Add new field
|
||||
format!("{} {}: {}\n", planning_text.trim(), field, ts)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue