na 1.1.11 → 1.1.13

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: 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