na 1.2.5 → 1.2.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 145c2efb28f23a047dced5bad341aa9339ff437fd4f31faeb44923fdc5d6af0c
4
- data.tar.gz: b16951edb2e2753c8c1a83a52b1650db7770a4d6ac2615aa4995e37bdcf20a02
3
+ metadata.gz: f912e92e3b61231275ab69102ce1dc2c1073f0417c5904409234ceaac96e0f1e
4
+ data.tar.gz: a1906bf77861faa3dd80dec19c813a09871c1582f7e9ca60aad1cabebece4858
5
5
  SHA512:
6
- metadata.gz: e790052ffdabf6ee749f545d4dade110a45167174700fc3f351d4e688b870c8b7bebf063678232613c12d1e3db3f0bc4491f12a01eededd0c8fd0d8710cfc63c
7
- data.tar.gz: c433b14e48f7ee09ca8a3a27a305b67e21087e0230a16d6f75c1b9d404e46ee5f120a8411f1f1d139b6e36512ac866ad5edc0ca2f29530b7b990baf0da776b16
6
+ metadata.gz: 887e0052681ee2ed7035aea0d7b4c8509be0e54f2699a8718350caec92632954fc0fa14ffcdfa8263f5bf03d2d0083ef5baaa6b89a17f314bef3b52a49243582
7
+ data.tar.gz: 3922599c050aadd8dd09de10904d9e245e7bc547bc75aa0eb985d94be14f04489adfccef17950fc3ffccfbd49d8fc723031bc20ff96a16f523bbec1a8b9f6341
data/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ ### 1.2.6
2
+
3
+ 2022-10-26 10:50
4
+
5
+ #### NEW
6
+
7
+ - Pass notes to STDIN using piped input when using the `--note` switch
8
+ - `--notes` switch for next, find, and tagged to include action notes in output
9
+
10
+ #### IMPROVED
11
+
12
+ - Update na saved examples and documentation
13
+ - Better handling of unknown commands, affecting `na -a ACTION` and `na SAVED_SEARCH`
14
+ - Additional help documentation and examples
15
+ - Updated documentation
16
+ - If a todo query contains only a negative, display all non-matching todos
17
+ - Don't display readline prompts if not a TTY
18
+ - Prompt hook generator recognizes when a global file is being used and modifies prompt hooks to search for project name or tag based on the value of `--cwd_as`.
19
+
20
+ #### FIXED
21
+
22
+ - Debug messages showing when not using --debug
23
+
1
24
  ### 1.2.5
2
25
 
3
26
  2022-10-26 07:39
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- na (1.2.5)
4
+ na (1.2.6)
5
5
  chronic (~> 0.10, >= 0.10.2)
6
6
  gli (~> 2.21.0)
7
7
  mdless (~> 1.0, >= 1.0.32)
data/README.md CHANGED
@@ -9,12 +9,12 @@
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.5
12
+ The current version of `na` is 1.2.6
13
13
  .
14
14
 
15
15
  `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.
16
16
 
17
- Used with Taskpaper files, it can add new todo items quickly from the command line, automatically tagging them as next actions.
17
+ Used with Taskpaper files, it can add new action items quickly from the command line, automatically tagging them as next actions. It can also mark actions as completed, delete them, archive them, and move them between projects.
18
18
 
19
19
  It can also auto-display next actions when you enter a project directory, automatically locating any todo files and listing their next actions when you `cd` to the project (optionally recursive). See the [Prompt Hooks](#prompt-hooks) section for details.
20
20
 
@@ -53,6 +53,16 @@ If found, it will try to locate an `Inbox:` project, or create one if it doesn't
53
53
 
54
54
  You can mark todos as complete, delete them, add and remove tags, change priority, and even move them between projects with the `na update` command.
55
55
 
56
+ ### Terminology
57
+
58
+ **Todo**: Refers to a todo file, usually a TaskPaper document
59
+
60
+ **Project**: Refers to a project within the TaskPaper document, specified by an alphanumeric name (spaces allowed) followed by a colon. Projects can be nested by indenting a tab beyond the parent projects indentation.
61
+
62
+ **Action**: Refers to an individual task, specified by a line starting with a hyphen (`-`)
63
+
64
+ **Note**: Refers to lines appearing between action lines that start without hyphens. The note is attached to the preceding action regardless of indentation.
65
+
56
66
  ### Usage
57
67
 
58
68
  ```
@@ -63,7 +73,7 @@ SYNOPSIS
63
73
  na [global options] command [command options] [arguments...]
64
74
 
65
75
  VERSION
66
- 1.2.5
76
+ 1.2.6
67
77
 
68
78
  GLOBAL OPTIONS
69
79
  -a, --[no-]add - Add a next action (deprecated, for backwards compatibility)
@@ -105,6 +115,12 @@ Example: `na add This feature @idea I have`
105
115
 
106
116
  If you run the `add` command with no arguments, you'll be asked for input on the command line.
107
117
 
