na 1.2.94 → 1.2.95
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/.cursor/commands/changelog.md +5 -2
- data/.rubocop_todo.yml +34 -12
- data/CHANGELOG.md +23 -0
- data/Gemfile.lock +23 -23
- data/README.md +146 -5
- data/bin/commands/add.rb +19 -1
- data/bin/commands/find.rb +61 -10
- data/bin/commands/next.rb +51 -0
- data/bin/commands/saved.rb +43 -8
- data/lib/na/action.rb +3 -1
- data/lib/na/next_action.rb +804 -3
- data/lib/na/todo.rb +42 -3
- data/lib/na/version.rb +1 -1
- data/src/_README.md +145 -4
- metadata +1 -1
data/lib/na/todo.rb
CHANGED
|
@@ -165,6 +165,10 @@ module NA
|
|
|
165
165
|
content = file.read_file
|
|
166
166
|
indent_level = 0
|
|
167
167
|
parent = []
|
|
168
|
+
# Track the indentation level of each project in the current parent
|
|
169
|
+
# chain so we can correctly assign actions to the nearest ancestor
|
|
170
|
+
# project based on their own indentation.
|
|
171
|
+
project_indent_stack = []
|
|
168
172
|
in_yaml = false
|
|
169
173
|
in_action = false
|
|
170
174
|
content.split("\n").each.with_index do |line, idx|
|
|
@@ -179,11 +183,15 @@ module NA
|
|
|
179
183
|
|
|
180
184
|
if indent.zero? # top level project
|
|
181
185
|
parent = [proj]
|
|
186
|
+
project_indent_stack = [indent]
|
|
182
187
|
elsif indent <= indent_level # if indent level is same or less, split parent before indent level and append
|
|
183
188
|
parent.slice!(indent, parent.count - indent)
|
|
189
|
+
project_indent_stack.slice!(indent, project_indent_stack.count - indent)
|
|
184
190
|
parent.push(proj)
|
|
191
|
+
project_indent_stack << indent
|
|
185
192
|
else # if indent level is greater, append project to parent
|
|
186
193
|
parent.push(proj)
|
|
194
|
+
project_indent_stack << indent
|
|
187
195
|
end
|
|
188
196
|
|
|
189
197
|
projects.push(NA::Project.new(parent.join(':'), indent, idx, idx))
|
|
@@ -194,18 +202,49 @@ module NA
|
|
|
194
202
|
elsif line.action?
|
|
195
203
|
in_action = false
|
|
196
204
|
|
|
205
|
+
action_indent = line.indent_level
|
|
206
|
+
|
|
207
|
+
# Determine the effective parent chain for this action based on
|
|
208
|
+
# its indentation. An action belongs to the deepest project
|
|
209
|
+
# whose indentation is strictly less than the action's
|
|
210
|
+
# indentation. If none match (e.g., top-level actions), fall
|
|
211
|
+
# back to the last project in the stack.
|
|
212
|
+
effective_parent = parent.dup
|
|
213
|
+
unless project_indent_stack.empty?
|
|
214
|
+
chosen_index = nil
|
|
215
|
+
project_indent_stack.each_with_index.reverse_each do |proj_indent, i|
|
|
216
|
+
if proj_indent < action_indent
|
|
217
|
+
chosen_index = i
|
|
218
|
+
break
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
chosen_index ||= project_indent_stack.length - 1
|
|
223
|
+
effective_parent = parent[0..chosen_index]
|
|
224
|
+
end
|
|
225
|
+
|
|
197
226
|
# Early exits before creating Action object
|
|
198
227
|
next if line.done? && !settings[:done]
|
|
199
228
|
|
|
200
229
|
next if settings[:require_na] && !line.na?
|
|
201
230
|
|
|
202
|
-
next if project_regex &&
|
|
231
|
+
next if project_regex && effective_parent.join('/') !~ project_regex
|
|
203
232
|
|
|
204
233
|
# Only create Action if we passed basic filters
|
|
205
234
|
action = line.action
|
|
206
|
-
new_action = NA::Action.new(file, File.basename(file, ".#{NA.extension}"),
|
|
235
|
+
new_action = NA::Action.new(file, File.basename(file, ".#{NA.extension}"), effective_parent, action, idx)
|
|
207
236
|
|
|
208
|
-
|
|
237
|
+
# Update the last_line for the owning project (the deepest
|
|
238
|
+
# project in the effective parent chain), rather than always
|
|
239
|
+
# the most recently seen project.
|
|
240
|
+
if projects.any? && effective_parent.any?
|
|
241
|
+
owning_path = effective_parent.join(':')
|
|
242
|
+
if (owning_project = projects.reverse.find { |p| p.project == owning_path })
|
|
243
|
+
owning_project.last_line = idx
|
|
244
|
+
end
|
|
245
|
+
elsif projects.any?
|
|
246
|
+
projects[-1].last_line = idx
|
|
247
|
+
end
|
|
209
248
|
|
|
210
249
|
# Tag matching
|
|
211
250
|
has_tag = !optional_tag.empty? || !required_tag.empty? || !negated_tag.empty?
|
data/lib/na/version.rb
CHANGED
data/src/_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 <!--VER-->1.2.
|
|
12
|
+
The current version of `na` is <!--VER-->1.2.94<!--END VER-->.
|
|
13
13
|
|
|
14
14
|
<!--GITHUB-->
|
|
15
15
|
### Table of contents
|
|
@@ -22,6 +22,7 @@ The current version of `na` is <!--VER-->1.2.93<!--END VER-->.
|
|
|
22
22
|
- [Adding todos](#adding-todos)
|
|
23
23
|
- [Updating todos](#updating-todos)
|
|
24
24
|
- [Terminology](#terminology)
|
|
25
|
+
- [TaskPaper Syntax](#taskpaper-syntax)
|
|
25
26
|
- [Usage](#usage)
|
|
26
27
|
- [Commands](#commands)
|
|
27
28
|
- [add](#add)
|
|
@@ -109,6 +110,146 @@ You can mark todos as complete, delete them, add and remove tags, change priorit
|
|
|
109
110
|
|
|
110
111
|
**Note**: Refers to lines appearing between action lines that start without hyphens. The note is attached to the preceding action regardless of indentation.
|
|
111
112
|
|
|
113
|
+
### TaskPaper Syntax
|
|
114
|
+
|
|
115
|
+
NA has its own syntax for searching tags, titles, and notes, but it also understands most of the [TaskPaper query syntax](https://guide.taskpaper.com/reference/searches/). TaskPaper-style searches are accepted anywhere you can pass a `@search(...)` expression, including:
|
|
116
|
+
|
|
117
|
+
- `na next "@search(...)"` (or via saved searches)
|
|
118
|
+
- `na find "@search(...)"` and `na saved NAME` when the saved value is `@search(...)`
|
|
119
|
+
- TaskPaper files themselves, on any line of the form `TITLE @search(PARAMS)` (these become runnable saved searches)
|
|
120
|
+
|
|
121
|
+
What follows documents the subset and extensions of TaskPaper search that `na` supports.
|
|
122
|
+
|
|
123
|
+
#### Predicates and tag searches
|
|
124
|
+
|
|
125
|
+
- **Simple text**: Bare words are treated as plain-text search tokens on the action line, combined with `and` by default.
|
|
126
|
+
- **Tag predicates**:
|
|
127
|
+
- `@tag` (no value) means "this tag exists with any value". For example, `@priority` matches `@priority(1)`, `@priority(3)`, etc.
|
|
128
|
+
- `@tag op VALUE` uses the following operators (TaskPaper relations are mapped to NA's comparison codes):
|
|
129
|
+
- `=` / `==` / no operator: equality
|
|
130
|
+
- `!=`: inequality (implemented as a negated equality)
|
|
131
|
+
- `<`, `>`, `<=`, `>=`: numeric or date comparison when the value parses as a time or number
|
|
132
|
+
- `contains` → `*=`: value substring match
|
|
133
|
+
- `beginswith` → `^=`: prefix match
|
|
134
|
+
- `endswith` → `$=`: suffix match
|
|
135
|
+
- `matches` → `=~`: regular expression match (case-insensitive)
|
|
136
|
+
- Relation modifiers like `[i]`, `[s]`, `[n]`, `[d]`, `[l]` are parsed and discarded; matching is always case-insensitive for string comparisons.
|
|
137
|
+
- **`@text`**:
|
|
138
|
+
- `@text "foo"` is treated as a plain-text predicate on the action line (equivalent to searching for `"foo"`).
|
|
139
|
+
- `@text` without a value is ignored.
|
|
140
|
+
- **`@done`**:
|
|
141
|
+
- `@done` sets an internal "include done" flag and is not treated as a normal tag filter.
|
|
142
|
+
- `not @done` (or `@done` combined with other predicates) correctly toggles whether completed actions are included.
|
|
143
|
+
|
|
144
|
+
#### Project predicates
|
|
145
|
+
|
|
146
|
+
- **Project equality**:
|
|
147
|
+
- `project = "Inbox"` or `project == "Inbox"` limits results to actions under the `Inbox` project (matching NA's project path).
|
|
148
|
+
- `project != "Archive"` excludes actions whose project chain ends in `Archive`.
|
|
149
|
+
- **Shortcuts**:
|
|
150
|
+
- `project NAME` at the start of an expression is treated as a shortcut:
|
|
151
|
+
- If it is the entire predicate, it becomes `project = "NAME"`.
|
|
152
|
+
- If it is followed by additional logic, e.g. `project Inbox and @na`, the leading `NAME and` is dropped and the rest (`@na ...`) is parsed as normal. This matches TaskPaper's "type shortcut" usage rather than combining project name and text search.
|
|
153
|
+
|
|
154
|
+
#### Type shortcuts
|
|
155
|
+
|
|
156
|
+
TaskPaper defines type shortcuts that expand to `@type = ... and`. NA supports them in a way that is practical for task searches:
|
|
157
|
+
|
|
158
|
+
- `project QUERY`:
|
|
159
|
+
- If used alone, becomes `project = "QUERY"`.
|
|
160
|
+
- If followed by more predicates joined by `and`/`or`, only the tail expression is used (e.g. `project Inbox and @na` becomes `@na`).
|
|
161
|
+
- `task QUERY` and `note QUERY`:
|
|
162
|
+
- The leading keyword is removed and the rest of the expression is interpreted as a normal text/tag predicate.
|
|
163
|
+
- NA does not currently distinguish task vs note types for filtering; these shortcuts are primarily syntactic sugar.
|
|
164
|
+
|
|
165
|
+
#### Boolean logic
|
|
166
|
+
|
|
167
|
+
- `and` and `or` are supported with parentheses for grouping:
|
|
168
|
+
- `@na and not @done`
|
|
169
|
+
- `(@priority >= 3 and @na) or @today`
|
|
170
|
+
- Expressions are parsed into an internal boolean AST and converted to disjunctive normal form (OR of AND-clauses) before evaluation, so:
|
|
171
|
+
- Complex nested groupings with multiple `and`/`or` operators behave as expected.
|
|
172
|
+
- The unary `not` operator is handled at the predicate level (e.g. `not @done`, `not @priority >= 3`).
|
|
173
|
+
|
|
174
|
+
#### Item paths
|
|
175
|
+
|
|
176
|
+
NA understands a subset of TaskPaper item-path syntax and maps it to project scopes in your todo files. Item paths can be used:
|
|
177
|
+
|
|
178
|
+
- As the leading part of a `@search(...)` expression:
|
|
179
|
+
- `@search(/Inbox//Bugs and not @done)`
|
|
180
|
+
- In TaskPaper saved searches (`TITLE @search(/Inbox//Project A and @na)`).
|
|
181
|
+
|
|
182
|
+
Supported item-path features:
|
|
183
|
+
|
|
184
|
+
- **Axes**:
|
|
185
|
+
- `/Name` selects top-level items whose title contains `Name`.
|
|
186
|
+
- `//Name` selects any descendants (at any depth) whose title contains `Name`.
|
|
187
|
+
- **Wildcards**:
|
|
188
|
+
- `*` matches "all items" on that step.
|
|
189
|
+
- Example: `/*` selects all top-level projects; `/Inbox//*` selects everything under `Inbox`.
|
|
190
|
+
- **Semantics in NA**:
|
|
191
|
+
- Each matching project path is turned into an NA project chain like `"Inbox:New Videos"`.
|
|
192
|
+
- Actions are filtered post-parse so that only actions whose parent chain starts with one of the resolved project paths are returned.
|
|
193
|
+
|
|
194
|
+
Current limitations:
|
|
195
|
+
|
|
196
|
+
- Set operations such as `union`, `intersect`, and `except` are not yet implemented.
|
|
197
|
+
- Slicing on item-path steps (e.g. `project *//not @done[0]` where `[0]` is attached to a path step) is not yet interpreted; see "Slicing results" below for what is supported.
|
|
198
|
+
|
|
199
|
+
#### Slicing results
|
|
200
|
+
|
|
201
|
+
NA supports TaskPaper-style slicing on the **result set of a `@search(...)` expression**, not (yet) on individual item-path steps:
|
|
202
|
+
|
|
203
|
+
- Supported forms:
|
|
204
|
+
- `[index]`
|
|
205
|
+
- `[start:end]`
|
|
206
|
+
- `[start:]`
|
|
207
|
+
- `[:end]`
|
|
208
|
+
- `[:]`
|
|
209
|
+
- Examples:
|
|
210
|
+
- `@search((project Inbox and @na and not @done)[0])`:
|
|
211
|
+
- Evaluates the predicates, then returns only the first matching action.
|
|
212
|
+
- `@search(/Inbox//Bugs and @na and not @done[0])`:
|
|
213
|
+
- Restricts to the `Inbox/Bugs` subtree, then returns only the first incomplete `@na` action under that subtree.
|
|
214
|
+
|
|
215
|
+
Slice semantics:
|
|
216
|
+
|
|
217
|
+
- Slices are applied per clause after all tag/project/item-path filters:
|
|
218
|
+
- `[index]` → the single action at `index` (0-based) if it exists.
|
|
219
|
+
- `[start:end]` → actions in the half-open range `start...end`.
|
|
220
|
+
- `[start:]` → actions from `start` to the end.
|
|
221
|
+
- `[:end]` / `[:]` → from the beginning to `end` (or all actions).
|
|
222
|
+
|
|
223
|
+
#### Saved searches and TaskPaper files
|
|
224
|
+
|
|
225
|
+
- **YAML saved searches**:
|
|
226
|
+
- `~/.local/share/na/saved_searches.yml` values that are plain strings continue to use NA's original search syntax.
|
|
227
|
+
- Values of the form `@search(...)` are parsed using the TaskPaper engine described above and support the full feature set (predicates, boolean logic, item paths, slicing).
|
|
228
|
+
- **TaskPaper-embedded saved searches**:
|
|
229
|
+
- Any line in a `.taskpaper` file that matches:
|
|
230
|
+
- `TITLE @search(PARAMS)`
|
|
231
|
+
- is treated as a saved search. `TITLE` becomes the search name, and `PARAMS` is parsed with the same TaskPaper engine. These searches are available via `na saved TITLE` and can coexist with YAML definitions.
|
|
232
|
+
|
|
233
|
+
#### Supported vs. unsupported TaskPaper features
|
|
234
|
+
|
|
235
|
+
In summary, NA's TaskPaper support includes:
|
|
236
|
+
|
|
237
|
+
- Tag predicates with most TaskPaper relations and modifiers.
|
|
238
|
+
- `@text` predicates for plain-text search.
|
|
239
|
+
- `@done` handling wired into NA's "done" flag.
|
|
240
|
+
- `project` equality and exclusions, plus a practical "project" type shortcut.
|
|
241
|
+
- Boolean logic with `and`, `or`, `not`, and parentheses.
|
|
242
|
+
- Item paths with `/`, `//`, and `*` to scope searches by project hierarchy.
|
|
243
|
+
- Result slicing on entire `@search(...)` expressions.
|
|
244
|
+
- Integration with `next`, `find`, and `saved` via `@search(...)`, including YAML and TaskPaper-defined saved searches.
|
|
245
|
+
|
|
246
|
+
The following TaskPaper features are **not** yet implemented:
|
|
247
|
+
|
|
248
|
+
- Item-path set operations (`union`, `intersect`, `except`).
|
|
249
|
+
- Slicing applied directly to individual item-path steps (only whole-expression slicing is currently supported).
|
|
250
|
+
|
|
251
|
+
Where NA already provided its own search syntax (e.g. `na find`, `na tagged`), TaskPaper searches are additive: you can choose whichever is more convenient for a given query, and `@search(...)` expressions are routed through a common TaskPaper engine so behavior is consistent across commands.
|
|
252
|
+
|
|
112
253
|
### Usage
|
|
113
254
|
|
|
114
255
|
```
|
|
@@ -143,7 +284,7 @@ Notes are not displayed by the `next/tagged/find` commands unless `--notes` is s
|
|
|
143
284
|
|
|
144
285
|
Example: `na find cool feature idea`
|
|
145
286
|
|
|
146
|
-
Unless `--exact` is specified, search is tokenized and combined with AND, so `na find cool feature idea` translates to `cool AND feature AND idea`, matching any string that contains all of the words. To make a token required and others optional, add a `+` before it (e.g. `cool +feature idea` is `(cool OR idea) AND feature`). Wildcards allowed (`*` and `?`), use `--regex` to interpret the search as a regular expression. Use `-v` to invert the results (display non-matching actions only).
|
|
287
|
+
Unless `--exact` is specified, search is tokenized and combined with AND, so `na find cool feature idea` translates to `cool AND feature AND idea`, matching any string that contains all of the words. To make a token required and others optional, add a `+` before it (e.g. `cool +feature idea` is `(cool OR idea) AND feature`). Wildcards allowed (`*` and `?`), use `--regex` to interpret the search as a regular expression. Use `-v` to invert the results (display non-matching actions only). Searches accept both NA search syntax and TaskPaper search syntax (see [TaskPaper search section](#taskpaper-syntax) above).
|
|
147
288
|
|
|
148
289
|
```
|
|
149
290
|
@cli(bundle exec bin/na help find)
|
|
@@ -177,7 +318,7 @@ Examples:
|
|
|
177
318
|
- `na next -d 3` (list all next actions in the current directory and look for additional files 3 levels deep from there)
|
|
178
319
|
- `na next marked2` (show next actions from another directory you've previously used na on)
|
|
179
320
|
|
|
180
|
-
To see all next actions across all known todos, use `na next "*"`. You can combine multiple arguments to see actions across multiple todos, e.g. `na next marked nvultra`.
|
|
321
|
+
To see all next actions across all known todos, use `na next "*"`. You can combine multiple arguments to see actions across multiple todos, e.g. `na next marked nvultra`. Filters and search terms accept both NA search syntax and TaskPaper search syntax (see [TaskPaper search section](#taskpaper-syntax) above).
|
|
181
322
|
|
|
182
323
|
```
|
|
183
324
|
@cli(bundle exec bin/na help next)
|
|
@@ -245,7 +386,7 @@ The saved command runs saved searches. To save a search, add `--save SEARCH_NAME
|
|
|
245
386
|
|
|
246
387
|
Search names can be partially matched when calling them, so if you have a search named "overdue," you can match it with `na saved over` (shortest match will be used).
|
|
247
388
|
|
|
248
|
-
Run `na saved` without an argument to list your saved searches.
|
|
389
|
+
Run `na saved` without an argument to list your saved searches. Saved searches preserve whether NA search syntax or TaskPaper search syntax was used, and both syntaxes are supported when defining or running them (see [TaskPaper search section](#taskpaper-syntax) above).
|
|
249
390
|
|
|
250
391
|
> As a shortcut, if `na` is run with one argument that matches the name of a saved search, it will execute that search, so running `na maybe` is the same as running `na saved maybe`.
|
|
251
392
|
<!--JEKYLL{:.tip}-->
|