na 1.2.5 → 1.2.7

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: 77270e07e7c7d4332a5fb8ec87f4e9ad5496bd220d188e75adfb71026aff1180
4
+ data.tar.gz: a3521feaefc9573bd78344b6a16ae57719284bde611e8f9f6e664238fcc1b5c3
5
5
  SHA512:
6
- metadata.gz: e790052ffdabf6ee749f545d4dade110a45167174700fc3f351d4e688b870c8b7bebf063678232613c12d1e3db3f0bc4491f12a01eededd0c8fd0d8710cfc63c
7
- data.tar.gz: c433b14e48f7ee09ca8a3a27a305b67e21087e0230a16d6f75c1b9d404e46ee5f120a8411f1f1d139b6e36512ac866ad5edc0ca2f29530b7b990baf0da776b16
6
+ metadata.gz: 2a714a764879036b9ad51626445e9ef920909da333e3fc6ecb18b8b3755cf8afad811c7baf1e3aea622a5d74b1ca41e1cf64c43541f772db9ae50cfc03c1de90
7
+ data.tar.gz: 06b865817db9731ded06c415f5c71a10a2f8b928b0f6da5ee9cca083f951e35ee716eaf6e2558daf89ec5378910286be4304fbdbe67e17756eae2be553fee0ea
data/CHANGELOG.md CHANGED
@@ -1,3 +1,39 @@
1
+ ### 1.2.7
2
+
3
+ 2022-10-28 07:29
4
+
5
+ #### IMPROVED
6
+
7
+ - Code refactoring
8
+
9
+ #### FIXED
10
+
11
+ - All_req error
12
+ - Adding entries to project names containing hyphen
13
+
14
+ ### 1.2.6
15
+
16
+ 2022-10-26 10:50
17
+
18
+ #### NEW
19
+
20
+ - Pass notes to STDIN using piped input when using the `--note` switch
21
+ - `--notes` switch for next, find, and tagged to include action notes in output
22
+
23
+ #### IMPROVED
24
+
25
+ - Update na saved examples and documentation
26
+ - Better handling of unknown commands, affecting `na -a ACTION` and `na SAVED_SEARCH`
27
+ - Additional help documentation and examples
28
+ - Updated documentation
29
+ - If a todo query contains only a negative, display all non-matching todos
30
+ - Don't display readline prompts if not a TTY
31
+ - 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`.
32
+
33
+ #### FIXED
34
+
35
+ - Debug messages showing when not using --debug
36
+
1
37
  ### 1.2.5
2
38
 
3
39
  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.7)
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.7
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
 
@@ -31,9 +31,13 @@ If you don't have Ruby/RubyGems, you can install them pretty easily with Homebre
31
31
 
32
32
 
33
33
 
34
+ ### Optional Dependencies
35
+
36
+ If you have [gum][] installed, na will use it for command line input when adding tasks and notes. If you have [fzf][] installed, it will be used for menus, falling back to gum if available.
37
+
34
38
  ### Features
35
39
 
36
- You can list next actions in files in the current directory by typing `na`. By default, `na` looks for `*.taskpaper` files and extracts items tagged `@na` and not `@done`. All of these can be changed in the configuration.
40
+ You can list next actions in files in the current directory by typing `na`. By default, `na` looks for `*.taskpaper` files and extracts items tagged `@na` and not `@done`. This can be modified to work with a single global file, and all of these options can be changed in the configuration.
37
41
 
38
42
  #### Easy matching
39
43
 
@@ -53,6 +57,16 @@ If found, it will try to locate an `Inbox:` project, or create one if it doesn't
53
57
 
54
58
  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
59
 
60
+ ### Terminology
61
+
62
+ **Todo**: Refers to a todo file, usually a TaskPaper document
63
+
64
+ **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.
65
+
66
+ **Action**: Refers to an individual task, specified by a line starting with a hyphen (`-`)
67
+
68
+ **Note**: Refers to lines appearing between action lines that start without hyphens. The note is attached to the preceding action regardless of indentation.
69
+
56
70
  ### Usage
57
71
 
58
72
  ```
@@ -63,7 +77,7 @@ SYNOPSIS
63
77
  na [global options] command [command options] [arguments...]
64
78
 
65
79
  VERSION
66
- 1.2.5
80
+ 1.2.7
67
81
 
68
82
  GLOBAL OPTIONS
69
83
  -a, --[no-]add - Add a next action (deprecated, for backwards compatibility)
@@ -105,6 +119,12 @@ Example: `na add This feature @idea I have`
105
119
 
106
120
  If you run the `add` command with no arguments, you'll be asked for input on the command line.
107
121
 
122
+ ###### Adding notes
123
+
124
+ 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.
125
+
126
+ Notes are not displayed by the `next/tagged/find` commands unless `--notes` is specified.
127
+
108
128
  ```
109
129
  NAME
110
130
  add - Add a new next action
@@ -122,7 +142,7 @@ COMMAND OPTIONS
122
142
  -f, --file=PATH - Specify the file to which the task should be added (default: none)
123
143
  --finish, --done - Mark task as @done with date
124
144
  --in, --todo=TODO_FILE - Add to a known todo file, partial matches allowed (default: none)
125
- -n, --note - Prompt for additional notes
145
+ -n, --note - Prompt for additional notes. STDIN input (piped) will be treated as a note if present.
126
146
  -p, --priority=PRIO - Add a priority level 1-5 (default: 0)
127
147
  -t, --tag=TAG - Use a tag other than the default next action tag (default: none)
128
148
  --to, --project, --proj=PROJECT - Add action to specific project (default: Inbox)
@@ -133,7 +153,7 @@ EXAMPLES
133
153
  # Add a new action to the Inbox, including a tag
134
154
  na add "A cool feature I thought of @idea"
135
155
 
136
- # Add a new action to the Inbox, set its @priority to 4, and prompt for an additional note
156
+ # Add a new action to the Inbox, set its @priority to 4, and prompt for an additional note.
137
157
  na add "A bug I need to fix" -p 4 -n
138
158
 
139
159
  # A parenthetical at the end of an action is interpreted as a note
@@ -190,6 +210,7 @@ COMMAND OPTIONS
190
210
  --[no-]done - Include @done actions
191
211
  -e, --regex - Interpret search pattern as regular expression
192
212
  --in=TODO_PATH - Show actions from a specific todo file in history. May use wildcards (* and ?) (default: none)
213
+ --[no-]notes - Include notes in output
193
214
  -o, --or - Combine search tokens with OR, displaying actions matching ANY of the terms
194
215
  --proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
195
216
  --save=TITLE - Save this search for future use (default: none)
@@ -232,6 +253,8 @@ Examples:
232
253
  - `na next -d 3` (list all next actions in the current directory and look for additional files 3 levels deep from there)
233
254
  - `na next marked2` (show next actions from another directory you've previously used na on)
234
255
 
256
+ 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`.
257
+
235
258
  ```
236
259
  NAME
237
260
  next - Show next actions
@@ -247,6 +270,7 @@ COMMAND OPTIONS
247
270
  -d, --depth=DEPTH - Recurse to depth (default: none)
248
271
  --[no-]done - Include @done actions
249
272
  --in, --todo=TODO_FILE - Display matches from a known todo file (may be used more than once, default: none)
273
+ --[no-]notes - Include notes in output
250
274
  --proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
251
275
  -t, --tag=TAG - Alternate tag to search for (default: none)
252
276
 
@@ -290,6 +314,9 @@ Search names can be partially matched when calling them, so if you have a search
290
314
 
291
315
  Run `na saved` without an argument to list your saved searches.
292
316
 
317
+ > 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`.
318
+
319
+
293
320
  ```
294
321
  NAME
295
322
  saved - Execute a saved search
@@ -307,9 +334,13 @@ COMMAND OPTIONS
307
334
 
308
335
  EXAMPLES
309
336
 
310
- na saved overdue
337
+ na tagged "+maybe,+priority<=3" --save maybelater
338
+
339
+ na saved maybelater
311
340
 
312
- na saved over
341
+ na saved maybe
342
+
343
+ na maybe
313
344
 
314
345
  na saved
315
346
  ```
@@ -339,6 +370,7 @@ COMMAND OPTIONS
339
370
  -d, --depth=DEPTH - Recurse to depth (default: none)
340
371
  --[no-]done - Include @done actions
341
372
  --in, --todo=TODO_FILE - Display matches from a known todo file (may be used more than once, default: none)
373
+ --[no-]notes - Include notes in output
342
374
  --proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
343
375
  -t, --tag=TAG - Alternate tag to search for (default: none)
344
376
 
@@ -382,11 +414,27 @@ You can specify a particular todo file using `--file PATH` or any todo from hist
382
414
 
383
415
  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
416
 
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.
417
+ 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.
418
+
419
+ ###### Marking a task as complete
420
+
421
+ 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`.
422
+
423
+ 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).
424
+
425
+ If you want to permanently delete an action, use `--delete` to remove it entirely.
426
+
427
+ ###### Moving between projects
386
428
 
387
429
  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
430
 
389
- See the help output for a list of available actions.
431
+ ###### Adding notes
432
+
433
+ 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.
434
+
435
+ Notes are not displayed by the `next/tagged/find` commands unless `--notes` is specified.
436
+
437
+ See the help output for a list of all available actions.
390
438
 
391
439
  ```
392
440
  NAME
@@ -410,7 +458,7 @@ COMMAND OPTIONS
410
458
  -f, --finish - Add a @done tag to action
411
459
  --file=PATH - Specify the file to search for the task (default: none)
412
460
  --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.
461
+ -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
462
  -o, --overwrite - Overwrite note instead of appending
415
463
  -p, --priority=PRIO - Add/change a priority level 1-5 (default: 0)
416
464
  -r, --remove=TAG - Remove a tag to the action (may be used more than once, default: none)
@@ -435,6 +483,9 @@ EXAMPLES
435
483
 
436
484
  Global options such as todo extension and default next action tag can be stored permanently by using the `na initconfig` command. Run na with the global options you'd like to set, and add `initconfig` at the end of the command. A file will be written to `~/.na.rc`. You can edit this manually, or just update it using the `initconfig --force` command to overwrite it with new settings.
437
485
 
486
+ > You can see all available global options by running `na help`.
487
+
488
+
438
489
  Example: `na --ext md --na_tag next initconfig --force`
439
490
 
440
491
  When this command is run, it doesn't include options for subcommands, but inserts placeholders for them. If you want to permanently set an option for a subcommand, you'll need to edit `~/.na.rc`. For example, if you wanted the `next` command to always recurse 2 levels deep, you could edit it to look like this:
