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 +4 -4
- data/CHANGELOG.md +36 -0
- data/Gemfile.lock +1 -1
- data/README.md +65 -15
- data/bin/na +107 -67
- data/lib/na/action.rb +20 -6
- data/lib/na/next_action.rb +48 -59
- data/lib/na/prompt.rb +43 -6
- data/lib/na/version.rb +1 -1
- data/src/README.md +52 -9
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 77270e07e7c7d4332a5fb8ec87f4e9ad5496bd220d188e75adfb71026aff1180
|
4
|
+
data.tar.gz: a3521feaefc9573bd78344b6a16ae57719284bde611e8f9f6e664238fcc1b5c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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.
|
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
|
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`.
|
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.
|
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
|
337
|
+
na tagged "+maybe,+priority<=3" --save maybelater
|
338
|
+
|
339
|
+
na saved maybelater
|
311
340
|
|
312
|
-
na saved
|
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 "
|
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
|
-
|
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
|
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(
|
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
|
-
|
209
|
-
res
|
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
|
-
|
222
|
-
res
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
263
|
-
res
|
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
|
-
|
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
|
-
|
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
|
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 =
|
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"',
|
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',
|
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
|
-
|
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 =
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
763
|
-
|
764
|
-
|
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
|
946
|
-
c.example 'na saved
|
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
|
-
|
1002
|
-
|
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
|
-
|
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.
|
1014
|
-
NA.
|
1015
|
-
|
1016
|
-
|
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
|
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
|
-
|
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))
|
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: [])
|
data/lib/na/next_action.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
127
|
-
|
128
|
-
|
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 =
|
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.
|
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]*)([
|
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 =
|
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() {
|
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}" &&
|
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)([ ]|$)) ]] &&
|
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
|
-
|
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
|
-
|
58
|
-
|
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
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.
|
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
|
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`.
|
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 "
|
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
|
-
|
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
|
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.
|
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-
|
11
|
+
date: 2022-10-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|