118
+ ###### Adding notes
119
+
120
+ Use the `--note` switch to add a note. If STDIN (piped) input is present when this switch is used, it will be included in the note. A prompt will be displayed for adding additional notes, which will be appended to any STDIN note passed. Press CTRL-d to end editing and save the note.
121
+
122
+ Notes are not displayed by the `next/tagged/find` commands unless `--notes` is specified.
123
+
108
124
  ```
109
125
  NAME
110
126
  add - Add a new next action
@@ -122,7 +138,7 @@ COMMAND OPTIONS
122
138
  -f, --file=PATH - Specify the file to which the task should be added (default: none)
123
139
  --finish, --done - Mark task as @done with date
124
140
  --in, --todo=TODO_FILE - Add to a known todo file, partial matches allowed (default: none)
125
- -n, --note - Prompt for additional notes
141
+ -n, --note - Prompt for additional notes. STDIN input (piped) will be treated as a note if present.
126
142
  -p, --priority=PRIO - Add a priority level 1-5 (default: 0)
127
143
  -t, --tag=TAG - Use a tag other than the default next action tag (default: none)
128
144
  --to, --project, --proj=PROJECT - Add action to specific project (default: Inbox)
@@ -133,7 +149,7 @@ EXAMPLES
133
149
  # Add a new action to the Inbox, including a tag
134
150
  na add "A cool feature I thought of @idea"
135
151
 
136
- # Add a new action to the Inbox, set its @priority to 4, and prompt for an additional note
152
+ # Add a new action to the Inbox, set its @priority to 4, and prompt for an additional note.
137
153
  na add "A bug I need to fix" -p 4 -n
138
154
 
139
155
  # A parenthetical at the end of an action is interpreted as a note
@@ -190,6 +206,7 @@ COMMAND OPTIONS
190
206
  --[no-]done - Include @done actions
191
207
  -e, --regex - Interpret search pattern as regular expression
192
208
  --in=TODO_PATH - Show actions from a specific todo file in history. May use wildcards (* and ?) (default: none)
209
+ --[no-]notes - Include notes in output
193
210
  -o, --or - Combine search tokens with OR, displaying actions matching ANY of the terms
194
211
  --proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
195
212
  --save=TITLE - Save this search for future use (default: none)
@@ -232,6 +249,8 @@ Examples:
232
249
  - `na next -d 3` (list all next actions in the current directory and look for additional files 3 levels deep from there)
233
250
  - `na next marked2` (show next actions from another directory you've previously used na on)
234
251
 
252
+ 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`.
253
+
235
254
  ```
236
255
  NAME
237
256
  next - Show next actions
@@ -247,6 +266,7 @@ COMMAND OPTIONS
247
266
  -d, --depth=DEPTH - Recurse to depth (default: none)
248
267
  --[no-]done - Include @done actions
249
268
  --in, --todo=TODO_FILE - Display matches from a known todo file (may be used more than once, default: none)
269
+ --[no-]notes - Include notes in output
250
270
  --proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
251
271
  -t, --tag=TAG - Alternate tag to search for (default: none)
252
272
 
@@ -290,6 +310,9 @@ Search names can be partially matched when calling them, so if you have a search
290
310
 
291
311
  Run `na saved` without an argument to list your saved searches.
292
312
 
313
+ > 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`.
314
+
315
+
293
316
  ```
294
317
  NAME
295
318
  saved - Execute a saved search
@@ -307,9 +330,13 @@ COMMAND OPTIONS
307
330
 
308
331
  EXAMPLES
309
332
 
310
- na saved overdue
333
+ na tagged "+maybe,+priority<=3" --save maybelater
311
334
 
312
- na saved over
335
+ na saved maybelater
336
+
337
+ na saved maybe
338
+
339
+ na maybe
313
340
 
314
341
  na saved
315
342
  ```
@@ -339,6 +366,7 @@ COMMAND OPTIONS
339
366
  -d, --depth=DEPTH - Recurse to depth (default: none)
340
367
  --[no-]done - Include @done actions
341
368
  --in, --todo=TODO_FILE - Display matches from a known todo file (may be used more than once, default: none)
369
+ --[no-]notes - Include notes in output
342
370
  --proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
343
371
  -t, --tag=TAG - Alternate tag to search for (default: none)
344
372
 
@@ -382,11 +410,27 @@ You can specify a particular todo file using `--file PATH` or any todo from hist
382
410
 
383
411
  If more than one file is matched, a menu will be presented, multiple selections allowed. If multiple actions match the search within the selected file(s), a menu will be presented. If you have fzf installed, you can select one action to update with return, or use tab to mark multiple tasks to which the action will be applied. With gum you can use j, k, and x to mark multiple actions. Use the `--all` switch to force operation on all matched tasks, skipping the menu.
384
412
 
385
- Any time an update action is carried out, a backup of the file before modification will be made in the same directory with a `.` prepended and `.bak` appended (e.g. `marked.taskpaper` is copied to `.marked.taskpaper.bak`). Only one undo step is available, but if something goes wrong (and this feature is still experimental, so be wary), you can just copy the "~" file back to the original.
413
+ Any time an update action is carried out, a backup of the file before modification will be made in the same directory with a `.` prepended and `.bak` appended (e.g. `marked.taskpaper` is copied to `.marked.taskpaper.bak`). Only one undo step is available, but if something goes wrong (and this feature is still experimental, so be wary), you can just copy the ".bak" file back to the original.
414
+
415
+ ###### Marking a task as complete
416
+
417
+ You can mark an action complete using `--finish`, which will add a dated @done tag to the action. You can also mark it @done and immediately move it to the Archive project using `--archive`.
418
+
419
+ If you just want the action to stop appearing as a "next action," you can remove the next action tag using `--remove na` (or whatever your next action tag is configured as).
420
+
421
+ If you want to permanently delete an action, use `--delete` to remove it entirely.
422
+
423
+ ###### Moving between projects
386
424
 
387
425
  You can specify a new project for an action (moving it) with `--proj PROJECT_PATH`. A project path is hierarchical, with each level separated by a colon or slash. If the project path provided roughly matches an existing project, e.g. "mark:bug" would match "Marked:Bugs", then that project will be used. If no match is found, na will offer to generate a new project/hierarchy for the path provided. Strings will be exact but the first letter will be uppercased.
388
426
 
389
- See the help output for a list of available actions.
427
+ ###### Adding notes
428
+
429
+ Use the `--note` switch to add a note. If STDIN (piped) input is present when this switch is used, it will be included in the note. A prompt will be displayed for adding additional notes, which will be appended to any STDIN note passed. Press CTRL-d to end editing and save the note.
430
+
431
+ Notes are not displayed by the `next/tagged/find` commands unless `--notes` is specified.
432
+
433
+ See the help output for a list of all available actions.
390
434
 
