na 1.1.11 → 1.1.13

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b309ba145cf0859f1a9011cf62268261766338d79cb956810fab9c64863c37f2
4
- data.tar.gz: 6fc7e065d4d85bf70b9122634414274e6a44d640ffffeeee7409e9102115f11d
3
+ metadata.gz: 03c240ef72b49cbb692ba1f55da9a36036d72bd51bb29549ea87262cceedfd51
4
+ data.tar.gz: e61a921f6af384ae5615f54116cb4e4e0c9472ab76d4a347fc50ea5633fc80a3
5
5
  SHA512:
6
- metadata.gz: 63ee0b1ac9370533379956e8cd5f6571c8d39731c12c02404b2912d5cb97f49d32c988e4d23c12c50d1b2080412296bc58351680122abd26281580c49d93f722
7
- data.tar.gz: b7500123c7ea092acc42ccba0873942fe871bd201405e3ef2faa66f3d155c3d0ac60b530078d962114a4d0d66bdb8ea427b1452873c14e2b06419cb2675e822e
6
+ metadata.gz: e577876361f3022e29d4c56e06d7c507fe05ac81da752debe476b481f92f671ee85ab25cb06e32a0608baf1b926454ea4e68aa6391e32ee7c5e7e02a5751dd11
7
+ data.tar.gz: 660039f2b13e23365ffa88fa8a59266f50941275830175458f867a26bfcbcd3553c8e5dc6ef70da1d1ab5946fc9d7a3361bdf932f8d63eda23c3c692657a5074
data/CHANGELOG.md CHANGED
@@ -1,3 +1,30 @@
1
+ ### 1.1.13
2
+
3
+ 2022-10-06 06:28
4
+
5
+ #### IMPROVED
6
+
7
+ - When specifying arguments to `next`, allow paths separated by / to do more exact matching
8
+
9
+ ### 1.1.12
10
+
11
+ 2022-10-06 05:42
12
+
13
+ #### NEW
14
+
15
+ - `na add -d X` to allow adding new actions to todo files in subdirectories
16
+ - You can now perform <>= queries on tag values (`na tagged "priority>=3"`)
17
+ - You can now perform string matches on tag values (`na tagged "note*=markdown"`)
18
+ - You can use `--project X` to display only actions within a specific project. Specify subprojects with a path, e.g. `na/bugs`. Partial matches allowed, works with `next`, `find`, and `tagged`
19
+ - Find and tagged recognize * and ? as wildcards
20
+ - --regex flag for find command
21
+ - --invert command (like grep -v) for find
22
+ - -v/--invert for tagged command
23
+
24
+ #### IMPROVED
25
+
26
+ - Require value 1-9 for --depth option
27
+
1
28
  ### 1.1.11
2
29
 
3
30
  2022-10-05 08:56
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- na (1.1.11)
4
+ na (1.1.13)
5
5
  gli (~> 2.21.0)
6
6
  tty-reader (~> 0.9, >= 0.9.0)
7
7
  tty-screen (~> 0.8, >= 0.8.1)
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.11
12
+ The current version of `na` is 1.1.13
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.
data/bin/na CHANGED
@@ -20,7 +20,7 @@ class App
20
20
 
21
21
  desc 'File extension to consider a todo file'
22
22
  default_value 'taskpaper'
23
- arg_name 'FILE_EXTENSION'
23
+ arg_name 'EXT'
24
24
  flag :ext
25
25
 
26
26
  desc 'Tag to consider a next action'
@@ -48,7 +48,7 @@ class App
48
48
  desc 'Recurse to depth'
49
49
  arg_name 'DEPTH'
50
50
  default_value 1
51
- flag %i[d depth], type: :integer, must_match: /^\d+$/
51
+ flag %i[d depth], type: :integer, must_match: /^[1-9]$/
52
52
 
53
53
  desc 'Display verbose output'
54
54
  switch %i[debug]
@@ -59,13 +59,19 @@ class App
59
59
  c.example 'na next', desc: 'display the next actions from any todo files in the current directory'
60
60
  c.example 'na next -d 3', desc: 'display the next actions from the current directory, traversing 3 levels deep'
61
61
  c.example 'na next marked', desc: 'display next actions for a project you visited in the past'
62
+
62
63
  c.desc 'Recurse to depth'
63
64
  c.arg_name 'DEPTH'
