na 1.1.21 → 1.1.23

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: '0430897c007446c9ad1a1e3463ddc3d8e192a43e8d60a46a9669745086ea45f3'
4
- data.tar.gz: e33799f49bbaf623d188c64f549e71dcea41b317b95b3b5d0f72f8a378a3f9fd
3
+ metadata.gz: c8bee2cada1590cdc80fe9943947c719649d441ed15a6c51228fa0bebe4e94eb
4
+ data.tar.gz: 3dfc5a53bb88952243388b86e853af744b76938cbc69ef3b6f6ac8e58c03a9fc
5
5
  SHA512:
6
- metadata.gz: d8c9a03b08abf20cb826e452ad75478b0f58dc0309ce51bee29e3b31b6c37de8e99cdd22926aef1ffe10183b151b54b6047196ce5c8987c32b0fa013942d5673
7
- data.tar.gz: 7bb18376e8b53aa2c283206058c48dce786576d3a50d6e9567a0958011cab7f2f7203fecf0142cc49f3d5abc49a0342a4a3c20ff19318f79ac2d26ca00af206b
6
+ metadata.gz: 0603c3187363e3c5895f9e49af6deab11779cbf47442f4f76b7b5ad2608e4676d0cb337368e2a0d6a5f9bad7f5aff957d63e986cefe1997c671a6b6b5193e3e4
7
+ data.tar.gz: e570fe4a32d7e45b4bd2171c73bee68adb6017ae22901c92486ccbe1bdb2461a50dac988d8ed862aa85237c070ebb305d3b6c2c61afc920826568c628d630faf
data/CHANGELOG.md CHANGED
@@ -1,3 +1,30 @@
1
+ ### 1.1.23
2
+
3
+ 2022-10-07 10:02
4
+
5
+ #### NEW
6
+
7
+ - Saved searches. Add `--save TITLE` to `tagged` or `find` commands to save the parameters for use with `na saved TITLE`
8
+
9
+ #### FIXED
10
+
11
+ - Restore wildcard capability of tag searches
12
+
13
+ ### 1.1.22
14
+
15
+ 2022-10-07 05:58
16
+
17
+ #### IMPROVED
18
+
19
+ - Help output and code documentation
20
+ - Allow wildcards (* and ?) when matching todo history
21
+ - Allow multiple todo queries separated by comma
22
+
23
+ #### FIXED
24
+
25
+ - Remove file extension when matching todo history
26
+ - Todo history query failed on exact match
27
+
1
28
  ### 1.1.21
2
29
 
3
30
  2022-10-07 04:26
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- na (1.1.21)
4
+ na (1.1.23)
5
5
  chronic (~> 0.10, >= 0.10.2)
6
6
  gli (~> 2.21.0)
7
7
  tty-reader (~> 0.9, >= 0.9.0)
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.21
12
+ The current version of `na` is 1.1.23
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.
@@ -59,7 +59,7 @@ SYNOPSIS
59
59
  na [global options] command [command options] [arguments...]
60
60
 
61
61
  VERSION
62
- 1.1.21
62
+ 1.1.23
63
63
 
64
64
  GLOBAL OPTIONS
65
65
  -a, --[no-]add - Add a next action (deprecated, for backwards compatibility)
@@ -82,6 +82,7 @@ COMMANDS
82
82
  initconfig - Initialize the config file using current global options
83
83
  next, show - Show next actions
84
84
  prompt - Show or install prompt hooks for the current shell
85
+ saved - Execute a saved search
85
86
  tagged - Find actions matching a tag
86
87
  todos - Show list of known todo files
87
88
  ```
@@ -171,9 +172,10 @@ DESCRIPTION
171
172
  COMMAND OPTIONS
172
173
  -d, --depth=DEPTH - Recurse to depth (default: none)
173
174
  -e, --regex - Interpret search pattern as regular expression
174
- --in=TODO_PATH - Show actions from a specific todo file in history (default: none)
175
+ --in=TODO_PATH - Show actions from a specific todo file in history. May use wildcards (* and ?) (default: none)
175
176
  -o, --or - Combine search tokens with OR, displaying actions matching ANY of the terms
176
177
  --proj, --project=PROJECT[/SUBPROJECT] - Show actions from a specific project (default: none)
178
+ --save=TITLE - Save this search for future use (default: none)
177
179
  -v, --invert - Show actions not matching search pattern
178
180
  -x, --exact - Match pattern exactly
179
181
 
@@ -209,9 +211,9 @@ EXAMPLES
209
211
 
210
212
  Examples:
211
213
 
212
- - `na show` (list all next actions in the current directory)
213
- - `na show -d 3` (list all next actions in the current directory and look for additional files 3 levels deep from there)
214
- - `na show marked2` (show next actions from another directory you've previously used na on)
214
+ - `na next` (list all next actions in the current directory)
215
+ - `na next -d 3` (list all next actions in the current directory and look for additional files 3 levels deep from there)
216
+ - `na next marked2` (show next actions from another directory you've previously used na on)
215
217
 
216
218
  ```
