na 1.1.22 → 1.1.23
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 +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
|
|