na 1.2.87 → 1.2.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +17 -9
- data/2025-10-29-one-more-na-update.md +142 -0
- data/CHANGELOG.md +39 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +4 -1
- data/README.md +128 -2
- data/bin/commands/find.rb +62 -0
- data/bin/commands/next.rb +65 -0
- data/bin/commands/plugin.rb +75 -0
- data/bin/commands/tagged.rb +63 -0
- data/bin/commands/update.rb +54 -1
- data/bin/na +1 -0
- data/lib/na/action.rb +13 -0
- data/lib/na/next_action.rb +92 -0
- data/lib/na/plugins.rb +419 -0
- data/lib/na/string.rb +6 -4
- data/lib/na/version.rb +1 -1
- data/lib/na.rb +1 -0
- data/na/Test.todo.markdown +32 -0
- data/na/test.md +21 -0
- data/na.gemspec +1 -0
- data/plugins.md +38 -0
- data/src/_README.md +110 -1
- metadata +21 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b06708529c5a33331a67f4295783bee29e559343ff3e2aa097f0802efac04ca6
|
|
4
|
+
data.tar.gz: 4bf4437737504d54b4cd018c339a4643ca1d108d94e407624040905c0fa5e9f8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b3aac3037d6a51782dad07a452e4fc3e987ef4b3165dfc718119a7b9b5afc23ad42c069243769bf160cd6852f65a239ab655300e465524d03e9480fbef853282
|
|
7
|
+
data.tar.gz: 67aa8edd664f3023a9cef4478c1ef607aba7f8b435413579d51110fdeeddbd9786674ad9320c65a3b7ef3d3b00b34860510fcae089fba37843fc010253306a92
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on 2025-10-28
|
|
3
|
+
# on 2025-10-28 13:13:50 UTC using RuboCop version 1.75.7.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
|
@@ -21,7 +21,7 @@ Lint/UnusedMethodArgument:
|
|
|
21
21
|
Exclude:
|
|
22
22
|
- 'lib/na/string.rb'
|
|
23
23
|
|
|
24
|
-
# Offense count:
|
|
24
|
+
# Offense count: 45
|
|
25
25
|
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
|
26
26
|
Metrics/AbcSize:
|
|
27
27
|
Max: 276
|
|
@@ -40,29 +40,29 @@ Metrics/BlockNesting:
|
|
|
40
40
|
# Offense count: 6
|
|
41
41
|
# Configuration parameters: CountComments, CountAsOne.
|
|
42
42
|
Metrics/ClassLength:
|
|
43
|
-
Max:
|
|
43
|
+
Max: 904
|
|
44
44
|
|
|
45
|
-
# Offense count:
|
|
45
|
+
# Offense count: 34
|
|
46
46
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
|
47
47
|
Metrics/CyclomaticComplexity:
|
|
48
48
|
Max: 82
|
|
49
49
|
|
|
50
|
-
# Offense count:
|
|
50
|
+
# Offense count: 53
|
|
51
51
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
|
52
52
|
Metrics/MethodLength:
|
|
53
53
|
Max: 183
|
|
54
54
|
|
|
55
|
-
# Offense count:
|
|
55
|
+
# Offense count: 5
|
|
56
56
|
# Configuration parameters: CountComments, CountAsOne.
|
|
57
57
|
Metrics/ModuleLength:
|
|
58
|
-
Max:
|
|
58
|
+
Max: 906
|
|
59
59
|
|
|
60
60
|
# Offense count: 5
|
|
61
61
|
# Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
|
|
62
62
|
Metrics/ParameterLists:
|
|
63
63
|
Max: 22
|
|
64
64
|
|
|
65
|
-
# Offense count:
|
|
65
|
+
# Offense count: 33
|
|
66
66
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
|
67
67
|
Metrics/PerceivedComplexity:
|
|
68
68
|
Max: 94
|
|
@@ -96,6 +96,14 @@ Style/ModuleFunction:
|
|
|
96
96
|
Exclude:
|
|
97
97
|
- 'lib/na/colors.rb'
|
|
98
98
|
|
|
99
|
+
# Offense count: 1
|
|
100
|
+
# This cop supports safe autocorrection (--autocorrect).
|
|
101
|
+
# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
|
|
102
|
+
# SupportedStyles: single_quotes, double_quotes
|
|
103
|
+
Style/StringLiterals:
|
|
104
|
+
Exclude:
|
|
105
|
+
- 'lib/na/next_action.rb'
|
|
106
|
+
|
|
99
107
|
# Offense count: 1
|
|
100
108
|
# This cop supports safe autocorrection (--autocorrect).
|
|
101
109
|
# Configuration parameters: EnforcedStyle.
|
|
@@ -110,7 +118,7 @@ Style/YAMLFileRead:
|
|
|
110
118
|
Exclude:
|
|
111
119
|
- 'lib/na/theme.rb'
|
|
112
120
|
|
|
113
|
-
# Offense count:
|
|
121
|
+
# Offense count: 30
|
|
114
122
|
# This cop supports safe autocorrection (--autocorrect).
|
|
115
123
|
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
|
|
116
124
|
# URISchemes: http, https
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: post
|
|
3
|
+
title: One more na update
|
|
4
|
+
categories:
|
|
5
|
+
- Code
|
|
6
|
+
- Blog
|
|
7
|
+
tags:
|
|
8
|
+
- scripting
|
|
9
|
+
- zsh
|
|
10
|
+
- tagging
|
|
11
|
+
- na
|
|
12
|
+
- cli
|
|
13
|
+
- ruby
|
|
14
|
+
- productivity
|
|
15
|
+
- taskpaper
|
|
16
|
+
date: 2025-10-29 08:00
|
|
17
|
+
slug: one-more-na-update
|
|
18
|
+
post_class: code
|
|
19
|
+
keywords: [next action]
|
|
20
|
+
---
|
|
21
|
+
This week I pushed a set of focused improvements to [NA](https://brettterpstra.com/projects/na) that make interactive workflows a lot smoother and the codebase a little more robust — including first‑class time tracking.
|
|
22
|
+
|
|
23
|
+
If you run `na update` without arguments you'll now get an interactive menu that helps you pick the file(s) and actions to operate on, with multiple selection and small but important quality-of-life fixes across parsing, move/edit behavior, and documentation.
|
|
24
|
+
|
|
25
|
+
## TL;DR
|
|
26
|
+
|
|
27
|
+
- `na update` (no args) now launches an interactive, consistent selection flow for files and actions.
|
|
28
|
+
- The update submenu supports multi-select, edit, move, and direct action modes more reliably.
|
|
29
|
+
- Fixed many edge cases: nil-safe string helpers, clearer YARD docs, and better tests for helper code.
|
|
30
|
+
- Under-the-hood improvements to file selection, action movement, and tagging logic.
|
|
31
|
+
|
|
32
|
+
### New: Time tracking
|
|
33
|
+
|
|
34
|
+
- Add start/finish times right from the CLI:
|
|
35
|
+
- `--started TIME` on `add`, `complete`/`finish`, and `update`
|
|
36
|
+
- `--end TIME` (alias `--finished`) on `add`, `complete`/`finish`, and `update`
|
|
37
|
+
- `--duration DURATION` to backfill a start time from the finish
|
|
38
|
+
- Natural language and shorthand supported everywhere: `30m ago`, `-2h`, `2h30m`, `2:30 ago`, `yesterday 5pm`
|
|
39
|
+
- Durations aren’t stored; they’re computed from `@started(...)` and `@done(...)` when displayed.
|
|
40
|
+
- Display enhancements in `next`/`tagged`:
|
|
41
|
+
- `--times` shows per‑action durations and a grand total (implies `--done`)
|
|
42
|
+
- `--human` switches durations to friendly text
|
|
43
|
+
- `--only_timed` filters to actions with both `@started` and `@done` (implies `--times --done`)
|
|
44
|
+
- `--only_times` outputs only the totals (no action list; implies `--times --done`)
|
|
45
|
+
- `--json_times` emits a JSON object with timed actions, per‑tag totals, and overall totals (implies `--times --done`)
|
|
46
|
+
- Per‑tag totals are shown as a Markdown table with aligned columns and a footer row for the grand total
|
|
47
|
+
- Duration color is theme‑configurable via a `duration` key (default `{y}`)
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
na add --started "30 minutes ago" "Investigate bug"
|
|
53
|
+
na complete --finished now --duration 2h30m "Investigate bug"
|
|
54
|
+
na next --times --human
|
|
55
|
+
na next --only_times
|
|
56
|
+
na tagged bug --json_times | jq
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## What triggered this
|
|
60
|
+
|
|
61
|
+
A number of small UX issues had crept in over time: the interactive menu sometimes re-prompted or skipped expected options, move/edit operations wouldn't always update a project's indexes correctly, and a few helper methods could raise when given `nil` paths or values. I wanted to make the interactive flow predictable and to harden the helpers so the command-line experience is less brittle.
|
|
62
|
+
|
|
63
|
+
## Interactive `na update` flow
|
|
64
|
+
|
|
65
|
+
Run `na update` with no arguments and you'll see a consistent selection flow:
|
|
66
|
+
|
|
67
|
+
- Choose one or more todo files (fuzzy search / [fzf](https://github.com/junegunn/fzf) / [gum](https://github.com/charmbracelet/gum) used when available).
|
|
68
|
+
- Pick which actions to update (multi-selection supported).
|
|
69
|
+
- Choose an operation: edit, move, add tag, remove tag, mark done, delete.
|
|
70
|
+
|
|
71
|
+
Examples:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
$ na update
|
|
75
|
+
Select files: (interactive list)
|
|
76
|
+
Select actions (multi):
|
|
77
|
+
[x] 23 % Inbox/Work : - Fix X
|
|
78
|
+
[x] 45 % Inbox/Personal : - Call Y
|
|
79
|
+
Choose operation: (edit / move / done / delete / tag)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The menu now behaves consistently whether you pick a single file or multiple files; if you choose multi-select the update command applies as you'd expect to the set of chosen actions.
|
|
83
|
+
|
|
84
|
+
There's a direct action mode when you know the file and action: `na update PATH -l 23` still works as before. The interactive flow only kicks in when no explicit target is provided.
|
|
85
|
+
|
|
86
|
+
## Notable fixes
|
|
87
|
+
|
|
88
|
+
A lot of the work was small but important:
|
|
89
|
+
|
|
90
|
+
- Nil-safe string helpers: `trunc_middle`, `highlight_filename`, and friends were guarded against `nil` inputs so tests and UI code don't explode when a file is missing or the database contains a stray blank line.
|
|
91
|
+
- Action move/edit correctness: moving an action to a different project now updates parent indexes and project line numbers properly, avoiding off-by-one bugs that could leave the file in a strange state.
|
|
92
|
+
- `select_file` and fuzzy matching: the fuzzy and database-driven file selection was made more robust — the code handles directories that have a `file.na` or `file/file.na` pattern and falls back to a clear error instead of failing silently.
|
|
93
|
+
- YARD docs: cleaned up a number of `@!method` directives and added top-level `@example` blocks for the main classes and helpers so the docs are friendlier and generate without warnings.
|
|
94
|
+
- Tests: added and fixed unit tests for `Array`, `Hash`, and `String` helpers. TTY screen and color-related test stubs were improved for reliability on CI.
|
|
95
|
+
- New tests for time features: JSON output, totals‑only output, timed‑only filtering.
|
|
96
|
+
|
|
97
|
+
## Try it
|
|
98
|
+
|
|
99
|
+
You can update to the latest version with:
|
|
100
|
+
|
|
101
|
+
{% iterm "gem install na" %}
|
|
102
|
+
|
|
103
|
+
That should give you v1.2.85 or higher.
|
|
104
|
+
|
|
105
|
+
If you're on a recent development build or want to try the updates locally:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# From the gem checkout
|
|
109
|
+
bundle exec bin/na update
|
|
110
|
+
|
|
111
|
+
# Or after building the gem and installing
|
|
112
|
+
na update
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
If you run into anything odd, please open an issue with the command you ran and a short description of what you expected vs what happened. Small, reproducible steps are the fastest way to a fix.
|
|
116
|
+
|
|
117
|
+
If you hit an error and want to include a backtrace, run the command with debug enabled and paste the output:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
GLI_DEBUG=true na [COMMAND]
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Other updates
|
|
124
|
+
|
|
125
|
+
I last wrote about 1.2.80. Here are a few highlights from the subsequent releases:
|
|
126
|
+
|
|
127
|
+
- 1.2.85 (2025-10-26)
|
|
128
|
+
- YARD docs polish — coverage is now effectively complete
|
|
129
|
+
- Nil-safety: guards for `trunc_middle` and `highlight_filename`
|
|
130
|
+
- 1.2.84 (2025-10-25)
|
|
131
|
+
- Fix: handle nil input when traversing depth
|
|
132
|
+
- 1.2.83 (2025-10-25)
|
|
133
|
+
- Fix: ignore `-d X` values that exceed existing structure depth
|
|
134
|
+
- Allow depth > 9 for `-d`
|
|
135
|
+
- 1.2.82 (2025-10-25)
|
|
136
|
+
- New: multi‑select menu when using `na update`
|
|
137
|
+
- 1.2.81 (2025-10-25)
|
|
138
|
+
- New: `na scan` to find untracked todo files (thanks @rhsev)
|
|
139
|
+
- Improvements: RuboCop cleanup, YARD docs, and test coverage
|
|
140
|
+
- Fixes: color reset in parent display; subdirectory traversal with `na next -d X`
|
|
141
|
+
|
|
142
|
+
Thanks for playing with it and for the helpful feedback you've been sending. Check out the [NA project page](https://brettterpstra.com/projects/na) for more info.
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,42 @@
|
|
|
1
|
+
### 1.2.88
|
|
2
|
+
|
|
3
|
+
2025-10-28 08:22
|
|
4
|
+
|
|
5
|
+
#### CHANGED
|
|
6
|
+
|
|
7
|
+
- Update command interactive menu lists available plugins
|
|
8
|
+
- README: add detailed Plugins section with examples and schema
|
|
9
|
+
- CHANGELOG: add 1.2.88 entry for plugins feature
|
|
10
|
+
|
|
11
|
+
#### NEW
|
|
12
|
+
|
|
13
|
+
- Plugin system with scripts in ~/.local/share/na/plugins
|
|
14
|
+
- Na plugin command to run plugins on selected actions
|
|
15
|
+
- --plugin/--input/--output/--divider flags on update/next/tagged/find
|
|
16
|
+
- Plugin IO supports json, yaml, csv, and text formats
|
|
17
|
+
- ACTION support in plugin IO (UPDATE/DELETE/COMPLETE/RESTORE/ARCHIVE/ADD_TAG/DELETE_TAG/MOVE)
|
|
18
|
+
- Auto-create plugins dir with README and sample plugins (Add Foo.py, Add Bar.sh)
|
|
19
|
+
- Display commands can pipe through plugins (STDOUT-only, no writes)
|
|
20
|
+
- Add --json_times to next/tagged (JSON of timed actions, tags, total)
|
|
21
|
+
- Add --only_times to next/tagged (show only totals, no action list)
|
|
22
|
+
|
|
23
|
+
#### IMPROVED
|
|
24
|
+
|
|
25
|
+
- Duration/time docs and output clarifications in README
|
|
26
|
+
- Internal plugin README with metadata, IO, and examples
|
|
27
|
+
- --only_timed, --times, and --json_times imply --done automatically
|
|
28
|
+
- Per-tag duration totals rendered as aligned Markdown table with footer
|
|
29
|
+
- Duration color configurable via theme key `duration` (default {y})
|
|
30
|
+
|
|
31
|
+
#### FIXED
|
|
32
|
+
|
|
33
|
+
- Plugin update path writing duplicate actions; now in-place by line
|
|
34
|
+
- Plugin apply finds action via target_line, avoids Symbol->Integer error
|
|
35
|
+
- String#wrap indentation and width handling for multi-line wrapping
|
|
36
|
+
- Lint/DuplicateBranch in plugin parse_actions
|
|
37
|
+
- Style/MapToHash in apply_plugin_result
|
|
38
|
+
- String wrapping now wraps at requested widths (e.g., 60 cols) and indents
|
|
39
|
+
|
|
1
40
|
### 1.2.87
|
|
2
41
|
|
|
3
42
|
2025-10-28 06:21
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
na (1.2.
|
|
4
|
+
na (1.2.88)
|
|
5
5
|
chronic (~> 0.10, >= 0.10.2)
|
|
6
|
+
csv (>= 3.2)
|
|
6
7
|
git (~> 3.0.0)
|
|
7
8
|
gli (~> 2.21.0)
|
|
8
9
|
mdless (~> 1.0, >= 1.0.32)
|
|
@@ -36,6 +37,7 @@ GEM
|
|
|
36
37
|
chronic (0.10.2)
|
|
37
38
|
concurrent-ruby (1.3.5)
|
|
38
39
|
connection_pool (2.5.4)
|
|
40
|
+
csv (3.3.5)
|
|
39
41
|
diff-lcs (1.6.2)
|
|
40
42
|
docile (1.4.1)
|
|
41
43
|
drb (2.2.3)
|
|
@@ -139,6 +141,7 @@ PLATFORMS
|
|
|
139
141
|
DEPENDENCIES
|
|
140
142
|
bump (~> 0.6.0)
|
|
141
143
|
chronic
|
|
144
|
+
csv
|
|
142
145
|
minitest (~> 5.14)
|
|
143
146
|
na!
|
|
144
147
|
rake
|
data/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
_If you're one of the rare people like me who find this useful, feel free to
|
|
10
10
|
[buy me some coffee][donate]._
|
|
11
11
|
|
|
12
|
-
The current version of `na` is 1.2.
|
|
12
|
+
The current version of `na` is 1.2.88.
|
|
13
13
|
|
|
14
14
|
`na` ("next action") is a command line tool designed to make it easy to see what your next actions are for any project, right from the command line. It works with TaskPaper-formatted files (but any plain text format will do), looking for `@na` tags (or whatever you specify) in todo files in your current folder.
|
|
15
15
|
|
|
@@ -76,7 +76,7 @@ SYNOPSIS
|
|
|
76
76
|
na [global options] command [command options] [arguments...]
|
|
77
77
|
|
|
78
78
|
VERSION
|
|
79
|
-
1.2.
|
|
79
|
+
1.2.88
|
|
80
80
|
|
|
81
81
|
GLOBAL OPTIONS
|
|
82
82
|
-a, --add - Add a next action (deprecated, for backwards compatibility)
|
|
@@ -112,6 +112,7 @@ COMMANDS
|
|
|
112
112
|
move - Move an existing action to a different section
|
|
113
113
|
next, show - Show next actions
|
|
114
114
|
open - Open a todo file in the default editor
|
|
115
|
+
plugin - Run a plugin on selected actions
|
|
115
116
|
projects - Show list of projects for a file
|
|
116
117
|
prompt - Show or install prompt hooks for the current shell
|
|
117
118
|
restore, unfinish - Find and remove @done tag from an action
|
|
@@ -224,15 +225,19 @@ DESCRIPTION
|
|
|
224
225
|
|
|
225
226
|
COMMAND OPTIONS
|
|
226
227
|
-d, --depth=DEPTH - Recurse to depth (default: none)
|
|
228
|
+
--divider=STRING - Divider string for text IO (default: none)
|
|
227
229
|
--[no-]done - Include @done actions
|
|
228
230
|
-e, --regex - Interpret search pattern as regular expression
|
|
229
231
|
--human - Format durations in human-friendly form
|
|
230
232
|
--in=TODO_PATH - Show actions from a specific todo file in history. May use wildcards (* and ?) (default: none)
|
|
233
|
+
--input=TYPE - Plugin input format (json|yaml|csv|text) (default: none)
|
|
231
234
|
--nest - Output actions nested by file
|
|
232
235
|
--no_file - No filename in output
|
|
233
236
|
--[no-]notes - Include notes in output
|
|
234
237
|
-o, --or - Combine search tokens with OR, displaying actions matching ANY of the terms
|
|
235
238
|
--omnifocus - Output actions nested by file and project
|
|
239
|
+
--output=TYPE - Plugin output format (json|yaml|csv|text) (default: none)
|
|
240
|
+
--plugin=NAME - Run a plugin on results (STDOUT only; no file writes) (default: none)
|
|
236
241
|
--proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
|
|
237
242
|
--save=TITLE - Save this search for future use (default: none)
|
|
238
243
|
--[no-]search_notes - Include notes in search (default: enabled)
|
|
@@ -338,12 +343,14 @@ DESCRIPTION
|
|
|
338
343
|
COMMAND OPTIONS
|
|
339
344
|
--all - Show next actions from all known todo files (in any directory)
|
|
340
345
|
-d, --depth=DEPTH - Recurse to depth (default: none)
|
|
346
|
+
--divider=STRING - Divider string for text IO (default: none)
|
|
341
347
|
--[no-]done - Include @done actions
|
|
342
348
|
--exact - Search query is exact text match (not tokens)
|
|
343
349
|
--file=TODO_FILE - Display matches from specific todo file ([relative] path) (default: none)
|
|
344
350
|
--hidden - Include hidden directories while traversing
|
|
345
351
|
--human - Format durations in human-friendly form
|
|
346
352
|
--in, --todo=TODO - Display matches from a known todo file anywhere in history (short name) (may be used more than once, default: none)
|
|
353
|
+
--input=TYPE - Plugin input format (json|yaml|csv|text) (default: none)
|
|
347
354
|
--json_times - Output times as JSON object (implies --times and --done)
|
|
348
355
|
--nest - Output actions nested by file
|
|
349
356
|
--no_file - No filename in output
|
|
@@ -351,7 +358,9 @@ COMMAND OPTIONS
|
|
|
351
358
|
--omnifocus - Output actions nested by file and project
|
|
352
359
|
--only_timed - Show only actions that have a duration (@started and @done)
|
|
353
360
|
--only_times - Output only elapsed time totals (implies --times and --done)
|
|
361
|
+
--output=TYPE - Plugin output format (json|yaml|csv|text) (default: none)
|
|
354
362
|
-p, --prio, --priority=PRIORITY - Match actions with priority, allows <>= comparison (may be used more than once, default: none)
|
|
363
|
+
--plugin=NAME - Run a plugin on results (STDOUT only; no file writes) (default: none)
|
|
355
364
|
--proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
|
|
356
365
|
--regex - Search query is regular expression
|
|
357
366
|
--save=TITLE - Save this search for future use (default: none)
|
|
@@ -499,10 +508,12 @@ DESCRIPTION
|
|
|
499
508
|
|
|
500
509
|
COMMAND OPTIONS
|
|
501
510
|
-d, --depth=DEPTH - Recurse to depth (default: 1)
|
|
511
|
+
--divider=STRING - Divider string for text IO (default: none)
|
|
502
512
|
--[no-]done - Include @done actions
|
|
503
513
|
--exact - Search query is exact text match (not tokens)
|
|
504
514
|
--human - Format durations in human-friendly form
|
|
505
515
|
--in=TODO_PATH - Show actions from a specific todo file in history. May use wildcards (* and ?) (default: none)
|
|
516
|
+
--input=TYPE - Plugin input format (json|yaml|csv|text) (default: none)
|
|
506
517
|
--json_times - Output times as JSON object (implies --times and --done)
|
|
507
518
|
--nest - Output actions nested by file
|
|
508
519
|
--no_file - No filename in output
|
|
@@ -511,6 +522,8 @@ COMMAND OPTIONS
|
|
|
511
522
|
--omnifocus - Output actions nested by file and project
|
|
512
523
|
--only_timed - Show only actions that have a duration (@started and @done)
|
|
513
524
|
--only_times - Output only elapsed time totals (implies --times and --done)
|
|
525
|
+
--output=TYPE - Plugin output format (json|yaml|csv|text) (default: none)
|
|
526
|
+
--plugin=NAME - Run a plugin on results (STDOUT only; no file writes) (default: none)
|
|
514
527
|
--proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
|
|
515
528
|
--regex - Search query is regular expression
|
|
516
529
|
--save=TITLE - Save this search for future use (default: none)
|
|
@@ -609,6 +622,7 @@ COMMAND OPTIONS
|
|
|
609
622
|
--at=POSITION - When moving task, add at [s]tart or [e]nd of target project (default: none)
|
|
610
623
|
-d, --depth=DEPTH - Search for files X directories deep (default: 1)
|
|
611
624
|
--delete - Delete an action
|
|
625
|
+
--divider=STRING - Divider string for text IO (default: none)
|
|
612
626
|
--[no-]done - Include @done actions
|
|
613
627
|
--duration=DURATION - Duration (e.g. 45m, 2h, 1d2h30m, or minutes) (default: none)
|
|
614
628
|
-e, --regex - Interpret search pattern as regular expression
|
|
@@ -617,9 +631,12 @@ COMMAND OPTIONS
|
|
|
617
631
|
-f, --finish - Add a @done tag to action
|
|
618
632
|
--file=PATH - Specify the file to search for the task (default: none)
|
|
619
633
|
--in, --todo=TODO_FILE - Use a known todo file, partial matches allowed (default: none)
|
|
634
|
+
--input=TYPE - Plugin input format (json|yaml|csv|text) (default: none)
|
|
620
635
|
-n, --note - Prompt for additional notes. Input will be appended to any existing note. If STDIN input (piped) is detected, it will be used as a note.
|
|
621
636
|
-o, --overwrite - Overwrite note instead of appending
|
|
637
|
+
--output=TYPE - Plugin output format (json|yaml|csv|text) (default: none)
|
|
622
638
|
-p, --priority=PRIO - Add/change a priority level 1-5 (default: 0)
|
|
639
|
+
--plugin=NAME - Run a plugin by name on selected actions (default: none)
|
|
623
640
|
--proj, --project=PROJECT[/SUBPROJECT] - Affect actions from a specific project (default: none)
|
|
624
641
|
-r, --remove=TAG - Remove a tag from the action, use multiple times or combine multiple tags with a comma, wildcards (* and ?) allowed (may be used more than once, default: none)
|
|
625
642
|
--replace=TEXT - Use with --find to find and replace with new text. Enables --exact when used (default: none)
|
|
@@ -898,6 +915,115 @@ If you're using a single global file, you'll need `--cwd_as` to be `tag` or `pro
|
|
|
898
915
|
|
|
899
916
|
After installing a hook, you'll need to close your terminal and start a new session to initialize the new commands.
|
|
900
917
|
|
|
918
|
+
### Plugins
|
|
919
|
+
|
|
920
|
+
NA supports a plugin system that allows you to run external scripts to transform or process actions. Plugins are stored in `~/.local/share/na/plugins` and can be written in any language with a shebang.
|
|
921
|
+
|
|
922
|
+
#### Getting Started
|
|
923
|
+
|
|
924
|
+
The first time NA runs, it will create the plugins directory with a README and two sample plugins:
|
|
925
|
+
- `Add Foo.py` - Adds a `@foo` tag with a timestamp
|
|
926
|
+
- `Add Bar.sh` - Adds a `@bar` tag
|
|
927
|
+
|
|
928
|
+
You can delete or modify these sample plugins as needed.
|
|
929
|
+
|
|
930
|
+
#### Running Plugins
|
|
931
|
+
|
|
932
|
+
Run a plugin with:
|
|
933
|
+
```bash
|
|
934
|
+
na plugin PLUGIN_NAME
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
Or use plugins through the `update` command's interactive menu, or pipe actions through plugins on display commands:
|
|
938
|
+
|
|
939
|
+
```bash
|
|
940
|
+
na update --plugin PLUGIN_NAME # Run plugin on selected actions
|
|
941
|
+
na next --plugin PLUGIN_NAME # Transform output only (no file writes)
|
|
942
|
+
na tagged bug --plugin PLUGIN_NAME # Filter and transform
|
|
943
|
+
na find "search term" --plugin PLUGIN_NAME
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
#### Plugin Metadata
|
|
947
|
+
|
|
948
|
+
Plugins can specify their behavior in a metadata block after the shebang:
|
|
949
|
+
|
|
950
|
+
```bash
|
|
951
|
+
#!/usr/bin/env python3
|
|
952
|
+
# name: My Plugin
|
|
953
|
+
# input: json
|
|
954
|
+
# output: json
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
Available metadata keys (case-insensitive):
|
|
958
|
+
- `input`: Input format (`json`, `yaml`, `csv`, `text`)
|
|
959
|
+
- `output`: Output format
|
|
960
|
+
- `name` or `title`: Display name (defaults to filename)
|
|
961
|
+
|
|
962
|
+
#### Input/Output Formats
|
|
963
|
+
|
|
964
|
+
Plugins accept and return action data. Use `--input` and `--output` flags to override metadata:
|
|
965
|
+
|
|
966
|
+
```bash
|
|
967
|
+
na plugin MY_PLUGIN --input text --output json --divider "||"
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
**JSON/YAML Schema:**
|
|
971
|
+
```json
|
|
972
|
+
[
|
|
973
|
+
{
|
|
974
|
+
"file_path": "todo.taskpaper",
|
|
975
|
+
"line": 15,
|
|
976
|
+
"parents": ["Project", "Subproject"],
|
|
977
|
+
"text": "- Action text @tag(value)",
|
|
978
|
+
"note": "Note content",
|
|
979
|
+
"tags": [
|
|
980
|
+
{ "name": "tag", "value": "value" }
|
|
981
|
+
]
|
|
982
|
+
}
|
|
983
|
+
]
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
**Text Format:**
|
|
987
|
+
```
|
|
988
|
+
ACTION||ARGS||file_path:line||parents||text||note||tags
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
Default divider is `||` (configurable with `--divider`).
|
|
992
|
+
- `parents`: `Parent>Child>Leaf`
|
|
993
|
+
- `tags`: `name(value);name;other(value)`
|
|
994
|
+
|
|
995
|
+
If the first token isn???t a known action, it???s treated as `file_path:line` and the action defaults to UPDATE.
|
|
996
|
+
|
|
997
|
+
#### Actions
|
|
998
|
+
|
|
999
|
+
Plugins may return an optional ACTION with arguments. Supported (case-insensitive):
|
|
1000
|
+
- UPDATE (default; replace text/note/tags/parents)
|
|
1001
|
+
- DELETE
|
|
1002
|
+
- COMPLETE/FINISH
|
|
1003
|
+
- RESTORE/UNFINISH
|
|
1004
|
+
- ARCHIVE
|
|
1005
|
+
- ADD_TAG (args: one or more tags)
|
|
1006
|
+
- DELETE_TAG/REMOVE_TAG (args: one or more tags)
|
|
1007
|
+
- MOVE (args: target project path)
|
|
1008
|
+
|
|
1009
|
+
#### Plugin Behavior
|
|
1010
|
+
|
|
1011
|
+
**On `update` or `plugin` command:**
|
|
1012
|
+
- Plugins can modify text, notes, tags, and parents
|
|
1013
|
+
- Changing `parents` will move the action to the new project location
|
|
1014
|
+
- `file_path` and `line` cannot be changed
|
|
1015
|
+
|
|
1016
|
+
**On display commands (`next`, `tagged`, `find`):**
|
|
1017
|
+
- Plugins only transform STDOUT (no file writes)
|
|
1018
|
+
- Use returned text/note/tags/parents for rendering
|
|
1019
|
+
- Parent changes affect display but not file structure
|
|
1020
|
+
|
|
1021
|
+
#### Override Formats
|
|
1022
|
+
|
|
1023
|
+
You can override plugin defaults with flags on any command that supports `--plugin`:
|
|
1024
|
+
```bash
|
|
1025
|
+
na next --plugin FOO --input csv --output text
|
|
1026
|
+
```
|
|
901
1027
|
|
|
902
1028
|
[fzf]: https://github.com/junegunn/fzf
|
|
903
1029
|
[gum]: https://github.com/charmbracelet/gum
|
data/bin/commands/find.rb
CHANGED
|
@@ -68,6 +68,22 @@ class App
|
|
|
68
68
|
c.desc "Output actions nested by file and project"
|
|
69
69
|
c.switch %[omnifocus], negatable: false
|
|
70
70
|
|
|
71
|
+
c.desc 'Run a plugin on results (STDOUT only; no file writes)'
|
|
72
|
+
c.arg_name 'NAME'
|
|
73
|
+
c.flag %i[plugin]
|
|
74
|
+
|
|
75
|
+
c.desc 'Plugin input format (json|yaml|csv|text)'
|
|
76
|
+
c.arg_name 'TYPE'
|
|
77
|
+
c.flag %i[input]
|
|
78
|
+
|
|
79
|
+
c.desc 'Plugin output format (json|yaml|csv|text)'
|
|
80
|
+
c.arg_name 'TYPE'
|
|
81
|
+
c.flag %i[output]
|
|
82
|
+
|
|
83
|
+
c.desc 'Divider string for text IO'
|
|
84
|
+
c.arg_name 'STRING'
|
|
85
|
+
c.flag %i[divider]
|
|
86
|
+
|
|
71
87
|
c.action do |global_options, options, args|
|
|
72
88
|
options[:nest] = true if options[:omnifocus]
|
|
73
89
|
|
|
@@ -175,6 +191,52 @@ class App
|
|
|
175
191
|
[tokens]
|
|
176
192
|
end
|
|
177
193
|
|
|
194
|
+
# Plugin piping (display-only)
|
|
195
|
+
if options[:plugin]
|
|
196
|
+
NA::Plugins.ensure_plugins_home
|
|
197
|
+
plugin_path = options[:plugin]
|
|
198
|
+
unless File.exist?(plugin_path)
|
|
199
|
+
resolved = NA::Plugins.resolve_plugin(plugin_path)
|
|
200
|
+
plugin_path = resolved if resolved
|
|
201
|
+
end
|
|
202
|
+
if plugin_path && File.exist?(plugin_path)
|
|
203
|
+
meta = NA::Plugins.parse_plugin_metadata(plugin_path)
|
|
204
|
+
input_fmt = (options[:input] || meta['input'] || 'json').to_s
|
|
205
|
+
output_fmt = (options[:output] || meta['output'] || input_fmt).to_s
|
|
206
|
+
divider = (options[:divider] || '||')
|
|
207
|
+
|
|
208
|
+
io_actions = todo.actions.map(&:to_plugin_io_hash)
|
|
209
|
+
stdin_str = NA::Plugins.serialize_actions(io_actions, format: input_fmt, divider: divider)
|
|
210
|
+
stdout = NA::Plugins.run_plugin(plugin_path, stdin_str)
|
|
211
|
+
returned = Array(NA::Plugins.parse_actions(stdout, format: output_fmt, divider: divider))
|
|
212
|
+
index = {}
|
|
213
|
+
todo.actions.each { |a| index["#{a.file_path}:#{a.file_line}"] = a }
|
|
214
|
+
returned.each do |h|
|
|
215
|
+
key = "#{h['file_path']}:#{h['line'].to_i}"
|
|
216
|
+
a = index[key]
|
|
217
|
+
next unless a
|
|
218
|
+
new_text = h['text'].to_s
|
|
219
|
+
new_note = h['note'].to_s
|
|
220
|
+
new_tags = Array(h['tags']).map { |t| [t['name'].to_s, t['value'].to_s] }
|
|
221
|
+
new_text = new_text.gsub(/(?<=\A| )@\S+(?:\(.*?\))?/, '')
|
|
222
|
+
unless new_tags.empty?
|
|
223
|
+
tag_str = new_tags.map { |k, v| v.to_s.empty? ? "@#{k}" : "@#{k}(#{v})" }.join(' ')
|
|
224
|
+
new_text = new_text.strip + (tag_str.empty? ? '' : " #{tag_str}")
|
|
225
|
+
end
|
|
226
|
+
a.action = new_text
|
|
227
|
+
a.note = new_note.empty? ? [] : new_note.split("\n")
|
|
228
|
+
a.instance_variable_set(:@tags, a.scan_tags)
|
|
229
|
+
parents = Array(h['parents']).map(&:to_s)
|
|
230
|
+
if parents.any?
|
|
231
|
+
new_proj = parents.first.to_s
|
|
232
|
+
new_chain = parents[1..] || []
|
|
233
|
+
a.instance_variable_set(:@project, new_proj)
|
|
234
|
+
a.parent = new_chain
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
178
240
|
todo.actions.output(depth,
|
|
179
241
|
{ files: todo.files,
|
|
180
242
|
regexes: regexes,
|