217
219
  NAME
@@ -222,7 +224,7 @@ SYNOPSIS
222
224
  na [global options] next [command options] [QUERY]
223
225
 
224
226
  DESCRIPTION
225
- Next actions are actions which contain the next action tag (default @na), do not contain @done, and are not in the Archive project.
227
+ Next actions are actions which contain the next action tag (default @na), do not contain @done, and are not in the Archive project. Arguments will target a todo file from history, whether it's in the current directory or not. Todo file queries can include path components separated by / or :, and may use wildcards (`*` to match any text, `?` to match a single character). Multiple queries allowed (separate arguments or separated by comma).
226
228
 
227
229
  COMMAND OPTIONS
228
230
  -d, --depth=DEPTH - Recurse to depth (default: 2)
@@ -256,7 +258,7 @@ SYNOPSIS
256
258
  na [global options] next [command options] [QUERY]
257
259
 
258
260
  DESCRIPTION
259
- Next actions are actions which contain the next action tag (default @na), do not contain @done, and are not in the Archive project.
261
+ Next actions are actions which contain the next action tag (default @na), do not contain @done, and are not in the Archive project. Arguments will target a todo file from history, whether it's in the current directory or not. Todo file queries can include path components separated by / or :, and may use wildcards (`*` to match any text, `?` to match a single character). Multiple queries allowed (separate arguments or separated by comma).
260
262
 
261
263
  COMMAND OPTIONS
262
264
  -d, --depth=DEPTH - Recurse to depth (default: 2)
data/bin/na CHANGED
@@ -54,8 +54,12 @@ class App
54
54
  switch %i[debug]
55
55
 
56
56
  desc 'Show next actions'
57
- long_desc "Next actions are actions which contain the next action tag (default @na),
58
- do not contain @done, and are not in the Archive project."
57
+ long_desc 'Next actions are actions which contain the next action tag (default @na),
58
+ do not contain @done, and are not in the Archive project.
59
+
60
+ Arguments will target a todo file from history, whether it\'s in the current
61
+ directory or not. Todo file queries can include path components separated by /
62
+ or :, and may use wildcards (`*` to match any text, `?` to match a single character). Multiple queries allowed (separate arguments or separated by comma).'
59
63
  arg_name 'QUERY', optional: true
60
64
  command %i[next show] do |c|
61
65
  c.example 'na next', desc: 'display the next actions from any todo files in the current directory'
@@ -93,11 +97,13 @@ class App
93
97
  if args.count.positive?
94
98
  tokens = []
95
99
  args.each do |arg|
96
- m = arg.match(/^(?<req>\+)?(?<tok>.*?)$/)
97
- tokens.push({
98
- token: m['tok'],
99
- required: !m['req'].nil?
100
- })
100
+ arg.split(/ *, */).each do |a|
101
+ m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
102
+ tokens.push({
103
+ token: m['tok'],
104
+ required: !m['req'].nil?
105
+ })
106
+ end
101
107
  end
102
108
  end
103
109
 
@@ -251,7 +257,7 @@ class App
251
257
  c.arg_name 'DEPTH'
252
258
  c.flag %i[d depth], type: :integer, must_match: /^\d+$/
253
259
 
254
- c.desc 'Show actions from a specific todo file in history'
260
+ c.desc 'Show actions from a specific todo file in history. May use wildcards (* and ?)'
255
261
  c.arg_name 'TODO_PATH'
256
262
  c.flag %i[in]
257
263
 
@@ -265,7 +271,16 @@ class App
265
271
  c.desc 'Show actions not matching search pattern'
266
272
  c.switch %i[v invert], negatable: false
267
273
 
274
+ c.desc 'Save this search for future use'
275
+ c.arg_name 'TITLE'
276
+ c.flag %i[save]
277
+
268
278
  c.action do |global_options, options, args|
279
+ if options[:save]
280
+ title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
281
+ NA.save_search(title, "find #{NA.command_line.map { |c| "\"#{c}\"" }.join(' ')}")
282
+ end
283
+
269
284
  depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
270
285
  3
271
286
  else
@@ -292,10 +307,14 @@ class App
292
307
 
293
308
  todo = nil
294
309
  if options[:in]
295
- todo = [{
296
- token: options[:in],
297
- required: true
298
- }]
310
+ todo = []
311
+ options[:in].split(/ *, */).each do |a|
312
+ m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
313
+ todo.push({
314
+ token: m['tok'],
315
+ required: !m['req'].nil?
316
+ })
317
+ end
299
318
  end
300
319
 
301
320
  files, actions = NA.parse_actions(depth: depth,
@@ -335,7 +354,7 @@ class App
335
354
  c.default_value 1
336
355
  c.flag %i[d depth], type: :integer, must_match: /^\d+$/
337
356
 
338
- c.desc 'Show actions from a specific todo file in history'
357
+ c.desc 'Show actions from a specific todo file in history. May use wildcards (* and ?)'
339
358
  c.arg_name 'TODO_PATH'
340
359
  c.flag %i[in]
341
360
 
@@ -349,7 +368,16 @@ class App
349
368
  c.desc 'Show actions not matching tags'
350
369
  c.switch %i[v invert], negatable: false
351
370
 
371
+ c.desc 'Save this search for future use'
372
+ c.arg_name 'TITLE'
373
+ c.flag %i[save]
374
+
352
375
  c.action do |global_options, options, args|
376
+ if options[:save]
377
+ title = options[:save].gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
378
+ NA.save_search(title, "tagged #{NA.command_line.map { |cmd| "\"#{cmd}\"" }.join(' ')}")
379
+ end
380
+
353
381
  depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
354
382
  3
355
383
  else
@@ -361,9 +389,10 @@ class App
361
389
  all_req = args.join(' ') !~ /[+!\-]/ && !options[:or]
362
390
  args.join(',').split(/ *, */).each do |arg|
363
391
  # TODO: <> comparisons do nothing right now