@@ -465,12 +516,14 @@ When using a global file, you can additionally include `--cwd_as TYPE` to determ
465
516
 
466
517
  #### Add tasks at the end of a project
467
518
 
468
- By default, tasks are added at the top of the target project (Inbox, etc.). If you prefer new tasks to go at the bottom by default, include `--add_at end` as a global option when running `initconfig`.
519
+ By default, tasks are added at the top of the target project (Inbox, etc.). If you prefer new tasks to go at the end of the project by default, include `--add_at end` as a global option when running `initconfig`.
469
520
 
470
521
  ### Prompt Hooks
471
522
 
472
523
  You can add a prompt command to your shell to have na automatically list your next actions when you `cd` into a directory. To install a prompt command for your current shell, just run `na prompt install`. It works with Zsh, Bash, and Fish. If you'd rather make the changes to your startup file yourself, run `na prompt show` to get the hook and instructions printed out for copying.
473
524
 
525
+ If you're using a single global file, you'll need `--cwd_as` to be `tag` or `project` for a prompt command to work. na will detect which system you're using and provide a prompt command that lists actions based on the current directory using either project or tag.
526
+
474
527
  > You can also get output for shells other than the one you're currently using by adding "bash", "zsh", or "fish" to the show or install command.
475
528
 
476
529
 
@@ -479,9 +532,6 @@ You can add a prompt command to your shell to have na automatically list your ne
479
532
 
480
533
  After installing a hook, you'll need to close your terminal and start a new session to initialize the new commands.
481
534
 
482
- ### Misc
483
-
484
- If you have [gum][] installed, na will use it for command line input when adding tasks and notes. If you have [fzf][] installed, it will be used for menus, falling back to gum if available.
485
535
 
486
536
  [fzf]: https://github.com/junegunn/fzf
487
537
  [gum]: https://github.com/charmbracelet/gum
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
@@ -230,11 +232,13 @@ class App
230
232
  end
231
233
  elsif options[:todo]
232
234
  todo = []
235
+ all_req = options[:todo] !~ /[+!\-]/
233
236
  options[:todo].split(/ *, */).each do |a|
234
- m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
237
+ m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
235
238
  todo.push({
236
239
  token: m['tok'],
237
- required: !m['req'].nil?
240
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
241
+ negate: !m['req'].nil? && m['req'] =~ /[!\-]/
238
242
  })
239
243
  end
240
244
  dirs = NA.match_working_dir(todo)
@@ -246,22 +250,18 @@ class App
246
250
  unless File.exist?(target)
247
251
 
248
252
  res = NA.yn(NA::Color.template("{by}Specified file not found, create #{todo}"), default: true)
249
- if res
250
- basename = File.basename(target, ".#{NA.extension}")
251
- NA.create_todo(target, basename)
252
- else
253
- NA.notify('{r}Cancelled{x}', exit_code: 1)
253
+ NA.notify('{r}Cancelled{x}', exit_code: 1) unless res
254
254
 
255
- end
255
+ basename = File.basename(target, ".#{NA.extension}")
256
+ NA.create_todo(target, basename)
256
257
  end
257
258
 
258
259
  end
259
260
  else
260
261
  files = NA.find_files(depth: options[:depth])
261
262
  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
263
+ res = NA.yn(NA::Color.template('{by}No todo file found, create one'), default: true)
264
+ if res
265
265
  basename = File.expand_path('.').split('/').last
266
266
  target = "#{basename}.#{NA.extension}"
267
267
  NA.create_todo(target, basename)
@@ -269,17 +269,15 @@ class App
269
269
  end
270
270
  end
271
271
  target = files.count > 1 ? NA.select_file(files) : files[0]
272
- unless files.count.positive? && File.exist?(target)
273
- NA.notify('{r}Cancelled{x}', exit_code: 1)
272
+ NA.notify('{r}Cancelled{x}', exit_code: 1) unless files.count.positive? && File.exist?(target)
274
273
 
275
- end
276
274
  end
277
275
 
278
276
  action = if args.count.positive?
279
277
  args.join(' ').strip
280
- elsif TTY::Which.exist?('gum')
278
+ elsif $stdin.isatty && TTY::Which.exist?('gum')
281
279
  `gum input --placeholder "Enter a task" --char-limit=500 --width=#{TTY::Screen.columns}`.strip
282
- else
280
+ elsif $stdin.isatty
283
281
  puts NA::Color.template('{bm}Enter task:{x}')
284
282
  reader.read_line(NA::Color.template('{by}> {bw}')).strip
285
283
  end
@@ -310,9 +308,12 @@ class App
310
308
 
311
309
  action = "#{action.gsub(/#{na_tag}\b/, '')}#{na_tag}"
312
310
 
313
- line_note = if options[:note]
311
+ stdin_note = NA.stdin ? NA.stdin.split("\n") : []
312
+
313
+ line_note = if options[:note] && $stdin.isatty
314
+ puts stdin_note unless stdin_note.nil?
314
315
  if TTY::Which.exist?('gum')
315
- args = ['--placeholder "Enter a note, CTRL-d to save"']
316
+ args = ['--placeholder "Enter additional note, CTRL-d to save"']
316
317
  args << '--char-limit 0'
317
318
  args << '--width $(tput cols)'
318
319
  `gum write #{args.join(' ')}`.strip.split("\n")
@@ -322,9 +323,9 @@ class App
322
323
  end
323
324
  end
324
325
 
325
- note = split_note.nil? ? [] : [split_note]
326
+ note = stdin_note.empty? ? [] : stdin_note
327
+ note.concat(split_note) unless split_note.nil?
326
328
  note.concat(line_note) unless line_note.nil?
327
- note = [] if note.empty?
328
329
 
329
330
  NA.add_action(target, options[:project], action, note, finish: options[:finish], append: append)
330
331
  end
@@ -337,12 +338,15 @@ class App
337
338
  allow you to pick which file to act on.'
338
339
  arg_name 'ACTION'
339
340
  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'
341
+ c.example 'na update --remove na "An existing task"',
342
+ desc: 'Find "An existing task" action and remove the @na tag from it'
341
343
  c.example 'na update --tag waiting "A bug I need to fix" -p 4 -n',
342
344
  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'
345
+ c.example 'na update --archive My cool action',
346
+ desc: 'Add @done to "My cool action" and immediately move to Archive'
344
347
 
345
- c.desc 'Prompt for additional notes. Input will be appended to any existing note.'
348
+ c.desc 'Prompt for additional notes. Input will be appended to any existing note.
349
+ If STDIN input (piped) is detected, it will be used as a note.'
346
350
  c.switch %i[n note], negatable: false
347
351
 
348
352
  c.desc 'Overwrite note instead of appending'
@@ -411,14 +415,14 @@ class App
411
415
 
412
416
  action = if args.count.positive?
413
417
  args.join(' ').strip
414
- elsif TTY::Which.exist?('gum') && options[:tagged].empty?
418
+ elsif $stdin.isatty && TTY::Which.exist?('gum') && options[:tagged].empty?
415
419
  options = [
416
420
  %(--placeholder "Enter a task to search for"),
417
421
  '--char-limit=500',
418
422
  "--width=#{TTY::Screen.columns}"
419
423
  ]
420
424
  `gum input #{options.join(' ')}`.strip
421
- elsif options[:tagged].empty?
425
+ elsif $stdin.isatty && options[:tagged].empty?
422
426
  puts NA::Color.template('{bm}Enter search string:{x}')
423
427
  reader.read_line(NA::Color.template('{by}> {bw}')).strip
424
428
  end
@@ -467,7 +471,10 @@ class App
467
471
  add_tags = options[:tag].map { |t| t.sub(/^@/, '').wildcard_to_rx }
468
472
  remove_tags = options[:remove].map { |t| t.sub(/^@/, '').wildcard_to_rx }
469
473
 
470
- line_note = if options[:note]
474
+ stdin_note = NA.stdin ? NA.stdin.split("\n") : []
475
+
476
+ line_note = if options[:note] && $stdin.isatty
477
+ puts stdin_note unless stdin_note.nil?
471
478
  if TTY::Which.exist?('gum')
472
479
  args = ['--placeholder "Enter a note, CTRL-d to save"']
473
480
  args << '--char-limit 0'
@@ -479,7 +486,8 @@ class App
479
486
  end
480
487
  end
481
488
 
482
- note = line_note.nil? || line_note.empty? ? [] : line_note
489
+ note = stdin_note.empty? ? [] : stdin_note
490
+ note.concat(line_note) unless line_note.nil? || line_note.empty?
483
491
 
484
492
  target_proj = if options[:project]
485
493
  options[:project]
@@ -497,10 +505,11 @@ class App
497
505
  elsif options[:todo]
498
506
  todo = []
499
507
  options[:todo].split(/ *, */).each do |a|
500
- m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
508
+ m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
501
509
  todo.push({
502
510
  token: m['tok'],
503
- required: !m['req'].nil?
511
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
512
+ negate: !m['req'].nil? && m['req'] =~ /[!\-]/
504
513
  })
505
514
  end
506
515
  dirs = NA.match_working_dir(todo)
@@ -568,6 +577,9 @@ class App
568
577
  c.arg_name 'TODO_PATH'
569
578
  c.flag %i[in]
570
579
 
580
+ c.desc 'Include notes in output'
581
+ c.switch %i[notes], negatable: true, default_value: false
582
+
571
583
  c.desc 'Combine search tokens with OR, displaying actions matching ANY of the terms'
572
584
  c.switch %i[o or], negatable: false
573
585
 
@@ -619,10 +631,11 @@ class App
619
631
  if options[:in]
620
632
  todo = []
621
633
  options[:in].split(/ *, */).each do |a|
622
- m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
634
+ m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
623
635
  todo.push({
624
636
  token: m['tok'],
625
- required: !m['req'].nil?
637
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
638
+ negate: !m['req'].nil? && m['req'] =~ /[!\-]/
626
639
  })
627
640
  end
628
641
  end
@@ -641,7 +654,7 @@ class App
641
654
  [tokens]
642
655
  end
643
656
 
644
- NA.output_actions(actions, depth, files: files, regexes: regexes)
657
+ NA.output_actions(actions, depth, files: files, regexes: regexes, notes: options[:notes])
645
658
  end
646
659
  end
647
660
 
@@ -669,6 +682,9 @@ class App
669
682
  c.arg_name 'TODO_PATH'
670
683
  c.flag %i[in]
671
684
 
685
+ c.desc 'Include notes in output'
686
+ c.switch %i[notes], negatable: true, default_value: false
687
+
672
688
  c.desc 'Combine tags with OR, displaying actions matching ANY of the tags'