391
435
  ```
392
436
  NAME
@@ -410,7 +454,7 @@ COMMAND OPTIONS
410
454
  -f, --finish - Add a @done tag to action
411
455
  --file=PATH - Specify the file to search for the task (default: none)
412
456
  --in, --todo=TODO_FILE - Use a known todo file, partial matches allowed (default: none)
413
- -n, --note - Prompt for additional notes. Input will be appended to any existing note.
457
+ -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.
414
458
  -o, --overwrite - Overwrite note instead of appending
415
459
  -p, --priority=PRIO - Add/change a priority level 1-5 (default: 0)
416
460
  -r, --remove=TAG - Remove a tag to the action (may be used more than once, default: none)
data/bin/na CHANGED
@@ -4,6 +4,7 @@
4
4
  $LOAD_PATH.unshift File.join(__dir__, '..', 'lib')
5
5
  require 'gli'
6
6
  require 'na'
7
+ require 'fcntl'
7
8
 
8
9
  # Main application
9
10
  class App
@@ -63,7 +64,7 @@ class App
63
64
  flag %i[d depth], type: :integer, must_match: /^[1-9]$/
64
65
 
65
66
  desc 'Display verbose output'
66
- switch %i[debug]
67
+ switch %i[debug], default_value: false
67
68
 
68
69
  desc 'Show next actions'
69
70
  long_desc 'Next actions are actions which contain the next action tag (default @na),
@@ -94,6 +95,9 @@ class App
94
95
  c.arg_name 'PROJECT[/SUBPROJECT]'
95
96
  c.flag %i[proj project]
96
97
 
98
+ c.desc 'Include notes in output'
99
+ c.switch %i[notes], negatable: true, default_value: false
100
+
97
101
  c.desc 'Include @done actions'
98
102
  c.switch %i[done]
99
103
 
@@ -102,8 +106,8 @@ class App
102
106
  cmd = ['add']
103
107
  cmd.push('--note') if global_options[:note]
104
108
  cmd.concat(['--priority', global_options[:priority]]) if global_options[:priority]
105
- cmd.push(ARGV.unshift(NA.command_line[0])) if NA.command_line.count > 1
106
-
109
+ cmd.push(NA.command_line) if NA.command_line.count > 1
110
+ cmd.unshift(*NA.globals)
107
111
  exit run(cmd)
108
112
  end
109
113
 
@@ -141,7 +145,7 @@ class App
141
145
  project: options[:project],
142
146
  require_na: require_na)
143
147
 
144
- NA.output_actions(actions, depth, files: files)
148
+ NA.output_actions(actions, depth, files: files, notes: options[:notes])
145
149
  end
146
150
  end
147
151
 
@@ -156,11 +160,11 @@ class App
156
160
  command :add do |c|
157
161
  c.example 'na add "A cool feature I thought of @idea"', desc: 'Add a new action to the Inbox, including a tag'
158
162
  c.example 'na add "A bug I need to fix" -p 4 -n',
159
- desc: 'Add a new action to the Inbox, set its @priority to 4, and prompt for an additional note'
163
+ desc: 'Add a new action to the Inbox, set its @priority to 4, and prompt for an additional note.'
160
164
  c.example 'na add "An action item (with a note)"',
161
165
  desc: 'A parenthetical at the end of an action is interpreted as a note'
162
166
 
163
- c.desc 'Prompt for additional notes'
167
+ c.desc 'Prompt for additional notes. STDIN input (piped) will be treated as a note if present.'
164
168
  c.switch %i[n note], negatable: false
165
169
 
166
170
  c.desc 'Add a priority level 1-5'
@@ -205,9 +209,8 @@ class App
205
209
  if NA.global_file
206
210
  target = File.expand_path(NA.global_file)
207
211
  unless File.exist?(target)
208
- print NA::Color.template('{by}Specified file not found, create it? {w}(y/{g}N{w}){x} ')
209
- res = reader.read_char
210
- if res =~ /y/i
212
+ res = NA.yn(NA::Color.template('{by}Specified file not found, create it'), default: true)
213
+ if res
211
214
  basename = File.basename(target, ".#{NA.extension}")
212
215
  NA.create_todo(target, basename)
213
216
  else
@@ -218,9 +221,8 @@ class App
218
221
  elsif options[:file]
219
222
  target = File.expand_path(options[:file])
220
223
  unless File.exist?(target)
221
- print NA::Color.template('{by}Specified file not found, create it? {w}(y/{g}N{w}){x} ')
222
- res = reader.read_char
223
- if res =~ /y/i
224
+ res = NA.yn(NA::Color.template('{by}Specified file not found, create it'), default: true)
225
+ if res
224
226
  basename = File.basename(target, ".#{NA.extension}")
225
227
  NA.create_todo(target, basename)
226
228
  else
@@ -231,10 +233,11 @@ class App
231
233
  elsif options[:todo]
232
234
  todo = []
233
235
  options[:todo].split(/ *, */).each do |a|
234
- m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
236
+ m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
235
237
  todo.push({
236
238
  token: m['tok'],
237
- required: !m['req'].nil?
239
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
240
+ negate: !m['req'].nil? && m['req'] =~ /[!\-]/
238
241
  })
239
242
  end
240
243
  dirs = NA.match_working_dir(todo)
@@ -259,9 +262,8 @@ class App
259
262
  else
260
263
  files = NA.find_files(depth: options[:depth])
261
264
  if files.count.zero?
262
- print NA::Color.template('{by}No todo file found, create one? {w}(y/{g}N{w}){x} ')
263
- res = reader.read_char
264
- if res =~ /y/i
265
+ res = NA.yn(NA::Color.template('{by}No todo file found, create one'), default: true)
266
+ if res
265
267
  basename = File.expand_path('.').split('/').last
266
268
  target = "#{basename}.#{NA.extension}"
267
269
  NA.create_todo(target, basename)
@@ -277,9 +279,9 @@ class App
277
279
 
278
280
  action = if args.count.positive?
279
281
  args.join(' ').strip
280
- elsif TTY::Which.exist?('gum')
282
+ elsif $stdin.isatty && TTY::Which.exist?('gum')
281
283
  `gum input --placeholder "Enter a task" --char-limit=500 --width=#{TTY::Screen.columns}`.strip
