na 1.1.22 → 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: 584d55c84c26fddeb65bf537b4743b5cac4308f39d796d336d95c900fc966deb
4
- data.tar.gz: 73ea4289a1e43ce0b67f66f803a0db07359fe3f077358328a58c172c06daa107
3
+ metadata.gz: c8bee2cada1590cdc80fe9943947c719649d441ed15a6c51228fa0bebe4e94eb
4
+ data.tar.gz: 3dfc5a53bb88952243388b86e853af744b76938cbc69ef3b6f6ac8e58c03a9fc
5
5
  SHA512:
6
- metadata.gz: 89945d2a8df1c1ea11360da4258c09f9b5c8ad38f41ca5ef98a68b99c33b41fb0b718919cc680eb356903f284cef8dd4271acea94af7f8c4adca031d445a6a26
7
- data.tar.gz: 7c3f8c30bf5a4c69270641aadfb036303c42ee8678a09c2e8b5faad7883aff0ac0139caf465f9d37b84cf170ed4ee072ad20d4872b8c760b97d0e2ca1f3b6e2e
6
+ metadata.gz: 0603c3187363e3c5895f9e49af6deab11779cbf47442f4f76b7b5ad2608e4676d0cb337368e2a0d6a5f9bad7f5aff957d63e986cefe1997c671a6b6b5193e3e4
7
+ data.tar.gz: e570fe4a32d7e45b4bd2171c73bee68adb6017ae22901c92486ccbe1bdb2461a50dac988d8ed862aa85237c070ebb305d3b6c2c61afc920826568c628d630faf
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
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
+
1
13
  ### 1.1.22
2
14
 
3
15
  2022-10-07 05:58
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- na (1.1.22)
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.22
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.22
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
  ```
@@ -174,6 +175,7 @@ COMMAND OPTIONS
174
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
 
data/bin/na CHANGED
@@ -271,7 +271,16 @@ class App
271
271
  c.desc 'Show actions not matching search pattern'
272
272
  c.switch %i[v invert], negatable: false
273
273
 
274
+ c.desc 'Save this search for future use'
275
+ c.arg_name 'TITLE'
276
+ c.flag %i[save]
277
+
274
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
+
275
284
  depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
276
285
  3
277
286
  else
@@ -359,7 +368,16 @@ class App
359
368
  c.desc 'Show actions not matching tags'
360
369
  c.switch %i[v invert], negatable: false
361
370
 
371
+ c.desc 'Save this search for future use'
372
+ c.arg_name 'TITLE'
373
+ c.flag %i[save]
374
+
362
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
+
363
381
  depth = if global_options[:recurse] && options[:depth].nil? && global_options[:depth] == 1
364
382
  3
365
383
  else
@@ -371,9 +389,10 @@ class App
371
389
  all_req = args.join(' ') !~ /[+!\-]/ && !options[:or]
372
390
  args.join(',').split(/ *, */).each do |arg|
373
391
  # TODO: <> comparisons do nothing right now
374
- m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>*$\^]+)(?:(?<op>[=<>*$\^]+)(?<val>.*?))?$/)
392
+ m = arg.match(/^(?<req>[+\-!])?(?<tag>[^ =<>$\^]+?)(?:(?<op>[=<>]{1,2}|[*$\^]=)(?<val>.*?))?$/)
393
+
375
394
  tags.push({
376
- tag: m['tag'],
395
+ tag: m['tag'].wildcard_to_rx,
377
396
  comp: m['op'],
378
397
  value: m['val'],
379
398
  required: all_req || (!m['req'].nil? && m['req'] == '+'),
@@ -387,9 +406,9 @@ class App
387
406
  options[:in].split(/ *, */).each do |a|
388
407
  m = a.match(/^(?<req>\+)?(?<tok>.*?)$/)
389
408
  todo.push({
390
- token: m['tok'],
391
- required: !m['req'].nil?
392
- })
409
+ token: m['tok'],
410
+ required: !m['req'].nil?
411
+ })
393
412
  end
394
413
  end
395
414
 
@@ -545,6 +564,26 @@ class App
545
564
  end
546
565
  end
547
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
+
548
587
  pre do |global, _command, _options, _args|
549
588
  NA.verbose = global[:debug]
550
589
  NA.extension = global[:ext]
@@ -574,6 +613,7 @@ class App
574
613
  end
575
614
  end
576
615
 
616
+ NA.command_line = ARGV
577
617
  @command = ARGV[0]
578
618
  $first_arg = ARGV[1]
579
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
data/lib/na/string.rb CHANGED
@@ -106,7 +106,7 @@ class ::String
106
106
  ## @return [String] Regex string
107
107
  ##
108
108
  def wildcard_to_rx
109
- gsub(/\./, '\\.').gsub(/\*/, '[^ ]*?').gsub(/\?/, '.')
109
+ gsub(/\./, '\\.').gsub(/\?/, '.').gsub(/\*/, '[^ ]*?')
110
110
  end
111
111
 
112
112
  def cap_first!
data/lib/na/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Na
2
- VERSION = '1.1.22'
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.21<!--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
 
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.22
4
+ version: 1.1.23
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra