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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2d39100de907ee238aac6e9f51cb976e0736822294bc602cec67886aecba12e0
4
- data.tar.gz: 9936c5bb6fb340d1a9ef756b3b6245560021685e7a538c0a72d10e300ecf527d
3
+ metadata.gz: da1fc18252895707dfb04a598aca5ac535b4c65959b564d11d56497796a09249
4
+ data.tar.gz: 7d483b22ddd0015b02bbea54c5026ba11b3cfc58597d790eed5d0902bbb285ba
5
5
  SHA512:
6
- metadata.gz: 3c6c082c7f2d104916449a1a984191c3e9c06f53f64a6c5860200569d2981e677dc0fc05a47472f186fee46867ccdb0fc5a52925789242ebef87ae441d36f758
7
- data.tar.gz: f5f3a48fecd5debb35e9d9e03eb3e6f85565912f478395100e63934edfae0b32171a7b4619c474645c7562de426de24e0d6d62312dff9e198423c6850823d6ca
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- na (1.2.13)
4
+ na (1.2.14)
5
5
  chronic (~> 0.10, >= 0.10.2)
6
6
  gli (~> 2.21.0)
7
7
  mdless (~> 1.0, >= 1.0.32)
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.13
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.13
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: tag, value: nil }, { tag: 'done', value: nil, negate: true}]
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
@@ -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 !~ /^[ \t]*- / && line =~ /^([ \t]*)([^\-][^@()]+?): *(@\S+ *)*$/
515
+ if line.project?
516
516
  in_action = false
517
- proj = Regexp.last_match(2)
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
- elsif indent > indent_level
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.strip =~ /^$/
532
+ elsif line.blank?
533
533
  in_action = false
534
- elsif line =~ /^[ \t]*- /
534
+ elsif line.action?
535
535
  in_action = false
536
536
 
537
- action = line.sub(/^[ \t]*- /, '')
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 =~ /@done/ && !done
542
+ next if line.done? && !done
543
543
 
544
- next if require_na && line !~ /@#{NA.na_tag}\b/
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
- if File.exist?(file)
604
- dirs = file.read_file.split("\n")
605
- dirs.delete_if { |f| !File.exist?(f) }
606
- File.open(file, 'w') { |f| f.puts dirs.join("\n") }
607
- end
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("{bw}—{bg}", 0..-2)
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
- dirs = 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
- dirs = content.split(/\n/)
644
- end
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
@@ -1,3 +1,3 @@
1
1
  module Na
2
- VERSION = '1.2.13'
2
+ VERSION = '1.2.14'
3
3
  end
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<!--END VER-->.
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.13
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-01 00:00:00.000000000 Z
11
+ date: 2022-11-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake