na 1.1.22 → 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: 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