282
- else
284
+ elsif $stdin.isatty
283
285
  puts NA::Color.template('{bm}Enter task:{x}')
284
286
  reader.read_line(NA::Color.template('{by}> {bw}')).strip
285
287
  end
@@ -310,9 +312,12 @@ class App
310
312
 
311
313
  action = "#{action.gsub(/#{na_tag}\b/, '')}#{na_tag}"
312
314
 
313
- line_note = if options[:note]
315
+ stdin_note = NA.stdin ? NA.stdin.split("\n") : []
316
+
317
+ line_note = if options[:note] && $stdin.isatty
318
+ puts stdin_note unless stdin_note.nil?
314
319
  if TTY::Which.exist?('gum')
315
- args = ['--placeholder "Enter a note, CTRL-d to save"']
320
+ args = ['--placeholder "Enter additional note, CTRL-d to save"']
316
321
  args << '--char-limit 0'
317
322
  args << '--width $(tput cols)'
318
323
  `gum write #{args.join(' ')}`.strip.split("\n")
@@ -322,9 +327,9 @@ class App
322
327
  end
323
328
  end
324
329
 
325
- note = split_note.nil? ? [] : [split_note]
330
+ note = stdin_note.empty? ? [] : stdin_note
331
+ note.concat(split_note) unless split_note.nil?
326
332
  note.concat(line_note) unless line_note.nil?
327
- note = [] if note.empty?
328
333
 
329
334
  NA.add_action(target, options[:project], action, note, finish: options[:finish], append: append)
330
335
  end
@@ -337,12 +342,15 @@ class App
337
342
  allow you to pick which file to act on.'
338
343
  arg_name 'ACTION'
339
344
  command %i[update] do |c|
340
- c.example 'na update --remove na "An existing task"', desc: 'Find "An existing task" action and remove the @na tag from it'
345
+ c.example 'na update --remove na "An existing task"',
346
+ desc: 'Find "An existing task" action and remove the @na tag from it'
341
347
  c.example 'na update --tag waiting "A bug I need to fix" -p 4 -n',
342
348
  desc: 'Find "A bug..." action, add @waiting, add/update @priority(4), and prompt for an additional note'
343
- c.example 'na update --archive My cool action', desc: 'Add @done to "My cool action" and immediately move to Archive'
349
+ c.example 'na update --archive My cool action',
350
+ desc: 'Add @done to "My cool action" and immediately move to Archive'
344
351
 
345
- c.desc 'Prompt for additional notes. Input will be appended to any existing note.'
352
+ c.desc 'Prompt for additional notes. Input will be appended to any existing note.
353
+ If STDIN input (piped) is detected, it will be used as a note.'
346
354
  c.switch %i[n note], negatable: false
347
355
 
348
356
  c.desc 'Overwrite note instead of appending'
@@ -411,14 +419,14 @@ class App
411
419
 
412
420
  action = if args.count.positive?
413
421
  args.join(' ').strip
414
- elsif TTY::Which.exist?('gum') && options[:tagged].empty?
422
+ elsif $stdin.isatty && TTY::Which.exist?('gum') && options[:tagged].empty?
415
423
  options = [
416
424
  %(--placeholder "Enter a task to search for"),
417
425
  '--char-limit=500',
418
426
  "--width=#{TTY::Screen.columns}"
419
427
  ]
420
428
  `gum input #{options.join(' ')}`.strip
421
- elsif options[:tagged].empty?
429
+ elsif $stdin.isatty && options[:tagged].empty?
422
430
  puts NA::Color.template('{bm}Enter search string:{x}')
423
431
  reader.read_line(NA::Color.template('{by}> {bw}')).strip
424
432
  end
@@ -467,7 +475,10 @@ class App
467
475
  add_tags = options[:tag].map { |t| t.sub(/^@/, '').wildcard_to_rx }
468
476
  remove_tags = options[:remove].map { |t| t.sub(/^@/, '').wildcard_to_rx }
469
477
 
470
- line_note = if options[:note]
478
+ stdin_note = NA.stdin ? NA.stdin.split("\n") : []
479
+
480
+ line_note = if options[:note] && $stdin.isatty
481
+ puts stdin_note unless stdin_note.nil?
471
482
  if TTY::Which.exist?('gum')
472
483
  args = ['--placeholder "Enter a note, CTRL-d to save"']
473
484
  args << '--char-limit 0'
@@ -479,7 +490,8 @@ class App
479
490
  end
480
491
  end
481
492
 
482
- note = line_note.nil? || line_note.empty? ? [] : line_note
493
+ note = stdin_note.empty? ? [] : stdin_note
494
+ note.concat(line_note) unless line_note.nil? || line_note.empty?
483
495
 
484
496
  target_proj = if options[:project]
485
497
  options[:project]
@@ -497,10 +509,11 @@ class App
497
509
  elsif options[:todo]
498
510
  todo = []
499
511
  options[:todo].split(/ *, */).each do |a|
500
- m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
512
+ m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
501
513
  todo.push({
502
514
  token: m['tok'],
503
- required: !m['req'].nil?
515
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
516
+ negate: !m['req'].nil? && m['req'] =~ /[!\-]/
504
517
  })
505
518
  end
506
519
  dirs = NA.match_working_dir(todo)
@@ -568,6 +581,9 @@ class App
568
581
  c.arg_name 'TODO_PATH'
569
582
  c.flag %i[in]
570
583
 
