na 1.2.12 → 1.2.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/Gemfile.lock +1 -1
- data/README.md +11 -2
- data/bin/na +112 -4
- data/lib/na/next_action.rb +25 -25
- data/lib/na/string.rb +29 -0
- data/lib/na/version.rb +1 -1
- data/src/README.md +1 -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: da1fc18252895707dfb04a598aca5ac535b4c65959b564d11d56497796a09249
|
4
|
+
data.tar.gz: 7d483b22ddd0015b02bbea54c5026ba11b3cfc58597d790eed5d0902bbb285ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bfde6ccd9c352ef8932df8937218701b54db37994d1d0642323b4bc69a5bb6bf566588a9a4908a37312dc7b9787547427bf68609b124dfe752c3acec5b807bb8
|
7
|
+
data.tar.gz: 87ce30671ba3cc2bf415744df17df187e834966db481c7e5b2310eb873315a68061541f9342c0578f51d8d93f8a0accdde6df6030393d1d28aaacbfc33ac170e
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
### 1.2.14
|
2
|
+
|
3
|
+
2022-11-12 10:57
|
4
|
+
|
5
|
+
#### NEW
|
6
|
+
|
7
|
+
- `na find --tagged` allows narrowing search results with tag queries
|
8
|
+
- `na tagged --search` allows narrowing tag results with text search
|
9
|
+
- `na next` accepts --tagged and --search (as well as --exact and --regex) for filtering actions
|
10
|
+
|
11
|
+
#### FIXED
|
12
|
+
|
13
|
+
- Error when a todo file contained a task without a project
|
14
|
+
|
15
|
+
### 1.2.13
|
16
|
+
|
17
|
+
2022-11-01 12:43
|
18
|
+
|
19
|
+
#### FIXED
|
20
|
+
|
21
|
+
- Allow colon at end of action without recognizing as project
|
22
|
+
|
1
23
|
### 1.2.12
|
2
24
|
|
3
25
|
2022-10-31 13:52
|
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.14
|
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.
|
@@ -77,7 +77,7 @@ SYNOPSIS
|
|
77
77
|
na [global options] command [command options] [arguments...]
|
78
78
|
|
79
79
|
VERSION
|
80
|
-
1.2.
|
80
|
+
1.2.14
|
81
81
|
|
82
82
|
GLOBAL OPTIONS
|
83
83
|
-a, --[no-]add - Add a next action (deprecated, for backwards compatibility)
|
@@ -214,6 +214,7 @@ COMMAND OPTIONS
|
|
214
214
|
-o, --or - Combine search tokens with OR, displaying actions matching ANY of the terms
|
215
215
|
--proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
|
216
216
|
--save=TITLE - Save this search for future use (default: none)
|
217
|
+
--tagged=TAG - Match actions containing tag. Allows value comparisons (may be used more than once, default: none)
|
217
218
|
-v, --invert - Show actions not matching search pattern
|
218
219
|
-x, --exact - Match pattern exactly
|
219
220
|
|
@@ -269,10 +270,14 @@ DESCRIPTION
|
|
269
270
|
COMMAND OPTIONS
|
270
271
|
-d, --depth=DEPTH - Recurse to depth (default: none)
|
271
272
|
--[no-]done - Include @done actions
|
273
|
+
--[no-]exact - Search query is exact text match (not tokens)
|
272
274
|
--in, --todo=TODO_FILE - Display matches from a known todo file (may be used more than once, default: none)
|
273
275
|
--[no-]notes - Include notes in output
|
274
276
|
--proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
|
277
|
+
--[no-]regex - Search query is regular expression
|
278
|
+
--search=QUERY - Filter results using search terms (may be used more than once, default: none)
|
275
279
|
-t, --tag=TAG - Alternate tag to search for (default: none)
|
280
|
+
--tagged=TAG - Match actions containing tag. Allows value comparisons (may be used more than once, default: none)
|
276
281
|
|
277
282
|
EXAMPLES
|
278
283
|
|
@@ -369,10 +374,14 @@ DESCRIPTION
|
|
369
374
|
COMMAND OPTIONS
|
370
375
|
-d, --depth=DEPTH - Recurse to depth (default: none)
|
371
376
|
--[no-]done - Include @done actions
|
377
|
+
--[no-]exact - Search query is exact text match (not tokens)
|
372
378
|
--in, --todo=TODO_FILE - Display matches from a known todo file (may be used more than once, default: none)
|
373
379
|
--[no-]notes - Include notes in output
|
374
380
|
--proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
|
381
|
+
--[no-]regex - Search query is regular expression
|
382
|
+
--search=QUERY - Filter results using search terms (may be used more than once, default: none)
|
375
383
|
-t, --tag=TAG - Alternate tag to search for (default: none)
|
384
|
+
--tagged=TAG - Match actions containing tag. Allows value comparisons (may be used more than once, default: none)
|
376
385
|
|
377
386
|
EXAMPLES
|
378
387
|
|
data/bin/na
CHANGED
@@ -95,6 +95,20 @@ class App
|
|
95
95
|
c.arg_name 'PROJECT[/SUBPROJECT]'
|
96
96
|
c.flag %i[proj project]
|
97
97
|
|
98
|
+
c.desc 'Match actions containing tag. Allows value comparisons'
|
99
|
+
c.arg_name 'TAG'
|
100
|
+
c.flag %i[tagged], multiple: true
|
101
|
+
|
102
|
+
c.desc 'Filter results using search terms'
|
103
|
+
c.arg_name 'QUERY'
|
104
|
+
c.flag %i[search], multiple: true
|
105
|
+
|
106
|
+
c.desc 'Search query is regular expression'
|
107
|
+
c.switch %i[regex]
|
108
|
+
|
109
|
+
c.desc 'Search query is exact text match (not tokens)'
|
110
|
+
c.switch %i[exact]
|
111
|
+
|
98
112
|
c.desc 'Include notes in output'
|
99
113
|
c.switch %i[notes], negatable: true, default_value: false
|
100
114
|
|
@@ -117,6 +131,20 @@ class App
|
|
117
131
|
options[:depth].nil? ? global_options[:depth].to_i : options[:depth].to_i
|
118
132
|
end
|
119
133
|
|
134
|
+
all_req = options[:tagged].join(' ') !~ /[+!\-]/ && !options[:or]
|
135
|
+
tags = []
|
136
|
+
options[:tagged].join(',').split(/ *, */).each do |arg|
|
137
|
+
m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
|
138
|
+
|
139
|
+
tags.push({
|
140
|
+
tag: m['tag'].wildcard_to_rx,
|
141
|
+
comp: m['op'],
|
142
|
+
value: m['val'],
|
143
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
144
|
+
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
145
|
+
})
|
146
|
+
end
|
147
|
+
|
120
148
|
args.concat(options[:in])
|
121
149
|
if args.count.positive?
|
122
150
|
all_req = args.join(' ') !~ /[+!\-]/
|
@@ -134,14 +162,37 @@ class App
|
|
134
162
|
end
|
135
163
|
end
|
136
164
|
|
165
|
+
search = nil
|
166
|
+
if options[:search]
|
167
|
+
if options[:exact]
|
168
|
+
search = options[:search].join(' ')
|
169
|
+
elsif options[:regex]
|
170
|
+
search = Regexp.new(options[:search].join(' '), Regexp::IGNORECASE)
|
171
|
+
else
|
172
|
+
search = []
|
173
|
+
all_req = options[:search].join(' ') !~ /[+!\-]/ && !options[:or]
|
174
|
+
|
175
|
+
options[:search].join(' ').split(/ /).each do |arg|
|
176
|
+
m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
177
|
+
search.push({
|
178
|
+
token: m['tok'],
|
179
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
180
|
+
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
181
|
+
})
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
137
186
|
NA.na_tag = options[:tag] unless options[:tag].nil?
|
138
187
|
require_na = true
|
139
188
|
|
140
|
-
tag = [{ tag:
|
189
|
+
tag = [{ tag: NA.na_tag, value: nil }, { tag: 'done', value: nil, negate: true }]
|
190
|
+
tag.concat(tags)
|
141
191
|
files, actions, = NA.parse_actions(depth: depth,
|
142
192
|
done: options[:done],
|
143
193
|
query: tokens,
|
144
194
|
tag: tag,
|
195
|
+
search: search,
|
145
196
|
project: options[:project],
|
146
197
|
require_na: require_na)
|
147
198
|
|
@@ -587,6 +638,10 @@ class App
|
|
587
638
|
c.arg_name 'PROJECT[/SUBPROJECT]'
|
588
639
|
c.flag %i[proj project]
|
589
640
|
|
641
|
+
c.desc 'Match actions containing tag. Allows value comparisons'
|
642
|
+
c.arg_name 'TAG'
|
643
|
+
c.flag %i[tagged], multiple: true
|
644
|
+
|
590
645
|
c.desc 'Include @done actions'
|
591
646
|
c.switch %i[done]
|
592
647
|
|
@@ -600,7 +655,7 @@ class App
|
|
600
655
|
c.action do |global_options, options, args|
|
601
656
|
if options[:save]
|
602
657
|
title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
|
603
|
-
NA.save_search(title, "#{NA.command_line.join(' ').sub(/ --save[= ]*\S+/, '')}")
|
658
|
+
NA.save_search(title, "#{NA.command_line.join(' ').sub(/ --save[= ]*\S+/, '').split(' ').map { |t| %("#{t}") }.join(' ')}")
|
604
659
|
end
|
605
660
|
|
606
661
|
depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
|
@@ -608,6 +663,21 @@ class App
|
|
608
663
|
else
|
609
664
|
options[:depth].nil? ? global_options[:depth].to_i : options[:depth].to_i
|
610
665
|
end
|
666
|
+
|
667
|
+
all_req = options[:tagged].join(' ') !~ /[+!\-]/ && !options[:or]
|
668
|
+
tags = []
|
669
|
+
options[:tagged].join(',').split(/ *, */).each do |arg|
|
670
|
+
m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
|
671
|
+
|
672
|
+
tags.push({
|
673
|
+
tag: m['tag'].wildcard_to_rx,
|
674
|
+
comp: m['op'],
|
675
|
+
value: m['val'],
|
676
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
677
|
+
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
678
|
+
})
|
679
|
+
end
|
680
|
+
|
611
681
|
tokens = nil
|
612
682
|
if options[:exact]
|
613
683
|
tokens = args.join(' ')
|
@@ -644,6 +714,7 @@ class App
|
|
644
714
|
done: options[:done],
|
645
715
|
query: todo,
|
646
716
|
search: tokens,
|
717
|
+
tag: tags,
|
647
718
|
negate: options[:invert],
|
648
719
|
regex: options[:regex],
|
649
720
|
project: options[:project],
|
@@ -692,6 +763,16 @@ class App
|
|
692
763
|
c.arg_name 'PROJECT[/SUBPROJECT]'
|
693
764
|
c.flag %i[proj project]
|
694
765
|
|
766
|
+
c.desc 'Filter results using search terms'
|
767
|
+
c.arg_name 'QUERY'
|
768
|
+
c.flag %i[search], multiple: true
|
769
|
+
|
770
|
+
c.desc 'Search query is regular expression'
|
771
|
+
c.switch %i[regex]
|
772
|
+
|
773
|
+
c.desc 'Search query is exact text match (not tokens)'
|
774
|
+
c.switch %i[exact]
|
775
|
+
|
695
776
|
c.desc 'Include @done actions'
|
696
777
|
c.switch %i[done]
|
697
778
|
|
@@ -705,7 +786,7 @@ class App
|
|
705
786
|
c.action do |global_options, options, args|
|
706
787
|
if options[:save]
|
707
788
|
title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
|
708
|
-
NA.save_search(title, "#{NA.command_line.join(' ').sub(/ --save[= ]*\S+/, '')}")
|
789
|
+
NA.save_search(title, "#{NA.command_line.join(' ').sub(/ --save[= ]*\S+/, '').split(' ').map { |t| %("#{t}") }.join(' ')}")
|
709
790
|
end
|
710
791
|
|
711
792
|
depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
|
@@ -733,6 +814,27 @@ class App
|
|
733
814
|
tags.each { |tag| search_for_done = true if tag[:tag] =~ /done/ }
|
734
815
|
tags.push({ tag: 'done', value: nil, negate: true}) unless search_for_done
|
735
816
|
|
817
|
+
tokens = nil
|
818
|
+
if options[:search]
|
819
|
+
if options[:exact]
|
820
|
+
tokens = options[:search].join(' ')
|
821
|
+
elsif options[:regex]
|
822
|
+
tokens = Regexp.new(options[:search].join(' '), Regexp::IGNORECASE)
|
823
|
+
else
|
824
|
+
tokens = []
|
825
|
+
all_req = options[:search].join(' ') !~ /[+!\-]/ && !options[:or]
|
826
|
+
|
827
|
+
options[:search].join(' ').split(/ /).each do |arg|
|
828
|
+
m = arg.match(/^(?<req>[+\-!])?(?<tok>.*?)$/)
|
829
|
+
tokens.push({
|
830
|
+
token: m['tok'],
|
831
|
+
required: all_req || (!m['req'].nil? && m['req'] == '+'),
|
832
|
+
negate: !m['req'].nil? && m['req'] =~ /[!\-]/
|
833
|
+
})
|
834
|
+
end
|
835
|
+
end
|
836
|
+
end
|
837
|
+
|
736
838
|
todo = nil
|
737
839
|
if options[:in]
|
738
840
|
todo = []
|
@@ -749,11 +851,17 @@ class App
|
|
749
851
|
files, actions, = NA.parse_actions(depth: depth,
|
750
852
|
done: options[:done],
|
751
853
|
query: todo,
|
854
|
+
search: tokens,
|
752
855
|
tag: tags,
|
753
856
|
negate: options[:invert],
|
754
857
|
project: options[:project],
|
755
858
|
require_na: false)
|
756
|
-
regexes = tags.delete_if { |token| token[:negate] }.map { |token| token[:token] }
|
859
|
+
# regexes = tags.delete_if { |token| token[:negate] }.map { |token| token[:token] }
|
860
|
+
regexes = if tokens.is_a?(Array)
|
861
|
+
tokens.delete_if { |token| token[:negate] }.map { |token| token[:token] }
|
862
|
+
else
|
863
|
+
[tokens]
|
864
|
+
end
|
757
865
|
NA.output_actions(actions, depth, files: files, regexes: regexes, notes: options[:notes])
|
758
866
|
end
|
759
867
|
end
|
data/lib/na/next_action.rb
CHANGED
@@ -512,36 +512,36 @@ module NA
|
|
512
512
|
last_line = 0
|
513
513
|
in_action = false
|
514
514
|
content.split("\n").each.with_index do |line, idx|
|
515
|
-
if line
|
515
|
+
if line.project?
|
516
516
|
in_action = false
|
517
|
-
proj =
|
517
|
+
proj = line.project
|
518
518
|
indent = line.indent_level
|
519
519
|
|
520
|
-
if indent.zero?
|
520
|
+
if indent.zero? # top level project
|
521
521
|
parent = [proj]
|
522
|
-
elsif indent <= indent_level
|
522
|
+
elsif indent <= indent_level # if indent level is same or less, split parent before indent level and append
|
523
523
|
parent.slice!(indent, parent.count - indent)
|
524
524
|
parent.push(proj)
|
525
|
-
|
525
|
+
else # if indent level is greater, append project to parent
|
526
526
|
parent.push(proj)
|
527
527
|
end
|
528
528
|
|
529
529
|
projects.push(NA::Project.new(parent.join(':'), indent, idx, idx))
|
530
530
|
|
531
531
|
indent_level = indent
|
532
|
-
elsif line.
|
532
|
+
elsif line.blank?
|
533
533
|
in_action = false
|
534
|
-
elsif line
|
534
|
+
elsif line.action?
|
535
535
|
in_action = false
|
536
536
|
|
537
|
-
action = line.
|
537
|
+
action = line.action
|
538
538
|
new_action = NA::Action.new(file, File.basename(file, ".#{NA.extension}"), parent.dup, action, idx)
|
539
539
|
|
540
540
|
projects[-1].last_line = idx if projects.count.positive?
|
541
541
|
|
542
|
-
next if line
|
542
|
+
next if line.done? && !done
|
543
543
|
|
544
|
-
next if require_na && line
|
544
|
+
next if require_na && !line.na?
|
545
545
|
|
546
546
|
has_search = !optional.empty? || !required.empty? || !negated.empty?
|
547
547
|
|
@@ -600,11 +600,11 @@ module NA
|
|
600
600
|
db_dir = File.expand_path('~/.local/share/na')
|
601
601
|
db_file = 'tdlist.txt'
|
602
602
|
file = File.join(db_dir, db_file)
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
603
|
+
return unless File.exist?(file)
|
604
|
+
|
605
|
+
dirs = file.read_file.split("\n")
|
606
|
+
dirs.delete_if { |f| !File.exist?(f) }
|
607
|
+
File.open(file, 'w') { |f| f.puts dirs.join("\n") }
|
608
608
|
end
|
609
609
|
|
610
610
|
def list_projects(query: [], file_path: nil, depth: 1, paths: true)
|
@@ -624,7 +624,7 @@ module NA
|
|
624
624
|
output = if paths
|
625
625
|
"{bg}#{parts.join('{bw}/{bg}')}{x}"
|
626
626
|
else
|
627
|
-
parts.fill(
|
627
|
+
parts.fill('{bw}—{bg}', 0..-2)
|
628
628
|
"{bg}#{parts.join(' ')}{x}"
|
629
629
|
end
|
630
630
|
|
@@ -633,15 +633,15 @@ module NA
|
|
633
633
|
end
|
634
634
|
|
635
635
|
def list_todos(query: [])
|
636
|
-
if query
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
636
|
+
dirs = if query
|
637
|
+
match_working_dir(query, distance: 2, require_last: false)
|
638
|
+
else
|
639
|
+
file = database_path
|
640
|
+
content = File.exist?(file) ? file.read_file.strip : ''
|
641
|
+
notify('{br}Database empty', exit_code: 1) if content.empty?
|
642
|
+
|
643
|
+
content.split(/\n/)
|
644
|
+
end
|
645
645
|
|
646
646
|
dirs.map! do |dir|
|
647
647
|
"{xg}#{dir.sub(/^#{ENV['HOME']}/, '~').sub(%r{/([^/]+)\.#{NA.extension}$}, '/{xbw}\1{x}')}"
|
data/lib/na/string.rb
CHANGED
@@ -22,6 +22,35 @@ class ::String
|
|
22
22
|
prefix[1].gsub(/ /, "\t").scan(/\t/).count
|
23
23
|
end
|
24
24
|
|
25
|
+
def action?
|
26
|
+
self =~ /^[ \t]*- /
|
27
|
+
end
|
28
|
+
|
29
|
+
def blank?
|
30
|
+
strip =~ /^$/
|
31
|
+
end
|
32
|
+
|
33
|
+
def project?
|
34
|
+
!action? && self =~ /:( +@\S+(\([^)]*\))?)*$/
|
35
|
+
end
|
36
|
+
|
37
|
+
def project
|
38
|
+
m = match(/^([ \t]*)([^\-][^@()]+?): *(@\S+ *)*$/)
|
39
|
+
m ? m[2] : nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def action
|
43
|
+
sub(/^[ \t]*- /, '')
|
44
|
+
end
|
45
|
+
|
46
|
+
def done?
|
47
|
+
self =~ /@done/
|
48
|
+
end
|
49
|
+
|
50
|
+
def na?
|
51
|
+
self =~ /@#{NA.na_tag}\b/
|
52
|
+
end
|
53
|
+
|
25
54
|
##
|
26
55
|
## Colorize @tags with ANSI escapes
|
27
56
|
##
|
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.13<!--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
|
|
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.14
|
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-
|
11
|
+
date: 2022-11-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|