673
689
  c.switch %i[o or], negatable: false
674
690
 
@@ -721,10 +737,11 @@ class App
721
737
  if options[:in]
722
738
  todo = []
723
739
  options[:in].split(/ *, */).each do |a|
724
- m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
740
+ m = a.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
725
741
  todo.push({
726
742
  token: m['tok'],
727
- required: !m['req'].nil?
743
+ required: all_req || (!m['req'].nil? && m['req'] == '+'),
744
+ negate: !m['req'].nil? && m['req'] =~ /[!\-]/
728
745
  })
729
746
  end
730
747
  end
@@ -737,7 +754,7 @@ class App
737
754
  project: options[:project],
738
755
  require_na: false)
739
756
  regexes = tags.delete_if { |token| token[:negate] }.map { |token| token[:token] }
740
- NA.output_actions(actions, depth, files: files, regexes: regexes)
757
+ NA.output_actions(actions, depth, files: files, regexes: regexes, notes: options[:notes])
741
758
  end
742
759
  end
743
760
 
@@ -751,17 +768,17 @@ class App
751
768
  reader = TTY::Reader.new
752
769
  if args.count.positive?
753
770
  project = args.join(' ')
754
- else
771
+ elsif
755
772
  project = File.expand_path('.').split('/').last
756
- project = reader.read_line(NA::Color.template('{y}Project name {bw}> {x}'), value: project).strip
773
+ project = reader.read_line(NA::Color.template('{y}Project name {bw}> {x}'), value: project).strip if $stdin.isatty
757
774
  end
758
775
 
759
776
  target = "#{project}.#{NA.extension}"
760
777
 
761
778
  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
779
+ res = NA.yn(NA::Color.template("{r}File {bw}#{target}{r} already exists, overwrite it"), default: false)
780
+ Process.exit 1 unless res
781
+
765
782
  end
766
783
 
767
784
  NA.create_todo(target, project)
@@ -942,8 +959,12 @@ class App
942
959
  long_desc 'Run without argument to list saved searches'
943
960
  arg_name 'SEARCH_TITLE', optional: true
944
961
  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"'
962
+ c.example 'na tagged "+maybe,+priority<=3" --save maybelater', description: 'save a search called "maybelater"'
963
+ c.example 'na saved maybelater', description: 'perform the search named "maybelater"'
964
+ c.example 'na saved maybe',
965
+ description: 'perform the search named "maybelater", assuming no other searches match "may"'
966
+ c.example 'na maybe',
967
+ description: 'na run with no command and a single argument automatically performs a matching saved search'
947
968
  c.example 'na saved', description: 'list available searches'
948
969
 
949
970
  c.desc 'Open the saved search file in $EDITOR'
@@ -953,18 +974,14 @@ class App
953
974
  c.switch %i[d delete]
954
975
 
955
976
  c.action do |_global_options, options, args|
956
- if options[:edit]
957
- NA.edit_searches
958
- end
977
+ NA.edit_searches if options[:edit]
959
978
 
960
979
  searches = NA.load_searches
961
980
  if args.empty?
962
981
  NA.notify("{bg}Saved searches stored in {bw}#{NA.database_path(file: 'saved_searches.yml')}")
963
982
  NA.notify(searches.map { |k, v| "{y}#{k}: {w}#{v}" }.join("\n"), exit_code: 0)
964
983
  else
965
- if options[:delete]
966
- NA.delete_search(args)
967
- end
984
+ NA.delete_search(args) if options[:delete]
968
985
 
969
986
  keys = searches.keys.delete_if { |k| k !~ /#{args[0]}/ }
970
987
  NA.notify("{r}Search #{args[0]} not found", exit_code: 1) if keys.empty?
@@ -988,6 +1005,7 @@ class App
988
1005
  global[:cwd_as] =~ /^p/ ? :project : :tag
989
1006
  end
990
1007
  NA.weed_cache_file
1008
+ NA.notify("{dw}{ globals: #{NA.globals}, command_line: #{NA.command_line}, command: #{NA.command}}", debug: true)
991
1009
  true
992
1010
  end
993
1011
 
@@ -998,10 +1016,21 @@ class App
998
1016
  on_error do |exception|
999
1017
  case exception
1000
1018
  when GLI::UnknownCommand
1001
- cmd = ['saved']
1002
- cmd.concat(ARGV.unshift(NA.command_line[0]))
1019
+ if NA.command_line.count == 1
1020
+ cmd = ['saved']
1021
+ cmd.concat(ARGV.unshift(NA.command_line[0]))
1022
+
1023
+ exit run(cmd)
1024
+ elsif NA.globals.include?('-a') || NA.globals.include?('--add')
1025
+ cmd = ['add']
1026
+ cmd.concat(NA.command_line)
1027
+ NA.globals.delete('-a')
1028
+ NA.globals.delete('--add')
1029
+ cmd.unshift(*NA.globals)
1003
1030
 
1004
- exit run(cmd)
1031
+ exit run(cmd)
1032
+ end
1033
+ true
1005
1034
  when SystemExit
1006
1035
  false
1007
1036
  else
@@ -1010,9 +1039,20 @@ class App
1010
1039
  end
1011
1040
  end
1012
1041
 
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]
1042
+ NA.stdin = $stdin.read.strip if $stdin.stat.size.positive? || $stdin.fcntl(Fcntl::F_GETFL, 0).zero?
1043
+ NA.stdin = nil unless NA.stdin && NA.stdin.length.positive?
1044
+
1045
+ NA.globals = []
1046
+ NA.command_line = []
1047
+ in_globals = true
1048
+ ARGV.each do |arg|
1049
+ if arg =~ /^-/ && in_globals
1050
+ NA.globals.push(arg)
1051
+ else
1052
+ NA.command_line.push(arg)
1053
+ in_globals = false
1054
+ end
1055
+ end
1056
+ NA.command = NA.command_line[0]
1017
1057
 
