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.
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 && parent.join('/') !~ 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}"), parent.dup, action, idx)
235
+ new_action = NA::Action.new(file, File.basename(file, ".#{NA.extension}"), effective_parent, action, idx)
207
236
 
208
- projects[-1].last_line = idx if projects.any?
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
@@ -5,5 +5,5 @@
5
5
  module Na
6
6
  ##
7
7
  # Current version of the na gem.
8
- VERSION = '1.2.94'
8
+ VERSION = '1.2.95'
9
9
  end
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.93<!--END VER-->.
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}-->
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: na
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.94
4
+ version: 1.2.95
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra