doing 2.1.36 → 2.1.39

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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -4912
  3. data/Gemfile.lock +1 -1
  4. data/README.md +1 -1
  5. data/Rakefile +7 -1
  6. data/bin/commands/done.rb +4 -4
  7. data/bin/commands/meanwhile.rb +3 -3
  8. data/bin/commands/note.rb +2 -2
  9. data/bin/commands/now.rb +5 -7
  10. data/bin/commands/on.rb +5 -2
  11. data/bin/commands/show.rb +3 -0
  12. data/bin/doing +35 -30
  13. data/docs/doc/Array.html +1 -1
  14. data/docs/doc/BooleanTermParser/Clause.html +1 -1
  15. data/docs/doc/BooleanTermParser/Operator.html +1 -1
  16. data/docs/doc/BooleanTermParser/Query.html +1 -1
  17. data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
  18. data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
  19. data/docs/doc/BooleanTermParser.html +1 -1
  20. data/docs/doc/Doing/Color.html +1 -1
  21. data/docs/doc/Doing/Completion.html +1 -1
  22. data/docs/doc/Doing/Configuration.html +1 -1
  23. data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
  24. data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
  25. data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
  26. data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
  27. data/docs/doc/Doing/Errors/NoResults.html +1 -1
  28. data/docs/doc/Doing/Errors/PluginException.html +1 -1
  29. data/docs/doc/Doing/Errors/UserCancelled.html +1 -1
  30. data/docs/doc/Doing/Errors/WrongCommand.html +1 -1
  31. data/docs/doc/Doing/Errors.html +1 -1
  32. data/docs/doc/Doing/Hooks.html +1 -1
  33. data/docs/doc/Doing/Item.html +64 -1
  34. data/docs/doc/Doing/Items.html +1 -1
  35. data/docs/doc/Doing/Logger.html +1 -1
  36. data/docs/doc/Doing/Note.html +1 -1
  37. data/docs/doc/Doing/Pager.html +1 -1
  38. data/docs/doc/Doing/Plugins.html +1 -1
  39. data/docs/doc/Doing/Prompt.html +1 -1
  40. data/docs/doc/Doing/Section.html +1 -1
  41. data/docs/doc/Doing/TemplateString.html +1 -1
  42. data/docs/doc/Doing/Types.html +1 -1
  43. data/docs/doc/Doing/Util/Backup.html +1 -1
  44. data/docs/doc/Doing/Util.html +1 -1
  45. data/docs/doc/Doing/WWID.html +1 -1
  46. data/docs/doc/Doing.html +2 -2
  47. data/docs/doc/FalseClass.html +1 -1
  48. data/docs/doc/GLI/Commands/Help.html +1 -1
  49. data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
  50. data/docs/doc/GLI/Commands.html +1 -1
  51. data/docs/doc/GLI.html +1 -1
  52. data/docs/doc/Hash.html +1 -1
  53. data/docs/doc/Object.html +1 -1
  54. data/docs/doc/PhraseParser/Operator.html +1 -1
  55. data/docs/doc/PhraseParser/PhraseClause.html +1 -1
  56. data/docs/doc/PhraseParser/Query.html +1 -1
  57. data/docs/doc/PhraseParser/QueryParser.html +1 -1
  58. data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
  59. data/docs/doc/PhraseParser/TermClause.html +1 -1
  60. data/docs/doc/PhraseParser.html +1 -1
  61. data/docs/doc/Status.html +1 -1
  62. data/docs/doc/String.html +1 -1
  63. data/docs/doc/Symbol.html +1 -1
  64. data/docs/doc/Time.html +1 -1
  65. data/docs/doc/TrueClass.html +1 -1
  66. data/docs/doc/_index.html +1 -1
  67. data/docs/doc/file.README.html +2 -2
  68. data/docs/doc/index.html +2 -2
  69. data/docs/doc/method_list.html +159 -151
  70. data/docs/doc/top-level-namespace.html +1 -1
  71. data/doing.rdoc +6 -1
  72. data/lib/completion/_doing.zsh +1 -1
  73. data/lib/completion/doing.bash +2 -2
  74. data/lib/completion/doing.fish +1 -0
  75. data/lib/doing/chronify/array.rb +8 -0
  76. data/lib/doing/chronify/string.rb +8 -5
  77. data/lib/doing/item.rb +166 -36
  78. data/lib/doing/items.rb +1 -1
  79. data/lib/doing/prompt.rb +6 -6
  80. data/lib/doing/string/tags.rb +8 -2
  81. data/lib/doing/version.rb +1 -1
  82. data/lib/doing/wwid.rb +11 -2
  83. data/lib/doing.rb +1 -0
  84. metadata +2 -2