364
- m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>*$\^]+)(?:(?<op>[=<>*$\^]+)(?<val>.*?))?$/)
392
+ m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
393
+
365
394
  tags.push({
366
- tag: m['tag'],
395
+ tag: m['tag'].wildcard_to_rx,
367
396
  comp: m['op'],
368
397
  value: m['val'],
369
398
  required: all_req || (!m['req'].nil? && m['req'] == '+'),
@@ -373,10 +402,14 @@ class App
373
402
 
374
403
  todo = nil
375
404
  if options[:in]
376
- todo = [{
377
- token: options[:in],
378
- required: true
379
- }]
405
+ todo = []
406
+ options[:in].split(/ *, */).each do |a|
407
+ m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
408
+ todo.push({
409
+ token: m['tok'],
410
+ required: !m['req'].nil?
411
+ })
412
+ end
380
413
  end
381
414
 
382
415
  files, actions = NA.parse_actions(depth: depth,
@@ -469,11 +502,13 @@ class App
469
502
  if args.count.positive?
470
503
  tokens = []
471
504
  args.each do |arg|
472
- m = arg.match(/^(?<req>\+)?(?<tok>.*?)$/)
473
- tokens.push({
474
- token: m['tok'],
475
- required: !m['req'].nil?
476
- })
505
+ arg.split(/ *, */).each do |a|
506
+ m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
507
+ tokens.push({
508
+ token: m['tok'],
509
+ required: !m['req'].nil?
510
+ })
511
+ end
477
512
  end
478
513
  end
479
514
 
@@ -529,6 +564,26 @@ class App
529
564
  end
530
565
  end
531
566
 
567
+ desc 'Execute a saved search'
568
+ long_desc 'Run without argument to list saved searches'
569
+ arg_name 'SEARCH_TITLE', optional: true
570
+ command %i[saved] do |c|
571
+ c.action do |_global_options, _options, args|
572
+ searches = NA.load_searches
573
+ if args.empty?
574
+ NA.notify("{bg}Saved searches stored in {bw}#{NA.database_path(file: 'saved_searches.yml')}")
575
+ NA.notify(searches.map { |k, v| "{y}#{k}: {w}#{v}" }.join("\n"), exit_code: 0)
576
+ else
577
+ keys = searches.keys.delete_if { |k| k !~ /#{args[0]}/ }
578
+ NA.notify("{r}Search #{args[0]} not found", exit_code: 1) if keys.empty?
579
+
580
+ key = keys[0]
581
+ cmd = Shellwords.shellsplit(searches[key])
582
+ exit run(cmd)
583
+ end
584
+ end
585
+ end
586
+
532
587
  pre do |global, _command, _options, _args|
533
588
  NA.verbose = global[:debug]
534
589
  NA.extension = global[:ext]
@@ -558,6 +613,7 @@ class App
558
613
  end
559
614
  end
560
615
 
616
+ NA.command_line = ARGV
561
617
  @command = ARGV[0]
562
618
  $first_arg = ARGV[1]
563
619
 
data/lib/na/action.rb CHANGED
@@ -127,11 +127,14 @@ module NA
127
127
  end
128
128
 
129
129
  def compare_tag(tag)
130
- return false unless @tags.key?(tag[:tag])
130
+ keys = @tags.keys.delete_if { |k| k !~ Regexp.new(tag[:tag], Regexp::IGNORECASE) }
131
+ return false if keys.empty?
132
+
133
+ key = keys[0]
131
134
 
132
135
  return true if tag[:comp].nil?
133
136
 
134
- tag_val = @tags[tag[:tag]]
137
+ tag_val = @tags[key]
135
138
  val = tag[:value]
136
139
 
137
140
  return false if tag_val.nil?
@@ -3,7 +3,7 @@
3
3
  # Next Action methods
4
4
  module NA
5
5
  class << self
6
- attr_accessor :verbose, :extension, :na_tag
6
+ attr_accessor :verbose, :extension, :na_tag, :command_line
7
7
 
8
8
  ##
9
9
  ## Output to STDERR
@@ -15,11 +15,57 @@ module NA
15
15
  return if debug && !@verbose
16
16
 
17
17
  $stderr.puts NA::Color.template("{x}#{msg}{x}")
18
- if exit_code && exit_code.is_a?(Number)
18
+ if exit_code
19
19
  Process.exit exit_code
20
20
  end
21
21
  end
22
22
 
23
+ ##
24
+ ## Display and read a Yes/No prompt
25
+ ##
26
+ ## @param prompt [String] The prompt string
27
+ ## @param default [Boolean] default value if
28
+ ## return is pressed or prompt is
29
+ ## skipped
30
+ ##
31
+ ## @return [Boolean] result
32
+ ##
33
+ def yn(prompt, default: true)
34
+ return default unless $stdout.isatty
35
+
36
+ tty_state = `stty -g`
37
+ system 'stty raw -echo cbreak isig'
38
+ yn = color_single_options(default ? %w[Y n] : %w[y N])
39
+ $stdout.syswrite "\e[1;37m#{prompt} #{yn}\e[1;37m? \e[0m"
40
+ res = $stdin.sysread 1
41
+ res.chomp!
42
+ puts
43
+ system 'stty cooked'
44
+ system "stty #{tty_state}"
45
+ res.empty? ? default : res =~ /y/i
46
+ end
47
+
48
+ ##
49
+ ## Helper function to colorize the Y/N prompt
50
+ ##
51
+ ## @param choices [Array] The choices with
52
+ ## default capitalized
53
+ ##
54
+ ## @return [String] colorized string
55
+ ##
56
+ def color_single_options(choices = %w[y n])
57
+ out = []
58
+ choices.each do |choice|
59
+ case choice
60
+ when /[A-Z]/
61
+ out.push(NA::Color.template("{bw}#{choice}{x}"))
62
+ else
63
+ out.push(NA::Color.template("{dw}#{choice}{xg}"))
64
+ end
65
+ end
66
+ NA::Color.template("{xg}[#{out.join('/')}{xg}]{x}")
67
+ end
68
+
23
69
  ##
24
70
  ## Create a new todo file
25
71
  ##
@@ -308,6 +354,50 @@ module NA
308
354
  puts NA::Color.template(dirs.join("\n"))
309
355
  end
310
356
 
357
+ def save_search(title, search)
358
+ file = database_path(file: 'saved_searches.yml')
359
+ searches = load_searches
360
+ title = title.gsub(/[^a-z0-9]/, '_').gsub(/_+/, '_')
361
+
362
+ if searches.key?(title)
363
+ res = yn('Overwrite existing definition?', default: true)
364
+ notify('{r}Cancelled', exit_code: 0) unless res
365
+
366
+ end
367
+
368
+ searches[title] = search
369
+ File.open(file, 'w') { |f| f.puts(YAML.dump(searches)) }
370
+ NA.notify("{y}Search #{title} saved", exit_code: 0)
371
+ end
372
+
373
+ def load_searches
374
+ file = database_path(file: 'saved_searches.yml')
375
+ if File.exist?(file)
376
+ searches = YAML.safe_load(IO.read(file))
377
+ else
378
+ searches = {
379
+ 'soon' => 'tagged "due<in 2 days,due>yesterday"',
380
+ 'overdue' => 'tagged "due<now"',
381
+ 'high' => 'tagged "prio>3"',
382
+ 'maybe' => 'tagged "maybe"'
383
+ }
384
+ File.open(file, 'w') { |f| f.puts(YAML.dump(searches)) }
385
+ end
386
+ searches
387
+ end
388
+
389
+ ##
390
+ ## Get path to database of known todo files
391
+ ##
392
+ ## @return [String] File path
393
+ ##
394
+ def database_path(file: 'tdlist.txt')
395
+ db_dir = File.expand_path('~/.local/share/na')
396
+ # Create directory if needed
397
+ FileUtils.mkdir_p(db_dir) unless File.directory?(db_dir)
398
+ File.join(db_dir, file)
399
+ end
400
+
311
401
  private
312
402
 
313
403
  ##
@@ -356,19 +446,6 @@ module NA
356
446
  [optional, required, negated]
357
447
  end
358
448
 
359
- ##
360
- ## Get path to database of known todo files
361
- ##
362
- ## @return [String] File path
363
- ##
364
- def database_path
365
- db_dir = File.expand_path('~/.local/share/na')
366
- # Create directory if needed
367
- FileUtils.mkdir_p(db_dir) unless File.directory?(db_dir)
368
- db_file = 'tdlist.txt'
369
- File.join(db_dir, db_file)
370
- end
371
-
372
449
  ##
373
450
  ## Find a matching path using semi-fuzzy matching.
374
451
  ## Search tokens can include ! and + to negate or make
@@ -379,18 +456,18 @@ module NA
379
456
  ## between characters
380
457
  ##
381
458
  def match_working_dir(search, distance: 1)
382
- search = search.map { |t| t[:token] }.join('/')
383
- optional = [search]
384
- required = [search]
385
-
386
459
  file = database_path
387
460
  notify('{r}No na database found', exit_code: 1) unless File.exist?(file)
388
461
 
389
462
  dirs = IO.read(file).split("\n")
390
463
 
391
- NA.notify("{bw}Directory regex: {x}#{required.map(&:dir_to_rx)}", debug: true)
464
+ optional = search.map { |t| t[:token] }
465
+ required = search.filter { |s| s[:required] }.map { |t| t[:token] }
466
+
467
+ NA.notify("{bw}Optional directory regex: {x}#{optional.map(&:dir_to_rx)}", debug: true)
468
+ NA.notify("{bw}Required directory regex: {x}#{required.map(&:dir_to_rx)}", debug: true)
392
469
 
393
- dirs.delete_if { |d| !d.dir_matches(any: optional, all: required) }
470
+ dirs.delete_if { |d| !d.sub(/\.#{NA.extension}$/, '').dir_matches(any: optional, all: required) }
394
471
  dirs.sort.uniq
395
472
  end
396
473
 
data/lib/na/string.rb CHANGED
@@ -1,6 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # String helpers
3
4
  class ::String
5
+ ##
6
+ ## Determine indentation level of line
7
+ ##
8
+ ## @return [Number] number of indents detected
9
+ ##
4
10
  def indent_level
5
11
  prefix = match(/(^[ \t]+)/)
6
12
  return 0 if prefix.nil?
@@ -11,7 +17,13 @@ class ::String
11
17
  ##
12
18
  ## Colorize @tags with ANSI escapes
13
19
  ##
14
- ## @param color [String] color (see #Color)
20
+ ## @param color [String] color (see #Color)
21
+ ## @param value [String] The value color
22
+ ## template
23
+ ## @param parens [String] The parens color
24
+ ## template
25
+ ## @param last_color [String] Color to restore after
26
+ ## tag highlight
15
27
  ##
16
28
  ## @return [String] string with @tags highlighted
17
29
  ##
@@ -23,6 +35,16 @@ class ::String
23
35
  "\\1#{tag_color}\\2#{paren_color}\\3#{value_color}\\4#{paren_color}\\5#{last_color}")
24
36
  end
25
37
 
38
+ ##
39
+ ## Highlight search results
40
+ ##
41
+ ## @param regexes [Array] The regexes for the
42
+ ## search
43
+ ## @param color [String] The highlight color
44
+ ## template
45
+ ## @param last_color [String] Color to restore after
46
+ ## highlight
47
+ ##
26
48
  def highlight_search(regexes, color: '{y}', last_color: '{xg}')
27
49
  string = dup
28
50
  color = NA::Color.template(color)
@@ -42,12 +64,13 @@ class ::String
42
64
 
43
65
  # Returns the last escape sequence from a string.
44
66
  #
45
- # Actually returns all escape codes, with the assumption
46
- # that the result of inserting them will generate the
47
- # same color as was set at the end of the string.
48
- # Because you can send modifiers like dark and bold
49
- # separate from color codes, only using the last code
50
- # may not render the same style.
67
+ # @note Actually returns all escape codes, with the
68
+ # assumption that the result of inserting them
69
+ # will generate the same color as was set at
70
+ # the end of the string. Because you can send
71
+ # modifiers like dark and bold separate from
72
+ # color codes, only using the last code may
73
+ # not render the same style.
51
74
  #
52
75
  # @return [String] All escape codes in string
53
76
  #
@@ -55,8 +78,18 @@ class ::String
55
78
  scan(/\e\[[\d;]+m/).join('').gsub(/\e\[0m/, '')
56
79
  end
57
80
 
81
+ ##
82
+ ## Convert a directory path to a regular expression
83
+ ##
84
+ ## @note Splits at / or :, adds variable distance
85
+ ## between characters, joins segments with
86
+ ## slashes and requires that last segment
87
+ ## match last segment of target path
88
+ ##
89
+ ## @param distance The distance
90
+ ##
58
91
  def dir_to_rx(distance: 2)
59
- "#{split(%r{[/:]}).map { |comp| comp.split('').join(".{0,#{distance}}") }.join('.*?/.*?')}[^/]+$"
92
+ "#{split(%r{[/:]}).map { |comp| comp.split('').join(".{0,#{distance}}").gsub(/\*/, '[^ ]*?') }.join('.*?/.*?')}[^/]*?$"
60
93
  end
61
94
 
62
95
  def dir_matches(any: [], all: [])
@@ -67,14 +100,25 @@ class ::String
67
100
  matches_any(any) && matches_all(all) && matches_none(none)
68
101
  end
69
102
 
103
+ ##
104
+ ## Convert wildcard characters to regular expressions
105
+ ##
106
+ ## @return [String] Regex string
107
+ ##
70
108
  def wildcard_to_rx
71
- gsub(/\./, '\\.').gsub(/\*/, '[^ ]*?').gsub(/\?/, '.')
109
+ gsub(/\./, '\\.').gsub(/\?/, '.').gsub(/\*/, '[^ ]*?')
72
110
  end
73
111
 
74
112
  def cap_first!
75
113
  replace cap_first
76
114
  end
77
115
 
116
+ ##
117
+ ## Capitalize first character, leaving other
118
+ ## capitalization in place
119
+ ##
120
+ ## @return [String] capitalized string
121
+ ##
78
122
  def cap_first
79
123
  sub(/^([a-z])(.*)$/) do
80
124
  m = Regexp.last_match
data/lib/na/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Na
2
- VERSION = '1.1.21'
2
+ VERSION = '1.1.23'
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.1.20<!--END VER-->.
12
+ The current version of `na` is <!--VER-->1.1.22<!--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
 
@@ -92,9 +92,9 @@ Unless `--exact` is specified, search is tokenized and combined with AND, so `na
92
92
 
93
93
  Examples:
94
94
 
95
- - `na show` (list all next actions in the current directory)
96
- - `na show -d 3` (list all next actions in the current directory and look for additional files 3 levels deep from there)
97
- - `na show marked2` (show next actions from another directory you've previously used na on)
95
+ - `na next` (list all next actions in the current directory)
96
+ - `na next -d 3` (list all next actions in the current directory and look for additional files 3 levels deep from there)
97
+ - `na next marked2` (show next actions from another directory you've previously used na on)
98
98
 
99
99
  ```
100
100
  @cli(bundle exec bin/na help next)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: na
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.21
4
+ version: 1.1.23
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra