na 1.2.86 → 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.
data/lib/na/string.rb CHANGED
@@ -189,22 +189,24 @@ class ::String
189
189
  # @param indent [Integer] Number of spaces to indent each line
190
190
  # @return [String] Wrapped string
191
191
  def wrap(width, indent)
192
- return "\n#{self}" if width <= 80
192
+ return to_s if width.nil? || width <= 0
193
193
 
194
194
  output = []
195
195
  line = []
196
- length = 0
196
+ # Track visible length of current line (exclude the separating space before first word)
197
+ length = -1
197
198
  text = gsub(/(@\S+)\((.*?)\)/) { "#{Regexp.last_match(1)}(#{Regexp.last_match(2).gsub(/ /, '†')})" }
198
199
 
199
200
  text.split.each do |word|
200
201
  uncolored = NA::Color.uncolor(word)
201
- if (length + uncolored.length + 1) <= width
202
+ candidate = length + 1 + uncolored.length
203
+ if candidate <= width
202
204
  line << word
203
- length += uncolored.length + 1
205
+ length = candidate
204
206
  else
205
207
  output << line.join(' ')
206
208
  line = [word]
207
- length = uncolored.length + 1
209
+ length = uncolored.length
208
210
  end
209
211
  end
210
212
  output << line.join(' ')
@@ -310,7 +312,14 @@ class ::String
310
312
  m = Regexp.last_match
311
313
  t = m['tag']
312
314
  d = m['date']
313
- future = t =~ /^(done|complete)/ ? false : true
315
+ # Determine whether to bias toward future or past parsing
316
+ # Non-done tags usually bias to future, except explicit past phrases like "ago", "yesterday", or "last ..."
317
+ explicit_past = d =~ /(\bago\b|yesterday|\blast\b)/i
318
+ future = if t =~ /^(done|complete)/
319
+ false
320
+ else
321
+ explicit_past ? false : true
322
+ end
314
323
  parsed_date = d =~ iso_rx ? Time.parse(d) : d.chronify(guess: :begin, future: future)
315
324
  parsed_date.nil? ? m[0] : "@#{t}(#{parsed_date.strftime('%F %R')})"
316
325
  end
data/lib/na/theme.rb CHANGED
@@ -57,6 +57,7 @@ module NA
57
57
  tags: '{m}',
58
58
  value_parens: '{m}',
59
59
  values: '{c}',
60
+ duration: '{y}',
60
61
  search_highlight: '{y}',
61
62
  note: '{dw}',
62
63
  dirname: '{xdw}',
data/lib/na/types.rb ADDED
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'na/string'
4
+
5
+ module NA
6
+ # Custom types for GLI
7
+ # Provides natural language date/time and duration parsing
8
+ # Uses chronify gem for parsing
9
+ module Types
10
+ module_function
11
+
12
+ # Normalize shorthand relative durations to phrases Chronic can parse.
13
+ # Examples:
14
+ # - "30m ago" => "30 minutes ago"
15
+ # - "-30m" => "30 minutes ago"
16
+ # - "2h30m" => "2 hours 30 minutes ago" (when default_past)
17
+ # - "2h 30m ago" => "2 hours 30 minutes ago"
18
+ # - "2:30 ago" => "2 hours 30 minutes ago"
19
+ # - "-2:30" => "2 hours 30 minutes ago"
20
+ # Accepts d,h,m units; hours:minutes pattern; optional leading '-'; optional 'ago'.
21
+ # @param value [String] the duration string to normalize
22
+ # @param default_past [Boolean] whether to default to past tense
23
+ # @return [String] the normalized duration string
24
+ def normalize_relative_duration(value, default_past: false)
25
+ return value if value.nil?
26
+
27
+ s = value.to_s.strip
28
+ return s if s.empty?
29
+
30
+ has_ago = s =~ /\bago\b/i
31
+ negative = s.start_with?('-')
32
+
33
+ text = s.sub(/^[-+]/, '')
34
+
35
+ # hours:minutes pattern (e.g., 2:30, 02:30)
36
+ if (m = text.match(/^(\d{1,2}):(\d{1,2})(?:\s*ago)?$/i))
37
+ hours = m[1].to_i
38
+ minutes = m[2].to_i
39
+ parts = []
40
+ parts << "#{hours} hours" if hours.positive?
41
+ parts << "#{minutes} minutes" if minutes.positive?
42
+ return "#{parts.join(' ')} ago"
43
+ end
44
+
45
+ # Compound d/h/m (order independent, allow spaces): e.g., 1d2h30m, 2h 30m, 30m
46
+ days = hours = minutes = 0
47
+ found = false
48
+ if (dm = text.match(/(?:(\d+)\s*d)/i))
49
+ days = dm[1].to_i
50
+ found = true
51
+ end
52
+ if (hm = text.match(/(?:(\d+)\s*h)/i))
53
+ hours = hm[1].to_i
54
+ found = true
55
+ end
56
+ if (mm = text.match(/(?:(\d+)\s*m)/i))
57
+ minutes = mm[1].to_i
58
+ found = true
59
+ end
60
+
61
+ if found
62
+ parts = []
63
+ parts << "#{days} days" if days.positive?
64
+ parts << "#{hours} hours" if hours.positive?
65
+ parts << "#{minutes} minutes" if minutes.positive?
66
+ # Determine if we should make it past-tense
67
+ return "#{parts.join(' ')} ago" if negative || has_ago || default_past
68
+
69
+ return parts.join(' ')
70
+
71
+ end
72
+
73
+ # Fall through: not a shorthand we handle
74
+ s
75
+ end
76
+
77
+ # Parse a natural-language/iso date string for a start time
78
+ # @param value [String] the date string to parse
79
+ # @return [Time] the parsed date, or nil if parsing fails
80
+ def parse_date_begin(value)
81
+ return nil if value.nil? || value.to_s.strip.empty?
82
+
83
+ # Prefer explicit ISO first (only if the value looks ISO-like)
84
+ iso_rx = /\A\d{4}-\d{2}-\d{2}(?:[ T]\d{1,2}:\d{2}(?::\d{2})?)?\z/
85
+ if value.to_s.strip =~ iso_rx
86
+ begin
87
+ return Time.parse(value)
88
+ rescue StandardError
89
+ # fall through to chronify
90
+ end
91
+ end
92
+
93
+ # Fallback to chronify with guess begin
94
+ begin
95
+ # Normalize shorthand (e.g., 2h30m, -2:30, 30m ago)
96
+ txt = normalize_relative_duration(value.to_s, default_past: true)
97
+ # Bias to past for expressions like "ago", "yesterday", or "last ..."
98
+ future = txt !~ /(\bago\b|yesterday|\blast\b)/i
99
+ result = txt.chronify(guess: :begin, future: future)
100
+ NA.notify("Parsed '#{value}' as #{result}", debug: true) if result
101
+ result
102
+ rescue StandardError
103
+ nil
104
+ end
105
+ end
106
+
107
+ # Parse a natural-language/iso date string for an end time
108
+ # @param value [String] the date string to parse
109
+ # @return [Time] the parsed date, or nil if parsing fails
110
+ def parse_date_end(value)
111
+ return nil if value.nil? || value.to_s.strip.empty?
112
+
113
+ # Prefer explicit ISO first (only if the value looks ISO-like)
114
+ iso_rx = /\A\d{4}-\d{2}-\d{2}(?:[ T]\d{1,2}:\d{2}(?::\d{2})?)?\z/
115
+ if value.to_s.strip =~ iso_rx
116
+ begin
117
+ return Time.parse(value)
118
+ rescue StandardError
119
+ # fall through to chronify
120
+ end
121
+ end
122
+
123
+ # Fallback to chronify with guess end
124
+ value.to_s.chronify(guess: :end, future: false)
125
+ end
126
+
127
+ # Convert duration expressions to seconds
128
+ # Supports: "90" (minutes), "45m", "2h", "1d2h30m", with optional leading '-' or trailing 'ago'
129
+ # Also supports "2:30", "2:30 ago", and word forms like "2 hours 30 minutes (ago)"
130
+ # @param value [String] the duration string to parse
131
+ # @return [Integer] the duration in seconds, or nil if parsing fails
132
+ def parse_duration_seconds(value)
133
+ return nil if value.nil?
134
+
135
+ s = value.to_s.strip
136
+ return nil if s.empty?
137
+
138
+ # Strip leading sign and optional 'ago'
139
+ s = s.sub(/^[-+]/, '')
140
+ s = s.sub(/\bago\b/i, '').strip
141
+
142
+ # H:MM pattern
143
+ m = s.match(/^(\d{1,2}):(\d{1,2})$/)
144
+ if m
145
+ hours = m[1].to_i
146
+ minutes = m[2].to_i
147
+ return (hours * 3600) + (minutes * 60)
148
+ end
149
+
150
+ # d/h/m compact with letters, order independent (e.g., 1d2h30m, 2h 30m, 30m)
151
+ m = s.match(/^(?:(?<day>\d+)\s*d)?\s*(?:(?<hour>\d+)\s*h)?\s*(?:(?<min>\d+)\s*m)?$/i)
152
+ if m && !m[0].strip.empty? && (m['day'] || m['hour'] || m['min'])
153
+ return [[m['day'], 86_400], [m['hour'], 3600], [m['min'], 60]].map { |q, mult| q ? q.to_i * mult : 0 }.sum
154
+ end
155
+
156
+ # Word forms: e.g., "2 hours 30 minutes", "1 day 2 hours", etc.
157
+ days = 0
158
+ hours = 0
159
+ minutes = 0
160
+ found_word = false
161
+ if (dm = s.match(/(\d+)\s*(?:day|days)\b/i))
162
+ days = dm[1].to_i
163
+ found_word = true
164
+ end
165
+ if (hm = s.match(/(\d+)\s*(?:hour|hours|hr|hrs)\b/i))
166
+ hours = hm[1].to_i
167
+ found_word = true
168
+ end
169
+ if (mm = s.match(/(\d+)\s*(?:minute|minutes|min|mins)\b/i))
170
+ minutes = mm[1].to_i
171
+ found_word = true
172
+ end
173
+ return (days * 86_400) + (hours * 3600) + (minutes * 60) if found_word
174
+
175
+ # Plain number => minutes
176
+ return s.to_i * 60 if s =~ /^\d+$/
177
+
178
+ # Last resort: try chronify two points and take delta
179
+ begin
180
+ start = Time.now
181
+ finish = s.chronify(context: 'now', guess: :end, future: false)
182
+ return (finish - start).abs.to_i if finish
183
+ rescue StandardError
184
+ # ignore
185
+ end
186
+
187
+ nil
188
+ end
189
+ end
190
+ end
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.86'
8
+ VERSION = '1.2.88'
9
9
  end