@@ -206,7 +206,7 @@
206
206
  </div>
207
207
 
208
208
  <div id="footer">
209
- Generated on Fri Feb 25 09:00:49 2022 by
209
+ Generated on Sun Mar 13 05:08:08 2022 by
210
210
  <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
211
211
  0.9.27 (ruby-3.0.1).
212
212
  </div>
data/doing.rdoc CHANGED
@@ -5,7 +5,7 @@ record of what you've been doing, complete with tag-based time tracking. The
5
5
  command line tool allows you to add entries, annotate with tags and notes, and
6
6
  view your entries with myriad options, with a focus on a "natural" language syntax.
7
7
 
8
- v2.1.36
8
+ v2.1.39
9
9
 
10
10
  === Global Options
11
11
  === --config_file arg
@@ -2283,6 +2283,11 @@ Show elapsed time on entries without @done tag
2283
2283
 
2284
2284
 
2285
2285
 
2286
+ ===== -e|--editor
2287
+ Edit matching entries with vim
2288
+
2289
+
2290
+
2286
2291
  ===== -h|--[no-]hilite
2287
2292
  Highlight search matches in output. Only affects command line output
2288
2293
 
@@ -174,7 +174,7 @@ function _doing() {
174
174
  args=( {'(--archive)-a','(-a)--archive'}"[Archive selected items]" "--after[Select entries newer than date]:DATE_STRING:" "--resume[Copy selection as a new entry with current time and no @done tag]" "--before[Select entries older than date]:DATE_STRING:" {'(--cancel)-c','(-c)--cancel'}"[Cancel selected items]" "--case[Case sensitivity for search string matching ((c)ase-sensitive]:TYPE:" {'(--delete)-d','(-d)--delete'}"[Delete selected items]" {'(--editor)-e','(-e)--editor'}"[Edit selected item(s)]" {'(--finish)-f','(-f)--finish'}"[Add @done with current time to selected item(s)]" "--flag[Add flag to selected item(s)]" "--force[Perform action without confirmation]" "--from[Date range]:DATE_OR_RANGE:" {'(--move)-m','(-m)--move'}"[Move selected items to section]:SECTION:" "--menu[Use --no-menu to skip the interactive menu]" "--not[Select items that *dont* match search/tag filterst* match search/tag filters]" {'(--output)-o','(-o)--output'}"[Output entries to format]:FORMAT:" {'(--query)-q','(-q)--query'}"[Initial search query for filtering]:QUERY:" {'(--remove)-r','(-r)--remove'}"[Reverse -c]" {'(--section)-s','(-s)--section'}"[Select from a specific section]:SECTION:" "--save_to[Save selected entries to file using --output format]:FILE:" "--search[Filter entries using a search query]:QUERY:" {'(--tag)-t','(-t)--tag'}"[Tag selected entries]:TAG:" "--val[Perform a tag value query]:QUERY:" {'(--exact)-x','(-x)--exact'}"[Force exact search string matching]" )
175
175
  ;;
176
176
  show)
177
- args=( {'(--age)-a','(-a)--age'}"[Age]:AGE:" "--after[Show entries newer than date]:DATE_STRING:" "--before[Show entries older than date]:DATE_STRING:" "--bool[Boolean used to combine multiple tags]:BOOLEAN:" {'(--count)-c','(-c)--count'}"[Max count to show]:MAX:" "--case[Case sensitivity for search string matching ((c)ase-sensitive]:TYPE:" "--config_template[Output using a template from configuration]:TEMPLATE_KEY:" "--duration[Show elapsed time on entries without @done tag]" "--from[Date range]:DATE_OR_RANGE:" {'(--hilite)-h','(-h)--hilite'}"[Highlight search matches in output]" {'(--interactive)-i','(-i)--interactive'}"[Select from a menu of matching entries to perform additional operations]" {'(--menu)-m','(-m)--menu'}"[Select section or tag to display from a menu]" "--not[Show items that *dont* match search/tag filterst* match search/tag filters]" {'(--output)-o','(-o)--output'}"[Output to export format]:FORMAT:" "--only_timed[Only show items with recorded time intervals]" {'(--sort)-s','(-s)--sort'}"[Sort order]:ORDER:" "--search[Filter entries using a search query]:QUERY:" {'(--times)-t','(-t)--times'}"[Show time intervals on @done tasks]" "--tag[Filter entries by tag]:TAG:" "--tag_order[Tag sort direction]:DIRECTION:" "--tag_sort[Sort tags by]:KEY:" "--template[Override output format with a template string containing %placeholders]:TEMPLATE_STRING:" "--totals[Show time totals at the end of output]" "--val[Perform a tag value query]:QUERY:" {'(--exact)-x','(-x)--exact'}"[Force exact search string matching]" )
177
+ args=( {'(--age)-a','(-a)--age'}"[Age]:AGE:" "--after[Show entries newer than date]:DATE_STRING:" "--before[Show entries older than date]:DATE_STRING:" "--bool[Boolean used to combine multiple tags]:BOOLEAN:" {'(--count)-c','(-c)--count'}"[Max count to show]:MAX:" "--case[Case sensitivity for search string matching ((c)ase-sensitive]:TYPE:" "--config_template[Output using a template from configuration]:TEMPLATE_KEY:" "--duration[Show elapsed time on entries without @done tag]" {'(--editor)-e','(-e)--editor'}"[Edit matching entries with vim]" "--from[Date range]:DATE_OR_RANGE:" {'(--hilite)-h','(-h)--hilite'}"[Highlight search matches in output]" {'(--interactive)-i','(-i)--interactive'}"[Select from a menu of matching entries to perform additional operations]" {'(--menu)-m','(-m)--menu'}"[Select section or tag to display from a menu]" "--not[Show items that *dont* match search/tag filterst* match search/tag filters]" {'(--output)-o','(-o)--output'}"[Output to export format]:FORMAT:" "--only_timed[Only show items with recorded time intervals]" {'(--sort)-s','(-s)--sort'}"[Sort order]:ORDER:" "--search[Filter entries using a search query]:QUERY:" {'(--times)-t','(-t)--times'}"[Show time intervals on @done tasks]" "--tag[Filter entries by tag]:TAG:" "--tag_order[Tag sort direction]:DIRECTION:" "--tag_sort[Sort tags by]:KEY:" "--template[Override output format with a template string containing %placeholders]:TEMPLATE_STRING:" "--totals[Show time totals at the end of output]" "--val[Perform a tag value query]:QUERY:" {'(--exact)-x','(-x)--exact'}"[Force exact search string matching]" )
178
178
  ;;
179
179
  since)
180
180
  args=( "--bool[Boolean used to combine multiple tags]:BOOLEAN:" "--case[Case sensitivity for search string matching ((c)ase-sensitive]:TYPE:" "--config_template[Output using a template from configuration]:TEMPLATE_KEY:" "--duration[Show elapsed time on entries without @done tag]" "--not[Since items that *dont* match search/tag filterst* match search/tag filters]" {'(--output)-o','(-o)--output'}"[Output to export format]:FORMAT:" "--only_timed[Only show items with recorded time intervals]" {'(--section)-s','(-s)--section'}"[Section]:NAME:" "--search[Filter entries using a search query]:QUERY:" {'(--times)-t','(-t)--times'}"[Show time intervals on @done tasks]" "--tag[Filter entries by tag]:TAG:" "--tag_order[Tag sort direction]:DIRECTION:" "--tag_sort[Sort tags by]:KEY:" "--template[Override output format with a template string containing %placeholders]:TEMPLATE_STRING:" "--totals[Show time totals at the end of output]" "--val[Perform a tag value query]:QUERY:" {'(--exact)-x','(-x)--exact'}"[Force exact search string matching]" )
@@ -276,9 +276,9 @@ local words=$(doing sections)
276
276
  IFS="$OLD_IFS"
277
277
 
278
278
  if [[ "$token" == --* ]]; then
279
- COMPREPLY=( $( compgen -W '--age --after --before --bool --count --case --config_template --duration --from --hilite --interactive --menu --not --output --only_timed --sort --search --times --tag --tag_order --tag_sort --template --totals --val --exact' -- $token ) )
279
+ COMPREPLY=( $( compgen -W '--age --after --before --bool --count --case --config_template --duration --editor --from --hilite --interactive --menu --not --output --only_timed --sort --search --times --tag --tag_order --tag_sort --template --totals --val --exact' -- $token ) )
280
280
  elif [[ "$token" == -* ]]; then
281
- COMPREPLY=( $( compgen -W '-a -c -h -i -m -o -s -t -x --age --after --before --bool --count --case --config_template --duration --from --hilite --interactive --menu --not --output --only_timed --sort --search --times --tag --tag_order --tag_sort --template --totals --val --exact' -- $token ) )
281
+ COMPREPLY=( $( compgen -W '-a -c -e -h -i -m -o -s -t -x --age --after --before --bool --count --case --config_template --duration --editor --from --hilite --interactive --menu --not --output --only_timed --sort --search --times --tag --tag_order --tag_sort --template --totals --val --exact' -- $token ) )
282
282
  else
283
283
  local nocasematchWasOff=0
284
284
  shopt nocasematch >/dev/null || nocasematchWasOff=1
@@ -447,6 +447,7 @@ complete -c doing -l count -s c -f -r -n '__fish_doing_using_command show' -d Ma
447
447
  complete -c doing -l case -f -r -n '__fish_doing_using_command show' -d Case\ sensitivity\ for\ search\ string\ matching\ \[\(c\)ase-sensitive
448
448
  complete -c doing -l config_template -f -r -n '__fish_doing_using_command show' -d Output\ using\ a\ template\ from\ configuration
449
449
  complete -c doing -l duration -f -n '__fish_doing_using_command show' -d Show\ elapsed\ time\ on\ entries\ without\ @done\ tag
450
+ complete -c doing -l editor -s e -f -n '__fish_doing_using_command show' -d Edit\ matching\ entries\ with\ vim
450
451
  complete -c doing -l from -f -r -n '__fish_doing_using_command show' -d Date\ range
451
452
  complete -c doing -l hilite -s h -f -n '__fish_doing_using_command show' -d Highlight\ search\ matches\ in\ output
452
453
  complete -c doing -l interactive -s i -f -n '__fish_doing_using_command show' -d Select\ from\ a\ menu\ of\ matching\ entries\ to\ perform\ additional\ operations
@@ -18,8 +18,16 @@ module Doing
18
18
  d, h, m = self
19
19
  case format
20
20
  when :clock
21
+ if d.zero? && h > 24
22
+ d = (h / 24).floor
23
+ h = h % 24
24
+ end
21
25
  format('%<d>02d:%<h>02d:%<m>02d', d: d, h: h, m: m)
22
26
  when :dhm
27
+ if d.zero? && h > 24
28
+ d = (h / 24).floor
29
+ h = h % 24
30
+ end
23
31
  output = []
24
32
  output.push(format('%<d>dd', d: d)) if d.positive?
25
33
  output.push(format('%<h>dh', h: h)) if h.positive?
@@ -43,8 +43,8 @@ module Doing
43
43
  Doing.logger.debug('Parser:', %(date/time string "#{self}" interpreted as #{res} (#{secs_ago} seconds ago)))
44
44
  else
45
45
  date_string = dup
46
- date_string = 'today' if date_string.match(REGEX_DAY) && now.strftime('%a') =~ /^#{Regexp.last_match(1)}/i
47
- date_string = "#{options[:context].to_s} #{date_string}" if date_string =~ REGEX_TIME && options[:context]
46
+ date_string = 'today' if date_string.match(Types::REGEX_DAY) && now.strftime('%a') =~ /^#{Regexp.last_match(1)}/i
47
+ date_string = "#{options[:context].to_s} #{date_string}" if date_string =~ Types::REGEX_TIME && options[:context]
48
48
 
49
49
  res = Chronic.parse(date_string, {
50
50
  guess: options.fetch(:guess, :begin),
@@ -176,7 +176,7 @@ module Doing
176
176
  ## "mon 3pm to mon 5pm".split_date_range
177
177
  ##
178
178
  def split_date_range
179
- time_rx = /^(\d{1,2}+(:\d{1,2}+)?( *(am|pm))?|midnight|noon)$/
179
+ time_rx = /^(\d{1,2}(:\d{1,2})?( *(am|pm))?|midnight|noon)$/
180
180
  range_rx = / (to|through|thru|(?:un)?til|-+) /
181
181
 
182
182
  date_string = dup
@@ -187,15 +187,18 @@ module Doing
187
187
  inclusive = true
188
188
 
189
189
  dates = date_string.split(range_rx)
190
+
190
191
  if dates[0].strip =~ time_rx && dates[-1].strip =~ time_rx
191
192
  start = dates[0].strip
192
193
  finish = dates[-1].strip
193
194
  else
194
195
  start = dates[0].chronify(guess: :begin, future: false)
195
- finish = dates[-1].chronify(guess: inclusive ? :end : :begin, future: false)
196
+ finish = dates[-1].chronify(guess: inclusive ? :end : :begin, future: true)
196
197
  end
197
198
 
198
- raise Errors::InvalidTimeExpression, 'Unrecognized date string' if start.nil? || finish.nil?
199
+ raise Errors::InvalidTimeExpression, "Unrecognized date string (#{dates[0]})" if start.nil?
200
+
201
+ raise Errors::InvalidTimeExpression, "Unrecognized date string (#{dates[-1]})" if finish.nil?
199
202
 
200
203
  else
201
204
  if date_string.strip =~ time_rx
data/lib/doing/item.rb CHANGED
@@ -180,8 +180,14 @@ module Doing
180
180
 
181
181
  remove = options.fetch(:remove, false)
182
182
  tags.each do |tag|
183
+ if tag =~ /^(\S+)\((.*?)\)$/
184
+ m = Regexp.last_match
185
+ tag = m[1]
186
+ options[:value] ||= m[2]
187
+ end
188
+
183
189
  bool = remove ? :and : :not
184
- if tags?(tag, bool)
190
+ if tags?(tag, bool) || options[:value]
185
191
  @title = @title.tag(tag, **options).strip
186
192
  remove ? removed.push(tag) : added.push(tag)
187
193
  end
@@ -201,6 +207,16 @@ module Doing
201
207
  @title.scan(/(?<= |\A)@([^\s(]+)/).map { |tag| tag[0] }.sort.uniq
202
208
  end
203
209
 
210
+ ##
211
+ ## Return all tags including parenthetical values
212
+ ##
213
+ ## @return [Array<Array>] Array of array pairs,
214
+ ## [[tag1, value], [tag2, value]]
215
+ ##
216
+ def tags_with_values
217
+ @title.scan(/(?<= |\A)@([^\s(]+)(?:\((.*?)\))?/).map { |tag| [tag[0], tag[1]] }.sort.uniq
218
+ end
219
+
204
220
  ##
205
221
  ## convert tags on item to an array with @ symbols removed
206
222
  ##
@@ -522,7 +538,11 @@ module Doing
522
538
  return true unless tags.good?
523
539
 
524
540
  tags.each do |tag|
525
- return false unless @title =~ /@#{tag.wildcard_to_rx}(?= |\(|\Z)/i
541
+ if tag =~ /done/ && !should_finish?
542
+ next
543
+ else
544
+ return false unless @title =~ /@#{tag.wildcard_to_rx}(?= |\(|\Z)/i
545
+ end
526
546
  end
527
547
  true
528
548
  end
@@ -531,7 +551,11 @@ module Doing
531
551
  return true unless tags.good?
532
552
 
533
553
  tags.each do |tag|
534
- return false if @title =~ /@#{tag.wildcard_to_rx}(?= |\(|\Z)/i
554
+ if tag =~ /done/ && !should_finish?
555
+ return false
556
+ else
557
+ return false if @title =~ /@#{tag.wildcard_to_rx}(?= |\(|\Z)/i
558
+ end
535
559
  end
536
560
  true
537
561
  end
@@ -540,11 +564,21 @@ module Doing
540
564
  return true unless tags.good?
541
565
 
542
566
  tags.each do |tag|
543
- return true if @title =~ /@#{tag.wildcard_to_rx}(?= |\(|\Z)/i
567
+ if tag =~ /done/ && !should_finish?
568
+ return true
569
+ else
570
+ return true if @title =~ /@#{tag.wildcard_to_rx}(?= |\(|\Z)/i
571
+ end
544
572
  end
545
573
  false
546
574
  end
547
575
 
576
+ def tag_pattern?(tags)
577
+ query = tags.to_query
578
+
579
+ no_tags?(query[:must_not]) && all_tags?(query[:must]) && any_tags?(query[:should])
580
+ end
581
+
548
582
  def tag_value(tag)
549
583
  res = @title.match(/@#{tag.sub(/^@/, '').wildcard_to_rx}\((.*?)\)/)
550
584
  res ? res[1] : nil
@@ -580,6 +614,7 @@ module Doing
580
614
 
581
615
  queries.each do |q|
582
616
  parts = split_value_query(q)
617
+
583
618
  return false unless tag_value_matches?(parts[2], parts[3], parts[4], parts[1])
584
619
  end
585
620
  true
@@ -595,58 +630,153 @@ module Doing
595
630
  true
596
631
  end
597
632
 
633
+ def duration_matches?(value, comp)
634
+ return false if interval.nil?
635
+
636
+ val = value.chronify_qty
637
+ case comp
638
+ when /^<$/
639
+ interval < val
640
+ when /^<=$/
641
+ interval <= val
642
+ when /^>$/
643
+ interval > val
644
+ when /^>=$/
645
+ interval >= val
646
+ when /^!=/
647
+ interval != val
648
+ when /^=/
649
+ interval == val
650
+ end
651
+ end
652
+
653
+ def date_matches?(value, comp)
654
+ time_rx = /^(\d{1,2}+(:\d{1,2}+)?( *(am|pm))?|midnight|noon)$/i
655
+ value = "#{@date.strftime('%Y-%m-%d')} #{value}" if value =~ time_rx
656
+
657
+ val = value.chronify(guess: :begin)
658
+ raise InvalidTimeExpression, "Unrecognized date/time expression (#{value})" if val.nil?
659
+
660
+ case comp
661
+ when /^<$/
662
+ @date < val
663
+ when /^<=$/
664
+ @date <= val
665
+ when /^>$/
666
+ @date > val
667
+ when /^>=$/
668
+ @date >= val
669
+ when /^!=/
670
+ @date != val
671
+ when /^=/
672
+ @date == val
673
+ end
674
+ end
675
+
676
+ def value_string_matches?(tag_val, comp, value)
677
+ case comp
678
+ when /\^=/
679
+ tag_val =~ /^#{value.wildcard_to_rx}/i
680
+ when /\$=/
681
+ tag_val =~ /#{value.wildcard_to_rx}$/i
682
+ when %r{==}
683
+ tag_val =~ /^#{value.wildcard_to_rx}$/i
684
+ else
685
+ tag_val =~ /#{value.wildcard_to_rx}/i
686
+ end
687
+ end
688
+
689
+ def value_number_matches?(tag_val, comp, value)
690
+ case comp
691
+ when /^<$/
692
+ tag_val < value
693
+ when /^<=$/
694
+ tag_val <= value
695
+ when /^>$/
696
+ tag_val > value
697
+ when /^>=$/
698
+ tag_val >= value
699
+ when /^!=/
700
+ tag_val != value
701
+ when /^=/
702
+ tag_val == value
703
+ end
704
+ end
705
+
706
+ ##
707
+ ## Test if a tag's value matches a given value. Value
708
+ ## can be a date string, a text string, or a
709
+ ## number/percentage. Type of comparison is determined
710
+ ## by the comparitor and the objects being compared.
711
+ ##
712
+ ## @param tag [String] The tag name from which
713
+ ## to get the value
714
+ ## @param comp [String] The comparator (e.g. >=
715
+ ## or *=)
716
+ ## @param value [String] The value to test
717
+ ## against
718
+ ## @param negate [Boolean] Negate the response
719
+ ##
720
+ ## @return True if tag value matches, False otherwise.
721
+ ##
598
722
  def tag_value_matches?(tag, comp, value, negate)
599
- if all_tags?([tag])
723
+ # If tag matches existing tag
724
+ if tags?(tag, :and)
600
725
  tag_val = tag_value(tag)
601
726
 
727
+ # If the tag value is not a date and contains alpha
728
+ # characters and comparison is ==, or comparison is
729
+ # a string comparitor (*= ^= $=)
602
730
  if (value.chronify.nil? && value =~ /[a-z]/i && comp =~ /^!?==?$/) || comp =~ /[$*^]=/
603
- is_match = case comp
604
- when /\^=/
605
- tag_val =~ /^#{value.wildcard_to_rx}/i
606
- when /\$=/
607
- tag_val =~ /#{value.wildcard_to_rx}$/i
608
- when %r{==}
609
- tag_val =~ /^#{value.wildcard_to_rx}$/i
610
- else
611
- tag_val =~ /#{value.wildcard_to_rx}/i
612
- end
731
+ is_match = value_string_matches?(tag_val, comp, value)
613
732
 
614
733
  comp =~ /!/ || negate ? !is_match : is_match
615
734
  else
735
+ # Convert values to either a number or a date
616
736
  tag_val = number_or_date(tag_val)
617
737
  val = number_or_date(value)
618
738
 
739
+ # Fail if either value is nil
619
740
  return false if val.nil? || tag_val.nil?
620
741
 
742
+ # Fail unless both values are of the same class (float or date)
621
743
  return false unless val.class == tag_val.class
622
744
 
623
- matches = case comp
624
- when /^<$/
625
- tag_val < val
626
- when /^<=$/
627
- tag_val <= val
628
- when /^>$/
629
- tag_val > val
630
- when /^>=$/
631
- tag_val >= val
632
- when /^!=/
633
- tag_val != val
634
- when /^=/
635
- tag_val == val
636
- end
637
- negate.nil? ? matches : !matches
745
+ is_match = value_number_matches?(tag_val, comp, val)
746
+
747
+ negate.nil? ? is_match : !is_match
638
748
  end
749
+ # If tag name matches a trigger for elapsed time test
750
+ elsif tag =~ /^(elapsed|dur(ation)?|int(erval)?)$/i
751
+ is_match = duration_matches?(value, comp)
752
+
753
+ comp =~ /!/ || negate ? !is_match : is_match
754
+ # Else if tag name matches a trigger for start date
755
+ elsif tag =~ /^(d(ate)?|t(ime)?)$/i
756
+ is_match = date_matches?(value, comp)
757
+
758
+ comp =~ /!/ || negate ? !is_match : is_match
759
+ # Else if tag name matches a trigger for all text
760
+ elsif tag =~ /^text$/i
761
+ is_match = value_string_matches?([@title, @note.to_s(prefix: '')].join(' '), comp, value)
762
+
763
+ comp =~ /!/ || negate ? !is_match : is_match
764
+ # Else if tag name matches a trigger for title
765
+ elsif tag =~ /^title$/i
766
+ is_match = value_string_matches?(@title, comp, value)
767
+
768
+ comp =~ /!/ || negate ? !is_match : is_match
769
+ # Else if tag name matches a trigger for note
770
+ elsif tag =~ /^note$/i
771
+ is_match = value_string_matches?(@note.to_s(prefix: ''), comp, value)
772
+
773
+ comp =~ /!/ || negate ? !is_match : is_match
774
+ # Else if item contains tag being tested
639
775
  else
640
776
  false
641
777
  end
642
778
  end
643
779
 
644
- def tag_pattern?(tags)
645
- query = tags.to_query
646
-
647
- no_tags?(query[:must_not]) && all_tags?(query[:must]) && any_tags?(query[:should])
648
- end
649
-
650
780
  def split_tags(tags)
651
781
  tags.to_tags.tags_to_array
652
782
  end
data/lib/doing/items.rb CHANGED
@@ -179,7 +179,7 @@ module Doing
179
179
  out = []
180
180
  @sections.each do |section|
181
181
  out.push(section.original)
182
- items = in_section(section.title).sort_by(&:date)
182
+ items = in_section(section.title).sort_by { |i| [i.date, i.title] }
183
183
  items.reverse! if Doing.setting('doing_file_sort').normalize_order == :desc
184
184
  items.each { |item| out.push(item.to_s) }
185
185
  end
data/lib/doing/prompt.rb CHANGED
@@ -58,12 +58,12 @@ module Doing
58
58
  comp = proc { |s| completions.grep(/^#{Regexp.escape(s)}/) }
59
59
  Readline.completion_append_character = ' '
60
60
  Readline.completion_proc = comp
61
- prompt_text = []
62
- prompt_text << boldgreen(prompt.sub(/:?$/, ':'))
63
- prompt_text << yellow(' Enter a blank line (')
64
- prompt_text << boldwhite('return twice')
65
- prompt_text << yellow(') to end editing')
66
- puts prompt_text.join('')
61
+ puts format(['%<promptcolor>s%<prompt>s %<textcolor>sEnter a blank line',
62
+ '(%<keycolor>sreturn twice%<textcolor>s)',
63
+ 'to end editing and save,',
64
+ '%<keycolor>sCTRL-C%<textcolor>s to cancel%<reset>s'].join(' '),
65
+ { promptcolor: boldgreen, prompt: prompt.sub(/:?$/, ':'),
66
+ textcolor: yellow, keycolor: boldwhite, reset: reset })
67
67
 
68
68
  res = []
69
69
 
@@ -117,14 +117,20 @@ module Doing
117
117
  else
118
118
  Doing.logger.debug('Skipped:', "not tagged #{"@#{tag}".cyan}")
119
119
  end
120
- elsif title =~ /@#{tag}(?=[ (]|$)/
120
+ elsif title =~ /@#{tag}(?=[ (]|$)/ && !value.good?
121
121
  Doing.logger.debug('Skipped:', "already tagged #{"@#{tag}".cyan}")
122
122
  return title
123
123
  else
124
124
  add = tag
125
125
  add += "(#{value})" unless value.nil?
126
+
126
127
  title.chomp!
127
- title += " @#{add}"
128
+
129
+ if value && title =~ /@#{tag}(?=[ (]|$)/
130
+ title.sub!(/@#{tag}(\(.*?\))?/, "@#{add}")
131
+ else
132
+ title += " @#{add}"
133
+ end
128
134
 
129
135
  title.dedup_tags!
130
136
  title.chomp!
data/lib/doing/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Doing
2
- VERSION = '2.1.36'
2
+ VERSION = '2.1.39'
3
3
  end
data/lib/doing/wwid.rb CHANGED
@@ -665,7 +665,7 @@ module Doing
665
665
  ##
666
666
  def filter_items(items = Items.new, opt: {})
667
667
  logger.benchmark(:filter_items, :start)
668
- time_rx = /^(\d{1,2}+(:\d{1,2}+)?( *(am|pm))?|midnight|noon)$/
668
+ time_rx = /^(\d{1,2}+(:\d{1,2}+)?( *(am|pm))?|midnight|noon)$/i
669
669
 
670
670
  if items.nil? || items.empty?
671
671
  section = opt[:section] ? guess_section(opt[:section]) : 'All'
@@ -1292,9 +1292,14 @@ module Doing
1292
1292
  next
1293
1293
  end
1294
1294
 
1295
-
1296
1295
  tag = tag.strip
1297
1296
 
1297
+ if tag =~ /^(\S+)\((.*?)\)$/
1298
+ m = Regexp.last_match
1299
+ tag = m[1]
1300
+ opt[:value] ||= m[2]
1301
+ end
1302
+
1298
1303
  if tag =~ /^done$/ && opt[:date] && item.should_time?
1299
1304
  max_elapsed = Doing.setting('interaction.confirm_longer_than', 0)
1300
1305
  max_elapsed = max_elapsed.chronify_qty if max_elapsed.is_a?(String)
@@ -1688,9 +1693,13 @@ module Doing
1688
1693
 
1689
1694
  if opt[:delete]
1690
1695
  delete_items(items, force: opt[:force])
1696
+
1697
+ write(@doing_file)
1691
1698
  return
1692
1699
  elsif opt[:editor]
1693
1700
  edit_items(items)
1701
+
1702
+ write(@doing_file)
1694
1703
  return
1695
1704
  elsif opt[:interactive]
1696
1705
  opt[:menu] = !opt[:force]
data/lib/doing.rb CHANGED
@@ -15,6 +15,7 @@ require 'haml'
15
15
  require 'json'
16
16
  require 'logger'
17
17
  require 'safe_yaml/load'
18
+ require 'fcntl'
18
19
 
19
20
  require 'chronic'
20
21
  require 'tty-link'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doing
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.36
4
+ version: 2.1.39
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-02-25 00:00:00.000000000 Z
11
+ date: 2022-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: github-markup