64
- c.flag %i[d depth], type: :integer, must_match: /^\d+$/
65
+ c.flag %i[d depth], type: :integer, must_match: /^[1-9]$/
65
66
 
66
67
  c.desc 'Alternate tag to search for'
68
+ c.arg_name 'TAG'
67
69
  c.flag %i[t tag]
68
70
 
71
+ c.desc 'Show actions from a specific project'
72
+ c.arg_name 'PROJECT[/SUBPROJECT]'
73
+ c.flag %i[proj project]
74
+
69
75
  c.action do |global_options, options, args|
70
76
  if global_options[:add]
71
77
  cmd = ['add']
@@ -100,6 +106,7 @@ class App
100
106
  files, actions = NA.parse_actions(depth: depth,
101
107
  query: tokens,
102
108
  tag: tag,
109
+ project: options[:project],
103
110
  require_na: require_na)
104
111
 
105
112
  NA.output_actions(actions, depth, files: files)
@@ -122,9 +129,11 @@ class App
122
129
  c.switch %i[n note], negatable: false
123
130
 
124
131
  c.desc 'Add a priority level 1-5'
132
+ c.arg_name 'PRIO'
125
133
  c.flag %i[p priority], must_match: /[1-5]/, type: :integer, default_value: 0
126
134
 
127
135
  c.desc 'Add action to specific project'
136
+ c.arg_name 'PROJECT'
128
137
  c.default_value 'Inbox'
129
138
  c.flag %i[to]
130
139
 
@@ -139,6 +148,10 @@ class App
139
148
  c.arg_name 'PATH'
140
149
  c.flag %i[f file]
141
150
 
151
+ c.desc 'Search for files X directories deep'
152
+ c.arg_name 'DEPTH'
153
+ c.flag %i[d depth], must_match: /^[1-9]$/, type: :integer, default_value: 1
154
+
142
155
  c.action do |_global_options, options, args|
143
156
  reader = TTY::Reader.new
144
157
  action = if args.count.positive?
@@ -195,7 +208,7 @@ class App
195
208
  end
196
209
  end
197
210
  else
198
- files = NA.find_files(depth: 1)
211
+ files = NA.find_files(depth: options[:depth])
199
212
  if files.count.zero?
200
213
  print NA::Color.template('{by}No todo file found, create one? {w}(y/{g}N{w}){x} ')
201
214
  res = reader.read_char
@@ -221,19 +234,28 @@ class App
221
234
  long_desc 'Search tokens are separated by spaces. Actions matching any token in the pattern will be shown
222
235
  (partial matches allowed). Add a + before a token to make it required, e.g. `na find +feature +maybe`'
223
236
  arg_name 'PATTERN'
224
- command %i[find] do |c|
237
+ command %i[find grep] do |c|
225
238
  c.example 'na find feature +idea +swift', desc: 'Find all actions containing feature, idea, and swift'
226
239
  c.example 'na find -x feature idea', desc: 'Find all actions containing the exact text "feature idea"'
227
240
  c.example 'na find -d 3 swift obj-c', desc: 'Find all actions 3 directories deep containing either swift or obj-c'
228
241
 
242
+ c.desc 'Interpret search pattern as regular expression'
243
+ c.switch %i[e regex], negatable: false
244
+
229
245
  c.desc 'Match pattern exactly'
230
246
  c.switch %i[x exact], negatable: false
231
247
 
232
248
  c.desc 'Recurse to depth'
233
249
  c.arg_name 'DEPTH'
234
- c.default_value 1
235
250
  c.flag %i[d depth], type: :integer, must_match: /^\d+$/
236
251
 
252
+ c.desc 'Show actions from a specific project'
253
+ c.arg_name 'PROJECT[/SUBPROJECT]'
254
+ c.flag %i[proj project]
255
+
256
+ c.desc 'Show actions not matching search pattern'
257
+ c.switch %i[v invert], negatable: false
258
+
237
259
  c.action do |global_options, options, args|
238
260
  depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
239
261
  3
@@ -243,6 +265,8 @@ class App
243
265
  tokens = nil
244
266
  if options[:exact]
245
267
  tokens = args.join(' ')
268
+ elsif options[:regex]
269
+ tokens = Regexp.new(args.join(' '), Regexp::IGNORECASE)
246
270
  else
247
271
  tokens = []
248
272
  args.each do |arg|
@@ -257,6 +281,9 @@ class App
257
281
 