data/lib/na.rb CHANGED
@@ -31,6 +31,8 @@ require 'na/todo'
31
31
  require 'na/actions'
32
32
  require 'na/project'
33
33
  require 'na/action'
34
+ require 'na/types'
34
35
  require 'na/editor'
35
36
  require 'na/next_action'
36
37
  require 'na/prompt'
38
+ require 'na/plugins'
@@ -0,0 +1,32 @@
1
+ ---
2
+ comment: 2023-09-03
3
+ keywords:
4
+ ---
5
+
6
+ Project3:
7
+ Project0:
8
+ - This is another task @na
9
+ - How about this one? @na
10
+ Subproject:
11
+ - Bollocks @na
12
+ Subsub:
13
+ - Hey, I think it's all working @na
14
+ - Is this at the end? @na
15
+ - This better work @na
16
+ 2023-09-08:
17
+ Project2:
18
+ - new_task @na
19
+ - new_task @na
20
+ - test task @na
21
+ Project0:
22
+ - other task @na
23
+ - other task @na
24
+ - There, that's better @na
25
+ Subproject:
26
+ - new_task 2 @na
27
+ - new_task @na
28
+ - new_task 2 @na
29
+ Project1:
30
+ - Test4
31
+ - Test5
32
+ - Test6
data/na/test.md ADDED
@@ -0,0 +1,21 @@
1
+ ---
2
+ comment: 2023-09-03
3
+ keywords:
4
+ ---
5
+ Other New Project:
6
+ - testing @na @butter
7
+ Brand New Project:
8
+ - testing @na
9
+ A multi line (multiline) note
10
+ with a line break
11
+ - testing @na
12
+ Project0:
13
+
14
+ - Test1
15
+
16
+ - Test2
17
+
18
+ Project1:
19
+ - Test4
20
+ - Test5
21
+ - Test6
data/na.gemspec CHANGED
@@ -24,6 +24,7 @@ spec = Gem::Specification.new do |s|
24
24
  s.add_development_dependency('minitest', '~> 5.14')
25
25
  s.add_development_dependency('rdoc', '~> 4.3')
26
26
  s.add_runtime_dependency('chronic', '~> 0.10', '>= 0.10.2')
27
+ s.add_runtime_dependency('csv', '>= 3.2')
27
28
  s.add_runtime_dependency('git', '~> 3.0.0')
28
29
  s.add_runtime_dependency('gli','~> 2.21.0')
29
30
  s.add_runtime_dependency('mdless', '~> 1.0', '>= 1.0.32')
data/plugins.md ADDED
@@ -0,0 +1,38 @@
1
+ I would like to add a plugin architecture to na_gem. It should allow the user to add plugins to ~/.local/share/na/plugins/. These plugins can be any shell script (with a shebang). They can be run with `na plugin NAME`, which accepts the plugin filename with or without an extension, and with or without spaces (so that `plugin AddFoo` will run `Add Foo.sh` if found, but the user can also use `plugin "Add Foo"`).
2
+
3
+ A plugin will be a shell script that takes input on STDIN. The input should be an action as a JSON object, with the file path, line number, action text, note, and array of tags/values (`tags: [{ name: "done", value: "2025-10-29 03:00"}, { name: "na", value: ""}]`). That should be the default.
4
+
5
+ The `plugin` command should accept a `--input TYPE` flag that accepts `json`, `yaml` or `text`. The YAML should be the same as the JSON (but as YAML), and the text should just be the file_path:line_number, action text, and note, split with "||" (newlines in the note replaced with \n, and filename and line number are combined with : not the divider), with no colorization. One action per line. The "||" in `--input text` should also be a flag `--divider "STRING"` that defaults to "||", but allows the user to specify a different string to split the parts on.
6
+
7
+ The plugin will need to return output (on STDOUT) in the same format as the input (yaml, json, or text with specified divider), unless `--output FORMAT` is specified with a different type. The `plugin` command will execute the script for every command passed to it, and update the actions based on the returned output.
8
+
9
+ The `plugin` command should accept all the same filter flags as `finish` or other actions that update commands.
10
+
11
+ For the `update` command, it should accept a `--plugin NAME` flag, and if it's using interactive menus, a list of plugin names (basename minus extension) should be added to the list of available operations.
12
+
13
+ Also add a `--plugin NAME`, `--input TYPE`, and `--output TYPE` flag to all search and display commands (next, grep, tagged, etc.). That way the user can filter output with any command and run the result through the plugin.
14
+
15
+ In lieu of the `--input` and `--output` commands, the plugin itself can have a comment block after the shebang with `key: value` pairs. When reading a plugin, check for a comment block with `input: JSON` `output: YAML` (case insensitive). The user can also define a `name` or `title` (interchangeable) in this block, which will be used instead of the base name if provided. We need to ignore leading characters when scanning for this comment block (e.g. # or //). The block can have blank lines before it. The only keys we read are input, output, and name/title. Parsing stops at the first blank line or after all three keys are populated. Other keys might exist, like `author` or `description`, which should be ignored.
16
+
17
+ The plugins shouldn't need to be executable, the hashbang should be read and used to execute the script.
18
+
19
+ When `na` runs, it should check for the existence of the `plugins` directory, creating it if it's missing, and adding a `~/.local/share/na/plugsin/README.md` file with plugin instructions if one doesn't exist. Any `.md` or `.bak` file in the plugins directory should be ignored. In fact, let's have a helper validate the files in the directory by checking for a shebang and ignoring if none exists, and also ignoring any '.bak' or '.md' files.
20
+
21
+ Have NA also create 2 sample plugins in the `~/.local/share/na/plugins` folder when creating it (do not create plugins if the folder already exists). Have the sample plugins be a Python script and a Bash script. The sample plugins should just do something benign like add a tag with a dynamic value to the passed actions. In the README.md note that the user can delete the sample plugins. Give the sample plugins names "Add Foo.py" and "Add Bar.sh" and have them add @foo and @bar, respectively.
22
+
23
+ ### Summary ###
24
+
25
+ - plugins are script files in ~/.local/share/na/plugins
26
+ - plugins require a shebang, which is used to execute them
27
+ - plugin base names (without extension) becomes the command name (spaces are handled)
28
+ - Ignore
29
+ - `plugin` subcommand
30
+ - accepts plugin name as argument
31
+ - has a `--input TYPE` flag that determines the input type (yaml, json, or text)
32
+ - has a `--output TYPE` (yaml, json, or text)
33
+ - has a `--divider` flag that determines the divider when `--input text` is used
34
+ - `update` subcommand
35
+ - accepts a `--plugin NAME` flag
36
+ - Adds plugin names to interactive menu when no action is specified
37
+ - main script parses the output of the plugin, stripping whitespace and reading it as YAML, JSON, or text split on the divider (based on `--output` and defaulting to the value of `--input`), then updates each action in the result. Line numbers should be passed on both input and output and used to update the specific actions.
38
+ - Generate README and scripts
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.85<!--END VER-->.
12
+ The current version of `na` is <!--VER-->1.2.87<!--END VER-->.
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
 
@@ -235,6 +235,49 @@ See the help output for a list of all available actions.
235
235
  @cli(bundle exec bin/na help update)
236
236
  ```
237
237
 
238
+ #### Time tracking
239
+
240
+ `na` supports tracking elapsed time between a start and finish for actions using `@started(YYYY-MM-DD HH:MM)` and `@done(YYYY-MM-DD HH:MM)` tags. Durations are not stored; they are calculated on the fly from these tags.
241
+
242
+ - Add/Finish/Update flags:
243
+ - `--started TIME` set a start time when creating or finishing an item
244
+ - `--end TIME` (alias `--finished`) set a done time
245
+ - `--duration DURATION` backfill start time from the provided end time
246
+ - All flags accept natural language (via Chronic) and shorthand: `30m ago`, `-2h`, `2h30m`, `2:30 ago`, `yesterday 5pm`
247
+
248
+ Examples:
249
+
250
+ ```bash
251
+ na add --started "30 minutes ago" "Investigate bug"
252
+ na complete --finished now --duration 2h30m "Investigate bug"
253
+ na update --started "yesterday 3pm" --end "yesterday 5:15pm" "Investigate bug"
254
+ ```
255
+
256
+ - Display flags (next/tagged):
257
+ - `--times` show per‑action durations and a grand total (implies `--done`)
258
+ - `--human` format durations as human‑readable text instead of `DD:HH:MM:SS`
259
+ - `--only_timed` show only actions that have both `@started` and `@done` (implies `--times --done`)
260
+ - `--only_times` output only the totals section (no action lines; implies `--times --done`)
261
+ - `--json_times` output a JSON object with timed items, per‑tag totals, and overall total (implies `--times --done`)
262
+
263
+ Example outputs:
264
+
265
+ ```bash
266
+ # Per‑action durations appended and totals table
267
+ na next --times --human
268
+
269
+ # Only totals table (Markdown), no action lines
270
+ na tagged "tag*=bug" --only_times
271
+
272
+ # JSON for scripting
273
+ na next --json_times > times.json
274
+ ```
275
+
276
+ Notes:
277
+
278
+ - Any newly added or edited action text is scanned for natural‑language values in `@started(...)`/`@done(...)` and normalized to `YYYY‑MM‑DD HH:MM`.
279
+ - The color of durations in output is configurable via the theme key `duration` (defaults to `{y}`).
280
+
238
281
  ##### changelog
239
282
 
240
283
  View recent changes with `na changelog` or `na changes`.
@@ -332,6 +375,115 @@ If you're using a single global file, you'll need `--cwd_as` to be `tag` or `pro
332
375
 
333
376
  After installing a hook, you'll need to close your terminal and start a new session to initialize the new commands.
334
377
 
378
+ ### Plugins
379
+
380
+ 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.
381
+
382
+ #### Getting Started
383
+
384
+ The first time NA runs, it will create the plugins directory with a README and two sample plugins:
385
+ - `Add Foo.py` - Adds a `@foo` tag with a timestamp
386
+ - `Add Bar.sh` - Adds a `@bar` tag
387
+
388
+ You can delete or modify these sample plugins as needed.
389
+
390
+ #### Running Plugins
391
+
392
+ Run a plugin with:
393
+ ```bash
394
+ na plugin PLUGIN_NAME
395
+ ```
396
+
397
+ Or use plugins through the `update` command's interactive menu, or pipe actions through plugins on display commands:
398
+
399
+ ```bash
400
+ na update --plugin PLUGIN_NAME # Run plugin on selected actions
401
+ na next --plugin PLUGIN_NAME # Transform output only (no file writes)
402
+ na tagged bug --plugin PLUGIN_NAME # Filter and transform
403
+ na find "search term" --plugin PLUGIN_NAME
404
+ ```
405
+
406
+ #### Plugin Metadata
407
+
408
+ Plugins can specify their behavior in a metadata block after the shebang:
409
+
410
+ ```bash
411
+ #!/usr/bin/env python3
412
+ # name: My Plugin
413
+ # input: json
414
+ # output: json
415
+ ```
416
+
417
+ Available metadata keys (case-insensitive):
418
+ - `input`: Input format (`json`, `yaml`, `csv`, `text`)
419
+ - `output`: Output format
420
+ - `name` or `title`: Display name (defaults to filename)
421
+
422
+ #### Input/Output Formats
423
+
424
+ Plugins accept and return action data. Use `--input` and `--output` flags to override metadata:
425
+
426
+ ```bash
427
+ na plugin MY_PLUGIN --input text --output json --divider "||"
428
+ ```
429
+
430
+ **JSON/YAML Schema:**
431
+ ```json
432
+ [
433
+ {
434
+ "file_path": "todo.taskpaper",
435
+ "line": 15,
436
+ "parents": ["Project", "Subproject"],
437
+ "text": "- Action text @tag(value)",
438
+ "note": "Note content",
439
+ "tags": [
440
+ { "name": "tag", "value": "value" }
441
+ ]
442
+ }
443
+ ]
444
+ ```
445
+
446
+ **Text Format:**
447
+ ```
448
+ ACTION||ARGS||file_path:line||parents||text||note||tags
449
+ ```
450
+
451
+ Default divider is `||` (configurable with `--divider`).
452
+ - `parents`: `Parent>Child>Leaf`
453
+ - `tags`: `name(value);name;other(value)`
454
+
455
+ If the first token isn’t a known action, it’s treated as `file_path:line` and the action defaults to UPDATE.
456
+
457
+ #### Actions
458
+
459
+ Plugins may return an optional ACTION with arguments. Supported (case-insensitive):
460
+ - UPDATE (default; replace text/note/tags/parents)
461
+ - DELETE
462
+ - COMPLETE/FINISH
463
+ - RESTORE/UNFINISH
464
+ - ARCHIVE
465
+ - ADD_TAG (args: one or more tags)
466
+ - DELETE_TAG/REMOVE_TAG (args: one or more tags)
467
+ - MOVE (args: target project path)
468
+
469
+ #### Plugin Behavior
470
+
471
+ **On `update` or `plugin` command:**
472
+ - Plugins can modify text, notes, tags, and parents
473
+ - Changing `parents` will move the action to the new project location
474
+ - `file_path` and `line` cannot be changed
475
+
476
+ **On display commands (`next`, `tagged`, `find`):**
477
+ - Plugins only transform STDOUT (no file writes)
478
+ - Use returned text/note/tags/parents for rendering
479
+ - Parent changes affect display but not file structure
480
+
481
+ #### Override Formats
482
+
483
+ You can override plugin defaults with flags on any command that supports `--plugin`:
484
+ ```bash
485
+ na next --plugin FOO --input csv --output text
486
+ ```
335
487
 
336
488
  [fzf]: https://github.com/junegunn/fzf
337
489
  [gum]: https://github.com/charmbracelet/gum
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.86
4
+ version: 1.2.88
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
@@ -57,6 +57,20 @@ dependencies:
57
57
  - - ">="
58
58
  - !ruby/object:Gem::Version
59
59
  version: 0.10.2
60
+ - !ruby/object:Gem::Dependency
61
+ name: csv
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '3.2'
67
+ type: :runtime
68
+ prerelease: false
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '3.2'
60
74
  - !ruby/object:Gem::Dependency
61
75
  name: git
62
76
  requirement: !ruby/object:Gem::Requirement
@@ -245,10 +259,12 @@ extra_rdoc_files:
245
259
  - README.md
246
260
  - na.rdoc
247
261
  files:
262
+ - ".cursor/commands/changelog.md"
248
263
  - ".cursor/commands/priority35m36m335m32m.md"
249
264
  - ".rubocop.yml"
250
265
  - ".rubocop_todo.yml"
251
266
  - ".travis.yml"
267
+ - 2025-10-29-one-more-na-update.md
252
268
  - CHANGELOG.md
253
269
  - Gemfile
254
270
  - Gemfile.lock
@@ -268,6 +284,7 @@ files:
268
284
  - bin/commands/move.rb
269
285
  - bin/commands/next.rb
270
286
  - bin/commands/open.rb
287
+ - bin/commands/plugin.rb
271
288
  - bin/commands/projects.rb
272
289
  - bin/commands/prompt.rb
273
290
  - bin/commands/restore.rb
@@ -298,14 +315,19 @@ files:
298
315
  - lib/na/help_monkey_patch.rb
299
316
  - lib/na/next_action.rb
300
317
  - lib/na/pager.rb
318
+ - lib/na/plugins.rb
301
319
  - lib/na/project.rb
302
320
  - lib/na/prompt.rb
303
321
  - lib/na/string.rb
304
322
  - lib/na/theme.rb
305
323
  - lib/na/todo.rb
324
+ - lib/na/types.rb
306
325
  - lib/na/version.rb
307
326
  - na.gemspec
308
327
  - na.rdoc
328
+ - na/Test.todo.markdown
329
+ - na/test.md
330
+ - plugins.md
309
331
  - scripts/fixreadme.rb
310
332
  - scripts/generate-fish-completions.rb
311
333
  - scripts/runtests.sh