na 1.2.13 → 1.2.14
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 +14 -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,17 @@
|
|
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
|
+
|
1
15
|
### 1.2.13
|
2
16
|
|
3
17
|
2022-11-01 12:43
|
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-
|
11
|
+
date: 2022-11-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|