258
282
  files, actions = NA.parse_actions(depth: depth,
259
283
  search: tokens,
284
+ negate: options[:invert],
285
+ regex: options[:regex],
286
+ project: options[:project],
260
287
  require_na: false)
261
288
  NA.output_actions(actions, depth, files: files)
262
289
  end
@@ -277,6 +304,13 @@ class App
277
304
  c.default_value 1
278
305
  c.flag %i[d depth], type: :integer, must_match: /^\d+$/
279
306
 
307
+ c.desc 'Show actions from a specific project'
308
+ c.arg_name 'PROJECT[/SUBPROJECT]'
309
+ c.flag %i[proj project]
310
+
311
+ c.desc 'Show actions not matching tags'
312
+ c.switch %i[v invert], negatable: false
313
+
280
314
  c.action do |global_options, options, args|
281
315
  depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
282
316
  3
@@ -285,9 +319,9 @@ class App
285
319
  end
286
320
 
287
321
  tags = []
288
- args.each do |arg|
322
+ args.join(',').split(/ *, */).each do |arg|
289
323
  # TODO: <> comparisons do nothing right now
290
- m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>]+)(?:(?<op>[=<>]+)(?<val>\S+))?$/)
324
+ m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>*$\^]+)(?:(?<op>[=<>*$\^]+)(?<val>\S+))?$/)
291
325
  tags.push({
292
326
  tag: m['tag'],
293
327
  comp: m['op'],
@@ -299,6 +333,8 @@ class App
299
333
 
300
334
  files, actions = NA.parse_actions(depth: depth,
301
335
  tag: tags,
336
+ negate: options[:invert],
337
+ project: options[:project],
302
338
  require_na: false)
303
339
  NA.output_actions(actions, depth, files: files)
304
340
  end
data/lib/na/action.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  module NA
4
4
  class Action < Hash
5
- attr_reader :file, :project, :parent, :action
5
+ attr_reader :file, :project, :parent, :action, :tags
6
6
 
7
7
  def initialize(file, project, parent, action)
8
8
  super()
@@ -11,6 +11,7 @@ module NA
11
11
  @project = project
12
12
  @parent = parent
13
13
  @action = action
14
+ @tags = scan_tags
14
15
  end
15
16
 
16
17
  def to_s
@@ -68,5 +69,106 @@ module NA
68
69
  .gsub(/%parents?/, parents)
69
70
  .gsub(/%action/, action))
70
71
  end
72
+
73
+ def tags_match?(any: [], all: [], none: [])
74
+ tag_matches_any(any) && tag_matches_all(all) && tag_matches_none(none)
75
+ end
76
+
77
+ def search_match?(any: [], all: [], none: [])
78
+ search_matches_any(any) && search_matches_all(all) && search_matches_none(none)
79
+ end
80
+
81
+ private
82
+
83
+ def search_matches_none(regexes)
84
+ regexes.each do |rx|
85
+ return false if @action.match(Regexp.new(rx, Regexp::IGNORECASE))
86
+ end
87
+ true
88
+ end
89
+
90
+ def search_matches_any(regexes)
91
+ return true if regexes.empty?
92
+
93
+ regexes.each do |rx|
94
+ return true if @action.match(Regexp.new(rx, Regexp::IGNORECASE))
95
+ end
96
+ false
97
+ end
98
+
99
+ def search_matches_all(regexes)
100
+ regexes.each do |rx|
101
+ return false unless @action.match(Regexp.new(rx, Regexp::IGNORECASE))
102
+ end
103
+ true
104
+ end
105
+
106
+ def tag_matches_none(tags)
107
+ tags.each do |tag|
108
+ return false if compare_tag(tag)
109
+ end
110
+ true
111
+ end
112
+
113
+ def tag_matches_any(tags)
114
+ return true if tags.empty?
115
+
116
+ tags.each do |tag|
117
+ return true if compare_tag(tag)
118
+ end
119
+ false
120
+ end
121
+
122
+ def tag_matches_all(tags)
123
+ tags.each do |tag|
124
+ return false unless compare_tag(tag)
125
+ end
126
+ true
127
+ end
128
+
129
+ def compare_tag(tag)
130
+ return false unless @tags.key?(tag[:tag])
131
+
132
+ return true if tag[:comp].nil?
133
+
134
+ tag_val = @tags[tag[:tag]]
135
+ val = tag[:value]
136
+
137
+ return false if tag_val.nil?
138
+
139
+ return case tag[:comp]
140
+ when /^>$/
141
+ tag_val.to_f > val.to_f
142
+ when /^<$/
143
+ tag_val.to_f < val.to_f
144
+ when /^<=$/
145
+ tag_val.to_f <= val.to_f
146
+ when /^>=$/
147
+ tag_val.to_f >= val.to_f
148
+ when /^==?$/
149
+ tag_val =~ /^#{val.wildcard_to_rx}$/
150
+ when /^\$=$/
151
+ tag_val =~ /#{val.wildcard_to_rx}$/i
152
+ when /^\*=$/
153
+ tag_val =~ /#{val.wildcard_to_rx}/i
154
+ when /^\^=$/
155
+ tag_val =~ /^#{val.wildcard_to_rx}/
156
+ else
157
+ false
158
+ end
159
+ end
160
+
161
+ def scan_tags
162
+ tags = {}
163
+ rx = /(?<= |^)@(?<tag>\S+?)(?:\((?<value>.*?)\))?(?= |$)/
164
+ all_tags = []
165
+ @action.scan(rx) { all_tags << Regexp.last_match }
166
+ all_tags.each do |m|
167
+ tag = m.named_captures.symbolize_keys
168
+ tags[tag[:tag]] = tag[:value]
169
+ end
170
+
171
+ tags
172
+ end
71
173
  end
72
174
  end
data/lib/na/hash.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ::Hash
4
+ def symbolize_keys
5
+ each_with_object({}) { |(k, v), hsh| hsh[k.to_sym] = v.is_a?(Hash) ? v.symbolize_keys : v }
6
+ end
7
+ end
@@ -103,45 +103,49 @@ module NA
103
103
  puts actions.map { |action| action.pretty(template: { output: template }) }
104
104
  end
105
105
 
106
- def parse_actions(depth: 1, query: nil, tag: nil, search: nil, require_na: true)
106
+ def parse_actions(depth: 1, query: nil, tag: nil, search: nil, negate: false, regex: false, project: nil, require_na: true)
107
107
  actions = []
108
108
  required = []
109
109
  optional = []
110
110
  negated = []
111
+ required_tag = []
112
+ optional_tag = []
113
+ negated_tag = []
111
114
 
112
115
  tag&.each do |t|
113
116
  unless t[:tag].nil?
114
- new_rx = " @#{t[:tag]}\\b"
115
- new_rx = "#{new_rx}\\(#{t[:value]}\\)" if t[:value]
116
-
117
- optional.push(new_rx) unless t[:negate]
118
- required.push(new_rx) if t[:required] && !t[:negate]
119
- negated.push(new_rx) if t[:negate]
117
+ if negate
118
+ optional_tag.push(t) if t[:negate]
119
+ required_tag.push(t) if t[:required] && t[:negate]
120
+ negated_tag.push(t) unless t[:negate]
121
+ else
122
+ optional_tag.push(t) unless t[:negate]
123
+ required_tag.push(t) if t[:required] && !t[:negate]
124
+ negated_tag.push(t) if t[:negate]
125
+ end
120
126
  end
121
127
  end
122
128
 
123
129
  unless search.nil?
124
- if search.is_a?(String)
125
- optional.push(search)
126
- required.push(search)
130
+ if regex || search.is_a?(String)
131
+ if negate
132
+ negated.push(search)
133
+ else
134
+ optional.push(search)
135
+ required.push(search)
136
+ end
127
137
  else
128
138
  search.each do |t|
129
- new_rx = t[:token].to_s
130
-
131
- optional.push(new_rx) unless t[:negate]
132
- required.push(new_rx) if t[:required] && !t[:negate]
133
- negated.push(new_rx) if t[:negate]
139
+ optional, required, negated = parse_search(t, negate)
134
140
  end
135
141
  end
136
142
  end
137
143
 
138
- na_tag = "@#{NA.na_tag.sub(/^@/, '')}"
139
-
140
- if query.nil?
141
- files = find_files(depth: depth)
142
- else
143
- files = match_working_dir(query)
144
- end
144
+ files = if query.nil?
145
+ find_files(depth: depth)
146
+ else
147
+ match_working_dir(query)
148
+ end
145
149
 
146
150
  files.each do |file|
147
151
  save_working_dir(File.expand_path(file))
@@ -166,13 +170,24 @@ module NA
166
170
  elsif line =~ /^[ \t]*- / && line !~ / @done/
167
171
  next if require_na && line !~ /@#{NA.na_tag}\b/
168
172
 
169
- unless optional.empty? && required.empty? && negated.empty?
170
- next unless line.matches(any: optional, all: required, none: negated)
173
+ action = line.sub(/^[ \t]*- /, '').sub(/ @#{NA.na_tag}\b/, '')
174
+ new_action = NA::Action.new(file, File.basename(file, ".#{NA.extension}"), parent.dup, action)
171
175
 
176
+ has_search = !optional.empty? || !required.empty? || !negated.empty?
177
+ next if has_search && !new_action.search_match?(any: optional,
178
+ all: required,
179
+ none: negated)
180
+
181
+ if project
182
+ rx = project.split(%r{[/:]}).join('.*?/.*?')
183
+ next unless parent.join('/') =~ Regexp.new(rx, Regexp::IGNORECASE)
172
184
  end
173
185
 
174
- action = line.sub(/^[ \t]*- /, '').sub(/ @#{NA.na_tag}\b/, '')
175
- new_action = NA::Action.new(file, File.basename(file, ".#{NA.extension}"), parent.dup, action)
186
+ has_tag = !optional_tag.empty? || !required_tag.empty? || !negated_tag.empty?
187
+ next if has_tag && !new_action.tags_match?(any: optional_tag,
188
+ all: required_tag,
189
+ none: negated_tag)
190
+
176
191
  actions.push(new_action)
177
192
  end
178
193
  end
@@ -180,6 +195,40 @@ module NA
180
195
  [files, actions]
181
196
  end
182
197
 
198
+ def edit_file(file: nil, app: nil)
199
+ os_open(file, app: app) if file && File.exist?(file)
200
+ end
201
+
202
+ ##
203
+ ## Platform-agnostic open command
204
+ ##
205
+ ## @param file [String] The file to open
206
+ ##
207
+ def os_open(file, app: nil)
208
+ os = RbConfig::CONFIG['target_os']
209
+ case os
210
+ when /darwin.*/i
211
+ darwin_open(file, app: app)
212
+ when /mingw|mswin/i
213
+ win_open(file)
214
+ else
215
+ linux_open(file)
216
+ end
217
+ end
218
+
219
+ def weed_cache_file
220
+ db_dir = File.expand_path('~/.local/share/na')
221
+ db_file = 'tdlist.txt'
222
+ file = File.join(db_dir, db_file)
223
+ if File.exist?(file)
224
+ dirs = IO.read(file).split("\n")
225
+ dirs.delete_if { |f| !File.exist?(f) }
226
+ File.open(file, 'w') { |f| f.puts dirs.join("\n") }
227
+ end
228
+ end
229
+
230
+ private
231
+
183
232
  ##
184
233
  ## Generate a menu of options and allow user selection
185
234
  ##
@@ -207,6 +256,25 @@ module NA
207
256
  res
208
257
  end
209
258
 
259
+ def parse_search(tag, negate)
260
+ required = []
261
+ optional = []
262
+ negated = []
263
+ new_rx = tag[:token].to_s.wildcard_to_rx
264
+
265
+ if negate
266
+ optional.push(new_rx) if tag[:negate]
267
+ required.push(new_rx) if tag[:required] && tag[:negate]
268
+ negated.push(new_rx) unless tag[:negate]
269
+ else
270
+ optional.push(new_rx) unless tag[:negate]
271
+ required.push(new_rx) if tag[:required] && !tag[:negate]
272
+ negated.push(new_rx) if tag[:negate]
273
+ end
274
+
275
+ [optional, required, negated]
276
+ end
277
+
210
278
  ##
211
279
  ## Get path to database of known todo files
212
280
  ##
@@ -249,7 +317,7 @@ module NA
249
317
  notify('{r}No na database found', exit_code: 1) unless File.exist?(file)
250
318
 
251
319
  dirs = IO.read(file).split("\n")
252
- dirs.delete_if { |d| !d.matches(any: optional, all: required) }
320
+ dirs.delete_if { |d| !d.dir_matches(any: optional, all: required) }
253
321
  dirs.sort.uniq
254
322
  end
255
323
 
@@ -262,21 +330,6 @@ module NA
262
330
  File.open(file, 'w') { |f| f.puts dirs.join("\n") }
263
331
  end
264
332
 
265
- def weed_cache_file
266
- db_dir = File.expand_path('~/.local/share/na')
267
- db_file = 'tdlist.txt'
268
- file = File.join(db_dir, db_file)
269
- if File.exist?(file)
270
- dirs = IO.read(file).split("\n")
271
- dirs.delete_if { |f| !File.exist?(f) }
272
- File.open(file, 'w') { |f| f.puts dirs.join("\n") }
273
- end
274
- end
275
-
276
- def edit_file(file: nil, app: nil)
277
- os_open(file, app: app) if file && File.exist?(file)
278
- end
279
-
280
333
  def darwin_open(file, app: nil)
281
334
  if app
282
335
  `open -a "#{app}" #{Shellwords.escape(file)}`
@@ -296,22 +349,5 @@ module NA
296
349
  notify('{r}Unable to determine executable for `xdg-open`.')
297
350
  end
298
351
  end
299
-
300
- ##
301
- ## Platform-agnostic open command
302
- ##
303
- ## @param file [String] The file to open
304
- ##
305
- def os_open(file, app: nil)
306
- os = RbConfig::CONFIG['target_os']
307
- case os
308
- when /darwin.*/i
309
- darwin_open(file, app: app)
310
- when /mingw|mswin/i
311
- win_open(file)
312
- else
313
- linux_open(file)
314
- end
315
- end
316
352
  end
317
353
  end
data/lib/na/string.rb CHANGED
@@ -24,6 +24,14 @@ class ::String
24
24
  gsub(/(\s|m)(@[^ ("']+)(?:(\()(.*?)(\)))?/, "\\1#{tag_color}\\2#{paren_color}\\3#{value_color}\\4#{paren_color}\\5#{last_color}")
25
25
  end
26
26
 
27
+ def dir_to_rx
28
+ split(%r{[/:]}).join('.*?/.*?') + '[^/]+$'
29
+ end
30
+
31
+ def dir_matches(any: [], all: [])
32
+ matches_any(any.map { |token| token.dir_to_rx }) && matches_all(all.map { |token| token.dir_to_rx })
33
+ end
34
+
27
35
  def matches(any: [], all: [], none: [])
28
36
  matches_any(any) && matches_all(all) && matches_none(none)
29
37
  end
@@ -36,6 +44,7 @@ class ::String
36
44
  end
37
45
 
38
46
  def matches_any(regexes)
47
+ string = dup
39
48
  regexes.each do |rx|
40
49
  return true if match(Regexp.new(rx, Regexp::IGNORECASE))
41
50
  end
@@ -49,6 +58,10 @@ class ::String
49
58
  true
50
59
  end
51
60
 
61
+ def wildcard_to_rx
62
+ gsub(/\./, '\\.').gsub(/\*/, '.*?').gsub(/\?/, '.')
63
+ end
64
+
52
65
  def cap_first!
53
66
  replace cap_first
54
67
  end
data/lib/na/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Na
2
- VERSION = '1.1.11'
2
+ VERSION = '1.1.13'
3
3
  end
data/lib/na.rb CHANGED
@@ -1,14 +1,14 @@
1
- require 'na/version.rb'
1
+ # frozen_string_literal: true
2
2
 
3
- # Add requires for other files you add to your project here, so
4
- # you just need to require this one file in your bin file
3
+ require 'na/version'
5
4
  require 'fileutils'
6
5
  require 'shellwords'
7
6
  require 'tty-screen'
8
7
  require 'tty-reader'
9
8
  require 'tty-which'
10
- require 'na/colors.rb'
11
- require 'na/string.rb'
12
- require 'na/action.rb'
13
- require 'na/next_action.rb'
14
- require 'na/prompt.rb'
9
+ require 'na/hash'
10
+ require 'na/colors'
11
+ require 'na/string'
12
+ require 'na/action'
13
+ require 'na/next_action'
14
+ require 'na/prompt'
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.10<!--END VER-->.
12
+ The current version of `na` is <!--VER-->1.1.12<!--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.1.11
4
+ version: 1.1.13
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-10-05 00:00:00.000000000 Z
11
+ date: 2022-10-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -169,6 +169,7 @@ files:
169
169
  - lib/na.rb
170
170
  - lib/na/action.rb
171
171
  - lib/na/colors.rb
172
+ - lib/na/hash.rb
172
173
  - lib/na/next_action.rb
173
174
  - lib/na/prompt.rb
174
175
  - lib/na/string.rb