584
+ c.desc 'Include notes in output'
585
+ c.switch %i[notes], negatable: true, default_value: false
586
+
571
587
  c.desc 'Combine search tokens with OR, displaying actions matching ANY of the terms'
572
588
  c.switch %i[o or], negatable: false
573
589
 
@@ -619,10 +635,11 @@ class App
619
635
  if options[:in]
620
636
  todo = []
621
637
  options[:in].split(/ *, */).each do |a|
622
- m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
638
+ m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
623
639
  todo.push({
624
640
  token: m['tok'],
625
- required: !m['req'].nil?
641
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
642
+ negate: !m['req'].nil? && m['req'] =~ /[!\-]/
626
643
  })
627
644
  end
628
645
  end
@@ -641,7 +658,7 @@ class App
641
658
  [tokens]
642
659
  end
643
660
 
644
- NA.output_actions(actions, depth, files: files, regexes: regexes)
661
+ NA.output_actions(actions, depth, files: files, regexes: regexes, notes: options[:notes])
645
662
  end
646
663
  end
647
664
 
@@ -669,6 +686,9 @@ class App
669
686
  c.arg_name 'TODO_PATH'
670
687
  c.flag %i[in]
671
688
 
689
+ c.desc 'Include notes in output'
690
+ c.switch %i[notes], negatable: true, default_value: false
691
+
672
692
  c.desc 'Combine tags with OR, displaying actions matching ANY of the tags'
673
693
  c.switch %i[o or], negatable: false
674
694
 
@@ -721,10 +741,11 @@ class App
721
741
  if options[:in]
722
742
  todo = []
723
743
  options[:in].split(/ *, */).each do |a|
724
- m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
744
+ m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
725
745
  todo.push({
726
746
  token: m['tok'],
727
- required: !m['req'].nil?
747
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
748
+ negate: !m['req'].nil? && m['req'] =~ /[!\-]/
728
749
  })
729
750
  end
730
751
  end
@@ -737,7 +758,7 @@ class App
737
758
  project: options[:project],
738
759
  require_na: false)
739
760
  regexes = tags.delete_if { |token| token[:negate] }.map { |token| token[:token] }
740
- NA.output_actions(actions, depth, files: files, regexes: regexes)
761
+ NA.output_actions(actions, depth, files: files, regexes: regexes, notes: options[:notes])
741
762
  end
742
763
  end
743
764
 
@@ -751,17 +772,17 @@ class App
751
772
  reader = TTY::Reader.new
752
773
  if args.count.positive?
753
774
  project = args.join(' ')
754
- else
775
+ elsif
755
776
  project = File.expand_path('.').split('/').last
756
- project = reader.read_line(NA::Color.template('{y}Project name {bw}> {x}'), value: project).strip
777
+ project = reader.read_line(NA::Color.template('{y}Project name {bw}> {x}'), value: project).strip if $stdin.isatty
757
778
  end
758
779
 
759
780
  target = "#{project}.#{NA.extension}"
760
781
 
761
782
  if File.exist?(target)
762
- print NA::Color.template("{r}File {bw}#{target}{r} already exists, overwrite it? {br}y{w}/{bg}N{x} ")
763
- res = reader.read_char
764
- Process.exit 1 unless res =~ /y/i
783
+ res = NA.yn(NA::Color.template("{r}File {bw}#{target}{r} already exists, overwrite it"), default: false)
784
+ Process.exit 1 unless res
785
+
765
786
  end
766
787
 
767
788
  NA.create_todo(target, project)
@@ -942,8 +963,12 @@ class App
942
963
  long_desc 'Run without argument to list saved searches'
943
964
  arg_name 'SEARCH_TITLE', optional: true
944
965
  command %i[saved] do |c|
945
- c.example 'na saved overdue', description: 'perform the search named "overdue"'
946
- c.example 'na saved over', description: 'perform the search named "overdue", assuming no other searches match "over"'
966
+ c.example 'na tagged "+maybe,+priority<=3" --save maybelater', description: 'save a search called "maybelater"'
967
+ c.example 'na saved maybelater', description: 'perform the search named "maybelater"'
968
+ c.example 'na saved maybe',
969
+ description: 'perform the search named "maybelater", assuming no other searches match "may"'
970
+ c.example 'na maybe',
971
+ description: 'na run with no command and a single argument automatically performs a matching saved search'
947
972
  c.example 'na saved', description: 'list available searches'
948
973
 
949
974
  c.desc 'Open the saved search file in $EDITOR'
@@ -953,18 +978,14 @@ class App
953
978
  c.switch %i[d delete]
954
979
 
955
980
  c.action do |_global_options, options, args|
956
- if options[:edit]
957
- NA.edit_searches
958
- end
981
+ NA.edit_searches if options[:edit]
959
982
 
960
983
  searches = NA.load_searches
961
984
  if args.empty?
962
985
  NA.notify("{bg}Saved searches stored in {bw}#{NA.database_path(file: 'saved_searches.yml')}")
963
986
  NA.notify(searches.map { |k, v| "{y}#{k}: {w}#{v}" }.join("\n"), exit_code: 0)
964
987
  else
965
- if options[:delete]
966
- NA.delete_search(args)
967
- end
988
+ NA.delete_search(args) if options[:delete]
968
989
 
