na 1.1.10 → 1.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/bin/na +30 -25
- data/lib/na/next_action.rb +64 -33
- data/lib/na/version.rb +1 -1
- data/src/README.md +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b309ba145cf0859f1a9011cf62268261766338d79cb956810fab9c64863c37f2
|
4
|
+
data.tar.gz: 6fc7e065d4d85bf70b9122634414274e6a44d640ffffeeee7409e9102115f11d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 63ee0b1ac9370533379956e8cd5f6571c8d39731c12c02404b2912d5cb97f49d32c988e4d23c12c50d1b2080412296bc58351680122abd26281580c49d93f722
|
7
|
+
data.tar.gz: b7500123c7ea092acc42ccba0873942fe871bd201405e3ef2faa66f3d155c3d0ac60b530078d962114a4d0d66bdb8ea427b1452873c14e2b06419cb2675e822e
|
data/CHANGELOG.md
CHANGED
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.1.
|
12
|
+
The current version of `na` is 1.1.11
|
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.
|
data/bin/na
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
$LOAD_PATH.unshift File.join(__dir__, '..', 'lib')
|
3
5
|
require 'gli'
|
4
6
|
require 'na'
|
5
7
|
|
8
|
+
# Main application
|
6
9
|
class App
|
7
10
|
extend GLI::App
|
8
11
|
|
@@ -123,7 +126,7 @@ class App
|
|
123
126
|
|
124
127
|
c.desc 'Add action to specific project'
|
125
128
|
c.default_value 'Inbox'
|
126
|
-
c.flag %[to]
|
129
|
+
c.flag %i[to]
|
127
130
|
|
128
131
|
c.desc 'Use a tag other than the default next action tag'
|
129
132
|
c.arg_name 'TAG'
|
@@ -136,7 +139,7 @@ class App
|
|
136
139
|
c.arg_name 'PATH'
|
137
140
|
c.flag %i[f file]
|
138
141
|
|
139
|
-
c.action do |
|
142
|
+
c.action do |_global_options, options, args|
|
140
143
|
reader = TTY::Reader.new
|
141
144
|
action = if args.count.positive?
|
142
145
|
args.join(' ').strip
|
@@ -168,7 +171,10 @@ class App
|
|
168
171
|
|
169
172
|
note = if options[:note]
|
170
173
|
if TTY::Which.exist?('gum')
|
171
|
-
|
174
|
+
args = ['--placeholder "Enter a note, CTRL-d to save"']
|
175
|
+
args << '--char-limit 0'
|
176
|
+
args << '--width $(tput cols)'
|
177
|
+
`gum write #{args.join(' ')}`.strip.split("\n")
|
172
178
|
else
|
173
179
|
puts NA::Color.template('{bm}Enter a note, {bw}CTRL-d{bm} to end editing{bw}')
|
174
180
|
reader.read_multiline
|
@@ -190,7 +196,7 @@ class App
|
|
190
196
|
end
|
191
197
|
else
|
192
198
|
files = NA.find_files(depth: 1)
|
193
|
-
if files.count
|
199
|
+
if files.count.zero?
|
194
200
|
print NA::Color.template('{by}No todo file found, create one? {w}(y/{g}N{w}){x} ')
|
195
201
|
res = reader.read_char
|
196
202
|
if res =~ /y/i
|
@@ -256,10 +262,10 @@ class App
|
|
256
262
|
end
|
257
263
|
end
|
258
264
|
|
259
|
-
|
260
265
|
desc 'Find actions matching a tag'
|
261
266
|
long_desc 'Finds actions with tags matching the arguments. An action is shown if it
|
262
|
-
|
267
|
+
contains any of the tags listed. Add a + before a tag to make it required.
|
268
|
+
You can specify values using TAG=VALUE pairs.'
|
263
269
|
arg_name 'TAG [VALUE]'
|
264
270
|
command %i[tagged] do |c|
|
265
271
|
c.example 'na tagged +maybe', desc: 'Show all actions tagged @maybe'
|
@@ -304,7 +310,7 @@ class App
|
|
304
310
|
c.example 'na init', desc: 'Generate a new todo file, prompting for project name'
|
305
311
|
c.example 'na init warpspeed', desc: 'Generate a new todo for a project called warpspeed'
|
306
312
|
|
307
|
-
c.action do |
|
313
|
+
c.action do |_global_options, _options, args|
|
308
314
|
reader = TTY::Reader.new
|
309
315
|
if args.count.positive?
|
310
316
|
project = args.join(' ')
|
@@ -346,7 +352,7 @@ class App
|
|
346
352
|
c.arg_name 'EDITOR'
|
347
353
|
c.flag %i[a app]
|
348
354
|
|
349
|
-
c.action do |global_options, options,
|
355
|
+
c.action do |global_options, options, _args|
|
350
356
|
depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
|
351
357
|
3
|
352
358
|
else
|
@@ -371,15 +377,16 @@ class App
|
|
371
377
|
long_desc 'Installing the prompt hook allows you to automatically
|
372
378
|
list next actions when you cd into a directory'
|
373
379
|
command %i[prompt] do |c|
|
374
|
-
c.desc 'Output the prompt hook for the current shell to STDOUT. Pass an argument to
|
380
|
+
c.desc 'Output the prompt hook for the current shell to STDOUT. Pass an argument to
|
381
|
+
specify a shell (zsh, bash, fish)'
|
375
382
|
c.arg_name '[SHELL]'
|
376
383
|
c.command %i[show] do |s|
|
377
|
-
s.action do |
|
378
|
-
if args.count.positive?
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
384
|
+
s.action do |_global_options, _options, args|
|
385
|
+
shell = if args.count.positive?
|
386
|
+
args[0]
|
387
|
+
else
|
388
|
+
File.basename(ENV['SHELL'])
|
389
|
+
end
|
383
390
|
|
384
391
|
case shell
|
385
392
|
when /^f/i
|
@@ -395,12 +402,12 @@ class App
|
|
395
402
|
c.desc 'Install the hook for the current shell to the appropriate startup file.'
|
396
403
|
c.arg_name '[SHELL]'
|
397
404
|
c.command %i[install] do |s|
|
398
|
-
s.action do |
|
399
|
-
if args.count.positive?
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
405
|
+
s.action do |_global_options, _options, args|
|
406
|
+
shell = if args.count.positive?
|
407
|
+
args[0]
|
408
|
+
else
|
409
|
+
File.basename(ENV['SHELL'])
|
410
|
+
end
|
404
411
|
|
405
412
|
case shell
|
406
413
|
when /^f/i
|
@@ -414,7 +421,7 @@ class App
|
|
414
421
|
end
|
415
422
|
end
|
416
423
|
|
417
|
-
pre do |global,
|
424
|
+
pre do |global, _command, _options, _args|
|
418
425
|
NA.verbose = global[:debug]
|
419
426
|
NA.extension = global[:ext]
|
420
427
|
NA.na_tag = global[:na_tag]
|
@@ -432,9 +439,7 @@ class App
|
|
432
439
|
case exception
|
433
440
|
when GLI::UnknownCommand
|
434
441
|
cmd = ['add']
|
435
|
-
if ARGV.count.positive?
|
436
|
-
cmd.concat(ARGV.unshift($first_arg))
|
437
|
-
end
|
442
|
+
cmd.concat(ARGV.unshift($first_arg)) if ARGV.count.positive?
|
438
443
|
|
439
444
|
exit run(cmd)
|
440
445
|
when SystemExit
|
data/lib/na/next_action.rb
CHANGED
@@ -5,10 +5,17 @@ module NA
|
|
5
5
|
class << self
|
6
6
|
attr_accessor :verbose, :extension, :na_tag
|
7
7
|
|
8
|
+
def notify(msg, exit_code: false)
|
9
|
+
$stderr.puts NA::Color.template("{x}#{msg}{x}")
|
10
|
+
if exit_code && exit_code.is_a?(Number)
|
11
|
+
Process.exit exit_code
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
8
15
|
def create_todo(target, basename)
|
9
16
|
File.open(target, 'w') do |f|
|
10
17
|
content = <<~ENDCONTENT
|
11
|
-
Inbox:
|
18
|
+
Inbox:
|
12
19
|
#{basename}:
|
13
20
|
\tFeature Requests:
|
14
21
|
\tIdeas:
|
@@ -18,11 +25,11 @@ module NA
|
|
18
25
|
\tTop Priority @search(@priority = 5 and not @done)
|
19
26
|
\tHigh Priority @search(@priority > 3 and not @done)
|
20
27
|
\tMaybe @search(@maybe)
|
21
|
-
\tNext @search(
|
28
|
+
\tNext @search(@#{NA.na_tag} and not @done and not project = \"Archive\")
|
22
29
|
ENDCONTENT
|
23
30
|
f.puts(content)
|
24
31
|
end
|
25
|
-
|
32
|
+
notify("{y}Created {bw}#{target}")
|
26
33
|
end
|
27
34
|
|
28
35
|
def find_files(depth: 1)
|
@@ -41,8 +48,7 @@ module NA
|
|
41
48
|
elsif TTY::Which.exist?('fzf')
|
42
49
|
res = choose_from(files, prompt: 'Use which file?')
|
43
50
|
unless res
|
44
|
-
|
45
|
-
Process.exit 1
|
51
|
+
notify('{r}No file selected, cancelled', exit_code: 1)
|
46
52
|
end
|
47
53
|
|
48
54
|
res.strip
|
@@ -71,7 +77,7 @@ module NA
|
|
71
77
|
|
72
78
|
File.open(file, 'w') { |f| f.puts content }
|
73
79
|
|
74
|
-
|
80
|
+
notify("{by}Task added to {bw}#{file}")
|
75
81
|
end
|
76
82
|
|
77
83
|
def output_actions(actions, depth, files: nil)
|
@@ -91,7 +97,7 @@ module NA
|
|
91
97
|
'%parent%action'
|
92
98
|
end
|
93
99
|
if files && @verbose
|
94
|
-
|
100
|
+
files.map { |f| notify("{dw}#{f}") }
|
95
101
|
end
|
96
102
|
|
97
103
|
puts actions.map { |action| action.pretty(template: { output: template }) }
|
@@ -188,11 +194,7 @@ module NA
|
|
188
194
|
def choose_from(options, prompt: 'Make a selection: ', multiple: false, sorted: true, fzf_args: [])
|
189
195
|
return nil unless $stdout.isatty
|
190
196
|
|
191
|
-
|
192
|
-
default_args = []
|
193
|
-
default_args << %(--prompt="#{prompt}")
|
194
|
-
default_args << "--height=#{options.count + 2}"
|
195
|
-
default_args << '--info=inline'
|
197
|
+
default_args = [%(--prompt="#{prompt}"), "--height=#{options.count + 2}", '--info=inline']
|
196
198
|
default_args << '--multi' if multiple
|
197
199
|
header = "esc: cancel,#{multiple ? ' tab: multi-select, ctrl-a: select all,' : ''} return: confirm"
|
198
200
|
default_args << %(--header="#{header}")
|
@@ -205,33 +207,50 @@ module NA
|
|
205
207
|
res
|
206
208
|
end
|
207
209
|
|
210
|
+
##
|
211
|
+
## Get path to database of known todo files
|
212
|
+
##
|
213
|
+
## @return [String] File path
|
214
|
+
##
|
208
215
|
def database_path
|
209
216
|
db_dir = File.expand_path('~/.local/share/na')
|
217
|
+
# Create directory if needed
|
210
218
|
FileUtils.mkdir_p(db_dir) unless File.directory?(db_dir)
|
211
219
|
db_file = 'tdlist.txt'
|
212
220
|
File.join(db_dir, db_file)
|
213
221
|
end
|
214
222
|
|
215
|
-
|
223
|
+
##
|
224
|
+
## Find a matching path using semi-fuzzy matching.
|
225
|
+
## Search tokens can include ! and + to negate or make
|
226
|
+
## required.
|
227
|
+
##
|
228
|
+
## @param search [Array] search tokens to match
|
229
|
+
## @param distance [Integer] allowed distance
|
230
|
+
## between characters
|
231
|
+
##
|
232
|
+
def match_working_dir(search, distance: 1)
|
216
233
|
optional = []
|
217
234
|
required = []
|
218
235
|
|
219
236
|
search&.each do |t|
|
220
|
-
|
237
|
+
# Make "search" into "s.{0,1}e.{0,1}a.{0,1}r.{0,1}c.{0,1}h"
|
238
|
+
new_rx = t[:token].to_s.split('').join(".{0,#{distance}}")
|
221
239
|
|
222
240
|
optional.push(new_rx)
|
223
241
|
required.push(new_rx) if t[:required]
|
224
242
|
end
|
225
243
|
|
244
|
+
match_dir(optional, required)
|
245
|
+
end
|
246
|
+
|
247
|
+
def match_dir(optional, required)
|
226
248
|
file = database_path
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
$stderr.puts NA::Color.template('{r}No na database found{x}')
|
233
|
-
Process.exit 1
|
234
|
-
end
|
249
|
+
notify('{r}No na database found', exit_code: 1) unless File.exist?(file)
|
250
|
+
|
251
|
+
dirs = IO.read(file).split("\n")
|
252
|
+
dirs.delete_if { |d| !d.matches(any: optional, all: required) }
|
253
|
+
dirs.sort.uniq
|
235
254
|
end
|
236
255
|
|
237
256
|
def save_working_dir(todo_file)
|
@@ -258,6 +277,26 @@ module NA
|
|
258
277
|
os_open(file, app: app) if file && File.exist?(file)
|
259
278
|
end
|
260
279
|
|
280
|
+
def darwin_open(file, app: nil)
|
281
|
+
if app
|
282
|
+
`open -a "#{app}" #{Shellwords.escape(file)}`
|
283
|
+
else
|
284
|
+
`open #{Shellwords.escape(file)}`
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def win_open(file)
|
289
|
+
`start #{Shellwords.escape(file)}`
|
290
|
+
end
|
291
|
+
|
292
|
+
def linux_open(file)
|
293
|
+
if TTY::Which.exist?('xdg-open')
|
294
|
+
`xdg-open #{Shellwords.escape(file)}`
|
295
|
+
else
|
296
|
+
notify('{r}Unable to determine executable for `xdg-open`.')
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
261
300
|
##
|
262
301
|
## Platform-agnostic open command
|
263
302
|
##
|
@@ -267,19 +306,11 @@ module NA
|
|
267
306
|
os = RbConfig::CONFIG['target_os']
|
268
307
|
case os
|
269
308
|
when /darwin.*/i
|
270
|
-
|
271
|
-
`open -a "#{app}" #{Shellwords.escape(file)}`
|
272
|
-
else
|
273
|
-
`open #{Shellwords.escape(file)}`
|
274
|
-
end
|
309
|
+
darwin_open(file, app: app)
|
275
310
|
when /mingw|mswin/i
|
276
|
-
|
311
|
+
win_open(file)
|
277
312
|
else
|
278
|
-
|
279
|
-
`xdg-open #{Shellwords.escape(file)}`
|
280
|
-
else
|
281
|
-
$stderr.puts NA::Color.template('{r}Unable to determine executable for `open`.{x}')
|
282
|
-
end
|
313
|
+
linux_open(file)
|
283
314
|
end
|
284
315
|
end
|
285
316
|
end
|
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.1.
|
12
|
+
The current version of `na` is <!--VER-->1.1.10<!--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
|
|