na 1.1.21 → 1.1.23

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