1018
1058
  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, :command, :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
@@ -116,31 +116,13 @@ module NA
116
116
  ## @param files [Array] The files
117
117
  ## @param multiple [Boolean] allow multiple selections
118
118
  ##
119
+ ## @return [String, Array] array if multiple
119
120
  def select_file(files, multiple: false)
120
- if TTY::Which.exist?('fzf')
121
- res = choose_from(files, prompt: 'Use which file?', multiple: multiple)
122
- unless res
123
- notify('{r}No file selected, cancelled', exit_code: 1)
124
- end
121
+ res = choose_from(files, prompt: multiple ? 'Select files' : 'Select a file', multiple: multiple)
125
122
 
126
- multiple ? res.split("\n") : res.strip
127
- elsif TTY::Which.exist?('gum')
128
- args = [
129
- '--cursor.foreground="151"',
130
- '--item.foreground=""'
131
- ]
132
- args.push('--no-limit') if multiple
133
- res = `echo #{Shellwords.escape(files.join("\n"))}|#{TTY::Which.which('gum')} choose #{args.join(' ')}`
134
- multiple ? res.split("\n") : res.strip
135
- else
136
- reader = TTY::Reader.new
137
- puts
138
- files.each.with_index do |f, i|
139
- puts NA::Color.template(format("{bw}%<idx> 2d{xw}) {y}%<file>s{x}\n", idx: i + 1, file: f))
140
- end
141
- res = reader.read_line(NA::Color.template('{bw}Use which file? {x}')).strip.to_i
142
- files[res - 1]
143
- end
123
+ notify('{r}No file selected, cancelled', exit_code: 1) unless res && res.length.positive?
124
+
125
+ res
144
126
  end
145
127
 
146
128
  def shift_index_after(projects, idx, length = 1)
@@ -166,33 +148,12 @@ module NA
166
148
  return [projects, actions] if actions.count == 1 || all
167
149
 
168
150
  options = actions.map { |action| "#{action.line} % #{action.parent.join('/')} : #{action.action}" }
169
- res = if TTY::Which.exist?('fzf')
170
- choose_from(options, prompt: 'Make a selection: ', multiple: true, sorted: true)
171
- elsif TTY::Which.exist?('gum')
172
- args = [
173
- '--cursor.foreground="151"',
174
- '--item.foreground=""',
175
- '--no-limit'
176
- ]
177
- `echo #{Shellwords.escape(options.join("\n"))}|#{TTY::Which.which('gum')} choose #{args.join(' ')}`.strip
178
- else
179
- reader = TTY::Reader.new
180
- puts
181
- options.each.with_index do |f, i|
182
- puts NA::Color.template(format("{bw}%<idx> 2d{xw}) {y}%<action>s{x}\n", idx: i + 1, action: f))
183
- end
184
- result = reader.read_line(NA::Color.template('{bw}Use which file? {x}')).strip
185
- if result && result.to_i.positive?
186
- options[result.to_i - 1]
187
- else
188
- nil
189
- end
190
- end
151
+ res = choose_from(options, prompt: 'Make a selection: ', multiple: true, sorted: true)
191
152
 
192
153
  NA.notify('{r}Cancelled', exit_code: 1) unless res && res.length.positive?
193
154
 
194
155
  selected = []
195
- res.split(/\n/).each do |result|
156
+ res.each do |result|
196
157
  idx = result.match(/^(\d+)(?= % )/)[1]
197
158
  action = actions.select { |a| a.line == idx.to_i }.first
198
159
  selected.push(action)
@@ -327,6 +288,7 @@ module NA
327
288
  projects.select { |proj| proj.project =~ /^#{action.parent.join(':')}$/ }.first
328
289
  end
329
290
 
291
+
330
292
  indent = "\t" * target_proj.indent
331
293
  note = note.split("\n") unless note.is_a?(Array)
332
294
  note = if note.empty?
@@ -439,7 +401,7 @@ module NA
439
401
  ## @param files [Array] The files actions originally came from
440
402
  ## @param regexes [Array] The regexes used to gather actions
441
403
  ##
442
- def output_actions(actions, depth, files: nil, regexes: [])
404
+ def output_actions(actions, depth, files: nil, regexes: [], notes: false)
443
405
  return if files.nil?
444
406
 
445
407
  template = if files.count.positive?
@@ -457,10 +419,11 @@ module NA
457
419
  else
458
420
  '%parent%action'
459
421
  end
422
+ template += '%note' if notes
460
423
 
461
424
  files.map { |f| notify("{dw}#{f}", debug: true) } if files
462
425
 
463
- puts(actions.map { |action| action.pretty(template: { output: template }, regexes: regexes) })
426
+ puts(actions.map { |action| action.pretty(template: { output: template }, regexes: regexes, notes: notes) })
464
427
  end
465
428
 
466
429
  ##
@@ -536,7 +499,7 @@ module NA
536
499
  parent = []
537
500
  in_action = false
538
501
  content.split("\n").each.with_index do |line, idx|
539
- if line =~ /^([ \t]*)([^\-@()]+?): *(@\S+ *)*$/
502
+ if line =~ /^([ \t]*)([^\-][^@()]+?): *(@\S+ *)*$/
540
503
  in_action = false
541
504
  proj = Regexp.last_match(2)
542
505
  indent = line.indent_level
@@ -783,6 +746,12 @@ module NA
783
746
  required = search.filter { |s| s[:required] }.map { |t| t[:token] }
784
747
  negated = search.filter { |s| s[:negate] }.map { |t| t[:token] }
785
748
 
749
+ optional.push('*') if required.count.zero? && negated.count.positive?
750
+ if required == negated
751
+ required = ['*']
752
+ optional = ['*']
753
+ end
754
+
786
755
  NA.notify("{dw}Optional directory regex: {x}#{optional.map(&:dir_to_rx)}", debug: true)
787
756
  NA.notify("{dw}Required directory regex: {x}#{required.map(&:dir_to_rx)}", debug: true)
788
757
  NA.notify("{dw}Negated directory regex: {x}#{negated.map { |t| t.dir_to_rx(distance: 1, require_last: false) }}", debug: true)
@@ -815,21 +784,41 @@ module NA
815
784
  ## @param sorted [Boolean] If true, sort selections alphanumerically
816
785
  ## @param fzf_args [Array] Additional fzf arguments
817
786
  ##
787
+ ## @return [String, Array] array if multiple is true
818
788
  def choose_from(options, prompt: 'Make a selection: ', multiple: false, sorted: true, fzf_args: [])
819
789
  return nil unless $stdout.isatty
820
790
 
821
- default_args = [%(--prompt="#{prompt}"), "--height=#{options.count + 2}", '--info=inline']
822
- default_args << '--multi' if multiple
823
- default_args << '--bind ctrl-a:select-all'
824
- header = "esc: cancel,#{multiple ? ' tab: multi-select, ctrl-a: select all,' : ''} return: confirm"
825
- default_args << %(--header="#{header}")
826
- default_args.concat(fzf_args)
827
791
  options.sort! if sorted
828
792
 
829
- res = `echo #{Shellwords.escape(options.join("\n"))}|#{TTY::Which.which('fzf')} #{default_args.join(' ')}`
793
+ res = if TTY::Which.exist?('fzf')
794
+ default_args = [%(--prompt="#{prompt}"), "--height=#{options.count + 2}", '--info=inline']
795
+ default_args << '--multi' if multiple
796
+ default_args << '--bind ctrl-a:select-all' if multiple
797
+ header = "esc: cancel,#{multiple ? ' tab: multi-select, ctrl-a: select all,' : ''} return: confirm"
798
+ default_args << %(--header="#{header}")
799
+ default_args.concat(fzf_args)
800
+ `echo #{Shellwords.escape(options.join("\n"))}|#{TTY::Which.which('fzf')} #{default_args.join(' ')}`.strip
801
+ elsif TTY::Which.exist?('gum')
802
+ args = [
803
+ '--cursor.foreground="151"',
804
+ '--item.foreground=""'
805
+ ]
806
+ args.push '--no-limit' if multiple
807
+ puts NS::Color.template("{bw}#{prompt}{x}")
808
+ `echo #{Shellwords.escape(options.join("\n"))}|#{TTY::Which.which('gum')} choose #{args.join(' ')}`.strip
809
+ else
810
+ reader = TTY::Reader.new
811
+ puts
812
+ options.each.with_index do |f, i|
813
+ puts NA::Color.template(format("{bw}%<idx> 2d{xw}) {y}%<action>s{x}\n", idx: i + 1, action: f))
814
+ end
815
+ result = reader.read_line(NA::Color.template("{bw}#{prompt}{x}")).strip
816
+ result.to_i&.positive? ? options[result.to_i - 1] : nil
817
+ end
818
+
830
819
  return false if res.strip.size.zero?
831
820
 
832
- res
821
+ multiple ? res.split(/\n/) : res
833
822
  end
834
823
 
835
824
  def parse_search(tag, negate)
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.7'
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.6<!--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
 
@@ -30,9 +30,13 @@ If you don't have Ruby/RubyGems, you can install them pretty easily with Homebre
30
30
 
31
31
  <!--JEKYLL> You can find the na source code (MIT license) on [GitHub][].-->
32
32
 
33
+ ### Optional Dependencies
34
+
35
+ If you have [gum][] installed, na will use it for command line input when adding tasks and notes. If you have [fzf][] installed, it will be used for menus, falling back to gum if available.
36
+
33
37
  ### Features
34
38
 
35
- You can list next actions in files in the current directory by typing `na`. By default, `na` looks for `*.taskpaper` files and extracts items tagged `@na` and not `@done`. All of these can be changed in the configuration.
39
+ You can list next actions in files in the current directory by typing `na`. By default, `na` looks for `*.taskpaper` files and extracts items tagged `@na` and not `@done`. This can be modified to work with a single global file, and all of these options can be changed in the configuration.
36
40
 
37
41
  #### Easy matching
38
42
 
@@ -52,6 +56,16 @@ If found, it will try to locate an `Inbox:` project, or create one if it doesn't
52
56
 
53
57
  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
58
 
59
+ ### Terminology
60
+
61
+ **Todo**: Refers to a todo file, usually a TaskPaper document
62
+
63
+ **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.
64
+
65
+ **Action**: Refers to an individual task, specified by a line starting with a hyphen (`-`)
66
+
67
+ **Note**: Refers to lines appearing between action lines that start without hyphens. The note is attached to the preceding action regardless of indentation.
68
+
55
69
  ### Usage
56
70
 
57
71
  ```
@@ -66,6 +80,12 @@ Example: `na add This feature @idea I have`
66
80
 
67
81
  If you run the `add` command with no arguments, you'll be asked for input on the command line.
68
82
 
83
+ ###### Adding notes
84
+
85
+ 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.
86
+
87
+ Notes are not displayed by the `next/tagged/find` commands unless `--notes` is specified.
88
+
69
89
  ```
70
90
  @cli(bundle exec bin/na help add)
71
91
  ```
@@ -100,6 +120,8 @@ Examples:
100
120
  - `na next -d 3` (list all next actions in the current directory and look for additional files 3 levels deep from there)
101
121
  - `na next marked2` (show next actions from another directory you've previously used na on)
102
122
 
123
+ 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`.
124
+
103
125
  ```
104
126
  @cli(bundle exec bin/na help next)
105
127
  ```
@@ -120,6 +142,9 @@ Search names can be partially matched when calling them, so if you have a search
120
142
 
121
143
  Run `na saved` without an argument to list your saved searches.
122
144
 
145
+ > 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`.
146
+ <!--JEKYLL{:.tip}-->
147
+
123
148
  ```
124
149
  @cli(bundle exec bin/na help saved)
125
150
  ```
@@ -158,11 +183,27 @@ You can specify a particular todo file using `--file PATH` or any todo from hist
158
183
 
159
184
  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
185
 
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.
186
+ 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.
187
+
188
+ ###### Marking a task as complete
189
+
190
+ 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`.
191
+
192
+ 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).
193
+
194
+ If you want to permanently delete an action, use `--delete` to remove it entirely.
195
+
196
+ ###### Moving between projects
162
197
 