969
990
  keys = searches.keys.delete_if { |k| k !~ /#{args[0]}/ }
970
991
  NA.notify("{r}Search #{args[0]} not found", exit_code: 1) if keys.empty?
@@ -988,6 +1009,7 @@ class App
988
1009
  global[:cwd_as] =~ /^p/ ? :project : :tag
989
1010
  end
990
1011
  NA.weed_cache_file
1012
+ NA.notify("{dw}{ globals: #{NA.globals}, command_line: #{NA.command_line}, @command: #{@command}}", debug: true)
991
1013
  true
992
1014
  end
993
1015
 
@@ -998,10 +1020,21 @@ class App
998
1020
  on_error do |exception|
999
1021
  case exception
1000
1022
  when GLI::UnknownCommand
1001
- cmd = ['saved']
1002
- cmd.concat(ARGV.unshift(NA.command_line[0]))
1023
+ if NA.command_line.count == 1
1024
+ cmd = ['saved']
1025
+ cmd.concat(ARGV.unshift(NA.command_line[0]))
1003
1026
 
1004
- exit run(cmd)
1027
+ exit run(cmd)
1028
+ elsif NA.globals.include?('-a') || NA.globals.include?('--add')
1029
+ cmd = ['add']
1030
+ cmd.concat(NA.command_line)
1031
+ NA.globals.delete('-a')
1032
+ NA.globals.delete('--add')
1033
+ cmd.unshift(*NA.globals)
1034
+
1035
+ exit run(cmd)
1036
+ end
1037
+ true
1005
1038
  when SystemExit
1006
1039
  false
1007
1040
  else
@@ -1010,9 +1043,20 @@ class App
1010
1043
  end
1011
1044
  end
1012
1045
 
1013
- NA.command_line = ARGV.dup
1014
- NA.globals = NA.command_line.filter { |arg| arg =~ /^-/ }
1015
- NA.command_line.delete_if { |arg| arg =~ /^-/ }
1016
- @command = ARGV[0]
1046
+ NA.stdin = $stdin.read.strip if $stdin.stat.size.positive? || $stdin.fcntl(Fcntl::F_GETFL, 0).zero?
1047
+ NA.stdin = nil unless NA.stdin && NA.stdin.length.positive?
1048
+
1049
+ NA.globals = []
1050
+ NA.command_line = []
1051
+ in_globals = true
1052
+ ARGV.each do |arg|
1053
+ if arg =~ /^-/ && in_globals
1054
+ NA.globals.push(arg)
1055
+ else
1056
+ NA.command_line.push(arg)
1057
+ in_globals = false
1058
+ end
1059
+ end
1060
+ @command = NA.command_line[0]
1017
1061
 
1018
1062
  exit App.run(ARGV)
data/lib/na/action.rb CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  module NA
4
4
  class Action < Hash
5
- attr_reader :file, :project, :parent, :tags, :line, :note
5
+ attr_reader :file, :project, :parent, :tags, :line
6
6
 
7
- attr_accessor :action
7
+ attr_accessor :action, :note
8
8
 
9
9
  def initialize(file, project, parent, action, idx, note = [])
10
10
  super()
@@ -19,7 +19,12 @@ module NA
19
19
  end
20
20
 
21
21
  def to_s
22
- "(#{@file}:#{@line}) #{@project}:#{@parent.join('>')} | #{@action}"
22
+ note = if @note.count.positive?
23
+ "\n#{@note.join("\n")}"
24
+ else
25
+ ''
26
+ end
27
+ "(#{@file}:#{@line}) #{@project}:#{@parent.join('>')} | #{@action}#{@note}"
23
28
  end
24
29
 
25
30
  def inspect
@@ -28,10 +33,11 @@ module NA
28
33
  @project: #{@project}
29
34
  @parent: #{@parent.join('>')}
30
35
  @action: #{@action}
36
+ @note: #{@note}
31
37
  EOINSPECT
32
38
  end
33
39
 
34
- def pretty(extension: 'taskpaper', template: {}, regexes: [])
40
+ def pretty(extension: 'taskpaper', template: {}, regexes: [], notes: false)
35
41
  default_template = {
36
42
  file: '{xbk}',
37
43
  parent: '{c}',
@@ -41,7 +47,8 @@ module NA
41
47
  tags: '{m}',
42
48
  value_parens: '{m}',
43
49
  values: '{y}',
44
- output: '%filename%parents| %action'
50
+ output: '%filename%parents| %action',
51
+ note: '{dw}'
45
52
  }
46
53
  template = default_template.merge(template)
47
54
 
@@ -58,6 +65,12 @@ module NA
58
65
  file_tpl = "#{template[:file]}#{file} {x}"
59
66
  filename = NA::Color.template(file_tpl)
60
67
 
68
+ note = if notes && @note.count.positive?
69
+ NA::Color.template("\n#{@note.map { |l| " #{template[:note]}• #{l}{x}" }.join("\n")}")
70
+ else
71
+ ''
72
+ end
73
+
61
74
  action = NA::Color.template("#{template[:action]}#{@action.sub(/ @#{NA.na_tag}\b/, '')}{x}")
62
75
  action = action.highlight_tags(color: template[:tags],
63
76
  parens: template[:value_parens],
@@ -67,7 +80,8 @@ module NA
67
80
  NA::Color.template(template[:output].gsub(/%filename/, filename)
68
81
  .gsub(/%project/, project)
69
82
  .gsub(/%parents?/, parents)
70
- .gsub(/%action/, action.highlight_search(regexes))).gsub(/\\\{/, '{')
83
+ .gsub(/%action/, action.highlight_search(regexes))
84
+ .gsub(/%note/, note)).gsub(/\\\{/, '{')
71
85
  end
72
86
 
73
87
  def tags_match?(any: [], all: [], none: [])
@@ -3,7 +3,7 @@
3
3
  # Next Action methods
4
4
  module NA
5
5
  class << self
6
- attr_accessor :verbose, :extension, :na_tag, :command_line, :globals, :global_file, :cwd_is, :cwd
6
+ attr_accessor :verbose, :extension, :na_tag, :command_line, :globals, :global_file, :cwd_is, :cwd, :stdin
7
7
 
8
8
  ##
9
9
  ## Output to STDERR
@@ -14,7 +14,7 @@ module NA
14
14
  ## @param debug [Boolean] only display message if running :verbose
15
15
  ##
16
16
  def notify(msg, exit_code: false, debug: false)
17
- return if debug && NA.verbose
17
+ return if debug && !NA.verbose
18
18
 
19
19
  $stderr.puts NA::Color.template("{x}#{msg}{x}")
20
20
  Process.exit exit_code if exit_code
@@ -439,7 +439,7 @@ module NA
439
439
  ## @param files [Array] The files actions originally came from
440
440
  ## @param regexes [Array] The regexes used to gather actions
441
441
  ##
442
- def output_actions(actions, depth, files: nil, regexes: [])
442
+ def output_actions(actions, depth, files: nil, regexes: [], notes: false)
443
443
  return if files.nil?
444
444
 
445
445
  template = if files.count.positive?
@@ -457,10 +457,11 @@ module NA
457
457
  else
458
458
  '%parent%action'
459
459
  end
460
+ template += '%note' if notes
460
461
 
461
462
  files.map { |f| notify("{dw}#{f}", debug: true) } if files
462
463
 
463
- puts(actions.map { |action| action.pretty(template: { output: template }, regexes: regexes) })
464
+ puts(actions.map { |action| action.pretty(template: { output: template }, regexes: regexes, notes: notes) })
464
465
  end
465
466
 
466
467
  ##
@@ -783,6 +784,12 @@ module NA
783
784
  required = search.filter { |s| s[:required] }.map { |t| t[:token] }
784
785
  negated = search.filter { |s| s[:negate] }.map { |t| t[:token] }
785
786
 
787
+ optional.push('*') if required.count.zero? && negated.count.positive?
788
+ if required == negated
789
+ required = ['*']
790
+ optional = ['*']
791
+ end
792
+
786
793
  NA.notify("{dw}Optional directory regex: {x}#{optional.map(&:dir_to_rx)}", debug: true)
787
794
  NA.notify("{dw}Required directory regex: {x}#{required.map(&:dir_to_rx)}", debug: true)
788
795
  NA.notify("{dw}Negated directory regex: {x}#{negated.map { |t| t.dir_to_rx(distance: 1, require_last: false) }}", debug: true)
data/lib/na/prompt.rb CHANGED
@@ -7,22 +7,59 @@ module NA
7
7
  def prompt_hook(shell)
8
8
  case shell
9
9
  when :zsh
10
+ cmd = if NA.global_file
11
+ case NA.cwd_is
12
+ when :project
13
+ 'na next --proj $(basename "$PWD")'
14
+ when :tag
15
+ 'na tagged $(basename "$PWD")'
16
+ else
17
+ NA.notify('When using a global file, a prompt hook requires `--cwd_as [tag|project]`', exit_code: 1)
18
+ end
19
+ else
20
+ 'na next'
21
+ end
10
22
  <<~EOHOOK
11
23
  # zsh prompt hook for na
12
- chpwd() { na next }
24
+ chpwd() { #{cmd} }
13
25
  EOHOOK
14
26
  when :fish
27
+ cmd = if NA.global_file
28
+ case NA.cwd_is
29
+ when :project
30
+ 'na next --proj (basename "$PWD")'
31
+ when :tag
32
+ 'na tagged (basename "$PWD")'
33
+ else
34
+ NA.notify('When using a global file, a prompt hook requires `--cwd_as [tag|project]`', exit_code: 1)
35
+ end
36
+ else
37
+ 'na next'
38
+ end
15
39
  <<~EOHOOK
16
40
  # Fish Prompt Command
17
41
  function __should_na --on-variable PWD
18
- test -s (basename $PWD)".#{NA.extension}" && na next
42
+ test -s (basename $PWD)".#{NA.extension}" && #{cmd}
19
43
  end
20
44
  EOHOOK
21
45
  when :bash
46
+ cmd = if NA.global_file
47
+ case NA.cwd_is
48
+ when :project
49
+ 'na next --proj $(basename "$PWD")'
50
+ when :tag
51
+ 'na tagged $(basename "$PWD")'
52
+ else
53
+ NA.notify('When using a global file, a prompt hook requires `--cwd_as [tag|project]`', exit_code: 1)
54
+ end
55
+ else
56
+ 'na next'
57
+ end
58
+
22
59
  <<~EOHOOK
23
60
  # Bash PROMPT_COMMAND for na
24
61
  last_command_was_cd() {
25
- [[ $(history 1|sed -e "s/^[ ]*[0-9]*[ ]*//") =~ ^((cd|z|j|jump|g|f|pushd|popd|exit)([ ]|$)) ]] && na next
62
+ [[ $(history 1|sed -e "s/^[ ]*[0-9]*[ ]*//") =~ ^((cd|z|j|jump|g|f|pushd|popd|exit)([ ]|$)) ]] && #{cmd}
26
63
  }
27
64
  if [[ -z "$PROMPT_COMMAND" ]]; then
28
65
  PROMPT_COMMAND="eval 'last_command_was_cd'"
@@ -46,7 +83,7 @@ module NA
46
83
  def show_prompt_hook(shell)
47
84
  file = prompt_file(shell)
48
85
 
49
- $stderr.puts NA::Color.template("{bw}# Add this to {y}#{file}{x}")
86
+ NA.notify("{bw}# Add this to {y}#{file}{x}")
50
87
  puts prompt_hook(shell)
51
88
  end
52
89
 
@@ -54,8 +91,8 @@ module NA
54
91
  file = prompt_file(shell)
55
92
 
56
93
  File.open(File.expand_path(file), 'a') { |f| f.puts prompt_hook(shell) }
57
- $stderr.puts NA::Color.template("{y}Added {bw}#{shell}{xy} prompt hook to {bw}#{file}{xy}.{x}")
58
- $stderr.puts NA::Color.template("{y}You may need to close the current terminal and open a new one to enable the script.{x}")
94
+ NA.notify("{y}Added {bw}#{shell}{xy} prompt hook to {bw}#{file}{xy}.{x}")
95
+ NA.notify("{y}You may need to close the current terminal and open a new one to enable the script.{x}")
59
96
  end
60
97
  end
61
98
  end
data/lib/na/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Na
2
- VERSION = '1.2.5'
2
+ VERSION = '1.2.6'
3
3
  end
data/src/README.md CHANGED
@@ -9,11 +9,11 @@
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.4<!--END VER-->.
12
+ The current version of `na` is <!--VER-->1.2.5<!--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
 
16
- Used with Taskpaper files, it can add new todo items quickly from the command line, automatically tagging them as next actions.
16
+ Used with Taskpaper files, it can add new action items quickly from the command line, automatically tagging them as next actions. It can also mark actions as completed, delete them, archive them, and move them between projects.
17
17
 
18
18
  It can also auto-display next actions when you enter a project directory, automatically locating any todo files and listing their next actions when you `cd` to the project (optionally recursive). See the [Prompt Hooks](#prompt-hooks) section for details.
19
19
 
@@ -52,6 +52,16 @@ If found, it will try to locate an `Inbox:` project, or create one if it doesn't
52
52
 
53
53
  You can mark todos as complete, delete them, add and remove tags, change priority, and even move them between projects with the `na update` command.
54
54
 
55
+ ### Terminology
56
+
57
+ **Todo**: Refers to a todo file, usually a TaskPaper document
58
+
59
+ **Project**: Refers to a project within the TaskPaper document, specified by an alphanumeric name (spaces allowed) followed by a colon. Projects can be nested by indenting a tab beyond the parent projects indentation.
60
+
61
+ **Action**: Refers to an individual task, specified by a line starting with a hyphen (`-`)
62
+
63
+ **Note**: Refers to lines appearing between action lines that start without hyphens. The note is attached to the preceding action regardless of indentation.
64
+
55
65
  ### Usage
56
66
 
57
67
  ```
@@ -66,6 +76,12 @@ Example: `na add This feature @idea I have`
66
76
 
67
77
  If you run the `add` command with no arguments, you'll be asked for input on the command line.
68
78
 
79
+ ###### Adding notes
80
+
81
+ Use the `--note` switch to add a note. If STDIN (piped) input is present when this switch is used, it will be included in the note. A prompt will be displayed for adding additional notes, which will be appended to any STDIN note passed. Press CTRL-d to end editing and save the note.
82
+
83
+ Notes are not displayed by the `next/tagged/find` commands unless `--notes` is specified.
84
+
69
85
  ```
70
86
  @cli(bundle exec bin/na help add)
71
87
  ```
@@ -100,6 +116,8 @@ Examples:
100
116
  - `na next -d 3` (list all next actions in the current directory and look for additional files 3 levels deep from there)
101
117
  - `na next marked2` (show next actions from another directory you've previously used na on)
102
118
 
119
+ 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`.
120
+
103
121
  ```
104
122
  @cli(bundle exec bin/na help next)
105
123
  ```
@@ -120,6 +138,9 @@ Search names can be partially matched when calling them, so if you have a search
120
138
 
121
139
  Run `na saved` without an argument to list your saved searches.
122
140
 
141
+ > 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`.
142
+ <!--JEKYLL{:.tip}-->
143
+
123
144
  ```
124
145
  @cli(bundle exec bin/na help saved)
125
146
  ```
@@ -158,11 +179,27 @@ You can specify a particular todo file using `--file PATH` or any todo from hist
158
179
 
159
180
  If more than one file is matched, a menu will be presented, multiple selections allowed. If multiple actions match the search within the selected file(s), a menu will be presented. If you have fzf installed, you can select one action to update with return, or use tab to mark multiple tasks to which the action will be applied. With gum you can use j, k, and x to mark multiple actions. Use the `--all` switch to force operation on all matched tasks, skipping the menu.
160
181
 
161
- Any time an update action is carried out, a backup of the file before modification will be made in the same directory with a `.` prepended and `.bak` appended (e.g. `marked.taskpaper` is copied to `.marked.taskpaper.bak`). Only one undo step is available, but if something goes wrong (and this feature is still experimental, so be wary), you can just copy the "~" file back to the original.
182
+ Any time an update action is carried out, a backup of the file before modification will be made in the same directory with a `.` prepended and `.bak` appended (e.g. `marked.taskpaper` is copied to `.marked.taskpaper.bak`). Only one undo step is available, but if something goes wrong (and this feature is still experimental, so be wary), you can just copy the ".bak" file back to the original.
183
+
184
+ ###### Marking a task as complete
185
+
186
+ You can mark an action complete using `--finish`, which will add a dated @done tag to the action. You can also mark it @done and immediately move it to the Archive project using `--archive`.
187
+
188
+ If you just want the action to stop appearing as a "next action," you can remove the next action tag using `--remove na` (or whatever your next action tag is configured as).
189
+
190
+ If you want to permanently delete an action, use `--delete` to remove it entirely.
191
+
192
+ ###### Moving between projects
162
193
 
163
194
  You can specify a new project for an action (moving it) with `--proj PROJECT_PATH`. A project path is hierarchical, with each level separated by a colon or slash. If the project path provided roughly matches an existing project, e.g. "mark:bug" would match "Marked:Bugs", then that project will be used. If no match is found, na will offer to generate a new project/hierarchy for the path provided. Strings will be exact but the first letter will be uppercased.
164
195
 
165
- See the help output for a list of available actions.
196
+ ###### Adding notes
197
+
198
+ Use the `--note` switch to add a note. If STDIN (piped) input is present when this switch is used, it will be included in the note. A prompt will be displayed for adding additional notes, which will be appended to any STDIN note passed. Press CTRL-d to end editing and save the note.
199
+
200
+ Notes are not displayed by the `next/tagged/find` commands unless `--notes` is specified.
201
+
202
+ See the help output for a list of all available actions.
166
203
 
167
204
  ```
168
205
  @cli(bundle exec bin/na help update)
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.5
4
+ version: 1.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra