na 1.1.22 → 1.1.23
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +1 -1
- data/README.md +4 -2
- data/bin/na +45 -5
- data/lib/na/action.rb +5 -2
- data/lib/na/next_action.rb +92 -15
- data/lib/na/string.rb +1 -1
- 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: c8bee2cada1590cdc80fe9943947c719649d441ed15a6c51228fa0bebe4e94eb
|
4
|
+
data.tar.gz: 3dfc5a53bb88952243388b86e853af744b76938cbc69ef3b6f6ac8e58c03a9fc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0603c3187363e3c5895f9e49af6deab11779cbf47442f4f76b7b5ad2608e4676d0cb337368e2a0d6a5f9bad7f5aff957d63e986cefe1997c671a6b6b5193e3e4
|
7
|
+
data.tar.gz: e570fe4a32d7e45b4bd2171c73bee68adb6017ae22901c92486ccbe1bdb2461a50dac988d8ed862aa85237c070ebb305d3b6c2c61afc920826568c628d630faf
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
### 1.1.23
|
2
|
+
|
3
|
+
2022-10-07 10:02
|
4
|
+
|
5
|
+
#### NEW
|
6
|
+
|
7
|
+
- Saved searches. Add `--save TITLE` to `tagged` or `find` commands to save the parameters for use with `na saved TITLE`
|
8
|
+
|
9
|
+
#### FIXED
|
10
|
+
|
11
|
+
- Restore wildcard capability of tag searches
|
12
|
+
|
1
13
|
### 1.1.22
|
2
14
|
|
3
15
|
2022-10-07 05:58
|
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.23
|
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.
|
@@ -59,7 +59,7 @@ SYNOPSIS
|
|
59
59
|
na [global options] command [command options] [arguments...]
|
60
60
|
|
61
61
|
VERSION
|
62
|
-
1.1.
|
62
|
+
1.1.23
|
63
63
|
|
64
64
|
GLOBAL OPTIONS
|
65
65
|
-a, --[no-]add - Add a next action (deprecated, for backwards compatibility)
|
@@ -82,6 +82,7 @@ COMMANDS
|
|
82
82
|
initconfig - Initialize the config file using current global options
|
83
83
|
next, show - Show next actions
|
84
84
|
prompt - Show or install prompt hooks for the current shell
|
85
|
+
saved - Execute a saved search
|
85
86
|
tagged - Find actions matching a tag
|
86
87
|
todos - Show list of known todo files
|
87
88
|
```
|
@@ -174,6 +175,7 @@ COMMAND OPTIONS
|
|
174
175
|
--in=TODO_PATH - Show actions from a specific todo file in history. May use wildcards (* and ?) (default: none)
|
175
176
|
-o, --or - Combine search tokens with OR, displaying actions matching ANY of the terms
|
176
177
|
--proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
|
178
|
+
--save=TITLE - Save this search for future use (default: none)
|
177
179
|
-v, --invert - Show actions not matching search pattern
|
178
180
|
-x, --exact - Match pattern exactly
|
179
181
|
|
data/bin/na
CHANGED
@@ -271,7 +271,16 @@ class App
|
|
271
271
|
c.desc 'Show actions not matching search pattern'
|
272
272
|
c.switch %i[v invert], negatable: false
|
273
273
|
|
274
|
+
c.desc 'Save this search for future use'
|
275
|
+
c.arg_name 'TITLE'
|
276
|
+
c.flag %i[save]
|
277
|
+
|
274
278
|
c.action do |global_options, options, args|
|
279
|
+
if options[:save]
|
280
|
+
title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
|
281
|
+
NA.save_search(title, "find #{NA.command_line.map { |c| "\"#{c}\"" }.join(' ')}")
|
282
|
+
end
|
283
|
+
|
275
284
|
depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
|
276
285
|
3
|
277
286
|
else
|
@@ -359,7 +368,16 @@ class App
|
|
359
368
|
c.desc 'Show actions not matching tags'
|
360
369
|
c.switch %i[v invert], negatable: false
|
361
370
|
|
371
|
+
c.desc 'Save this search for future use'
|
372
|
+
c.arg_name 'TITLE'
|
373
|
+
c.flag %i[save]
|
374
|
+
|
362
375
|
c.action do |global_options, options, args|
|
376
|
+
if options[:save]
|
377
|
+
title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
|
378
|
+
NA.save_search(title, "tagged #{NA.command_line.map { |cmd| "\"#{cmd}\"" }.join(' ')}")
|
379
|
+
end
|
380
|
+
|
363
381
|
depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
|
364
382
|
3
|
365
383
|
else
|
@@ -371,9 +389,10 @@ class App
|
|
371
389
|
all_req = args.join(' ') !~ /[+!\-]/ && !options[:or]
|
372
390
|
args.join(',').split(/ *, */).each do |arg|
|
373
391
|
# TODO: <> comparisons do nothing right now
|
374
|
-
m = arg.match(/^(?<req>[+\-!])?(?<tag>[^
|
392
|
+
m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
|
393
|
+
|
375
394
|
tags.push({
|
376
|
-
tag: m['tag'],
|
395
|
+
tag: m['tag'].wildcard_to_rx,
|
377
396
|
comp: m['op'],
|
378
397
|
value: m['val'],
|
379
398
|
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
@@ -387,9 +406,9 @@ class App
|
|
387
406
|
options[:in].split(/ *, */).each do |a|
|
388
407
|
m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
|
389
408
|
todo.push({
|
390
|
-
|
391
|
-
|
392
|
-
|
409
|
+
token: m['tok'],
|
410
|
+
required: !m['req'].nil?
|
411
|
+
})
|
393
412
|
end
|
394
413
|
end
|
395
414
|
|
@@ -545,6 +564,26 @@ class App
|
|
545
564
|
end
|
546
565
|
end
|
547
566
|
|
567
|
+
desc 'Execute a saved search'
|
568
|
+
long_desc 'Run without argument to list saved searches'
|
569
|
+
arg_name 'SEARCH_TITLE', optional: true
|
570
|
+
command %i[saved] do |c|
|
571
|
+
c.action do |_global_options, _options, args|
|
572
|
+
searches = NA.load_searches
|
573
|
+
if args.empty?
|
574
|
+
NA.notify("{bg}Saved searches stored in {bw}#{NA.database_path(file: 'saved_searches.yml')}")
|
575
|
+
NA.notify(searches.map { |k, v| "{y}#{k}: {w}#{v}" }.join("\n"), exit_code: 0)
|
576
|
+
else
|
577
|
+
keys = searches.keys.delete_if { |k| k !~ /#{args[0]}/ }
|
578
|
+
NA.notify("{r}Search #{args[0]} not found", exit_code: 1) if keys.empty?
|
579
|
+
|
580
|
+
key = keys[0]
|
581
|
+
cmd = Shellwords.shellsplit(searches[key])
|
582
|
+
exit run(cmd)
|
583
|
+
end
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
548
587
|
pre do |global, _command, _options, _args|
|
549
588
|
NA.verbose = global[:debug]
|
550
589
|
NA.extension = global[:ext]
|
@@ -574,6 +613,7 @@ class App
|
|
574
613
|
end
|
575
614
|
end
|
576
615
|
|
616
|
+
NA.command_line = ARGV
|
577
617
|
@command = ARGV[0]
|
578
618
|
$first_arg = ARGV[1]
|
579
619
|
|
data/lib/na/action.rb
CHANGED
@@ -127,11 +127,14 @@ module NA
|
|
127
127
|
end
|
128
128
|
|
129
129
|
def compare_tag(tag)
|
130
|
-
|
130
|
+
keys = @tags.keys.delete_if { |k| k !~ Regexp.new(tag[:tag], Regexp::IGNORECASE) }
|
131
|
+
return false if keys.empty?
|
132
|
+
|
133
|
+
key = keys[0]
|
131
134
|
|
132
135
|
return true if tag[:comp].nil?
|
133
136
|
|
134
|
-
tag_val = @tags[
|
137
|
+
tag_val = @tags[key]
|
135
138
|
val = tag[:value]
|
136
139
|
|
137
140
|
return false if tag_val.nil?
|
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
|
6
|
+
attr_accessor :verbose, :extension, :na_tag, :command_line
|
7
7
|
|
8
8
|
##
|
9
9
|
## Output to STDERR
|
@@ -15,11 +15,57 @@ module NA
|
|
15
15
|
return if debug && !@verbose
|
16
16
|
|
17
17
|
$stderr.puts NA::Color.template("{x}#{msg}{x}")
|
18
|
-
if exit_code
|
18
|
+
if exit_code
|
19
19
|
Process.exit exit_code
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
##
|
24
|
+
## Display and read a Yes/No prompt
|
25
|
+
##
|
26
|
+
## @param prompt [String] The prompt string
|
27
|
+
## @param default [Boolean] default value if
|
28
|
+
## return is pressed or prompt is
|
29
|
+
## skipped
|
30
|
+
##
|
31
|
+
## @return [Boolean] result
|
32
|
+
##
|
33
|
+
def yn(prompt, default: true)
|
34
|
+
return default unless $stdout.isatty
|
35
|
+
|
36
|
+
tty_state = `stty -g`
|
37
|
+
system 'stty raw -echo cbreak isig'
|
38
|
+
yn = color_single_options(default ? %w[Y n] : %w[y N])
|
39
|
+
$stdout.syswrite "\e[1;37m#{prompt} #{yn}\e[1;37m? \e[0m"
|
40
|
+
res = $stdin.sysread 1
|
41
|
+
res.chomp!
|
42
|
+
puts
|
43
|
+
system 'stty cooked'
|
44
|
+
system "stty #{tty_state}"
|
45
|
+
res.empty? ? default : res =~ /y/i
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
## Helper function to colorize the Y/N prompt
|
50
|
+
##
|
51
|
+
## @param choices [Array] The choices with
|
52
|
+
## default capitalized
|
53
|
+
##
|
54
|
+
## @return [String] colorized string
|
55
|
+
##
|
56
|
+
def color_single_options(choices = %w[y n])
|
57
|
+
out = []
|
58
|
+
choices.each do |choice|
|
59
|
+
case choice
|
60
|
+
when /[A-Z]/
|
61
|
+
out.push(NA::Color.template("{bw}#{choice}{x}"))
|
62
|
+
else
|
63
|
+
out.push(NA::Color.template("{dw}#{choice}{xg}"))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
NA::Color.template("{xg}[#{out.join('/')}{xg}]{x}")
|
67
|
+
end
|
68
|
+
|
23
69
|
##
|
24
70
|
## Create a new todo file
|
25
71
|
##
|
@@ -308,6 +354,50 @@ module NA
|
|
308
354
|
puts NA::Color.template(dirs.join("\n"))
|
309
355
|
end
|
310
356
|
|
357
|
+
def save_search(title, search)
|
358
|
+
file = database_path(file: 'saved_searches.yml')
|
359
|
+
searches = load_searches
|
360
|
+
title = title.gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
|
361
|
+
|
362
|
+
if searches.key?(title)
|
363
|
+
res = yn('Overwrite existing definition?', default: true)
|
364
|
+
notify('{r}Cancelled', exit_code: 0) unless res
|
365
|
+
|
366
|
+
end
|
367
|
+
|
368
|
+
searches[title] = search
|
369
|
+
File.open(file, 'w') { |f| f.puts(YAML.dump(searches)) }
|
370
|
+
NA.notify("{y}Search #{title} saved", exit_code: 0)
|
371
|
+
end
|
372
|
+
|
373
|
+
def load_searches
|
374
|
+
file = database_path(file: 'saved_searches.yml')
|
375
|
+
if File.exist?(file)
|
376
|
+
searches = YAML.safe_load(IO.read(file))
|
377
|
+
else
|
378
|
+
searches = {
|
379
|
+
'soon' => 'tagged "due<in 2 days,due>yesterday"',
|
380
|
+
'overdue' => 'tagged "due<now"',
|
381
|
+
'high' => 'tagged "prio>3"',
|
382
|
+
'maybe' => 'tagged "maybe"'
|
383
|
+
}
|
384
|
+
File.open(file, 'w') { |f| f.puts(YAML.dump(searches)) }
|
385
|
+
end
|
386
|
+
searches
|
387
|
+
end
|
388
|
+
|
389
|
+
##
|
390
|
+
## Get path to database of known todo files
|
391
|
+
##
|
392
|
+
## @return [String] File path
|
393
|
+
##
|
394
|
+
def database_path(file: 'tdlist.txt')
|
395
|
+
db_dir = File.expand_path('~/.local/share/na')
|
396
|
+
# Create directory if needed
|
397
|
+
FileUtils.mkdir_p(db_dir) unless File.directory?(db_dir)
|
398
|
+
File.join(db_dir, file)
|
399
|
+
end
|
400
|
+
|
311
401
|
private
|
312
402
|
|
313
403
|
##
|
@@ -356,19 +446,6 @@ module NA
|
|
356
446
|
[optional, required, negated]
|
357
447
|
end
|
358
448
|
|
359
|
-
##
|
360
|
-
## Get path to database of known todo files
|
361
|
-
##
|
362
|
-
## @return [String] File path
|
363
|
-
##
|
364
|
-
def database_path
|
365
|
-
db_dir = File.expand_path('~/.local/share/na')
|
366
|
-
# Create directory if needed
|
367
|
-
FileUtils.mkdir_p(db_dir) unless File.directory?(db_dir)
|
368
|
-
db_file = 'tdlist.txt'
|
369
|
-
File.join(db_dir, db_file)
|
370
|
-
end
|
371
|
-
|
372
449
|
##
|
373
450
|
## Find a matching path using semi-fuzzy matching.
|
374
451
|
## Search tokens can include ! and + to negate or make
|
data/lib/na/string.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.1.
|
12
|
+
The current version of `na` is <!--VER-->1.1.22<!--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
|
|