163
198
  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
199
 
165
- See the help output for a list of available actions.
200
+ ###### Adding notes
201
+
202
+ 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.
203
+
204
+ Notes are not displayed by the `next/tagged/find` commands unless `--notes` is specified.
205
+
206
+ See the help output for a list of all available actions.
166
207
 
167
208
  ```
168
209
  @cli(bundle exec bin/na help update)
@@ -172,6 +213,9 @@ See the help output for a list of available actions.
172
213
 
173
214
  Global options such as todo extension and default next action tag can be stored permanently by using the `na initconfig` command. Run na with the global options you'd like to set, and add `initconfig` at the end of the command. A file will be written to `~/.na.rc`. You can edit this manually, or just update it using the `initconfig --force` command to overwrite it with new settings.
174
215
 
216
+ > You can see all available global options by running `na help`.
217
+ <!--JEKYLL{:.tip}-->
218
+
175
219
  Example: `na --ext md --na_tag next initconfig --force`
176
220
 
177
221
  When this command is run, it doesn't include options for subcommands, but inserts placeholders for them. If you want to permanently set an option for a subcommand, you'll need to edit `~/.na.rc`. For example, if you wanted the `next` command to always recurse 2 levels deep, you could edit it to look like this:
@@ -202,12 +246,14 @@ When using a global file, you can additionally include `--cwd_as TYPE` to determ
202
246
 
203
247
  #### Add tasks at the end of a project
204
248
 
205
- By default, tasks are added at the top of the target project (Inbox, etc.). If you prefer new tasks to go at the bottom by default, include `--add_at end` as a global option when running `initconfig`.
249
+ By default, tasks are added at the top of the target project (Inbox, etc.). If you prefer new tasks to go at the end of the project by default, include `--add_at end` as a global option when running `initconfig`.
206
250
 
207
251
  ### Prompt Hooks
208
252
 
209
253
  You can add a prompt command to your shell to have na automatically list your next actions when you `cd` into a directory. To install a prompt command for your current shell, just run `na prompt install`. It works with Zsh, Bash, and Fish. If you'd rather make the changes to your startup file yourself, run `na prompt show` to get the hook and instructions printed out for copying.
210
254
 
255
+ If you're using a single global file, you'll need `--cwd_as` to be `tag` or `project` for a prompt command to work. na will detect which system you're using and provide a prompt command that lists actions based on the current directory using either project or tag.
256
+
211
257
  > You can also get output for shells other than the one you're currently using by adding "bash", "zsh", or "fish" to the show or install command.
212
258
  <!--JEKYLL{:.tip}-->
213
259
 
@@ -216,9 +262,6 @@ You can add a prompt command to your shell to have na automatically list your ne
216
262
 
217
263
  After installing a hook, you'll need to close your terminal and start a new session to initialize the new commands.
218
264
 
219
- ### Misc
220
-
221
- If you have [gum][] installed, na will use it for command line input when adding tasks and notes. If you have [fzf][] installed, it will be used for menus, falling back to gum if available.
222
265
 
223
266
  [fzf]: https://github.com/junegunn/fzf
224
267
  [gum]: https://github.com/charmbracelet/gum
metadata CHANGED
@@ -1,14 +1,14 @@
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.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-10-26 00:00:00.000000000 Z
11
+ date: 2022-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake