na 1.2.1 → 1.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/Gemfile.lock +1 -1
- data/README.md +29 -6
- data/bin/na +128 -64
- data/lib/na/action.rb +3 -1
- data/lib/na/next_action.rb +131 -55
- data/lib/na/project.rb +1 -1
- data/lib/na/version.rb +1 -1
- data/src/README.md +15 -1
- 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: 0cc9bb0b48d32bab1e3ece2c1a9ab357dfde238e88a02ceeb5c5a172d4a6e747
|
4
|
+
data.tar.gz: 53300e826f4d4c56b30f846aa94039cb054d99c3d930709de419261409c4d9b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b16ed47431b61a853e03b44efd04c44909814e2bfbde897833f43f640a75a0932c2db14c9cbdfc25834d20bb5d4147ba10ebe84b137d9f6bcdda4266bb80d64d
|
7
|
+
data.tar.gz: 6f97489661677a3cd9fabfb01b1f99c39ff7b1e034d9936fdf1fa578c82fdbceb9c208c050d5e3b7a0bec956a3ea35534ba89c8764de85b41a2c9eceaf8bd207
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,33 @@
|
|
1
|
+
### 1.2.3
|
2
|
+
|
3
|
+
2022-10-25 17:05
|
4
|
+
|
5
|
+
#### CHANGED
|
6
|
+
|
7
|
+
- Add a preceding dot to backup files created when updating to make backups hidden from notetaking apps and the like.
|
8
|
+
|
9
|
+
### 1.2.2
|
10
|
+
|
11
|
+
2022-10-25 14:30
|
12
|
+
|
13
|
+
#### CHANGED
|
14
|
+
|
15
|
+
- `na update --done` now means "include @done actions in search"
|
16
|
+
- `na next --in QUERY` now searches for a known todo file (formerly required arguments, now both work)
|
17
|
+
|
18
|
+
#### NEW
|
19
|
+
|
20
|
+
- `--at [start|end]` switch for `add` and `update` to determine
|
21
|
+
- Global `--file PATH` flag to specify a single global todo file
|
22
|
+
- `--add_at [start|end]` global flag that can be added to config to make permanent
|
23
|
+
- `--finish` switch for `na add` to immediately mark an action as @done
|
24
|
+
- `--cwd_as [project|tag]` global flag when using a global `--file` to determine if the current working directory (last element) is added as an @tag or parent project
|
25
|
+
|
26
|
+
#### IMPROVED
|
27
|
+
|
28
|
+
- Refactor `na add` to use improved task update code
|
29
|
+
- Confirm target file before requesting task when running `na
|
30
|
+
|
1
31
|
### 1.2.1
|
2
32
|
|
3
33
|
2022-10-22 10:18
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -9,7 +9,7 @@
|
|
9
9
|
_If you're one of the rare people like me who find this useful, feel free to
|
10
10
|
[buy me some coffee][donate]._
|
11
11
|
|
12
|
-
The current version of `na` is 1.2.
|
12
|
+
The current version of `na` is 1.2.3
|
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.
|
@@ -49,6 +49,10 @@ You can also quickly add todo items from the command line with the `add` subcomm
|
|
49
49
|
|
50
50
|
If found, it will try to locate an `Inbox:` project, or create one if it doesn't exist. Any arguments after `add` will be combined to create a new task in TaskPaper format. They will automatically be assigned as next actions (tagged `@na`) and will show up when `na` lists the tasks for the project.
|
51
51
|
|
52
|
+
#### Updating todos
|
53
|
+
|
54
|
+
You can mark todos as complete, delete them, add and remove tags, change priority, and even move them between projects with the `na update` command.
|
55
|
+
|
52
56
|
### Usage
|
53
57
|
|
54
58
|
```
|
@@ -59,13 +63,16 @@ SYNOPSIS
|
|
59
63
|
na [global options] command [command options] [arguments...]
|
60
64
|
|
61
65
|
VERSION
|
62
|
-
1.2.
|
66
|
+
1.2.3
|
63
67
|
|
64
68
|
GLOBAL OPTIONS
|
65
69
|
-a, --[no-]add - Add a next action (deprecated, for backwards compatibility)
|
66
|
-
|
70
|
+
--add_at=POSITION - Add all new/moved entries at [s]tart or [e]nd of target project (default: end)
|
71
|
+
--cwd_as=TYPE - Use current working directory as [p]roject, [t]ag, or [n]one (default: none)
|
72
|
+
-d, --depth=DEPTH - Recurse to depth (default: 3)
|
67
73
|
--[no-]debug - Display verbose output
|
68
74
|
--ext=EXT - File extension to consider a todo file (default: taskpaper)
|
75
|
+
-f, --file=PATH - Use a single file as global todo, use --initconfig to make permanent (default: none)
|
69
76
|
--help - Show this message
|
70
77
|
-n, --note - Prompt for additional notes (deprecated, for backwards compatibility)
|
71
78
|
-p, --priority=PRIORITY - Set a priority 0-5 (deprecated, for backwards compatibility) (default: none)
|
@@ -110,8 +117,10 @@ DESCRIPTION
|
|
110
117
|
Provides an easy way to store todos while you work. Add quick reminders and (if you set up Prompt Hooks) they'll automatically display next time you enter the directory. If multiple todo files are found in the current directory, a menu will allow you to pick to which file the action gets added.
|
111
118
|
|
112
119
|
COMMAND OPTIONS
|
120
|
+
--at=POSITION - Add task at [s]tart or [e]nd of target project (default: none)
|
113
121
|
-d, --depth=DEPTH - Search for files X directories deep (default: 1)
|
114
122
|
-f, --file=PATH - Specify the file to which the task should be added (default: none)
|
123
|
+
--finish, --done - Mark task as @done with date
|
115
124
|
--in, --todo=TODO_FILE - Add to a known todo file, partial matches allowed (default: none)
|
116
125
|
-n, --note - Prompt for additional notes
|
117
126
|
-p, --priority=PRIO - Add a priority level 1-5 (default: 0)
|
@@ -235,8 +244,9 @@ DESCRIPTION
|
|
235
244
|
Next actions are actions which contain the next action tag (default @na), do not contain @done, and are not in the Archive project. Arguments will target a todo file from history, whether it's in the current directory or not. Todo file queries can include path components separated by / or :, and may use wildcards (`*` to match any text, `?` to match a single character). Multiple queries allowed (separate arguments or separated by comma).
|
236
245
|
|
237
246
|
COMMAND OPTIONS
|
238
|
-
-d, --depth=DEPTH - Recurse to depth (default:
|
247
|
+
-d, --depth=DEPTH - Recurse to depth (default: none)
|
239
248
|
--[no-]done - Include @done actions
|
249
|
+
--in, --todo=TODO_FILE - Display matches from a known todo file (may be used more than once, default: none)
|
240
250
|
--proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
|
241
251
|
-t, --tag=TAG - Alternate tag to search for (default: none)
|
242
252
|
|
@@ -326,8 +336,9 @@ DESCRIPTION
|
|
326
336
|
Next actions are actions which contain the next action tag (default @na), do not contain @done, and are not in the Archive project. Arguments will target a todo file from history, whether it's in the current directory or not. Todo file queries can include path components separated by / or :, and may use wildcards (`*` to match any text, `?` to match a single character). Multiple queries allowed (separate arguments or separated by comma).
|
327
337
|
|
328
338
|
COMMAND OPTIONS
|
329
|
-
-d, --depth=DEPTH - Recurse to depth (default:
|
339
|
+
-d, --depth=DEPTH - Recurse to depth (default: none)
|
330
340
|
--[no-]done - Include @done actions
|
341
|
+
--in, --todo=TODO_FILE - Display matches from a known todo file (may be used more than once, default: none)
|
331
342
|
--proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
|
332
343
|
-t, --tag=TAG - Alternate tag to search for (default: none)
|
333
344
|
|
@@ -391,10 +402,12 @@ DESCRIPTION
|
|
391
402
|
COMMAND OPTIONS
|
392
403
|
-a, --archive - Add a @done tag to action and move to Archive
|
393
404
|
--all - Act on all matches immediately (no menu)
|
405
|
+
--at=POSITION - When moving task, add at [s]tart or [e]nd of target project (default: none)
|
394
406
|
-d, --depth=DEPTH - Search for files X directories deep (default: 1)
|
395
407
|
--delete - Delete an action
|
408
|
+
--[no-]done - Include @done actions
|
396
409
|
-e, --regex - Interpret search pattern as regular expression
|
397
|
-
-f, --finish
|
410
|
+
-f, --finish - Add a @done tag to action
|
398
411
|
--file=PATH - Specify the file to search for the task (default: none)
|
399
412
|
--in, --todo=TODO_FILE - Use a known todo file, partial matches allowed (default: none)
|
400
413
|
-n, --note - Prompt for additional notes. Input will be appended to any existing note.
|
@@ -444,6 +457,16 @@ Note that I created a new YAML dictionary inside of the `:next:` command, and ad
|
|
444
457
|
> **WARNING** Don't touch most of the settings at the top of the auto-generated file. Setting any of them to true will alter the way na interprets the commands you're running. Most of those options are there for backwards compatibility with the bash version of this tool and will eventually be removed.
|
445
458
|
|
446
459
|
|
460
|
+
#### Working with a single global file
|
461
|
+
|
462
|
+
na is designed to work with one or more TaskPaper files in each project directory, but if you prefer to use a single global TaskPaper file, you can add `--file PATH` as a global option and specify a single file. This will bypass the detection of any files in the current directory. Make it permanent by including the `--file` flag when running `initconfig`.
|
463
|
+
|
464
|
+
When using a global file, you can additionally include `--cwd_as TYPE` to determine whether the current working directory is used as a tag or a project (default is neither). If you add `--cwd_as tag` to the global options (before the command), the last element of the current working directory will be appended as an @tag (e.g. if you're in ~/Code/project/doing, the action would be tagged @doing). If you use `--cwd_as project` the action will be put into a project with the same name as the current directory (e.g. `Doing:` from the previous example).
|
465
|
+
|
466
|
+
#### Add tasks at the end of a project
|
467
|
+
|
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`.
|
469
|
+
|
447
470
|
### Prompt Hooks
|
448
471
|
|
449
472
|
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.
|
data/bin/na
CHANGED
@@ -39,6 +39,18 @@ class App
|
|
39
39
|
arg_name 'PRIORITY'
|
40
40
|
flag %i[p priority]
|
41
41
|
|
42
|
+
desc 'Use a single file as global todo, use --initconfig to make permanent'
|
43
|
+
arg_name 'PATH'
|
44
|
+
flag %i[f file]
|
45
|
+
|
46
|
+
desc 'Use current working directory as [p]roject, [t]ag, or [n]one'
|
47
|
+
arg_name 'TYPE'
|
48
|
+
flag %i[cwd_as], must_match: /^[ptn].*?$/i, default_value: 'none'
|
49
|
+
|
50
|
+
desc 'Add all new/moved entries at [s]tart or [e]nd of target project'
|
51
|
+
arg_name 'POSITION'
|
52
|
+
flag %i[add_at], default_value: 'start'
|
53
|
+
|
42
54
|
desc 'Prompt for additional notes (deprecated, for backwards compatibility)'
|
43
55
|
switch %i[n note], negatable: false
|
44
56
|
|
@@ -70,6 +82,10 @@ class App
|
|
70
82
|
c.arg_name 'DEPTH'
|
71
83
|
c.flag %i[d depth], type: :integer, must_match: /^[1-9]$/
|
72
84
|
|
85
|
+
c.desc 'Display matches from a known todo file'
|
86
|
+
c.arg_name 'TODO_FILE'
|
87
|
+
c.flag %i[in todo], multiple: true
|
88
|
+
|
73
89
|
c.desc 'Alternate tag to search for'
|
74
90
|
c.arg_name 'TAG'
|
75
91
|
c.flag %i[t tag]
|
@@ -86,7 +102,7 @@ class App
|
|
86
102
|
cmd = ['add']
|
87
103
|
cmd.push('--note') if global_options[:note]
|
88
104
|
cmd.concat(['--priority', global_options[:priority]]) if global_options[:priority]
|
89
|
-
cmd.push(ARGV.unshift(
|
105
|
+
cmd.push(ARGV.unshift(NA.command_line[0])) if NA.command_line.count > 1
|
90
106
|
|
91
107
|
exit run(cmd)
|
92
108
|
end
|
@@ -97,6 +113,7 @@ class App
|
|
97
113
|
options[:depth].nil? ? global_options[:depth].to_i : options[:depth].to_i
|
98
114
|
end
|
99
115
|
|
116
|
+
args.concat(options[:in])
|
100
117
|
if args.count.positive?
|
101
118
|
all_req = false
|
102
119
|
|
@@ -155,6 +172,10 @@ class App
|
|
155
172
|
c.default_value 'Inbox'
|
156
173
|
c.flag %i[to project proj]
|
157
174
|
|
175
|
+
c.desc 'Add task at [s]tart or [e]nd of target project'
|
176
|
+
c.arg_name 'POSITION'
|
177
|
+
c.flag %i[at], must_match: /^[sbea].*?$/i
|
178
|
+
|
158
179
|
c.desc 'Add to a known todo file, partial matches allowed'
|
159
180
|
c.arg_name 'TODO_FILE'
|
160
181
|
c.flag %i[in todo]
|
@@ -170,64 +191,31 @@ class App
|
|
170
191
|
c.arg_name 'PATH'
|
171
192
|
c.flag %i[f file]
|
172
193
|
|
194
|
+
c.desc 'Mark task as @done with date'
|
195
|
+
c.switch %i[finish done], negatable: false
|
196
|
+
|
173
197
|
c.desc 'Search for files X directories deep'
|
174
198
|
c.arg_name 'DEPTH'
|
175
199
|
c.flag %i[d depth], must_match: /^[1-9]$/, type: :integer, default_value: 1
|
176
200
|
|
177
|
-
c.action do |
|
201
|
+
c.action do |global_options, options, args|
|
178
202
|
reader = TTY::Reader.new
|
179
|
-
|
180
|
-
args.join(' ').strip
|
181
|
-
elsif TTY::Which.exist?('gum')
|
182
|
-
`gum input --placeholder "Enter a task" --char-limit=500 --width=#{TTY::Screen.columns}`.strip
|
183
|
-
else
|
184
|
-
puts NA::Color.template('{bm}Enter task:{x}')
|
185
|
-
reader.read_line(NA::Color.template('{by}> {bw}')).strip
|
186
|
-
end
|
187
|
-
|
188
|
-
if action.nil? || action.empty?
|
189
|
-
puts 'Empty input, cancelled'
|
190
|
-
Process.exit 1
|
191
|
-
end
|
192
|
-
|
193
|
-
if options[:priority]&.to_i&.positive?
|
194
|
-
action = "#{action.gsub(/@priority\(\d+\)/, '')} @priority(#{options[:priority]})"
|
195
|
-
end
|
196
|
-
|
197
|
-
note_rx = /^(.+) \((.*?)\)$/
|
198
|
-
split_note = if action =~ note_rx
|
199
|
-
n = Regexp.last_match(2)
|
200
|
-
action.sub!(note_rx, '\1').strip!
|
201
|
-
n
|
202
|
-
end
|
203
|
+
append = options[:at] ? options[:at] =~ /^[ae]/i : global_options[:add_at] =~ /^[ae]/
|
203
204
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
args << '--width $(tput cols)'
|
219
|
-
`gum write #{args.join(' ')}`.strip.split("\n")
|
220
|
-
else
|
221
|
-
puts NA::Color.template('{bm}Enter a note, {bw}CTRL-d{bm} to end editing{bw}')
|
222
|
-
reader.read_multiline
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
note = split_note.nil? ? [] : [split_note]
|
227
|
-
note.concat(line_note) unless line_note.nil?
|
228
|
-
note = nil if note.empty?
|
229
|
-
|
230
|
-
if options[:file]
|
205
|
+
if NA.global_file
|
206
|
+
target = File.expand_path(NA.global_file)
|
207
|
+
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
|
211
|
+
basename = File.basename(target, ".#{NA.extension}")
|
212
|
+
NA.create_todo(target, basename)
|
213
|
+
else
|
214
|
+
puts NA::Color.template('{r}Cancelled{x}')
|
215
|
+
Process.exit 1
|
216
|
+
end
|
217
|
+
end
|
218
|
+
elsif options[:file]
|
231
219
|
target = File.expand_path(options[:file])
|
232
220
|
unless File.exist?(target)
|
233
221
|
print NA::Color.template('{by}Specified file not found, create it? {w}(y/{g}N{w}){x} ')
|
@@ -287,7 +275,58 @@ class App
|
|
287
275
|
end
|
288
276
|
end
|
289
277
|
|
290
|
-
|
278
|
+
action = if args.count.positive?
|
279
|
+
args.join(' ').strip
|
280
|
+
elsif TTY::Which.exist?('gum')
|
281
|
+
`gum input --placeholder "Enter a task" --char-limit=500 --width=#{TTY::Screen.columns}`.strip
|
282
|
+
else
|
283
|
+
puts NA::Color.template('{bm}Enter task:{x}')
|
284
|
+
reader.read_line(NA::Color.template('{by}> {bw}')).strip
|
285
|
+
end
|
286
|
+
|
287
|
+
if action.nil? || action.empty?
|
288
|
+
puts 'Empty input, cancelled'
|
289
|
+
Process.exit 1
|
290
|
+
end
|
291
|
+
|
292
|
+
if options[:priority]&.to_i&.positive?
|
293
|
+
action = "#{action.gsub(/@priority\(\d+\)/, '')} @priority(#{options[:priority]})"
|
294
|
+
end
|
295
|
+
|
296
|
+
note_rx = /^(.+) \((.*?)\)$/
|
297
|
+
split_note = if action =~ note_rx
|
298
|
+
n = Regexp.last_match(2)
|
299
|
+
action.sub!(note_rx, '\1').strip!
|
300
|
+
n
|
301
|
+
end
|
302
|
+
|
303
|
+
na_tag = NA.na_tag
|
304
|
+
if options[:x]
|
305
|
+
na_tag = ''
|
306
|
+
else
|
307
|
+
na_tag = options[:tag] unless options[:tag].nil?
|
308
|
+
na_tag = " @#{na_tag}"
|
309
|
+
end
|
310
|
+
|
311
|
+
action = "#{action.gsub(/#{na_tag}\b/, '')}#{na_tag}"
|
312
|
+
|
313
|
+
line_note = if options[:note]
|
314
|
+
if TTY::Which.exist?('gum')
|
315
|
+
args = ['--placeholder "Enter a note, CTRL-d to save"']
|
316
|
+
args << '--char-limit 0'
|
317
|
+
args << '--width $(tput cols)'
|
318
|
+
`gum write #{args.join(' ')}`.strip.split("\n")
|
319
|
+
else
|
320
|
+
puts NA::Color.template('{bm}Enter a note, {bw}CTRL-d{bm} to end editing{bw}')
|
321
|
+
reader.read_multiline
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
note = split_note.nil? ? [] : [split_note]
|
326
|
+
note.concat(line_note) unless line_note.nil?
|
327
|
+
note = [] if note.empty?
|
328
|
+
|
329
|
+
NA.add_action(target, options[:project], action, note, finish: options[:finish], append: append)
|
291
330
|
end
|
292
331
|
end
|
293
332
|
|
@@ -313,6 +352,10 @@ class App
|
|
313
352
|
c.arg_name 'PRIO'
|
314
353
|
c.flag %i[p priority], must_match: /[1-5]/, type: :integer, default_value: 0
|
315
354
|
|
355
|
+
c.desc 'When moving task, add at [s]tart or [e]nd of target project'
|
356
|
+
c.arg_name 'POSITION'
|
357
|
+
c.flag %i[at], must_match: /^[sbea].*?$/i
|
358
|
+
|
316
359
|
c.desc 'Move action to specific project'
|
317
360
|
c.arg_name 'PROJECT'
|
318
361
|
c.flag %i[to project proj]
|
@@ -321,6 +364,9 @@ class App
|
|
321
364
|
c.arg_name 'TODO_FILE'
|
322
365
|
c.flag %i[in todo]
|
323
366
|
|
367
|
+
c.desc 'Include @done actions'
|
368
|
+
c.switch %i[done]
|
369
|
+
|
324
370
|
c.desc 'Add a tag to the action, @tag(values) allowed'
|
325
371
|
c.arg_name 'TAG'
|
326
372
|
c.flag %i[t tag], multiple: true
|
@@ -330,7 +376,7 @@ class App
|
|
330
376
|
c.flag %i[r remove], multiple: true
|
331
377
|
|
332
378
|
c.desc 'Add a @done tag to action'
|
333
|
-
c.switch %i[f finish
|
379
|
+
c.switch %i[f finish], negatable: false
|
334
380
|
|
335
381
|
c.desc 'Add a @done tag to action and move to Archive'
|
336
382
|
c.switch %i[a archive], negatable: false
|
@@ -359,8 +405,10 @@ class App
|
|
359
405
|
c.desc 'Match pattern exactly'
|
360
406
|
c.switch %i[x exact], negatable: false
|
361
407
|
|
362
|
-
c.action do |
|
408
|
+
c.action do |global_options, options, args|
|
363
409
|
reader = TTY::Reader.new
|
410
|
+
append = options[:at] ? options[:at] =~ /^[ae]/i : global_options[:add_at] =~ /^[ae]/i
|
411
|
+
|
364
412
|
action = if args.count.positive?
|
365
413
|
args.join(' ').strip
|
366
414
|
elsif TTY::Which.exist?('gum') && options[:tagged].empty?
|
@@ -433,6 +481,14 @@ class App
|
|
433
481
|
|
434
482
|
note = line_note.nil? || line_note.empty? ? [] : line_note
|
435
483
|
|
484
|
+
target_proj = if options[:project]
|
485
|
+
options[:project]
|
486
|
+
elsif NA.cwd_is == :project
|
487
|
+
NA.cwd
|
488
|
+
else
|
489
|
+
nil
|
490
|
+
end
|
491
|
+
|
436
492
|
if options[:file]
|
437
493
|
file = File.expand_path(options[:file])
|
438
494
|
NA.notify('{r}File not found', exit_code: 1) unless File.exist?(file)
|
@@ -478,12 +534,14 @@ class App
|
|
478
534
|
add_tag: add_tags,
|
479
535
|
remove_tag: remove_tags,
|
480
536
|
finish: options[:finish],
|
481
|
-
project:
|
537
|
+
project: target_proj,
|
482
538
|
delete: options[:delete],
|
483
539
|
note: note,
|
484
540
|
overwrite: options[:overwrite],
|
485
541
|
tagged: tags,
|
486
|
-
all: options[:all]
|
542
|
+
all: options[:all],
|
543
|
+
done: options[:done],
|
544
|
+
append: append)
|
487
545
|
end
|
488
546
|
end
|
489
547
|
end
|
@@ -922,21 +980,26 @@ class App
|
|
922
980
|
NA.verbose = global[:debug]
|
923
981
|
NA.extension = global[:ext]
|
924
982
|
NA.na_tag = global[:na_tag]
|
983
|
+
NA.global_file = global[:file]
|
984
|
+
NA.cwd = File.basename(ENV['PWD'])
|
985
|
+
NA.cwd_is = if global[:cwd_as] =~ /^n/
|
986
|
+
:none
|
987
|
+
else
|
988
|
+
global[:cwd_as] =~ /^p/ ? :project : :tag
|
989
|
+
end
|
925
990
|
NA.weed_cache_file
|
926
991
|
true
|
927
992
|
end
|
928
993
|
|
929
994
|
post do |global, command, options, args|
|
930
|
-
#
|
931
|
-
# Use skips_post before a command to skip this
|
932
|
-
# block on that command only
|
995
|
+
# post actions
|
933
996
|
end
|
934
997
|
|
935
998
|
on_error do |exception|
|
936
999
|
case exception
|
937
1000
|
when GLI::UnknownCommand
|
938
1001
|
cmd = ['saved']
|
939
|
-
cmd.concat(ARGV.unshift(
|
1002
|
+
cmd.concat(ARGV.unshift(NA.command_line[0]))
|
940
1003
|
|
941
1004
|
exit run(cmd)
|
942
1005
|
when SystemExit
|
@@ -947,8 +1010,9 @@ class App
|
|
947
1010
|
end
|
948
1011
|
end
|
949
1012
|
|
950
|
-
NA.command_line = ARGV
|
1013
|
+
NA.command_line = ARGV.dup
|
1014
|
+
NA.globals = NA.command_line.filter { |arg| arg =~ /^-/ }
|
1015
|
+
NA.command_line.delete_if { |arg| arg =~ /^-/ }
|
951
1016
|
@command = ARGV[0]
|
952
|
-
$first_arg = ARGV[1]
|
953
1017
|
|
954
1018
|
exit App.run(ARGV)
|
data/lib/na/action.rb
CHANGED
@@ -2,7 +2,9 @@
|
|
2
2
|
|
3
3
|
module NA
|
4
4
|
class Action < Hash
|
5
|
-
attr_reader :file, :project, :parent, :
|
5
|
+
attr_reader :file, :project, :parent, :tags, :line, :note
|
6
|
+
|
7
|
+
attr_accessor :action
|
6
8
|
|
7
9
|
def initialize(file, project, parent, action, idx, note = [])
|
8
10
|
super()
|
data/lib/na/next_action.rb
CHANGED
@@ -3,21 +3,22 @@
|
|
3
3
|
# Next Action methods
|
4
4
|
module NA
|
5
5
|
class << self
|
6
|
-
attr_accessor :verbose, :extension, :na_tag, :command_line
|
6
|
+
attr_accessor :verbose, :extension, :na_tag, :command_line, :globals, :global_file, :cwd_is, :cwd
|
7
7
|
|
8
8
|
##
|
9
9
|
## Output to STDERR
|
10
10
|
##
|
11
11
|
## @param msg [String] The message
|
12
|
-
## @param exit_code [Number] The exit code, no
|
12
|
+
## @param exit_code [Number] The exit code, no
|
13
|
+
## exit if false
|
14
|
+
## @param debug [Boolean] only display message if running :verbose
|
13
15
|
##
|
14
16
|
def notify(msg, exit_code: false, debug: false)
|
15
|
-
return if debug &&
|
17
|
+
return if debug && NA.verbose
|
16
18
|
|
17
19
|
$stderr.puts NA::Color.template("{x}#{msg}{x}")
|
18
|
-
if exit_code
|
19
|
-
|
20
|
-
end
|
20
|
+
Process.exit exit_code if exit_code
|
21
|
+
|
21
22
|
end
|
22
23
|
|
23
24
|
##
|
@@ -99,6 +100,8 @@ module NA
|
|
99
100
|
## @param depth [Number] The depth at which to search
|
100
101
|
##
|
101
102
|
def find_files(depth: 1)
|
103
|
+
return [NA.global_file] if NA.global_file
|
104
|
+
|
102
105
|
files = `find . -name "*.#{NA.extension}" -maxdepth #{depth}`.strip.split("\n")
|
103
106
|
files.each { |f| save_working_dir(File.expand_path(f)) }
|
104
107
|
files
|
@@ -152,10 +155,14 @@ module NA
|
|
152
155
|
projects
|
153
156
|
end
|
154
157
|
|
155
|
-
def find_actions(target, search, tagged = nil, all: false)
|
156
|
-
_, actions, projects = parse_actions(search: search, require_na: false, file_path: target, tag: tagged)
|
158
|
+
def find_actions(target, search, tagged = nil, all: false, done: false)
|
159
|
+
_, actions, projects = parse_actions(search: search, require_na: false, file_path: target, tag: tagged, done: done)
|
160
|
+
|
161
|
+
unless actions.count.positive?
|
162
|
+
NA.notify("{r}No matching actions found in {bw}#{File.basename(target, ".#{NA.extension}")}")
|
163
|
+
return
|
164
|
+
end
|
157
165
|
|
158
|
-
NA.notify('{r}No matching actions found', exit_code: 1) unless actions.count.positive?
|
159
166
|
return [projects, actions] if actions.count == 1 || all
|
160
167
|
|
161
168
|
options = actions.map { |action| "#{action.line} % #{action.parent.join('/')} : #{action.action}" }
|
@@ -248,8 +255,35 @@ module NA
|
|
248
255
|
new_project
|
249
256
|
end
|
250
257
|
|
258
|
+
def process_action(action, priority: 0, finish: false, add_tag: [], remove_tag: [], note: [])
|
259
|
+
string = action.action
|
260
|
+
|
261
|
+
if priority&.positive?
|
262
|
+
string.gsub!(/(?<=\A| )@priority\(\d+\)/, '').strip!
|
263
|
+
string += " @priority(#{priority})"
|
264
|
+
end
|
265
|
+
|
266
|
+
add_tag.each do |tag|
|
267
|
+
string.gsub!(/(?<=\A| )@#{tag.gsub(/([()*?])/, '\\\\1')}(\(.*?\))?/, '')
|
268
|
+
string.strip!
|
269
|
+
string += " @#{tag}"
|
270
|
+
end
|
271
|
+
|
272
|
+
remove_tag.each do |tag|
|
273
|
+
string.gsub!(/(?<=\A| )@#{tag.gsub(/([()*?])/, '\\\\1')}(\(.*?\))?/, '')
|
274
|
+
string.strip!
|
275
|
+
end
|
276
|
+
|
277
|
+
string = "#{string.strip} @done(#{Time.now.strftime('%Y-%m-%d %H:%M')})" if finish && string !~ /(?<=\A| )@done/
|
278
|
+
|
279
|
+
action.action = string
|
280
|
+
|
281
|
+
action
|
282
|
+
end
|
283
|
+
|
251
284
|
def update_action(target,
|
252
285
|
search,
|
286
|
+
add: nil,
|
253
287
|
priority: 0,
|
254
288
|
add_tag: [],
|
255
289
|
remove_tag: [],
|
@@ -259,7 +293,9 @@ module NA
|
|
259
293
|
note: [],
|
260
294
|
overwrite: false,
|
261
295
|
tagged: nil,
|
262
|
-
all: false
|
296
|
+
all: false,
|
297
|
+
done: false,
|
298
|
+
append: false)
|
263
299
|
|
264
300
|
projects = find_projects(target)
|
265
301
|
|
@@ -277,35 +313,12 @@ module NA
|
|
277
313
|
end
|
278
314
|
end
|
279
315
|
|
280
|
-
projects, actions = find_actions(target, search, tagged, all: all)
|
281
|
-
|
282
316
|
contents = target.read_file.split(/\n/)
|
283
317
|
|
284
|
-
|
285
|
-
|
318
|
+
if add.is_a?(Action)
|
319
|
+
action = process_action(add, priority: priority, finish: finish, add_tag: add_tag, remove_tag: remove_tag)
|
286
320
|
|
287
|
-
|
288
|
-
string.gsub!(/@priority\(\d+\)/, '').strip!
|
289
|
-
string += " @priority(#{priority})"
|
290
|
-
end
|
291
|
-
|
292
|
-
add_tag.each do |tag|
|
293
|
-
string.gsub!(/@#{tag.gsub(/([()*?])/, '\\\\1')}(\(.*?\))?/, '')
|
294
|
-
string.strip!
|
295
|
-
string += " @#{tag}"
|
296
|
-
end
|
297
|
-
|
298
|
-
remove_tag.each do |tag|
|
299
|
-
string.gsub!(/@#{tag}(\(.*?\))?/, '')
|
300
|
-
string.strip!
|
301
|
-
end
|
302
|
-
|
303
|
-
string = "#{string.strip} @done(#{Time.now.strftime('%Y-%m-%d %H:%M')})" if finish && string !~ /@done/
|
304
|
-
|
305
|
-
contents.slice!(action.line, action.note.count + 1)
|
306
|
-
next if delete
|
307
|
-
|
308
|
-
projects = shift_index_after(projects, action.line, action.note.count + 1)
|
321
|
+
projects = find_projects(target)
|
309
322
|
|
310
323
|
target_proj = if target_proj
|
311
324
|
projects.select { |proj| proj.project =~ /^#{target_proj.project}$/ }.first
|
@@ -320,13 +333,74 @@ module NA
|
|
320
333
|
else
|
321
334
|
overwrite ? note : action.note.concat(note)
|
322
335
|
end
|
336
|
+
|
323
337
|
note = note.empty? ? '' : "\n#{indent}\t\t#{note.join("\n#{indent}\t\t").strip}"
|
324
|
-
|
338
|
+
|
339
|
+
if append
|
340
|
+
this_idx = 0
|
341
|
+
projects.each_with_index do |proj, idx|
|
342
|
+
if proj.line == target_proj.line
|
343
|
+
this_idx = idx
|
344
|
+
break
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
target_line = if this_idx == projects.length - 1
|
349
|
+
contents.count
|
350
|
+
else
|
351
|
+
projects[this_idx + 1].line - 1
|
352
|
+
end
|
353
|
+
else
|
354
|
+
target_line = target_proj.line
|
355
|
+
end
|
356
|
+
|
357
|
+
contents.insert(target_line, "#{indent}\t- #{action.action}#{note}")
|
358
|
+
else
|
359
|
+
projects, actions = find_actions(target, search, tagged, done: done, all: all)
|
360
|
+
|
361
|
+
return if actions.nil?
|
362
|
+
|
363
|
+
actions.sort_by(&:line).reverse.each do |action|
|
364
|
+
contents.slice!(action.line, action.note.count + 1)
|
365
|
+
next if delete
|
366
|
+
|
367
|
+
projects = shift_index_after(projects, action.line, action.note.count + 1)
|
368
|
+
|
369
|
+
action = process_action(action, priority: priority, finish: finish, add_tag: add_tag, remove_tag: remove_tag)
|
370
|
+
|
371
|
+
target_proj = if target_proj
|
372
|
+
projects.select { |proj| proj.project =~ /^#{target_proj.project}$/ }.first
|
373
|
+
else
|
374
|
+
projects.select { |proj| proj.project =~ /^#{action.parent.join(':')}$/ }.first
|
375
|
+
end
|
376
|
+
|
377
|
+
indent = "\t" * target_proj.indent
|
378
|
+
note = note.split("\n") unless note.is_a?(Array)
|
379
|
+
note = if note.empty?
|
380
|
+
action.note
|
381
|
+
else
|
382
|
+
overwrite ? note : action.note.concat(note)
|
383
|
+
end
|
384
|
+
note = note.empty? ? '' : "\n#{indent}\t\t#{note.join("\n#{indent}\t\t").strip}"
|
385
|
+
|
386
|
+
if append
|
387
|
+
this_idx = projects.index(target_proj)
|
388
|
+
if this_idx == projects.length - 1
|
389
|
+
target_line = contents.count
|
390
|
+
else
|
391
|
+
target_line = projects[this_idx + 1].line - 1
|
392
|
+
end
|
393
|
+
else
|
394
|
+
target_line = target_proj.line
|
395
|
+
end
|
396
|
+
|
397
|
+
contents.insert(target_line, "#{indent}\t- #{action.action}#{note}")
|
398
|
+
end
|
325
399
|
end
|
326
400
|
backup_file(target)
|
327
401
|
File.open(target, 'w') { |f| f.puts contents.join("\n") }
|
328
402
|
|
329
|
-
notify("{by}Task updated in {bw}#{target}")
|
403
|
+
add ? notify("{by}Task added to {bw}#{target}") : notify("{by}Task updated in {bw}#{target}")
|
330
404
|
end
|
331
405
|
|
332
406
|
##
|
@@ -337,24 +411,22 @@ module NA
|
|
337
411
|
## @param action [String] The action
|
338
412
|
## @param note [String] The note
|
339
413
|
##
|
340
|
-
def add_action(file, project, action, note =
|
341
|
-
|
342
|
-
# Insert the target project at the top if it doesn't exist
|
343
|
-
unless content =~ /^[ \t]*#{project}:/i
|
344
|
-
content = "#{project.cap_first}:\n#{content}"
|
345
|
-
end
|
414
|
+
def add_action(file, project, action, note = [], priority: 0, finish: false, append: false)
|
415
|
+
parent = project.split(%r{[:/]})
|
346
416
|
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
417
|
+
if NA.global_file
|
418
|
+
puts NA.global_file
|
419
|
+
if NA.cwd_is == :tag
|
420
|
+
add_tag = [NA.cwd]
|
421
|
+
else
|
422
|
+
project = NA.cwd
|
423
|
+
end
|
424
|
+
puts [add_tag, project]
|
353
425
|
end
|
354
426
|
|
355
|
-
|
427
|
+
action = Action.new(file, project, parent, action, nil, note)
|
356
428
|
|
357
|
-
|
429
|
+
update_action(file, nil, add: action, project: project, add_tag: add_tag, priority: priority, finish: finish, append: append)
|
358
430
|
end
|
359
431
|
|
360
432
|
##
|
@@ -394,6 +466,7 @@ module NA
|
|
394
466
|
##
|
395
467
|
## @param depth [Number] The directory depth
|
396
468
|
## to search for files
|
469
|
+
## @param done [Boolean] include @done actions
|
397
470
|
## @param query [Hash] The todo file query
|
398
471
|
## @param tag [Array] Tags to search for
|
399
472
|
## @param search [String] A search string
|
@@ -402,7 +475,7 @@ module NA
|
|
402
475
|
## regular expression
|
403
476
|
## @param project [String] The project
|
404
477
|
## @param require_na [Boolean] Require @na tag
|
405
|
-
## @param
|
478
|
+
## @param file_path [String] file path to parse
|
406
479
|
##
|
407
480
|
def parse_actions(depth: 1, done: false, query: nil, tag: nil, search: nil, negate: false, regex: false, project: nil, require_na: true, file_path: nil)
|
408
481
|
actions = []
|
@@ -552,7 +625,9 @@ module NA
|
|
552
625
|
end
|
553
626
|
|
554
627
|
def list_projects(query: [], file_path: nil, depth: 1, paths: true)
|
555
|
-
files = if
|
628
|
+
files = if NA.global_file
|
629
|
+
[NA.global_file]
|
630
|
+
elsif !file_path.nil?
|
556
631
|
[file_path]
|
557
632
|
elsif query.nil?
|
558
633
|
find_files(depth: depth)
|
@@ -676,7 +751,7 @@ module NA
|
|
676
751
|
## @param target [String] The file to back up
|
677
752
|
##
|
678
753
|
def backup_file(target)
|
679
|
-
FileUtils.cp(target, "
|
754
|
+
FileUtils.cp(target, ".#{target}~")
|
680
755
|
end
|
681
756
|
|
682
757
|
##
|
@@ -740,6 +815,7 @@ module NA
|
|
740
815
|
|
741
816
|
default_args = [%(--prompt="#{prompt}"), "--height=#{options.count + 2}", '--info=inline']
|
742
817
|
default_args << '--multi' if multiple
|
818
|
+
default_args << '--bind ctrl-a:select-all'
|
743
819
|
header = "esc: cancel,#{multiple ? ' tab: multi-select, ctrl-a: select all,' : ''} return: confirm"
|
744
820
|
default_args << %(--header="#{header}")
|
745
821
|
default_args.concat(fzf_args)
|
data/lib/na/project.rb
CHANGED
data/lib/na/version.rb
CHANGED
data/src/README.md
CHANGED
@@ -9,7 +9,7 @@
|
|
9
9
|
_If you're one of the rare people like me who find this useful, feel free to
|
10
10
|
[buy me some coffee][donate]._
|
11
11
|
|
12
|
-
The current version of `na` is <!--VER-->1.2.
|
12
|
+
The current version of `na` is <!--VER-->1.2.2<!--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
|
|
@@ -48,6 +48,10 @@ You can also quickly add todo items from the command line with the `add` subcomm
|
|
48
48
|
|
49
49
|
If found, it will try to locate an `Inbox:` project, or create one if it doesn't exist. Any arguments after `add` will be combined to create a new task in TaskPaper format. They will automatically be assigned as next actions (tagged `@na`) and will show up when `na` lists the tasks for the project.
|
50
50
|
|
51
|
+
#### Updating todos
|
52
|
+
|
53
|
+
You can mark todos as complete, delete them, add and remove tags, change priority, and even move them between projects with the `na update` command.
|
54
|
+
|
51
55
|
### Usage
|
52
56
|
|
53
57
|
```
|
@@ -190,6 +194,16 @@ Note that I created a new YAML dictionary inside of the `:next:` command, and ad
|
|
190
194
|
> **WARNING** Don't touch most of the settings at the top of the auto-generated file. Setting any of them to true will alter the way na interprets the commands you're running. Most of those options are there for backwards compatibility with the bash version of this tool and will eventually be removed.
|
191
195
|
<!--JEKYLL{:.warn}-->
|
192
196
|
|
197
|
+
#### Working with a single global file
|
198
|
+
|
199
|
+
na is designed to work with one or more TaskPaper files in each project directory, but if you prefer to use a single global TaskPaper file, you can add `--file PATH` as a global option and specify a single file. This will bypass the detection of any files in the current directory. Make it permanent by including the `--file` flag when running `initconfig`.
|
200
|
+
|
201
|
+
When using a global file, you can additionally include `--cwd_as TYPE` to determine whether the current working directory is used as a tag or a project (default is neither). If you add `--cwd_as tag` to the global options (before the command), the last element of the current working directory will be appended as an @tag (e.g. if you're in ~/Code/project/doing, the action would be tagged @doing). If you use `--cwd_as project` the action will be put into a project with the same name as the current directory (e.g. `Doing:` from the previous example).
|
202
|
+
|
203
|
+
#### Add tasks at the end of a project
|
204
|
+
|
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`.
|
206
|
+
|
193
207
|
### Prompt Hooks
|
194
208
|
|
195
209
|
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.
|
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.3
|
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-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|