From 14d8b4492597affcd15ca587cdd35ffe8781db3c Mon Sep 17 00:00:00 2001 From: Brett Terpstra Date: Sat, 14 Dec 2024 14:10:43 -0600 Subject: [PATCH] @fixed Trying to fix runtime slowness --- bin/commands/changes.rb | 1 + docs/doc/Array.html | 76 +- docs/doc/BooleanTermParser.html | 6 +- docs/doc/BooleanTermParser/Clause.html | 68 +- docs/doc/BooleanTermParser/Operator.html | 44 +- docs/doc/BooleanTermParser/Query.html | 172 +- docs/doc/BooleanTermParser/QueryParser.html | 6 +- .../BooleanTermParser/QueryTransformer.html | 6 +- docs/doc/Doing.html | 6 +- docs/doc/Doing/ArrayCleanup.html | 86 +- docs/doc/Doing/ArrayNestedHash.html | 30 +- docs/doc/Doing/ArrayTags.html | 98 +- docs/doc/Doing/ByDayExport.html | 146 +- docs/doc/Doing/CSVExport.html | 106 +- docs/doc/Doing/CalendarImport.html | 192 +- docs/doc/Doing/Change.html | 382 +- docs/doc/Doing/Changes.html | 200 +- docs/doc/Doing/ChronifyArray.html | 238 +- docs/doc/Doing/ChronifyNumeric.html | 72 +- docs/doc/Doing/ChronifyString.html | 418 +- docs/doc/Doing/Color.html | 236 +- docs/doc/Doing/Completion.html | 316 +- .../doc/Doing/Completion/BashCompletions.html | 376 +- docs/doc/Doing/Completion/FigCompletions.html | 316 +- .../doc/Doing/Completion/FishCompletions.html | 588 ++- docs/doc/Doing/Completion/StringUtils.html | 74 +- docs/doc/Doing/Completion/ZshCompletions.html | 288 +- docs/doc/Doing/Configuration.html | 682 ++- docs/doc/Doing/DayOneRenderer.html | 110 +- docs/doc/Doing/DayoneExport.html | 414 +- docs/doc/Doing/DoingExport.html | 82 +- docs/doc/Doing/DoingImport.html | 342 +- docs/doc/Doing/Entry.html | 150 +- docs/doc/Doing/Errors.html | 6 +- docs/doc/Doing/Errors/DoingNoTraceError.html | 38 +- docs/doc/Doing/Errors/DoingRuntimeError.html | 32 +- docs/doc/Doing/Errors/DoingStandardError.html | 30 +- docs/doc/Doing/Errors/EmptyInput.html | 26 +- docs/doc/Doing/Errors/HistoryLimitError.html | 26 +- docs/doc/Doing/Errors/InvalidPlugin.html | 26 +- docs/doc/Doing/Errors/MissingBackupFile.html | 26 +- docs/doc/Doing/Errors/NoResults.html | 28 +- docs/doc/Doing/Errors/PluginException.html | 86 +- docs/doc/Doing/Errors/UserCancelled.html | 26 +- docs/doc/Doing/Errors/WrongCommand.html | 26 +- docs/doc/Doing/HTMLExport.html | 180 +- docs/doc/Doing/Hooks.html | 148 +- docs/doc/Doing/Item.html | 314 +- docs/doc/Doing/ItemDates.html | 200 +- docs/doc/Doing/ItemQuery.html | 542 +- docs/doc/Doing/ItemState.html | 86 +- docs/doc/Doing/ItemTags.html | 144 +- docs/doc/Doing/Items.html | 640 ++- docs/doc/Doing/JSONExport.html | 308 +- docs/doc/Doing/JSONImport.html | 174 +- docs/doc/Doing/Logger.html | 772 ++- docs/doc/Doing/MarkdownExport.html | 164 +- docs/doc/Doing/Note.html | 206 +- docs/doc/Doing/Pager.html | 138 +- docs/doc/Doing/Plugins.html | 522 +- docs/doc/Doing/Prompt.html | 46 +- docs/doc/Doing/PromptChoose.html | 200 +- docs/doc/Doing/PromptFZF.html | 166 +- docs/doc/Doing/PromptInput.html | 198 +- docs/doc/Doing/PromptSTD.html | 72 +- docs/doc/Doing/PromptYN.html | 114 +- docs/doc/Doing/Section.html | 122 +- docs/doc/Doing/StringHighlight.html | 210 +- docs/doc/Doing/StringNormalize.html | 462 +- docs/doc/Doing/StringQuery.html | 262 +- docs/doc/Doing/StringTags.html | 362 +- docs/doc/Doing/StringTransform.html | 372 +- docs/doc/Doing/StringTruncate.html | 180 +- docs/doc/Doing/StringURL.html | 190 +- docs/doc/Doing/SymbolNormalize.html | 126 +- docs/doc/Doing/TaskPaperExport.html | 72 +- docs/doc/Doing/TemplateExport.html | 296 +- docs/doc/Doing/TemplateString.html | 426 +- docs/doc/Doing/TimingImport.html | 194 +- docs/doc/Doing/Types.html | 6 +- docs/doc/Doing/Util.html | 524 +- docs/doc/Doing/Util/Backup.html | 348 +- docs/doc/Doing/Version.html | 318 +- docs/doc/Doing/WWID.html | 4512 ++++++++++++++++- docs/doc/FalseClass.html | 46 +- docs/doc/GLI.html | 6 +- docs/doc/GLI/Commands.html | 6 +- docs/doc/GLI/Commands/Help.html | 66 +- .../Commands/MarkdownDocumentListener.html | 446 +- docs/doc/Hash.html | 452 +- docs/doc/Numeric.html | 26 +- docs/doc/Object.html | 26 +- docs/doc/PhraseParser.html | 6 +- docs/doc/PhraseParser/Operator.html | 44 +- docs/doc/PhraseParser/PhraseClause.html | 68 +- docs/doc/PhraseParser/Query.html | 226 +- docs/doc/PhraseParser/QueryParser.html | 6 +- docs/doc/PhraseParser/QueryTransformer.html | 6 +- docs/doc/PhraseParser/TermClause.html | 68 +- docs/doc/Status.html | 224 +- docs/doc/String.html | 262 +- docs/doc/Symbol.html | 6 +- docs/doc/Time.html | 150 +- docs/doc/TrueClass.html | 46 +- docs/doc/_index.html | 8 +- docs/doc/class_list.html | 9 +- docs/doc/css/full_list.css | 6 +- docs/doc/css/style.css | 6 + docs/doc/file.README.html | 6 +- docs/doc/file_list.html | 7 +- docs/doc/frames.html | 2 +- docs/doc/index.html | 6 +- docs/doc/js/app.js | 558 +- docs/doc/js/full_list.js | 34 +- docs/doc/method_list.html | 7 +- docs/doc/top-level-namespace.html | 426 +- lib/doing.rb | 1 - 117 files changed, 23090 insertions(+), 1191 deletions(-) diff --git a/bin/commands/changes.rb b/bin/commands/changes.rb index 2c6bd695..19be187a 100644 --- a/bin/commands/changes.rb +++ b/bin/commands/changes.rb @@ -1,4 +1,5 @@ # @@changelog @@changes +require 'tty-markdown' MARKDOWN_THEME = { em: %i[white dark], diff --git a/docs/doc/Array.html b/docs/doc/Array.html index d2474ce8..24f6f480 100644 --- a/docs/doc/Array.html +++ b/docs/doc/Array.html @@ -6,7 +6,7 @@ Class: Array - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -263,7 +263,25 @@

- + + + + + +
+
+
+
+31
+32
+33
+
+
# File 'lib/doing/array/array.rb', line 31
+
+def cap_first
+  map(&:cap_first)
+end
+
@@ -303,7 +321,25 @@

-

+ + + + + +
+
+
+
+59
+60
+61
+
+
# File 'lib/doing/good.rb', line 59
+
+def good?
+  !nil? && !empty?
+end
+
@@ -342,7 +378,35 @@

-

+ + + + + +
+
+
+
+17
+18
+19
+20
+21
+22
+23
+24
+
+
# File 'lib/doing/array/array.rb', line 17
+
+def utf8
+  c = self.class
+  if String.method_defined? :force_encoding
+    replace c.new(map(&:utf8))
+  else
+    self
+  end
+end
+
@@ -350,9 +414,9 @@

diff --git a/docs/doc/BooleanTermParser.html b/docs/doc/BooleanTermParser.html index 0c011269..06c9c52c 100644 --- a/docs/doc/BooleanTermParser.html +++ b/docs/doc/BooleanTermParser.html @@ -6,7 +6,7 @@ Module: BooleanTermParser - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -105,9 +105,9 @@

Defined Under Namespace

diff --git a/docs/doc/BooleanTermParser/Clause.html b/docs/doc/BooleanTermParser/Clause.html index 0dc8fc2e..0e2e756a 100644 --- a/docs/doc/BooleanTermParser/Clause.html +++ b/docs/doc/BooleanTermParser/Clause.html @@ -6,7 +6,7 @@ Class: BooleanTermParser::Clause - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -221,7 +221,27 @@

-
+ + + + + +
+
+
+
+42
+43
+44
+45
+
+
# File 'lib/doing/boolean_term_parser.rb', line 42
+
+def initialize(operator, term)
+  self.operator = Operator.symbol(operator)
+  self.term = term
+end
+
@@ -250,7 +270,25 @@

-
+ + + + + +
+
+
+
+40
+41
+42
+
+
# File 'lib/doing/boolean_term_parser.rb', line 40
+
+def operator
+  @operator
+end
+
@@ -274,7 +312,25 @@

-
+ + + + + +
+
+
+
+40
+41
+42
+
+
# File 'lib/doing/boolean_term_parser.rb', line 40
+
+def term
+  @term
+end
+
@@ -283,9 +339,9 @@

diff --git a/docs/doc/BooleanTermParser/Operator.html b/docs/doc/BooleanTermParser/Operator.html index 1f330256..970145d4 100644 --- a/docs/doc/BooleanTermParser/Operator.html +++ b/docs/doc/BooleanTermParser/Operator.html @@ -6,7 +6,7 @@ Class: BooleanTermParser::Operator - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -154,7 +154,43 @@

-

+ + + + + +
+
+
+
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+
+
# File 'lib/doing/boolean_term_parser.rb', line 25
+
+def self.symbol(str)
+  case str
+  when '+'
+    :must
+  when '-'
+    :must_not
+  when nil
+    :should
+  else
+    raise "Unknown operator: #{str}"
+  end
+end
+
@@ -162,9 +198,9 @@

diff --git a/docs/doc/BooleanTermParser/Query.html b/docs/doc/BooleanTermParser/Query.html index d216cc43..1a59f7a8 100644 --- a/docs/doc/BooleanTermParser/Query.html +++ b/docs/doc/BooleanTermParser/Query.html @@ -6,7 +6,7 @@ Class: BooleanTermParser::Query - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -291,7 +291,31 @@

-
+ + + + + +
+
+
+
+51
+52
+53
+54
+55
+56
+
+
# File 'lib/doing/boolean_term_parser.rb', line 51
+
+def initialize(clauses)
+  grouped = clauses.chunk { |c| c.operator }.to_h
+  self.should_terms = grouped.fetch(:should, []).map(&:term)
+  self.must_not_terms = grouped.fetch(:must_not, []).map(&:term)
+  self.must_terms = grouped.fetch(:must, []).map(&:term)
+end
+
@@ -320,7 +344,25 @@

-
+ + + + + +
+
+
+
+49
+50
+51
+
+
# File 'lib/doing/boolean_term_parser.rb', line 49
+
+def must_not_terms
+  @must_not_terms
+end
+
@@ -344,7 +386,25 @@

-
+ + + + + +
+
+
+
+49
+50
+51
+
+
# File 'lib/doing/boolean_term_parser.rb', line 49
+
+def must_terms
+  @must_terms
+end
+
@@ -368,7 +428,25 @@

-
+ + + + + +
+
+
+
+49
+50
+51
+
+
# File 'lib/doing/boolean_term_parser.rb', line 49
+
+def should_terms
+  @should_terms
+end
+
@@ -387,7 +465,25 @@

-

+ + + + + +
+
+
+
+82
+83
+84
+
+
# File 'lib/doing/boolean_term_parser.rb', line 82
+
+def match(term)
+  term
+end
+
@@ -399,7 +495,65 @@

-

+ + + + + +
+
+
+
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+
+
# File 'lib/doing/boolean_term_parser.rb', line 58
+
+def to_elasticsearch
+  query = {}
+
+  if should_terms.any?
+    query[:should] = should_terms.map do |term|
+      match(term)
+    end
+  end
+
+  if must_terms.any?
+    query[:must] = must_terms.map do |term|
+      match(term)
+    end
+  end
+
+  if must_not_terms.any?
+    query[:must_not] = must_not_terms.map do |term|
+      match(term)
+    end
+  end
+
+  query
+end
+
@@ -407,9 +561,9 @@

diff --git a/docs/doc/BooleanTermParser/QueryParser.html b/docs/doc/BooleanTermParser/QueryParser.html index 255d0048..9b3e811c 100644 --- a/docs/doc/BooleanTermParser/QueryParser.html +++ b/docs/doc/BooleanTermParser/QueryParser.html @@ -6,7 +6,7 @@ Class: BooleanTermParser::QueryParser - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -125,9 +125,9 @@

Overview

diff --git a/docs/doc/BooleanTermParser/QueryTransformer.html b/docs/doc/BooleanTermParser/QueryTransformer.html index 14dd8376..f576d646 100644 --- a/docs/doc/BooleanTermParser/QueryTransformer.html +++ b/docs/doc/BooleanTermParser/QueryTransformer.html @@ -6,7 +6,7 @@ Class: BooleanTermParser::QueryTransformer - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -114,9 +114,9 @@ diff --git a/docs/doc/Doing.html b/docs/doc/Doing.html index ab5693f6..813522fb 100644 --- a/docs/doc/Doing.html +++ b/docs/doc/Doing.html @@ -6,7 +6,7 @@ Module: Doing - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -138,9 +138,9 @@

diff --git a/docs/doc/Doing/ArrayCleanup.html b/docs/doc/Doing/ArrayCleanup.html index c1ec4a3f..d1af8049 100644 --- a/docs/doc/Doing/ArrayCleanup.html +++ b/docs/doc/Doing/ArrayCleanup.html @@ -6,7 +6,7 @@ Module: Doing::ArrayCleanup - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -234,7 +234,25 @@

- + + + + + +
+
+
+
+9
+10
+11
+
+
# File 'lib/doing/array/cleanup.rb', line 9
+
+def remove_bad
+  compact.map { |x| x.is_a?(String) ? x.strip : x }.select(&:good?)
+end
+
@@ -246,7 +264,25 @@

-

+

+ + + + +
+
+
+
+13
+14
+15
+
+
# File 'lib/doing/array/cleanup.rb', line 13
+
+def remove_bad!
+  replace remove_empty
+end
+
@@ -286,7 +322,25 @@

-

+ + + + + +
+
+
+
+23
+24
+25
+
+
# File 'lib/doing/array/cleanup.rb', line 23
+
+def remove_empty
+  compact.map { |x| x.is_a?(String) ? x.strip : x }.reject { |x| x.is_a?(String) ? x.empty? : false }
+end
+
@@ -298,7 +352,25 @@

-

+ + + + + +
+
+
+
+27
+28
+29
+
+
# File 'lib/doing/array/cleanup.rb', line 27
+
+def remove_empty!
+  replace remove_empty
+end
+
@@ -306,9 +378,9 @@

diff --git a/docs/doc/Doing/ArrayNestedHash.html b/docs/doc/Doing/ArrayNestedHash.html index 4124918e..9161ab1b 100644 --- a/docs/doc/Doing/ArrayNestedHash.html +++ b/docs/doc/Doing/ArrayNestedHash.html @@ -6,7 +6,7 @@ Module: Doing::ArrayNestedHash - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -180,7 +180,29 @@

- + + + + + +
+
+
+
+11
+12
+13
+14
+15
+
+
# File 'lib/doing/array/nested_hash.rb', line 11
+
+def nested_hash(value = nil)
+  hsh = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
+  hsh.dig(*self[0..-2])[fetch(-1)] = value
+  hsh
+end
+
@@ -188,9 +210,9 @@

diff --git a/docs/doc/Doing/ArrayTags.html b/docs/doc/Doing/ArrayTags.html index 27401264..f6711649 100644 --- a/docs/doc/Doing/ArrayTags.html +++ b/docs/doc/Doing/ArrayTags.html @@ -6,7 +6,7 @@ Module: Doing::ArrayTags - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -267,7 +267,25 @@

- + + + + + +
+
+
+
+37
+38
+39
+
+
# File 'lib/doing/array/tags.rb', line 37
+
+def highlight_tags(color = 'cyan')
+  to_tags.map { |t| Doing::Color.send(color.to_sym, t) }
+end
+
@@ -306,7 +324,25 @@

-

+ + + + + +
+
+
+
+46
+47
+48
+
+
# File 'lib/doing/array/tags.rb', line 46
+
+def log_tags(color = 'cyan')
+  highlight_tags(color).join(', ')
+end
+
@@ -328,11 +364,11 @@

-

Examples:

+

Examples:

-

Convert an array of tags to strings

-

+

Convert an array of tags to strings

+
['@one', '@two', 'three'].to_tags => ['one', 'two', 'three']
@@ -356,7 +392,25 @@

-

+
+ + + + +
+
+
+
+15
+16
+17
+
+
# File 'lib/doing/array/tags.rb', line 15
+
+def tags_to_array
+  map(&:remove_at).map(&:strip)
+end
+

@@ -378,11 +432,11 @@

-

Examples:

+

Examples:

-

Convert an array of strings with or without @ symbols

-

+

Convert an array of strings with or without @ symbols

+
['one', '@two', 'three'].to_tags => ['@one', '@two', '@three']
@@ -406,7 +460,25 @@

-

+
+ + + + +
+
+
+
+25
+26
+27
+
+
# File 'lib/doing/array/tags.rb', line 25
+
+def to_tags
+  map(&:add_at)
+end
+

@@ -414,9 +486,9 @@

diff --git a/docs/doc/Doing/ByDayExport.html b/docs/doc/Doing/ByDayExport.html index c76be054..a552f933 100644 --- a/docs/doc/Doing/ByDayExport.html +++ b/docs/doc/Doing/ByDayExport.html @@ -6,7 +6,7 @@ Class: Doing::ByDayExport - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -176,7 +176,115 @@

-

+ + + + + +
+
+
+
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+
+
# File 'lib/doing/plugins/export/byday.rb', line 18
+
+def self.render(wwid, items, variables: {})
+  return if items.nil?
+
+  days = {}
+
+  items.each do |item|
+    date = item.date.strftime('%Y-%m-%d')
+    days[date] ||= []
+    days[date].push(item)
+  end
+
+  totals = {}
+  total = 0
+
+  days.each do |day, day_items|
+    day_items.each do |item|
+      totals[day] ||= 0
+      duration = item.interval || 0
+      totals[day] += duration
+      total += duration
+    end
+  end
+  width = wwid.config['plugins']['byday']['item_width'].to_i || 60
+  divider = "{wd}+{xk}#{'-' *10}{wd}+{xk}#{'-' * width}{wd}+{xk}#{'-' * 8}{wd}+{x}"
+  out = []
+  out << divider
+  out << "{wd}|{xm}date      {wd}|{xbw}item#{' ' * (width - 4)}{wd}|{xy}duration{wd}|{x}"
+  out << divider
+  days.each do |day, day_items|
+    first = day_items.slice!(0, 1)[0]
+    interval = wwid.get_interval(first, formatted: true) || '00:00:00'
+    title = first.title.tag('done', remove: true).trunc(width - 2).ljust(width)
+    out << "{wd}|{xm}#{day}{wd}|{xbw}#{title}{wd}|{xy}#{interval}{wd}|{x}"
+    day_items.each do |item|
+      interval = wwid.get_interval(item, formatted: true) || '00:00:00'
+      title = item.title.tag('done', remove: true).trunc(width - 2).ljust(width)
+      out << "{wd}|          |{xbw}#{title}{wd}|{xy}#{interval}{wd}|{x}"
+    end
+    day_total = "Total: #{totals[day].time_string(format: :clock)}"
+    out << divider
+    out << "{wd}|{xg}#{day_total.rjust(width + 20)}{wd}|{x}"
+    out << divider
+  end
+  all_total = "Grand Total: #{total.time_string(format: :clock)}"
+  out << "{wd}|{xrb}#{all_total.rjust(width + 20)}{wd}|{x}"
+  out << divider
+  Doing::Color.template(out.join("\n"))
+end
+
@@ -188,7 +296,35 @@

-

+ + + + + +
+
+
+
+9
+10
+11
+12
+13
+14
+15
+16
+
+
# File 'lib/doing/plugins/export/byday.rb', line 9
+
+def self.settings
+  {
+    trigger: 'byday',
+    config: {
+      'item_width' => 60
+    }
+  }
+end
+
@@ -196,9 +332,9 @@

diff --git a/docs/doc/Doing/CSVExport.html b/docs/doc/Doing/CSVExport.html index c5d51627..475222c8 100644 --- a/docs/doc/Doing/CSVExport.html +++ b/docs/doc/Doing/CSVExport.html @@ -6,7 +6,7 @@ Class: Doing::CSVExport - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -224,7 +224,37 @@

-

+ + + + + +
+
+
+
+36
+37
+38
+39
+40
+41
+42
+43
+44
+
+
# File 'lib/doing/plugins/export/csv_export.rb', line 36
+
+def self.format_note(note)
+  out = ''
+  if note
+    arr = note.map(&:strip).delete_if { |e| e =~ /^\s*$/ }
+    out = arr.join("\n") unless arr.empty?
+  end
+
+  out
+end
+
@@ -236,7 +266,49 @@

-

+ + + + + +
+
+
+
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+
+
# File 'lib/doing/plugins/export/csv_export.rb', line 20
+
+def self.render(wwid, items, variables: {})
+  return if items.nil?
+
+  opt = variables[:options]
+
+  output = [CSV.generate_line(%w[start end title note timer section])]
+  items.each do |i|
+    note = format_note(i.note)
+    end_date = i.end_date
+    interval = end_date && opt[:times] ? wwid.get_interval(i, formatted: false) : 0
+    output.push(CSV.generate_line([i.date, end_date, i.title, note, interval, i.section]))
+  end
+  Doing.logger.debug('CSV Export:', "#{items.count} items output to CSV")
+  output.join('')
+end
+
@@ -248,7 +320,29 @@

-

+ + + + + +
+
+
+
+14
+15
+16
+17
+18
+
+
# File 'lib/doing/plugins/export/csv_export.rb', line 14
+
+def self.settings
+  {
+    trigger: 'csv'
+  }
+end
+
@@ -256,9 +350,9 @@

diff --git a/docs/doc/Doing/CalendarImport.html b/docs/doc/Doing/CalendarImport.html index ba6ea1be..6ececb37 100644 --- a/docs/doc/Doing/CalendarImport.html +++ b/docs/doc/Doing/CalendarImport.html @@ -6,7 +6,7 @@ Class: Doing::CalendarImport - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -202,7 +202,167 @@

-

+ + + + + +
+
+
+
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+
+
# File 'lib/doing/plugins/import/calendar_import.rb', line 22
+
+def self.import(wwid, _path, options: {})
+  limit_start = options[:start].to_i
+  limit_end = options[:end].to_i
+
+  section = options[:section] || Doing.setting('current_section')
+  options[:no_overlap] ||= false
+  options[:autotag] ||= Doing.auto_tag
+
+  wwid.content.add_section(section) unless wwid.content.section?(section)
+
+  tags = options[:tag] ? options[:tag].split(/[ ,]+/).map { |t| t.sub(/^@?/, '') } : []
+  prefix = options[:prefix] || '[Calendar.app]'
+
+  script = File.join(File.dirname(__FILE__), 'cal_to_json.scpt')
+  res = `/usr/bin/osascript "#{script}" #{limit_start} #{limit_end}`.strip
+  data = JSON.parse(res)
+
+  new_items = []
+  data.each do |entry|
+    # Only process entries with a start and end date
+    next unless entry.key?('start') && entry.key?('end')
+
+    # Round down seconds and convert UTC to local time
+    start_time = Time.parse(entry['start']).getlocal
+    end_time = Time.parse(entry['end']).getlocal
+    next unless start_time && end_time
+
+    title = "#{prefix} "
+    title += entry['name']
+    tags.each do |tag|
+      if title =~ /\b#{tag}\b/i
+        title.sub!(/\b#{tag}\b/i, "@#{tag}")
+      else
+        title += " @#{tag}"
+      end
+    end
+    title = wwid.autotag(title) if options[:autotag]
+    title += " @done(#{end_time.strftime('%Y-%m-%d %H:%M')})"
+    title.gsub!(/ +/, ' ')
+    title.strip!
+    new_entry = Item.new(start_time, title, section)
+    new_entry.note = entry['notes'].split(/\n/).map(&:chomp) if entry.key?('notes')
+
+    is_match = true
+
+    if options[:search]
+      is_match = new_entry.search(options[:search], case_type: options[:date], negate: options[:not])
+    end
+
+    if is_match && options[:date_filter]
+      is_match = start_time > options[:date_filter][0] && start_time < options[:date_filter][1]
+      is_match = options[:not] ? !is_match : is_match
+    end
+
+    new_items.push(new_entry) if is_match
+  end
+  total = new_items.count
+
+  new_items = wwid.filter_items(new_items, opt: options)
+  filtered = total - new_items.count
+  Doing.logger.debug('Skipped:' , %(#{filtered} items that didn't match filter criteria)) if filtered.positive?
+
+  new_items = wwid.dedup(new_items, no_overlap: options[:no_overlap])
+  dups = filtered - new_items.count
+  Doing.logger.info(%(Skipped #{dups} items with overlapping times)) if dups.positive?
+
+  new_items.map { |item| Hooks.trigger :pre_entry_add, self, item }
+
+  wwid.content.concat(new_items)
+
+  new_items.map { |item| Hooks.trigger :post_entry_added, self, item }
+
+  Doing.logger.info(%(Imported #{new_items.count} items to #{section}))
+end
+
@@ -214,7 +374,29 @@

-

+ + + + + +
+
+
+
+16
+17
+18
+19
+20
+
+
# File 'lib/doing/plugins/import/calendar_import.rb', line 16
+
+def self.settings
+  {
+    trigger: 'i?cal(?:endar)?'
+  }
+end
+
@@ -222,9 +404,9 @@

diff --git a/docs/doc/Doing/Change.html b/docs/doc/Doing/Change.html index 839e62f1..87a55555 100644 --- a/docs/doc/Doing/Change.html +++ b/docs/doc/Doing/Change.html @@ -6,7 +6,7 @@ Class: Doing::Change - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -290,7 +290,7 @@

  • - #initialize(version, content, prefix: false, only: %i[changed new improved fixed]) ⇒ Change + #initialize(version, content, prefix: false, only: %i[changed new improved fixed])) ⇒ Change @@ -431,7 +431,7 @@

    Constructor Details

    - #initialize(version, content, prefix: false, only: %i[changed new improved fixed]) ⇒ Change + #initialize(version, content, prefix: false, only: %i[changed new improved fixed])) ⇒ Change @@ -447,7 +447,33 @@

    -
    +

    + + + + +
    +
    +
    +
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +
    +
    # File 'lib/doing/changelog/change.rb', line 12
    +
    +def initialize(version, content, prefix: false, only: %i[changed new improved fixed])
    +  @version = Version.new(version)
    +  @content = content
    +  @prefix = prefix
    +  @only = only
    +  parse_entries
    +end
    +
    @@ -476,7 +502,25 @@

    -
    + + + + + +
    +
    +
    +
    +8
    +9
    +10
    +
    +
    # File 'lib/doing/changelog/change.rb', line 8
    +
    +def change_date
    +  @change_date
    +end
    +
    @@ -500,7 +544,25 @@

    -
    + + + + + +
    +
    +
    +
    +6
    +7
    +8
    +
    +
    # File 'lib/doing/changelog/change.rb', line 6
    +
    +def content
    +  @content
    +end
    +
    @@ -524,7 +586,25 @@

    -
    + + + + + +
    +
    +
    +
    +8
    +9
    +10
    +
    +
    # File 'lib/doing/changelog/change.rb', line 8
    +
    +def entries
    +  @entries
    +end
    +
    @@ -567,7 +647,25 @@

    - + + + + + +
    +
    +
    +
    +10
    +11
    +12
    +
    +
    # File 'lib/doing/changelog/change.rb', line 10
    +
    +def prefix=(value)
    +  @prefix = value
    +end
    +
    @@ -591,7 +689,25 @@

    -
    + + + + + +
    +
    +
    +
    +6
    +7
    +8
    +
    +
    # File 'lib/doing/changelog/change.rb', line 6
    +
    +def version
    +  @version
    +end
    +
    @@ -610,7 +726,41 @@

    -

    +
  • + + + + +
    +
    +
    +
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +
    +
    # File 'lib/doing/changelog/change.rb', line 94
    +
    +def changes_only
    +  out = []
    +
    +  split_items.each do |type, members|
    +    next unless @only.include?(type)
    +
    +    out << members.map(&:to_s).join("\n")
    +  end
    +
    +  out.join("\n")
    +end
    +
    @@ -622,7 +772,43 @@

    -

    + + + + + +
    +
    +
    +
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +
    +
    # File 'lib/doing/changelog/change.rb', line 20
    +
    +def parse_entries
    +  date = @content.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/)
    +  @change_date = Time.parse(date[0]) if date
    +
    +  @entries = []
    +  types = @content.scan(/(?<=\n|\A)#### (CHANGED|NEW|IMPROVED|FIXED)(.*?)(?=\n####|\Z)/m)
    +  types.each do |type|
    +    type[1].scan(/\s*- +(.*?)$/).each do |entry|
    +      @entries << Entry.new(entry[0].strip, type[0], prefix: @prefix)
    +    end
    +  end
    +end
    +
    @@ -634,7 +820,69 @@

    -

    + + + + + +
    +
    +
    +
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +
    +
    # File 'lib/doing/changelog/change.rb', line 33
    +
    +def search_entries(search_string)
    +  case_type = :ignore
    +
    +  matches = []
    +
    +  if search_string.rx?
    +    matches = @entries.select { |e| e.string =~ search_string.to_rx(distance: 2, case_type: case_type) }
    +  else
    +    query = search_string.gsub(/(-)?--/, '\1]]').to_phrase_query
    +
    +    if query[:must].nil? && query[:must_not].nil?
    +      query[:must] = query[:should]
    +      query[:should] = []
    +    end
    +
    +    @entries.each do |entry|
    +      m = no_searches?(entry.string, query[:must_not])
    +      m &&= all_searches?(entry.string, query[:must])
    +      m &&= any_searches?(entry.string, query[:should])
    +      matches << entry if m
    +    end
    +  end
    +
    +  @entries = matches.count.positive? ? matches : nil
    +end
    +
    @@ -646,7 +894,47 @@

    -

    + + + + + +
    +
    +
    +
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +
    +
    # File 'lib/doing/changelog/change.rb', line 63
    +
    +def split_items
    +  items = { changed: [], new: [], improved: [], fixed: [], other: [] }
    +
    +  @entries.each do |e|
    +    type = e.type.downcase.to_sym
    +    if items.key?(type)
    +      items[type] << e
    +    else
    +      items[:other] << e
    +    end
    +  end
    +
    +  items
    +end
    +
    @@ -658,7 +946,25 @@

    -

    + + + + + +
    +
    +
    +
    +59
    +60
    +61
    +
    +
    # File 'lib/doing/changelog/change.rb', line 59
    +
    +def to_h
    +  { version: @version, content: @content }
    +end
    +
    @@ -670,7 +976,49 @@

    -

    + + + + + +
    +
    +
    +
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +
    +
    # File 'lib/doing/changelog/change.rb', line 78
    +
    +def to_s
    +  date = @change_date.nil? ? '' : " _(#{@change_date.strftime('%F')})_"
    +  out = ["### __#{@version}__#{date}"]
    +
    +  split_items.each do |type, members|
    +    next unless @only.include?(type)
    +
    +    if members.count.positive?
    +      out << "#### #{type.to_s.capitalize}"
    +      out << members.map(&:to_s).join("\n")
    +    end
    +  end
    +
    +  out.join("\n\n")
    +end
    +
    @@ -678,9 +1026,9 @@

    diff --git a/docs/doc/Doing/Changes.html b/docs/doc/Doing/Changes.html index 46c56423..0c9c9f98 100644 --- a/docs/doc/Doing/Changes.html +++ b/docs/doc/Doing/Changes.html @@ -6,7 +6,7 @@ Class: Doing::Changes - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -188,7 +188,7 @@

  • - #initialize(lookup: nil, search: nil, changes: false, sort: :desc, prefix: false, only: %i[changed new improved fixed]) ⇒ Changes + #initialize(lookup: nil, search: nil, changes: false, sort: :desc, prefix: false, only: %i[changed new improved fixed])) ⇒ Changes @@ -307,7 +307,7 @@

    Constructor Details

    - #initialize(lookup: nil, search: nil, changes: false, sort: :desc, prefix: false, only: %i[changed new improved fixed]) ⇒ Changes + #initialize(lookup: nil, search: nil, changes: false, sort: :desc, prefix: false, only: %i[changed new improved fixed])) ⇒ Changes @@ -323,7 +323,43 @@

    -
    +

    + + + + +
    +
    +
    +
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +
    +
    # File 'lib/doing/changelog/changes.rb', line 9
    +
    +def initialize(lookup: nil, search: nil, changes: false, sort: :desc, prefix: false, only: %i[changed new improved fixed])
    +  @changes_only = changes
    +  @prefix = prefix
    +  @only = only
    +  changelog = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'CHANGELOG.md'))
    +  raise 'Error locating changelog' unless File.exist?(changelog)
    +
    +  @content = IO.read(changelog)
    +  parse_changes(lookup, search)
    +
    +  @changes.reverse! if sort == :asc
    +end
    +
    @@ -352,7 +388,25 @@

    -
    + + + + + +
    +
    +
    +
    +6
    +7
    +8
    +
    +
    # File 'lib/doing/changelog/changes.rb', line 6
    +
    +def changes
    +  @changes
    +end
    +
    @@ -395,7 +449,25 @@

    - + + + + + +
    +
    +
    +
    +7
    +8
    +9
    +
    +
    # File 'lib/doing/changelog/changes.rb', line 7
    +
    +def changes_only=(value)
    +  @changes_only = value
    +end
    +
    @@ -414,7 +486,41 @@

    -

    +
  • + + + + +
    +
    +
    +
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +
    +
    # File 'lib/doing/changelog/changes.rb', line 34
    +
    +def interactive
    +  Doing::Prompt.choose_from(versions,
    +                            prompt: 'Select a version to see its changelog',
    +                            sorted: false,
    +                            fzf_args: [
    +                              %(--preview='doing changes --render -l {1}'),
    +                              '--disabled',
    +                              '--height=50',
    +                              '--preview-window="right,70%"'
    +                            ])
    +end
    +
    @@ -426,7 +532,33 @@

    -

    + + + + + +
    +
    +
    +
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +
    +
    # File 'lib/doing/changelog/changes.rb', line 22
    +
    +def latest
    +  if @changes_only
    +    @changes[0].changes_only.force_encoding('utf-8')
    +  else
    +    @changes[0].to_s.force_encoding('utf-8')
    +  end
    +end
    +
    @@ -438,7 +570,33 @@

    -

    + + + + + +
    +
    +
    +
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +
    +
    # File 'lib/doing/changelog/changes.rb', line 46
    +
    +def to_s
    +  if @changes_only
    +    @changes.map(&:changes_only).delete_if(&:empty?).join().gsub(/\n+/, "\n").force_encoding('utf-8')
    +  else
    +    @changes.map(&:to_s).join("\n\n").force_encoding('utf-8')
    +  end
    +end
    +
    @@ -450,7 +608,25 @@

    -

    + + + + + +
    +
    +
    +
    +30
    +31
    +32
    +
    +
    # File 'lib/doing/changelog/changes.rb', line 30
    +
    +def versions
    +  @changes.select { |change| change.entries&.count > 0 }.map { |change| change.version }
    +end
    +
    @@ -458,9 +634,9 @@

    diff --git a/docs/doc/Doing/ChronifyArray.html b/docs/doc/Doing/ChronifyArray.html index d4cf1499..dea48572 100644 --- a/docs/doc/Doing/ChronifyArray.html +++ b/docs/doc/Doing/ChronifyArray.html @@ -6,7 +6,7 @@ Module: Doing::ChronifyArray - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -283,7 +283,103 @@

    - + + + + + +
    +
    +
    +
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +
    +
    # File 'lib/doing/chronify/array.rb', line 63
    +
    +def time_string(format: :dhm)
    +  raise InvalidArgument, 'Invalid array, must be [d,h,m]' unless count == 3
    +
    +  d, h, m = self
    +  case format
    +  when :clock
    +    if d.zero? && h > 24
    +      d = (h / 24).floor
    +      h = h % 24
    +    end
    +    format('%<d>02d:%<h>02d:%<m>02d', d: d, h: h, m: m)
    +  when :hmclock
    +    h += d * 24 if d.positive?
    +    format('%<h>02d:%<m>02d', h: h, m: m)
    +  when :ydhm
    +    to_abbr(years: true, separator: ' ')
    +  when :dhm
    +    to_abbr(years: false, separator: ' ')
    +  when :hm
    +    h += d * 24 if d.positive?
    +    format('%<h> 4dh %<m>02dm', h: h, m: m)
    +  when :m
    +    h += d * 24 if d.positive?
    +    m += h * 60 if h.positive?
    +    format('%<m> 4dm', m: m)
    +  when :tight
    +    to_abbr(years: true, separator: '')
    +  when :natural
    +    to_natural.join(', ')
    +  when :speech
    +    human = to_natural
    +    last = human.pop
    +    case human.count
    +    when 0
    +      last
    +    when 1
    +      "#{human[0]} and #{last}"
    +    else
    +      human.join(', ') + ", and #{last}"
    +    end
    +  end
    +end
    +
    @@ -295,7 +391,59 @@

    -

    +

    + + + + +
    +
    +
    +
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +
    +
    # File 'lib/doing/chronify/array.rb', line 35
    +
    +def to_abbr(years: false, separator: '')
    +  if years
    +    y, d, h, m = to_years
    +  else
    +    y = 0
    +    d, h, m = self
    +
    +    if d.zero? && h > 24
    +      d = (h / 24).floor
    +      h = h % 24
    +    end
    +  end
    +
    +  output = []
    +  output.push(format('%<y>dy', y: y)) if y.positive?
    +  output.push(format('%<d>dd', d: d)) if d.positive?
    +  output.push(format('%<h>dh', h: h)) if h.positive?
    +  output.push(format('%<m>dm', m: m)) if m.positive?
    +  output.join(separator)
    +end
    +
    @@ -307,7 +455,37 @@

    -

    + + + + + +
    +
    +
    +
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +
    +
    # File 'lib/doing/chronify/array.rb', line 25
    +
    +def to_natural
    +  y, d, h, m = to_years
    +  human = []
    +  human.push(format('%<y>d %<s>s', y: y, s: 'year'.to_p(y))) if y.positive?
    +  human.push(format('%<d>d %<s>s', d: d, s: 'day'.to_p(d))) if d.positive?
    +  human.push(format('%<h>d %<s>s', h: h, s: 'hour'.to_p(h))) if h.positive?
    +  human.push(format('%<m>d %<s>s', m: m, s: 'minute'.to_p(m))) if m.positive?
    +  human
    +end
    +
    @@ -329,7 +507,53 @@

    -
    +

    + + + + +
    +
    +
    +
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +
    +
    # File 'lib/doing/chronify/array.rb', line 7
    +
    +def to_years
    +  d, h, m = self
    +
    +  if d.zero? && h > 24
    +    d = (h / 24).floor
    +    h = h % 24
    +  end
    +
    +  if d > 365
    +    y = (d / 365).floor
    +    d = d % 365
    +  else
    +    y = 0
    +  end
    +
    +  [y, d, h, m]
    +end
    +
    @@ -337,9 +561,9 @@

    diff --git a/docs/doc/Doing/ChronifyNumeric.html b/docs/doc/Doing/ChronifyNumeric.html index c78893e3..78155be6 100644 --- a/docs/doc/Doing/ChronifyNumeric.html +++ b/docs/doc/Doing/ChronifyNumeric.html @@ -6,7 +6,7 @@ Module: Doing::ChronifyNumeric - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -209,7 +209,51 @@

    - + + + + + +
    +
    +
    +
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +
    +
    # File 'lib/doing/chronify/numeric.rb', line 14
    +
    +def format_time(human: false)
    +  return [0, 0, 0] if nil?
    +
    +  seconds = dup.to_i
    +  minutes = (seconds / 60).to_i
    +  hours = (minutes / 60).to_i
    +  if human
    +    minutes = (minutes % 60).to_i
    +    [0, hours, minutes]
    +  else
    +    days = (hours / 24).to_i
    +    hours = (hours % 24).to_i
    +    minutes = (minutes % 60).to_i
    +    [days, hours, minutes]
    +  end
    +end
    +
    @@ -253,7 +297,25 @@

    -

    + + + + + +
    +
    +
    +
    +37
    +38
    +39
    +
    +
    # File 'lib/doing/chronify/numeric.rb', line 37
    +
    +def time_string(format: :dhm)
    +  format_time(human: true).time_string(format: format)
    +end
    +
    @@ -261,9 +323,9 @@

    diff --git a/docs/doc/Doing/ChronifyString.html b/docs/doc/Doing/ChronifyString.html index 80d32fbd..26c8df7d 100644 --- a/docs/doc/Doing/ChronifyString.html +++ b/docs/doc/Doing/ChronifyString.html @@ -6,7 +6,7 @@ Module: Doing::ChronifyString - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -393,7 +393,85 @@

    - + + + + + +
    +
    +
    +
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +
    +
    # File 'lib/doing/chronify/string.rb', line 27
    +
    +def chronify(**options)
    +  now = Time.now
    +  raise Errors::InvalidTimeExpression, "Invalid time expression #{inspect}" if to_s.strip == ''
    +
    +  secs_ago = if match(/^(\d+)$/)
    +               # plain number, assume minutes
    +               Regexp.last_match(1).to_i * 60
    +             elsif (m = match(/^(?:(?<day>\d+)d)? *(?:(?<hour>\d+)h)? *(?:(?<min>\d+)m)?$/i))
    +               # day/hour/minute format e.g. 1d2h30m
    +               [[m['day'], 24 * 3600],
    +                [m['hour'], 3600],
    +                [m['min'], 60]].map { |qty, secs| qty ? (qty.to_i * secs) : 0 }.reduce(0, :+)
    +             end
    +
    +  if secs_ago
    +    res = now - secs_ago
    +    Doing.logger.debug('Parser:', %(date/time string "#{self}" interpreted as #{res} (#{secs_ago} seconds ago)))
    +  else
    +    date_string = dup
    +    date_string = 'today' if date_string.match(Types::REGEX_DAY) && now.strftime('%a') =~ /^#{Regexp.last_match(1)}/i
    +    date_string = "#{options[:context].to_s} #{date_string}" if date_string =~ Types::REGEX_TIME && options[:context]
    +
    +    res = Chronic.parse(date_string, {
    +                          guess: options.fetch(:guess, :begin),
    +                          context: options.fetch(:future, false) ? :future : :past,
    +                          ambiguous_time_range: 8
    +                        })
    +
    +    Doing.logger.debug('Parser:', %(date/time string "#{self}" interpreted as #{res}))
    +  end
    +
    +  res
    +end
    +
    @@ -435,7 +513,69 @@

    -

    + + + + + +
    +
    +
    +
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +
    +
    # File 'lib/doing/chronify/string.rb', line 70
    +
    +def chronify_qty
    +  minutes = 0
    +  case self.strip
    +  when /^(\d+):(\d\d)$/
    +    minutes += Regexp.last_match(1).to_i * 60
    +    minutes += Regexp.last_match(2).to_i
    +  when /^(\d+(?:\.\d+)?)([hmd])?/
    +    scan(/(\d+(?:\.\d+)?)([hmd])?/).each do |m|
    +      amt = m[0]
    +      type = m[1].nil? ? 'm' : m[1]
    +
    +      minutes += case type.downcase
    +                 when 'm'
    +                   amt.to_i
    +                 when 'h'
    +                   (amt.to_f * 60).round
    +                 when 'd'
    +                   (amt.to_f * 60 * 24).round
    +                 else
    +                   0
    +                 end
    +    end
    +  end
    +  minutes * 60
    +end
    +
    @@ -482,7 +622,85 @@

    -

    + + + + + +
    +
    +
    +
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +153
    +154
    +155
    +156
    +157
    +158
    +159
    +160
    +161
    +162
    +
    +
    # File 'lib/doing/chronify/string.rb', line 130
    +
    +def expand_date_tags(additional_tags = nil)
    +  iso_rx = /\d{4}-\d\d-\d\d \d\d:\d\d/
    +
    +  watch_tags = [
    +    'start(?:ed)?',
    +    'beg[ia]n',
    +    'done',
    +    'finished',
    +    'completed?',
    +    'waiting',
    +    'defer(?:red)?'
    +  ]
    +
    +  if additional_tags
    +    date_tags = additional_tags
    +    date_tags = date_tags.split(/ *, */) if date_tags.is_a?(String)
    +    date_tags.map! do |tag|
    +      tag.sub(/^@/, '').gsub(/\((?!\?:)(.*?)\)/, '(?:\1)').strip
    +    end
    +    watch_tags.concat(date_tags).uniq!
    +  end
    +
    +  done_rx = /(?<=^| )@(?<tag>#{watch_tags.join('|')})\((?<date>.*?)\)/i
    +
    +  gsub!(done_rx) do
    +    m = Regexp.last_match
    +    t = m['tag']
    +    d = m['date']
    +    future = t =~ /^(done|complete)/ ? false : true
    +    parsed_date = d =~ iso_rx ? Time.parse(d) : d.chronify(guess: :begin, future: future)
    +    parsed_date.nil? ? m[0] : "@#{t}(#{parsed_date.strftime('%F %R')})"
    +  end
    +end
    +
    @@ -516,7 +734,25 @@

    -

    + + + + + +
    +
    +
    +
    +164
    +165
    +166
    +
    +
    # File 'lib/doing/chronify/string.rb', line 164
    +
    +def is_range?
    +  self =~ / (to|through|thru|(un)?til|-+) /
    +end
    +
    @@ -540,11 +776,11 @@

    -

    Examples:

    +

    Examples:

    -

    Process a natural language date range

    -

    +

    Process a natural language date range

    +
    "mon 3pm to mon 5pm".split_date_range
    @@ -569,7 +805,113 @@

    -

    +
    + + + + +
    +
    +
    +
    +178
    +179
    +180
    +181
    +182
    +183
    +184
    +185
    +186
    +187
    +188
    +189
    +190
    +191
    +192
    +193
    +194
    +195
    +196
    +197
    +198
    +199
    +200
    +201
    +202
    +203
    +204
    +205
    +206
    +207
    +208
    +209
    +210
    +211
    +212
    +213
    +214
    +215
    +216
    +217
    +218
    +219
    +220
    +221
    +222
    +223
    +224
    +
    +
    # File 'lib/doing/chronify/string.rb', line 178
    +
    +def split_date_range
    +  time_rx = /^(\d{1,2}(:\d{1,2})?( *(am|pm))?|midnight|noon)$/
    +  range_rx = / (to|through|thru|(?:un)?til|-+) /
    +
    +  date_string = dup
    +
    +  if date_string.is_range?
    +    # Do we want to differentiate between "to" and "through"?
    +    # inclusive = date_string =~ / (through|thru|-+) / ? true : false
    +    inclusive = true
    +
    +    dates = date_string.split(range_rx)
    +
    +    if dates[0].strip =~ time_rx && dates[-1].strip =~ time_rx
    +      start = dates[0].strip
    +      finish = dates[-1].strip
    +    else
    +      start = dates[0].chronify(guess: :begin, future: false)
    +      finish = dates[-1].chronify(guess: inclusive ? :end : :begin, future: true)
    +    end
    +
    +    raise Errors::InvalidTimeExpression, "Unrecognized date string (#{dates[0]})" if start.nil?
    +
    +    raise Errors::InvalidTimeExpression, "Unrecognized date string (#{dates[-1]})" if finish.nil?
    +
    +  else
    +    if date_string.strip =~ time_rx
    +      start = date_string.strip
    +      finish = '11:59pm'
    +    else
    +      start = date_string.strip.chronify(guess: :begin, future: false)
    +      finish = start + (24 * 60 * 60)
    +    end
    +    raise Errors::InvalidTimeExpression, 'Unrecognized date string' unless start
    +
    +  end
    +
    +
    +  if start.is_a? String
    +    Doing.logger.debug('Parser:',
    +                       "--from string interpreted as time span, from #{start || '12am'} to #{finish || '11:59pm'}")
    +  else
    +    Doing.logger.debug('Parser:',
    +                       "date range interpreted as #{start.strftime('%F %R')} -- #{finish ? finish.strftime('%F %R') : 'now'}")
    +  end
    +  [start, finish]
    +end
    +

    @@ -612,7 +954,25 @@

    -

    + + + + + +
    +
    +
    +
    +117
    +118
    +119
    +
    +
    # File 'lib/doing/chronify/string.rb', line 117
    +
    +def time_string(format: :dhm)
    +  to_seconds.time_string(format: format)
    +end
    +
    @@ -664,7 +1024,39 @@

    -

    + + + + + +
    +
    +
    +
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +
    +
    # File 'lib/doing/chronify/string.rb', line 101
    +
    +def to_seconds
    +  mtch = match(/(\d+):(\d+):(\d+)/)
    +
    +  raise Errors::DoingRuntimeError, "Invalid time string: #{self}" unless mtch
    +
    +  h = mtch[1]
    +  m = mtch[2]
    +  s = mtch[3]
    +  (h.to_i * 60 * 60) + (m.to_i * 60) + s.to_i
    +end
    +
    @@ -672,9 +1064,9 @@

    diff --git a/docs/doc/Doing/Color.html b/docs/doc/Doing/Color.html index cec9edab..ed067a73 100644 --- a/docs/doc/Doing/Color.html +++ b/docs/doc/Doing/Color.html @@ -6,7 +6,7 @@ Module: Doing::Color - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -128,23 +128,23 @@

    -

    Examples:

    +

    Examples:

    -

    Use a color as a method. Color reset will be added to end of string.

    -

    +

    Use a color as a method. Color reset will be added to end of string.

    +
    Color.yellow('This text is yellow') => "\e[33mThis text is yellow\e[0m"
    -

    Use a color as a string extension. Color reset added automatically.

    -

    +

    Use a color as a string extension. Color reset added automatically.

    +
    'This text is green'.green => "\e[1;32mThis text is green\e[0m"
    -

    Send a text string as a color

    -

    +

    Send a text string as a color

    +
    Color.send('red') => "\e[31m"
    @@ -444,18 +444,36 @@

    -

    Examples:

    +

    Examples:

    -

    Turn color on or off based on TTY

    -

    +

    Turn color on or off based on TTY

    +
    Doing::Color.coloring = STDOUT.isatty
    -
    +

    + + + + +
    +
    +
    +
    +201
    +202
    +203
    +
    +
    # File 'lib/doing/colors.rb', line 201
    +
    +def coloring
    +  @coloring ||= true
    +end
    +
    @@ -498,7 +516,25 @@

    - + + + + + +
    +
    +
    +
    +190
    +191
    +192
    +
    +
    # File 'lib/doing/colors.rb', line 190
    +
    +def coloring?
    +  @coloring
    +end
    +
    @@ -526,11 +562,11 @@

    -

    Examples:

    +

    Examples:

    -

    Convert a templated string

    -

    +

    Convert a templated string

    +
    Color.template('{Rwb}Warning:{x} {w}you look a little {g}ill{x}')
    @@ -576,7 +612,49 @@

    -

    +
    + + + + +
    +
    +
    +
    +224
    +225
    +226
    +227
    +228
    +229
    +230
    +231
    +232
    +233
    +234
    +235
    +236
    +237
    +238
    +
    +
    # File 'lib/doing/colors.rb', line 224
    +
    +def template(input)
    +  input = input.join(' ') if input.is_a? Array
    +  fmt = input.gsub(/%/, '%%')
    +  fmt = fmt.gsub(/(?<!\\u|\$)\{(\w+)\}/i) do
    +    Regexp.last_match(1).split('').map { |c| "%<#{c}>s" }.join('')
    +  end
    +
    +  colors = { w: white, k: black, g: green, l: blue,
    +             y: yellow, c: cyan, m: magenta, r: red,
    +             W: bgwhite, K: bgblack, G: bggreen, L: bgblue,
    +             Y: bgyellow, C: bgcyan, M: bgmagenta, R: bgred,
    +             d: dark, b: bold, u: underline, i: italic, x: reset }
    +
    +  fmt.empty? ? input : format(fmt, colors)
    +end
    +

    @@ -604,7 +682,25 @@

    -
    + + + + + +
    +
    +
    +
    +314
    +315
    +316
    +
    +
    # File 'lib/doing/colors.rb', line 314
    +
    +def attributes
    +  ATTRIBUTE_NAMES
    +end
    +
    @@ -616,7 +712,43 @@

    -

    +

    + + + + +
    +
    +
    +
    +286
    +287
    +288
    +289
    +290
    +291
    +292
    +293
    +294
    +295
    +296
    +297
    +
    +
    # File 'lib/doing/colors.rb', line 286
    +
    +def rgb(hex)
    +  is_bg = hex.match(/^bg?#/) ? true : false
    +  hex_string = hex.sub(/^([fb]g?)?#/, '')
    +
    +  parts = hex_string.match(/(?<r>..)(?<g>..)(?<b>..)/)
    +  t = []
    +  %w[r g b].each do |e|
    +    t << parts[e].hex
    +  end
    +  color =
    +  "\e[#{is_bg ? '48' : '38'};2;#{t.join(';')}m"
    +end
    +
    @@ -656,7 +788,31 @@

    -

    + + + + + +
    +
    +
    +
    +90
    +91
    +92
    +93
    +94
    +95
    +
    +
    # File 'lib/doing/colors.rb', line 90
    +
    +def support?(feature)
    +  case feature
    +  when :clear
    +    !String.instance_methods(false).map(&:to_sym).include?(:clear)
    +  end
    +end
    +
    @@ -679,7 +835,41 @@

    -
    +

    + + + + +
    +
    +
    +
    +301
    +302
    +303
    +304
    +305
    +306
    +307
    +308
    +309
    +310
    +311
    +
    +
    # File 'lib/doing/colors.rb', line 301
    +
    +def uncolor(string = nil) # :yields:
    +  if block_given?
    +    yield.to_str.gsub(ESCAPE_REGEX, '')
    +  elsif string.respond_to?(:to_str)
    +    string.to_str.gsub(ESCAPE_REGEX, '')
    +  elsif respond_to?(:to_str)
    +    to_str.gsub(ESCAPE_REGEX, '')
    +  else
    +    ''
    +  end
    +end
    +
    @@ -687,9 +877,9 @@

    diff --git a/docs/doc/Doing/Completion.html b/docs/doc/Doing/Completion.html index 647249ed..ab4c4314 100644 --- a/docs/doc/Doing/Completion.html +++ b/docs/doc/Doing/Completion.html @@ -6,7 +6,7 @@ Module: Doing::Completion - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -414,7 +414,51 @@

    - + + + + + +
    +
    +
    +
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +
    +
    # File 'lib/doing/completion.rb', line 68
    +
    +def generate_completion(type: 'zsh', file: :default, link: true)
    +  return generate_all if type =~ /^all$/i
    +
    +  file = file == :default ? default_file(type) : file
    +  file = validate_target(file)
    +  result = generate_type(type)
    +
    +  if file =~ /^stdout$/i
    +    $stdout.puts result
    +  else
    +    File.open(file, 'w') { |f| f.puts result }
    +    Doing.logger.warn('File written:', "#{type} completions written to #{file}")
    +
    +    link_completion_type(type, file) if link
    +  end
    +end
    +
    @@ -426,7 +470,41 @@

    -

    +

    + + + + +
    +
    +
    +
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +
    +
    # File 'lib/doing/completion.rb', line 19
    +
    +def get_help_sections(command = "")
    +  res = `doing help #{command}|command cat`.strip
    +  scanned = res.scan(SECTIONS_RX)
    +  sections = {}
    +  scanned.each do |sect|
    +    title = sect[0].downcase.strip.gsub(/ +/, '_').to_sym
    +    content = sect[1].split(/\n/).map(&:strip).delete_if(&:empty?)
    +    sections[title] = content
    +  end
    +  sections
    +end
    +
    @@ -438,7 +516,43 @@

    -

    + + + + + +
    +
    +
    +
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +
    +
    # File 'lib/doing/completion.rb', line 96
    +
    +def install_builtin(type)
    +  FileUtils.mkdir_p(default_dir)
    +  src = File.expand_path(File.join(File.dirname(__FILE__), '..', 'completion', default_filenames[type]))
    +
    +  if File.exist?(File.join(default_dir, default_filenames[type]))
    +    return unless Doing::Prompt.yn("Update #{type} completion script", default_response: 'n')
    +
    +  end
    +
    +  FileUtils.cp(src, default_dir)
    +  Doing.logger.warn('File written:', "#{type} completions saved to #{default_file(type)}")
    +end
    +
    @@ -472,7 +586,39 @@
    + + + + + +
    +
    +
    +
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +
    +
    # File 'lib/doing/completion.rb', line 85
    +
    +def link_default(type)
    +  type = normalize_type(type)
    +  raise InvalidArgument, 'Unrecognized shell specified' if type == :invalid
    +
    +  return %i[zsh bash fish fig].each { |t| link_default(t) } if type == :all
    +
    +  install_builtin(type)
    +
    +  link_completion_type(type, File.join(default_dir, default_filenames[type]))
    +end
    +
    @@ -484,7 +630,51 @@

    -

    + + + + + +
    +
    +
    +
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +
    +
    # File 'lib/doing/completion.rb', line 109
    +
    +def normalize_type(type)
    +  case type.to_s
    +  when /^fig/i
    +    :fig
    +  when /^f/i
    +    :fish
    +  when /^b/i
    +    :bash
    +  when /^z/i
    +    :zsh
    +  when /^a/i
    +    :all
    +  else
    +    :invalid
    +  end
    +end
    +
    @@ -496,7 +686,39 @@

    -

    + + + + + +
    +
    +
    +
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +
    +
    # File 'lib/doing/completion.rb', line 47
    +
    +def parse_command(command)
    +  res = command.match(COMMAND_RX)
    +  commands = [res['cmd']]
    +  commands.concat(res['alias'].split(/, /).delete_if(&:empty?)) if res['alias']
    +
    +  {
    +    commands: commands,
    +    description: res['desc'].short_desc
    +  }
    +end
    +
    @@ -508,7 +730,25 @@

    -

    + + + + + +
    +
    +
    +
    +58
    +59
    +60
    +
    +
    # File 'lib/doing/completion.rb', line 58
    +
    +def parse_commands(commands)
    +  commands.map { |cmd| parse_command(cmd) }
    +end
    +
    @@ -520,7 +760,41 @@

    -

    + + + + + +
    +
    +
    +
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +
    +
    # File 'lib/doing/completion.rb', line 31
    +
    +def parse_option(option)
    +  res = option.match(OPTIONS_RX)
    +  return nil unless res
    +
    +  {
    +    short: res['short'],
    +    long: res['long'],
    +    arg: res['arg'],
    +    description: res['desc'].short_desc
    +  }
    +end
    +
    @@ -532,7 +806,25 @@

    -

    + + + + + +
    +
    +
    +
    +43
    +44
    +45
    +
    +
    # File 'lib/doing/completion.rb', line 43
    +
    +def parse_options(options)
    +  options.map { |opt| parse_option(opt) }
    +end
    +
    @@ -540,9 +832,9 @@

    diff --git a/docs/doc/Doing/Completion/BashCompletions.html b/docs/doc/Doing/Completion/BashCompletions.html index b9690b17..e9055197 100644 --- a/docs/doc/Doing/Completion/BashCompletions.html +++ b/docs/doc/Doing/Completion/BashCompletions.html @@ -6,7 +6,7 @@ Class: Doing::Completion::BashCompletions - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -319,7 +319,35 @@

    -
    + + + + + +
    +
    +
    +
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +
    +
    # File 'lib/doing/completion/bash_completion.rb', line 125
    +
    +def initialize
    +  data = Completion.get_help_sections
    +  @global_options = Completion.parse_options(data[:global_options])
    +  @commands = Completion.parse_commands(data[:commands])
    +  @bar = TTY::ProgressBar.new("\033[0;0;33mGenerating Bash completions: \033[0;35;40m[:bar] :status\033[0m", total: @commands.count + 1, bar_format: :square, hide_cursor: true, status: 'Reading subcommands')
    +  width = TTY::Screen.columns - 45
    +  @bar.resize(width)
    +end
    +
    @@ -348,7 +376,25 @@

    -
    + + + + + +
    +
    +
    +
    +7
    +8
    +9
    +
    +
    # File 'lib/doing/completion/bash_completion.rb', line 7
    +
    +def commands
    +  @commands
    +end
    +
    @@ -372,7 +418,25 @@

    -
    + + + + + +
    +
    +
    +
    +7
    +8
    +9
    +
    +
    # File 'lib/doing/completion/bash_completion.rb', line 7
    +
    +def global_options
    +  @global_options
    +end
    +
    @@ -391,7 +455,81 @@

    -

    + + + + + +
    +
    +
    +
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +
    +
    # File 'lib/doing/completion/bash_completion.rb', line 63
    +
    +def command_function(command, options, type)
    +  long_options = []
    +  short_options = []
    +
    +  options.each do |o|
    +    next if o.nil?
    +
    +    long_options << o[:long] if o[:long]
    +    short_options << o[:short] if o[:short]
    +  end
    +
    +  long = long_options.map! {|o| "--#{o}"}.join(' ')
    +  short = short_options.map! {|o| "-#{o}"}.join(' ')
    +  words = ''
    +  logic = ''
    +  words, logic = get_words(type) if type && type.is_a?(String)
    +
    +  func = <<~ENDFUNC
    +  _doing_#{command}() {
    +    #{words}
    +    if [[ "$token" == --* ]]; then
    +      COMPREPLY=( $( compgen -W '#{long}' -- $token ) )
    +    elif [[ "$token" == -* ]]; then
    +      COMPREPLY=( $( compgen -W '#{short} #{long}' -- $token ) )
    +    #{logic}
    +    fi
    +  }
    +  ENDFUNC
    +
    +  func
    +end
    +
    @@ -403,7 +541,37 @@

    -

    + + + + + +
    +
    +
    +
    +134
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +
    +
    # File 'lib/doing/completion/bash_completion.rb', line 134
    +
    +def generate_completions
    +  @bar.start
    +  out = []
    +  out << main_function
    +  out << 'complete -F _doing doing'
    +  @bar.advance(status: '')
    +  @bar.finish
    +  out.join("\n")
    +end
    +
    @@ -415,7 +583,77 @@

    -

    + + + + + +
    +
    +
    +
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +
    +
    # File 'lib/doing/completion/bash_completion.rb', line 95
    +
    +def get_words(type)
    +  func = <<~EOFUNC
    +    OLD_IFS="$IFS"
    +    local token=${COMP_WORDS[$COMP_CWORD]}
    +    IFS=$'\t'
    +    local words=$(doing #{type})
    +    IFS="$OLD_IFS"
    +  EOFUNC
    +
    +  logic = <<~EOLOGIC
    +    else
    +      local nocasematchWasOff=0
    +      shopt nocasematch >/dev/null || nocasematchWasOff=1
    +      (( nocasematchWasOff )) && shopt -s nocasematch
    +      local w matches=()
    +      OLD_IFS="$IFS"
    +      IFS=$'\t'‰
    +      for w in $words; do
    +        if [[ "$w" == "$token"* ]]; then
    +          matches+=("${w// /\ }")
    +        fi
    +      done
    +      IFS="$OLD_IFS"
    +      (( nocasematchWasOff )) && shopt -u nocasematch
    +      COMPREPLY=("${matches[@]}")
    +  EOLOGIC
    +
    +  [func, logic]
    +end
    +
    @@ -427,7 +665,125 @@

    -

    + + + + + +
    +
    +
    +
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +
    +
    # File 'lib/doing/completion/bash_completion.rb', line 9
    +
    +def main_function
    +  first = true
    +  out = []
    +  logic = []
    +
    +  @commands.each_with_index do |cmd, i|
    +    @bar.advance(status: cmd[:commands].first)
    +
    +    data = Completion.get_help_sections(cmd[:commands].first)
    +
    +    arg = data[:synopsis].join(' ').strip.split(/ /).last
    +    case arg
    +    when /(path|file)/i
    +      type = :file
    +    when /sect/i
    +      type = 'sections'
    +    when /view/i
    +      type = 'views'
    +    else
    +      type = nil
    +    end
    +
    +    if data[:command_options]
    +      options = Completion.parse_options(data[:command_options])
    +      out << command_function(cmd[:commands].first, options, type)
    +
    +      if first
    +        op = 'if'
    +        first = false
    +      else
    +        op = 'elif'
    +      end
    +      logic << %(#{op} [[ $last =~ (#{cmd[:commands].join('|')}) ]]; then _doing_#{cmd[:commands].first})
    +    end
    +  end
    +
    +  out << <<~EOFUNC
    +    _doing()
    +    {
    +      local last="${@: -1}"
    +      local token=${COMP_WORDS[$COMP_CWORD]}
    +
    +      #{logic.join("\n    ")}
    +      else
    +        OLD_IFS="$IFS"
    +        IFS=$'\n'
    +        COMPREPLY=( $(compgen -W "$(doing help -c)" -- $token) )
    +        IFS="$OLD_IFS"
    +      fi
    +    }
    +  EOFUNC
    +  out.join("\n")
    +end
    +
    @@ -435,9 +791,9 @@

    diff --git a/docs/doc/Doing/Completion/FigCompletions.html b/docs/doc/Doing/Completion/FigCompletions.html index 116d8bcd..c03f2b52 100644 --- a/docs/doc/Doing/Completion/FigCompletions.html +++ b/docs/doc/Doing/Completion/FigCompletions.html @@ -6,7 +6,7 @@ Class: Doing::Completion::FigCompletions - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -319,7 +319,35 @@

    -
    + + + + + +
    +
    +
    +
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +
    +
    # File 'lib/doing/completion/fig_completion.rb', line 106
    +
    +def initialize
    +  data = Completion.get_help_sections
    +  @global_options = Completion.parse_options(data[:global_options])
    +  @commands = Completion.parse_commands(data[:commands])
    +  @bar = TTY::ProgressBar.new(" \033[0;0;33mGenerating Fig completions: \033[0;35;40m[:bar] :status\033[0m", total: @commands.count + 1, bar_format: :square, hide_cursor: true, status: 'processing subcommands')
    +  width = TTY::Screen.columns - 45
    +  @bar.resize(width)
    +end
    +
    @@ -348,7 +376,25 @@

    -
    + + + + + +
    +
    +
    +
    +13
    +14
    +15
    +
    +
    # File 'lib/doing/completion/fig_completion.rb', line 13
    +
    +def commands
    +  @commands
    +end
    +
    @@ -372,7 +418,25 @@

    -
    + + + + + +
    +
    +
    +
    +13
    +14
    +15
    +
    +
    # File 'lib/doing/completion/fig_completion.rb', line 13
    +
    +def global_options
    +  @global_options
    +end
    +
    @@ -391,7 +455,27 @@

    -

    + + + + + +
    +
    +
    +
    +115
    +116
    +117
    +118
    +
    +
    # File 'lib/doing/completion/fig_completion.rb', line 115
    +
    +def generate_completions
    +  @bar.start
    +  generate_helpers
    +end
    +
    @@ -403,7 +487,49 @@

    -

    + + + + + +
    +
    +
    +
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +
    +
    # File 'lib/doing/completion/fig_completion.rb', line 15
    +
    +def generate_helpers
    +  out=<<~EOFUNCTIONS
    +  const completionSpec: Fig.Spec = {
    +    name: "doing",
    +    description: "A CLI for a What Was I Doing system",
    +    subcommands: [
    +      #{generate_subcommand_completions.join("\n    ")}
    +    ],
    +  };
    +  export default completionSpec;
    +  EOFUNCTIONS
    +  @bar.advance(status: '')
    +  @bar.finish
    +  out
    +end
    +
    @@ -415,7 +541,53 @@

    -

    + + + + + +
    +
    +
    +
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +
    +
    # File 'lib/doing/completion/fig_completion.rb', line 31
    +
    +def generate_subcommand_completions
    +  out = []
    +  indent = '      '
    +  @commands.each do |cmd|
    +    cmd[:commands].each do |c|
    +      out << <<~EOCOMMAND
    +        {
    +        #{indent}name: "#{c}",
    +        #{indent}description: "#{cmd[:description].sanitize}",
    +        #{indent}#{generate_subcommand_option_completions(cmd)}
    +            },
    +      EOCOMMAND
    +    end
    +  end
    +
    +  out
    +end
    +
    @@ -427,7 +599,131 @@

    + + +
    +
    +
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    + + +
    # File 'lib/doing/completion/fig_completion.rb', line 49
    +
    +def generate_subcommand_option_completions(cmd, indent: '          ')
    +  out = []
    +
    +  @bar.advance(status: cmd[:commands].first)
    +
    +  data = Completion.get_help_sections(cmd[:commands].first)
    +
    +  option_arr = []
    +
    +  if data[:command_options]
    +    Completion.parse_options(data[:command_options]).each do |option|
    +      next if option.nil?
    +
    +      arg = ''
    +
    +      if option[:arg]
    +        arg =<<~EOARG
    +        args: {
    +        #{indent}        name: "#{option[:arg]}",
    +        #{indent}        description: "#{option[:arg]}",
    +        #{indent}  },
    +        EOARG
    +      end
    +
    +      if option[:short]
    +        opt_data =<<~EOOPT
    +        {
    +        #{indent}  name: ["-#{option[:short]}", "--#{option[:long]}"],
    +        #{indent}  description: "#{option[:description].sanitize}",
    +        #{indent}  #{arg}
    +        #{indent}},
    +        EOOPT
    +      else
    +        opt_data = <<~EOOPT
    +        {
    +        #{indent}  name: ["--#{option[:long]}"],
    +        #{indent}  description: "#{option[:description].sanitize}",
    +        #{indent}  #{arg}
    +        #{indent}},
    +        EOOPT
    +      end
    +
    +      option_arr << opt_data
    +
    +    end
    +
    +    cmd_opts = <<~EOCMD
    +      options: [
    +      #{indent}#{option_arr.join("\n#{indent}")}
    +              ],
    +    EOCMD
    +    out << cmd_opts
    +  end
    +
    +  out.join("\n")
    +end
    + + +

    @@ -435,9 +731,9 @@

    - Generated on Tue Mar 26 11:00:42 2024 by + Generated on Sun Dec 1 11:01:06 2024 by yard - 0.9.36 (ruby-3.2.0). + 0.9.37 (ruby-3.3.0). diff --git a/docs/doc/Doing/Completion/FishCompletions.html b/docs/doc/Doing/Completion/FishCompletions.html index 0273c9bf..9cbec089 100644 --- a/docs/doc/Doing/Completion/FishCompletions.html +++ b/docs/doc/Doing/Completion/FishCompletions.html @@ -6,7 +6,7 @@ Class: Doing::Completion::FishCompletions - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -319,7 +319,35 @@

    -
    + + + + + +
    +
    +
    +
    +231
    +232
    +233
    +234
    +235
    +236
    +237
    +238
    +
    +
    # File 'lib/doing/completion/fish_completion.rb', line 231
    +
    +def initialize
    +  data = Completion.get_help_sections
    +  @global_options = Completion.parse_options(data[:global_options])
    +  @commands = Completion.parse_commands(data[:commands])
    +  @bar = TTY::ProgressBar.new("\033[0;0;33mGenerating Fish completions: \033[0;35;40m[:bar] :status\033[0m", total: @commands.count + 1, bar_format: :square, hide_cursor: true, status: 'processing subcommands')
    +  width = TTY::Screen.columns - 45
    +  @bar.resize(width)
    +end
    +
    @@ -348,7 +376,25 @@

    -
    + + + + + +
    +
    +
    +
    +8
    +9
    +10
    +
    +
    # File 'lib/doing/completion/fish_completion.rb', line 8
    +
    +def commands
    +  @commands
    +end
    +
    @@ -372,7 +418,25 @@

    -
    + + + + + +
    +
    +
    +
    +8
    +9
    +10
    +
    +
    # File 'lib/doing/completion/fish_completion.rb', line 8
    +
    +def global_options
    +  @global_options
    +end
    +
    @@ -391,7 +455,39 @@

    -

    + + + + + +
    +
    +
    +
    +240
    +241
    +242
    +243
    +244
    +245
    +246
    +247
    +248
    +249
    +
    +
    # File 'lib/doing/completion/fish_completion.rb', line 240
    +
    +def generate_completions
    +  @bar.start
    +  out = []
    +  out << generate_helpers
    +  out << generate_subcommand_completions
    +  out << generate_subcommand_option_completions
    +  @bar.advance(status: '')
    +  @bar.finish
    +  out.join("\n")
    +end
    +
    @@ -403,7 +499,283 @@

    -

    + + + + + +
    +
    +
    +
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +
    +
    # File 'lib/doing/completion/fish_completion.rb', line 10
    +
    +def generate_helpers
    +  <<~EOFUNCTIONS
    +    function __fish_doing_needs_command
    +      # Figure out if the current invocation already has a command.
    +
    +      set -l opts color h-help config_file= f-doing_file= n-notes v-version stdout debug default x-noauto no p-pager q-quiet yes
    +      set cmd (commandline -opc)
    +      set -e cmd[1]
    +      argparse -s $opts -- $cmd 2>/dev/null
    +      or return 0
    +      # These flags function as commands, effectively.
    +      if set -q argv[1]
    +        # Also print the command, so this can be used to figure out what it is.
    +        echo $argv[1]
    +        return 1
    +      end
    +      return 0
    +    end
    +
    +    function __fish_doing_using_command
    +      set -l cmd (__fish_doing_needs_command)
    +      test -z "$cmd"
    +      and return 1
    +      contains -- $cmd $argv
    +      and return 0
    +    end
    +
    +    function __fish_doing_cache_timer_expired
    +      set -l timer __fish_doing_cache_timer_$argv[1]
    +      if not set -q $timer
    +        set -g $timer (date '+%s')
    +      end
    +
    +      if test (math (date '+%s') - $$timer) -gt $argv[2]
    +        set -g $timer (date '+%s')
    +        return 1
    +      end
    +
    +      return 0
    +    end
    +
    +    function __fish_doing_subcommands
    +      if not set -q __fish_doing_subcommands_cache
    +        or __fish_doing_cache_timer_expired subcommands 86400
    +        set -g -a __fish_doing_subcommands_cache (doing help -c)
    +      end
    +      printf '%s\n' $__fish_doing_subcommands_cache
    +    end
    +
    +    function __fish_doing_complete_sections
    +      if not set -q __fish_doing_sections_cache
    +        or __fish_doing_cache_timer_expired sections 3600
    +        set -g -a __fish_doing_sections_cache (doing sections -c)
    +      end
    +      printf '%s\n' $__fish_doing_sections_cache
    +      __fish_doing_complete_show_tag
    +    end
    +
    +    function __fish_doing_complete_views
    +      if not set -q __fish_doing_views_cache
    +        or __fish_doing_cache_timer_expired views 3600
    +        set -g -a __fish_doing_views_cache (doing views -c)
    +      end
    +      printf '%s\n' $__fish_doing_views_cache
    +    end
    +
    +    function __fish_doing_export_plugin
    +      if not set -q __fish_doing_export_plugin_cache
    +        or __fish_doing_cache_timer_expired export_plugins 3600
    +        set -g -a __fish_doing_export_plugin_cache (doing plugins --type export -c)
    +      end
    +      printf '%s\n' $__fish_doing_export_plugin_cache
    +    end
    +
    +    function __fish_doing_import_plugin
    +      if not set -q __fish_doing_import_plugin_cache
    +        or __fish_doing_cache_timer_expired import_plugins 3600
    +        set -g -a __fish_doing_import_plugin_cache (doing plugins --type import -c)
    +      end
    +      printf '%s\n' $__fish_doing_import_plugin_cache
    +    end
    +
    +    function __fish_doing_complete_template
    +      if not set -q __fish_doing_template_cache
    +        or __fish_doing_cache_timer_expired template 3600
    +        set -g -a __fish_doing_template_cache (doing template -c)
    +      end
    +      printf '%s\n' $__fish_doing_template_cache
    +    end
    +
    +    function __fish_doing_complete_tag
    +      if not set -q __fish_doing_tag_cache
    +        or __fish_doing_cache_timer_expired tags 60
    +        set -g -a __fish_doing_tag_cache (doing tags)
    +      end
    +      printf '%s\n' $__fish_doing_tag_cache
    +    end
    +
    +    function __fish_doing_complete_show_tag
    +      if not set -q __fish_doing_tag_cache
    +        or __fish_doing_cache_timer_expired tags 60
    +        set -g -a __fish_doing_tag_cache (doing tags)
    +      end
    +      printf '@%s\n' $__fish_doing_tag_cache
    +    end
    +
    +    function __fish_doing_complete_args
    +      for cmd in (doing commands_accepting -c $argv[1])
    +        complete -x -c doing -l $argv[1] -n "__fish_doing_using_command $cmd" -a "(__fish_doing_complete_$argv[1])"
    +      end
    +    end
    +
    +    complete -c doing -f
    +    complete -xc doing -n '__fish_doing_needs_command' -a '(__fish_doing_subcommands)'
    +
    +    complete -f -c doing -n '__fish_doing_using_command show' -a '(__fish_doing_complete_sections)'
    +    complete -f -c doing -n '__fish_doing_using_command view' -a '(__fish_doing_complete_views)'
    +    complete -f -c doing -n '__fish_doing_using_command template' -a '(__fish_doing_complete_templates)'
    +    complete -f -c doing -s t -l type -x -n '__fish_doing_using_command import' -a '(__fish_doing_import_plugins)'
    +    complete -f -c doing -n '__fish_doing_using_command help' -a '(__fish_doing_subcommands)'
    +
    +    # complete -xc doing -n '__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from (doing help -c)' -a "(doing help -c)"
    +
    +    function __fish_doing_complete_args
    +      for cmd in (doing commands_accepting -c $argv[1])
    +        complete -x -c doing -l $argv[1] -n "__fish_doing_using_command $cmd" -a "(__fish_doing_complete_$argv[1])"
    +      end
    +    end
    +
    +    __fish_doing_complete_args tag
    +  EOFUNCTIONS
    +end
    +
    @@ -415,7 +787,39 @@

    -

    + + + + + +
    +
    +
    +
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +
    +
    # File 'lib/doing/completion/fish_completion.rb', line 143
    +
    +def generate_subcommand_completions
    +  out = []
    +  @commands.each do |cmd|
    +    desc = Shellwords.escape(cmd[:description])
    +    cmds = cmd[:commands].join(' ')
    +    out << "complete -xc doing -n '__fish_doing_needs_command' -a '#{cmds}' -d #{desc}"
    +  end
    +
    +  out.join("\n")
    +end
    +
    @@ -427,7 +831,171 @@

    + + +
    +
    +
    +154
    +155
    +156
    +157
    +158
    +159
    +160
    +161
    +162
    +163
    +164
    +165
    +166
    +167
    +168
    +169
    +170
    +171
    +172
    +173
    +174
    +175
    +176
    +177
    +178
    +179
    +180
    +181
    +182
    +183
    +184
    +185
    +186
    +187
    +188
    +189
    +190
    +191
    +192
    +193
    +194
    +195
    +196
    +197
    +198
    +199
    +200
    +201
    +202
    +203
    +204
    +205
    +206
    +207
    +208
    +209
    +210
    +211
    +212
    +213
    +214
    +215
    +216
    +217
    +218
    +219
    +220
    +221
    +222
    +223
    +224
    +225
    +226
    +227
    +228
    +229
    + + +
    # File 'lib/doing/completion/fish_completion.rb', line 154
    +
    +def generate_subcommand_option_completions
    +
    +  out = []
    +  need_export = []
    +  need_bool = []
    +  need_case = []
    +  need_sort = []
    +  need_tag_sort = []
    +  need_tag_order = []
    +  need_age = []
    +  need_section = []
    +
    +  @commands.each_with_index do |cmd, i|
    +    @bar.advance(status: cmd[:commands].first)
    +    data = Completion.get_help_sections(cmd[:commands].first)
    +
    +    if data[:synopsis].join(' ').strip.split(/ /).last =~ /(path|file)/i
    +      out << "complete -c doing -F -n '__fish_doing_using_command #{cmd[:commands].join(" ")}'"
    +    end
    +
    +    if data[:command_options]
    +      Completion.parse_options(data[:command_options]).each do |option|
    +        next if option.nil?
    +
    +        arg = option[:arg] ? '-r' : ''
    +        short = option[:short] ? "-s #{option[:short]}" : ''
    +        long = option[:long] ? "-l #{option[:long]}" : ''
    +        out << "complete -c doing #{long} #{short} -f #{arg} -n '__fish_doing_using_command #{cmd[:commands].join(' ')}' -d #{Shellwords.escape(option[:description])}"
    +
    +        need_export.concat(cmd[:commands]) if option[:long] == 'output'
    +        need_bool.concat(cmd[:commands]) if option[:long] == 'bool'
    +        need_case.concat(cmd[:commands]) if option[:long] == 'case'
    +        need_sort.concat(cmd[:commands]) if option[:long] == 'sort'
    +        need_tag_sort.concat(cmd[:commands]) if option[:long] == 'tag_sort'
    +        need_tag_order.concat(cmd[:commands]) if option[:long] == 'tag_order'
    +        need_age.concat(cmd[:commands]) if option[:long] == 'age'
    +        need_section.concat(cmd[:commands]) if option[:long] == 'section'
    +      end
    +    end
    +  end
    +
    +  unless need_export.empty?
    +    out << "complete -f -c doing -s o -l output -x -n '__fish_doing_using_command #{need_export.join(' ')}' -a '(__fish_doing_export_plugin)'"
    +  end
    +
    +  unless need_bool.empty?
    +    out << "complete -f -c doing -s b -l bool -x -n '__fish_doing_using_command #{need_bool.join(' ')}' -a 'and or not pattern'"
    +  end
    +
    +  unless need_case.empty?
    +    out << "complete -f -c doing -l case -x -n '__fish_doing_using_command #{need_case.join(' ')}' -a 'case-sensitive ignore smart'"
    +  end
    +
    +  unless need_sort.empty?
    +    out << "complete -f -c doing -l sort -x -n '__fish_doing_using_command #{need_sort.join(' ')}' -a 'asc desc'"
    +  end
    +
    +  unless need_tag_sort.empty?
    +    out << "complete -f -c doing -l tag_sort -x -n '__fish_doing_using_command #{need_tag_sort.join(' ')}' -a 'name time'"
    +  end
    +
    +  unless need_tag_order.empty?
    +    out << "complete -f -c doing -l tag_order -x -n '__fish_doing_using_command #{need_tag_order.join(' ')}' -a 'asc desc'"
    +  end
    +
    +  unless need_age.empty?
    +    out << "complete -f -c doing -s a -l age -x -n '__fish_doing_using_command #{need_age.join(' ')}' -a 'oldest newest'"
    +  end
    +
    +  unless need_section.empty?
    +    out << "complete -f -c doing -s s -l section -x -n '__fish_doing_using_command #{need_section.join(' ')}' -a '(__fish_doing_complete_sections)'"
    +  end
    +
    +  # clear
    +  out.join("\n")
    +end
    + + +

    @@ -435,9 +1003,9 @@

    - Generated on Tue Mar 26 11:00:42 2024 by + Generated on Sun Dec 1 11:01:06 2024 by yard - 0.9.36 (ruby-3.2.0). + 0.9.37 (ruby-3.3.0). diff --git a/docs/doc/Doing/Completion/StringUtils.html b/docs/doc/Doing/Completion/StringUtils.html index 16636d85..d713126c 100644 --- a/docs/doc/Doing/Completion/StringUtils.html +++ b/docs/doc/Doing/Completion/StringUtils.html @@ -6,7 +6,7 @@ Module: Doing::Completion::StringUtils - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -218,7 +218,33 @@

    - + + + + + +
    +
    +
    +
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +
    +
    # File 'lib/doing/completion/completion_string.rb', line 19
    +
    +def ltrunc(max)
    +  if length > max
    +    sub(/^.*?(.{#{max - 3}})$/, '...\1')
    +  else
    +    self
    +  end
    +end
    +
    @@ -230,7 +256,25 @@

    -

    +

    + + + + +
    +
    +
    +
    +27
    +28
    +29
    +
    +
    # File 'lib/doing/completion/completion_string.rb', line 27
    +
    +def ltrunc!(max)
    +  replace ltrunc(max)
    +end
    +
    @@ -269,7 +313,25 @@

    -

    + + + + + +
    +
    +
    +
    +10
    +11
    +12
    +
    +
    # File 'lib/doing/completion/completion_string.rb', line 10
    +
    +def short_desc
    +  split(/[,.]/)[0].sub(/ \(.*?\)?$/, '').strip
    +end
    +
    @@ -277,9 +339,9 @@

    diff --git a/docs/doc/Doing/Completion/ZshCompletions.html b/docs/doc/Doing/Completion/ZshCompletions.html index 3296e024..6ecf0f45 100644 --- a/docs/doc/Doing/Completion/ZshCompletions.html +++ b/docs/doc/Doing/Completion/ZshCompletions.html @@ -6,7 +6,7 @@ Class: Doing::Completion::ZshCompletions - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -319,7 +319,35 @@

    -
    + + + + + +
    +
    +
    +
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +
    +
    # File 'lib/doing/completion/zsh_completion.rb', line 92
    +
    +def initialize
    +  data = Completion.get_help_sections
    +  @global_options = Completion.parse_options(data[:global_options])
    +  @commands = Completion.parse_commands(data[:commands])
    +  @bar = TTY::ProgressBar.new(" \033[0;0;33mGenerating Zsh completions: \033[0;35;40m[:bar] :status\033[0m", total: @commands.count + 1, bar_format: :square, hide_cursor: true, status: 'processing subcommands')
    +  width = TTY::Screen.columns - 45
    +  @bar.resize(width)
    +end
    +
    @@ -348,7 +376,25 @@

    -
    + + + + + +
    +
    +
    +
    +13
    +14
    +15
    +
    +
    # File 'lib/doing/completion/zsh_completion.rb', line 13
    +
    +def commands
    +  @commands
    +end
    +
    @@ -372,7 +418,25 @@

    -
    + + + + + +
    +
    +
    +
    +13
    +14
    +15
    +
    +
    # File 'lib/doing/completion/zsh_completion.rb', line 13
    +
    +def global_options
    +  @global_options
    +end
    +
    @@ -391,7 +455,27 @@

    -

    + + + + + +
    +
    +
    +
    +101
    +102
    +103
    +104
    +
    +
    # File 'lib/doing/completion/zsh_completion.rb', line 101
    +
    +def generate_completions
    +  @bar.start
    +  generate_helpers
    +end
    +
    @@ -403,7 +487,87 @@

    -

    + + + + + +
    +
    +
    +
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +
    +
    # File 'lib/doing/completion/zsh_completion.rb', line 15
    +
    +def generate_helpers
    +  out=<<~EOFUNCTIONS
    +    compdef _doing doing
    +
    +    function _doing() {
    +        local line state
    +
    +        function _commands {
    +            local -a commands
    +
    +            commands=(
    +                      #{generate_subcommand_completions.join("\n                  ")}
    +            )
    +            _describe 'command' commands
    +        }
    +
    +        _arguments -C \
    +                "1: :_commands" \
    +                "*::arg:->args"
    +
    +
    +
    +        case $line[1] in
    +            #{generate_subcommand_option_completions(indent: '            ').join("\n            ")}
    +        esac
    +
    +        _arguments -s $args
    +    }
    +
    +  EOFUNCTIONS
    +  @bar.advance(status: '')
    +  @bar.finish
    +  out
    +end
    +
    @@ -415,7 +579,37 @@

    -

    + + + + + +
    +
    +
    +
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +
    +
    # File 'lib/doing/completion/zsh_completion.rb', line 50
    +
    +def generate_subcommand_completions
    +  out = []
    +  @commands.each_with_index do |cmd, i|
    +    cmd[:commands].each do |c|
    +      out << "'#{c}:#{cmd[:description].gsub(/'/, '\\\'')}'"
    +    end
    +  end
    +  out
    +end
    +
    @@ -427,7 +621,81 @@

    + + +
    +
    +
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    + + +
    # File 'lib/doing/completion/zsh_completion.rb', line 60
    +
    +def generate_subcommand_option_completions(indent: '        ')
    +
    +  out = []
    +
    +  @commands.each_with_index do |cmd, i|
    +    @bar.advance(status: cmd[:commands].first)
    +
    +    data = Completion.get_help_sections(cmd[:commands].first)
    +    option_arr = []
    +
    +    if data[:command_options]
    +      Completion.parse_options(data[:command_options]).each do |option|
    +        next if option.nil?
    +
    +        arg = option[:arg] ? ":#{option[:arg]}:" : ''
    +
    +        option_arr << if option[:short]
    +                        %({'(--#{option[:long]})-#{option[:short]}','(-#{option[:short]})--#{option[:long]}'}"[#{option[:description].sanitize}]#{arg}")
    +                      else
    +                        %("--#{option[:long]}[#{option[:description].sanitize}]#{arg}")
    +                      end
    +      end
    +    end
    +
    +    cmd[:commands].each do |c|
    +      out << "#{c}) \n#{indent}    args=( #{option_arr.join(' ')} )\n#{indent};;"
    +    end
    +  end
    +
    +  out
    +end
    + + +

    @@ -435,9 +703,9 @@

    - Generated on Tue Mar 26 11:00:42 2024 by + Generated on Sun Dec 1 11:01:06 2024 by yard - 0.9.36 (ruby-3.2.0). + 0.9.37 (ruby-3.3.0). diff --git a/docs/doc/Doing/Configuration.html b/docs/doc/Doing/Configuration.html index 282b958a..2e3296a8 100644 --- a/docs/doc/Doing/Configuration.html +++ b/docs/doc/Doing/Configuration.html @@ -6,7 +6,7 @@ Class: Doing::Configuration - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -679,7 +679,29 @@

    -
    + + + + + +
    +
    +
    +
    +121
    +122
    +123
    +124
    +125
    +
    +
    # File 'lib/doing/configuration.rb', line 121
    +
    +def initialize(file = nil, options: {})
    +  @config_file = file.nil? ? default_config_file : File.expand_path(file)
    +
    +  @settings = configure(options)
    +end
    +
    @@ -698,7 +720,25 @@

    -

    + + + + + +
    +
    +
    +
    +127
    +128
    +129
    +
    +
    # File 'lib/doing/configuration.rb', line 127
    +
    +def config_file
    +  @config_file ||= default_config_file
    +end
    +
    @@ -712,7 +752,25 @@

    -

    + + + + + +
    +
    +
    +
    +12
    +13
    +14
    +
    +
    # File 'lib/doing/configuration.rb', line 12
    +
    +def force_answer
    +  @force_answer ||= false
    +end
    +
    @@ -755,7 +813,25 @@

    - + + + + + +
    +
    +
    +
    +10
    +11
    +12
    +
    +
    # File 'lib/doing/configuration.rb', line 10
    +
    +def ignore_local=(value)
    +  @ignore_local = value
    +end
    +
    @@ -779,7 +855,25 @@

    -
    + + + + + +
    +
    +
    +
    +8
    +9
    +10
    +
    +
    # File 'lib/doing/configuration.rb', line 8
    +
    +def settings
    +  @settings
    +end
    +
    @@ -798,7 +892,25 @@

    -

    + + + + + +
    +
    +
    +
    +160
    +161
    +162
    +
    +
    # File 'lib/doing/configuration.rb', line 160
    +
    +def additional_configs
    +  @additional_configs ||= find_local_config
    +end
    +
    @@ -837,7 +949,75 @@

    -

    + + + + + +
    +
    +
    +
    +169
    +170
    +171
    +172
    +173
    +174
    +175
    +176
    +177
    +178
    +179
    +180
    +181
    +182
    +183
    +184
    +185
    +186
    +187
    +188
    +189
    +190
    +191
    +192
    +193
    +194
    +195
    +196
    +
    +
    # File 'lib/doing/configuration.rb', line 169
    +
    +def choose_config(create: false, local: false)
    +  if local && create
    +    res = File.expand_path('.doingrc')
    +    FileUtils.touch(res)
    +    return res
    +  end
    +
    +  return @config_file if @force_answer
    +
    +  if @additional_configs&.count&.positive? || create
    +    choices = [@config_file].concat(@additional_configs)
    +    choices.push('Create a new .doingrc in the current directory') if create && !File.exist?('.doingrc')
    +    res = Doing::Prompt.choose_from(choices.uniq.sort.reverse,
    +                                    sorted: false,
    +                                    prompt: 'Local configs found, select which to update > ')
    +
    +    raise UserCancelled, 'Cancelled' unless res
    +
    +    if res =~ /^Create a new/
    +      res = File.expand_path('.doingrc')
    +      FileUtils.touch(res)
    +    end
    +
    +    res.strip || @config_file
    +  else
    +    @config_file
    +  end
    +end
    +
    @@ -849,7 +1029,25 @@

    -

    + + + + + +
    +
    +
    +
    +131
    +132
    +133
    +
    +
    # File 'lib/doing/configuration.rb', line 131
    +
    +def config_dir
    +  @config_dir ||= File.join(Util.user_home, '.config', 'doing')
    +end
    +
    @@ -892,7 +1090,95 @@

    -

    + + + + + +
    +
    +
    +
    +338
    +339
    +340
    +341
    +342
    +343
    +344
    +345
    +346
    +347
    +348
    +349
    +350
    +351
    +352
    +353
    +354
    +355
    +356
    +357
    +358
    +359
    +360
    +361
    +362
    +363
    +364
    +365
    +366
    +367
    +368
    +369
    +370
    +371
    +372
    +373
    +374
    +375
    +
    +
    # File 'lib/doing/configuration.rb', line 338
    +
    +def configure(opt = {})
    +  update_deprecated_config if config_file == default_config_file
    +
    +  @ignore_local = opt[:ignore_local] if opt[:ignore_local]
    +
    +  config = read_config.clone
    +
    +  plugin_config = Util.deep_merge_hashes(DEFAULTS['plugins'], config['plugins'] || {})
    +
    +  load_plugins(plugin_config['plugin_path'])
    +
    +  Plugins.plugins.each do |_type, plugins|
    +    plugins.each do |title, plugin|
    +      plugin_config[title] = plugin[:config] if plugin[:config].good?
    +      config['export_templates'][title] ||= nil if plugin[:templates] && !plugin[:templates].empty?
    +    end
    +  end
    +
    +  config = Util.deep_merge_hashes({
    +                                    'plugins' => plugin_config
    +                                  }, config)
    +
    +  config = find_deprecations(config)
    +
    +  if !File.exist?(config_file) || opt[:rewrite]
    +    Util.write_to_file(config_file, YAML.dump(config), backup: true)
    +    Doing.logger.warn('Config:', "Config file written to #{config_file}")
    +  end
    +
    +  Hooks.trigger :post_config, self
    +
    +  config = local_config.deep_merge(config, { extend_existing_arrays: true, sort_merged_arrays: true }) unless @ignore_local
    +  # config = Util.deep_merge_hashes(config, local_config) unless @ignore_local
    +
    +  Hooks.trigger :post_local_config, self
    +
    +  config
    +end
    +
    @@ -904,7 +1190,45 @@

    -

    + + + + + +
    +
    +
    +
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +153
    +154
    +155
    +156
    +157
    +158
    +
    +
    # File 'lib/doing/configuration.rb', line 146
    +
    +def default_config_file
    +  if File.exist?(config_dir) && !File.directory?(config_dir)
    +    raise DoingRuntimeError, "#{config_dir} exists but is not a directory"
    +
    +  end
    +
    +  unless File.exist?(config_dir)
    +    FileUtils.mkdir_p(config_dir)
    +    Doing.logger.log_now(:warn, "Config directory created at #{config_dir}")
    +  end
    +
    +  File.join(config_dir, 'config.yml')
    +end
    +
    @@ -943,7 +1267,29 @@

    -

    + + + + + +
    +
    +
    +
    +140
    +141
    +142
    +143
    +144
    +
    +
    # File 'lib/doing/configuration.rb', line 140
    +
    +def exact_match?
    +  search_settings = @settings['search']
    +  matching = search_settings.fetch('matching', 'pattern').normalize_matching
    +  matching == :exact
    +end
    +
    @@ -955,7 +1301,25 @@

    -

    + + + + + +
    +
    +
    +
    +198
    +199
    +200
    +
    +
    # File 'lib/doing/configuration.rb', line 198
    +
    +def fetch(*path, default)
    +  @settings.dig(*path) || default
    +end
    +
    @@ -1016,7 +1380,27 @@

    -

    + + + + + +
    +
    +
    +
    +296
    +297
    +298
    +299
    +
    +
    # File 'lib/doing/configuration.rb', line 296
    +
    +def from(user_config)
    +  # Util.deep_merge_hashes(DEFAULTS, Configuration[user_config].stringify_keys)
    +  Configuration[user_config].stringify_keys.deep_merge(DEFAULTS, { extend_existing_arrays: true, sort_merged_arrays: true })
    +end
    +
    @@ -1078,7 +1462,121 @@

    -

    + + + + + +
    +
    +
    +
    +212
    +213
    +214
    +215
    +216
    +217
    +218
    +219
    +220
    +221
    +222
    +223
    +224
    +225
    +226
    +227
    +228
    +229
    +230
    +231
    +232
    +233
    +234
    +235
    +236
    +237
    +238
    +239
    +240
    +241
    +242
    +243
    +244
    +245
    +246
    +247
    +248
    +249
    +250
    +251
    +252
    +253
    +254
    +255
    +256
    +257
    +258
    +259
    +260
    +261
    +262
    +
    +
    # File 'lib/doing/configuration.rb', line 212
    +
    +def resolve_key_path(keypath, create: false, distance: 2, exact: false)
    +  cfg = @settings
    +  real_path = []
    +  unless keypath =~ /^[.*]?$/
    +    paths = keypath.split(/[:.]/)
    +    element_count = paths.count
    +    while paths.length.positive? && !cfg.nil?
    +      path = paths.shift
    +      new_cfg = nil
    +
    +      if cfg.is_a?(Hash)
    +        matches = if exact
    +                    cfg.select { |key, _| key == path }
    +                  else
    +                    cfg.select { |key, _| key =~ path.to_rx(distance: distance) }
    +                  end
    +        if matches.count.positive?
    +          shortest = matches.keys.group_by(&:length).min.last[0]
    +          real_path << shortest
    +          new_cfg = matches[shortest]
    +        end
    +      else
    +        new_cfg = cfg
    +      end
    +
    +      if new_cfg.nil?
    +        return real_path if real_path[-1] == path && real_path.count == element_count
    +
    +        if distance < 5 && !create
    +          return resolve_key_path(keypath, create: false, distance: distance + 1)
    +        else
    +          return nil unless create
    +        end
    +
    +        resolved = real_path.count.positive? ? "Resolved #{real_path.join('.')}, but " : ''
    +        Doing.logger.log_now(:warn, "#{resolved}#{path} is unknown")
    +        new_path = [*real_path, path, *paths].compact.join('.')
    +        Doing.logger.log_now(:warn, "Continuing will create the path #{new_path}")
    +        res = Prompt.yn('Key path not found, create it?', default_response: true)
    +        raise InvalidArgument, 'Invalid key path' unless res
    +
    +        real_path.push(path).concat(paths).compact!
    +        Doing.logger.debug('Config:', "translated key path #{keypath} to #{real_path.join('.')}") unless keypath == real_path.join('.')
    +        return real_path
    +      end
    +      cfg = new_cfg
    +    end
    +  end
    +  Doing.logger.debug('Config:', "translated key path #{keypath} to #{real_path.join('.')}") unless keypath == real_path.join('.')
    +  real_path
    +end
    +
    @@ -1134,7 +1632,47 @@

    -

    + + + + + +
    +
    +
    +
    +383
    +384
    +385
    +386
    +387
    +388
    +389
    +390
    +391
    +392
    +393
    +394
    +395
    +396
    +
    +
    # File 'lib/doing/configuration.rb', line 383
    +
    +def save_view(view, title)
    +  title.downcase!
    +  default_template = Doing.setting('templates.default')
    +  user_config = Util.safe_load_file(config_file)
    +  user_config['views'] = {} unless user_config.key?('views')
    +
    +  view.delete_if { |k, v| v == default_template[k] }
    +
    +  user_config['views'][title] = view
    +  Util.write_to_file(config_file, YAML.dump(user_config), backup: true)
    +  Doing.logger.warn('Config:', %(View "#{title}" saved to #{config_file}))
    +  Doing.logger.info('Config:', %(to use, run `doing view #{title}`))
    +  Hooks.trigger :post_config, self
    +end
    +
    @@ -1156,7 +1694,75 @@

    -
    +

    + + + + +
    +
    +
    +
    +304
    +305
    +306
    +307
    +308
    +309
    +310
    +311
    +312
    +313
    +314
    +315
    +316
    +317
    +318
    +319
    +320
    +321
    +322
    +323
    +324
    +325
    +326
    +327
    +328
    +329
    +330
    +331
    +
    +
    # File 'lib/doing/configuration.rb', line 304
    +
    +def update_deprecated_config
    +  # return # Until further notice
    +  return if File.exist?(default_config_file)
    +
    +  old_file = File.join(Util.user_home, '.doingrc')
    +  return unless File.exist?(old_file)
    +
    +  Doing.logger.log_now(:warn, 'Deprecated:', "main config file location has changed to #{config_file}")
    +  res = Prompt.yn("Move #{old_file} to new location, preserving settings?", default_response: true)
    +
    +  return unless res
    +
    +  if File.exist?(default_config_file)
    +    res = Prompt.yn("#{default_config_file} already exists, overwrite it?", default_response: false)
    +
    +    unless res
    +      @config_file = old_file
    +      return
    +    end
    +  end
    +
    +  FileUtils.mv old_file, default_config_file, force: true
    +  Doing.logger.log_now(:warn, 'Config:', "Config file moved to #{default_config_file}")
    +  Doing.logger.log_now(:warn, 'Config:', %(If ~/.doingrc exists in the future,
    +                       it will be considered a local config and its values will override the
    +                       default configuration.))
    +  Process.exit 0
    +end
    +
    @@ -1220,7 +1826,43 @@

    -

    + + + + + +
    +
    +
    +
    +274
    +275
    +276
    +277
    +278
    +279
    +280
    +281
    +282
    +283
    +284
    +285
    +
    +
    # File 'lib/doing/configuration.rb', line 274
    +
    +def value_for_key(keypath = '')
    +  cfg = @settings
    +  real_path = ['config']
    +  unless keypath =~ /^[.*]?$/
    +    real_path = resolve_key_path(keypath, create: false)
    +    return nil unless real_path&.count&.positive?
    +
    +    cfg = cfg.dig(*real_path)
    +  end
    +
    +  cfg.nil? ? nil : { real_path[-1] => cfg }
    +end
    +
    @@ -1228,9 +1870,9 @@

    diff --git a/docs/doc/Doing/DayOneRenderer.html b/docs/doc/Doing/DayOneRenderer.html index 3f3ae439..7246cb96 100644 --- a/docs/doc/Doing/DayOneRenderer.html +++ b/docs/doc/Doing/DayOneRenderer.html @@ -6,7 +6,7 @@ Class: Doing::DayOneRenderer - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -269,7 +269,29 @@

    -
    + + + + + +
    +
    +
    +
    +13
    +14
    +15
    +16
    +17
    +
    +
    # File 'lib/doing/plugins/export/dayone_export.rb', line 13
    +
    +def initialize(page_title, items, totals)
    +  @page_title = page_title
    +  @items = items
    +  @totals = totals
    +end
    +
    @@ -298,7 +320,25 @@

    -
    + + + + + +
    +
    +
    +
    +11
    +12
    +13
    +
    +
    # File 'lib/doing/plugins/export/dayone_export.rb', line 11
    +
    +def items
    +  @items
    +end
    +
    @@ -322,7 +362,25 @@

    -
    + + + + + +
    +
    +
    +
    +11
    +12
    +13
    +
    +
    # File 'lib/doing/plugins/export/dayone_export.rb', line 11
    +
    +def page_title
    +  @page_title
    +end
    +
    @@ -346,7 +404,25 @@

    -
    + + + + + +
    +
    +
    +
    +11
    +12
    +13
    +
    +
    # File 'lib/doing/plugins/export/dayone_export.rb', line 11
    +
    +def totals
    +  @totals
    +end
    +
    @@ -365,7 +441,25 @@

    -

    + + + + + +
    +
    +
    +
    +19
    +20
    +21
    +
    +
    # File 'lib/doing/plugins/export/dayone_export.rb', line 19
    +
    +def get_binding
    +  binding()
    +end
    +
    @@ -373,9 +467,9 @@

    diff --git a/docs/doc/Doing/DayoneExport.html b/docs/doc/Doing/DayoneExport.html index 2009615c..e71bfe09 100644 --- a/docs/doc/Doing/DayoneExport.html +++ b/docs/doc/Doing/DayoneExport.html @@ -6,7 +6,7 @@ Class: Doing::DayoneExport - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -236,7 +236,277 @@

    -

    + + + + + +
    +
    +
    +
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +153
    +154
    +155
    +156
    +157
    +158
    +159
    +160
    +161
    +162
    +163
    +164
    +165
    +166
    +167
    +168
    +169
    +170
    +171
    +172
    +173
    +174
    +
    +
    # File 'lib/doing/plugins/export/dayone_export.rb', line 46
    +
    +def self.render(wwid, items, variables: {})
    +
    +  return unless items.good?
    +
    +  config = Doing.settings
    +
    +  opt = variables[:options]
    +  trigger = opt[:output]
    +  digest = case trigger
    +           when /-days?$/
    +             :day
    +           when /-entries$/
    +             :entries
    +           else
    +             :digest
    +           end
    +
    +  all_items = []
    +  days = {}
    +  flagged = false
    +  tags = []
    +
    +  items.each do |i|
    +    day_flagged = false
    +    date_key = i.date.strftime('%Y-%m-%d')
    +
    +    if String.method_defined? :force_encoding
    +      title = i.title.force_encoding('utf-8').link_urls(format: :markdown)
    +      note = i.note.map { |line| line.force_encoding('utf-8').strip.link_urls(format: :markdown) } if i.note
    +    else
    +      title = i.title.link_urls(format: :markdown)
    +      note = i.note.map { |line| line.strip.link_urls(format: :markdown) } if i.note
    +    end
    +
    +    title = "#{title} @section(#{i.section})" unless variables[:is_single]
    +
    +    tags.concat(i.tag_array).sort!.uniq!
    +    flagged = day_flagged = true if i.tags?(config['marker_tag'])
    +
    +    interval = wwid.get_interval(i, record: true) if i.title =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
    +    interval ||= false
    +    human_time = false
    +    if interval
    +      d, h, m = wwid.get_interval(i, formatted: false).format_time
    +      human_times = []
    +      human_times << format('%<d>d day%<p>s', d: d, p: d == 1 ? '' : 's') if d > 0
    +      human_times << format('%<h>d hour%<p>s', h: h, p: h == 1 ? '' : 's') if h > 0
    +      human_times << format('%<m>d minute%<p>s', m: m, p: m == 1 ? '' : 's') if m > 0
    +      human_time = human_times.join(', ')
    +    end
    +
    +    done = i.tags?('done') ? ' ' : ' '
    +
    +    item = {
    +      date_object: i.date,
    +      date: i.date.strftime('%a %-I:%M%p'),
    +      shortdate: i.date.relative_date,
    +      done: done,
    +      note: note,
    +      section: i.section,
    +      time: interval,
    +      human_time: human_time,
    +      title: title.strip,
    +      starred: day_flagged,
    +      tags: i.tag_array
    +    }
    +    all_items << item
    +
    +
    +    if days.key?(date_key)
    +      days[date_key][:starred] = true if day_flagged
    +      days[date_key][:tags] = days[date_key][:tags].concat(i.tag_array).sort.uniq
    +      days[date_key][:entries].push(item)
    +    else
    +      days[date_key] ||= { tags: [], entries: [], starred: false }
    +      days[date_key][:starred] = true if day_flagged
    +      days[date_key][:tags] = days[date_key][:tags].concat(i.tag_array).sort.uniq
    +      days[date_key][:entries].push(item)
    +    end
    +  end
    +
    +
    +  template = if config['export_templates']['dayone'] && File.exist?(File.expand_path(config['export_templates']['dayone']))
    +               IO.read(File.expand_path(config['export_templates']['dayone']))
    +             else
    +               self.template('dayone')
    +             end
    +
    +  totals = opt[:totals] ? wwid.tag_times(format: :markdown, sort_by: opt[:sort_tags], sort_order: opt[:tag_order]) : ''
    +
    +  case digest
    +  when :day
    +    days.each do |k, hsh|
    +      title = "#{k}: #{variables[:page_title]}"
    +      to_dayone(template: template,
    +                title: title,
    +                items: hsh[:entries],
    +                totals: '',
    +                date: Time.parse(k),
    +                tags: tags,
    +                starred: hsh[:starred])
    +    end
    +  when :entries
    +    entry_template = if config['export_templates']['dayone_entry'] && File.exist?(File.expand_path(config['export_templates']['dayone_entry']))
    +                       IO.read(File.expand_path(config['export_templates']['dayone_entry']))
    +                     else
    +                       self.template('dayone-entry')
    +                     end
    +    all_items.each do |item|
    +      to_dayone(template: entry_template,
    +                title: '',
    +                items: [item],
    +                totals: '',
    +                date: item[:date_object],
    +                tags: item[:tags],
    +                starred: item[:starred])
    +    end
    +  else
    +    to_dayone(template: template,
    +                title: variables[:page_title],
    +                items: all_items,
    +                totals: totals,
    +                date: Time.now,
    +                tags: tags,
    +                starred: flagged)
    +  end
    +
    +  @out = ''
    +end
    +
    @@ -248,7 +518,37 @@

    -

    + + + + + +
    +
    +
    +
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +
    +
    # File 'lib/doing/plugins/export/dayone_export.rb', line 27
    +
    +def self.settings
    +  {
    +    trigger: 'day(?:one)?(?:-(?:days?|entries))?',
    +    templates: [
    +      { name: 'dayone', trigger: 'day(?:one)?$', format: 'erb', filename: 'dayone.erb' },
    +      { name: 'dayone_entry', trigger: 'day(?:one)-entr(?:y|ies)?$', format: 'erb', filename: 'dayone-entry.erb' }
    +    ]
    +  }
    +end
    +
    @@ -260,7 +560,35 @@

    -

    + + + + + +
    +
    +
    +
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +
    +
    # File 'lib/doing/plugins/export/dayone_export.rb', line 37
    +
    +def self.template(trigger)
    +  case trigger
    +  when /day(?:one)-entr(?:y|ies)?$/
    +    IO.read(File.join(File.dirname(__FILE__), '../../../templates/doing-dayone-entry.erb'))
    +  else
    +    IO.read(File.join(File.dirname(__FILE__), '../../../templates/doing-dayone.erb'))
    +  end
    +end
    +
    @@ -272,7 +600,79 @@

    -

    + + + + + +
    +
    +
    +
    +176
    +177
    +178
    +179
    +180
    +181
    +182
    +183
    +184
    +185
    +186
    +187
    +188
    +189
    +190
    +191
    +192
    +193
    +194
    +195
    +196
    +197
    +198
    +199
    +200
    +201
    +202
    +203
    +204
    +205
    +
    +
    # File 'lib/doing/plugins/export/dayone_export.rb', line 176
    +
    +def self.to_dayone(template: self.template(nil), title: 'doing', items: [], totals: '', date: Time.now, tags: [], starred: false)
    +  mdx = DayOneRenderer.new(title, items, totals)
    +
    +  engine = ERB.new(template)
    +  content = engine.result(mdx.get_binding)
    +
    +  uuid = SecureRandom.uuid
    +  # uuid = `uuidgen`.strip
    +
    +  plist = {
    +    'Creation Date' => date,
    +    'Creator' => { 'Software Agent' => 'Doing/2.0.0' },
    +    'Entry Text' => content,
    +    'Starred' => starred,
    +    'Tags' => tags.sort.uniq.delete_if { |t| t =~ /(done|cancell?ed|from)/ },
    +    'UUID' => uuid
    +  }
    +
    +  container = File.expand_path('~/Library/Group Containers/')
    +  dayone_dir = Dir.glob('*.dayoneapp2', base: container).first
    +  import_dir = File.join(container, dayone_dir, 'Data', 'Auto Import', 'Default Journal.dayone', 'entries')
    +  FileUtils.mkdir_p(import_dir) unless File.exist?(import_dir)
    +  entry_file = File.join(import_dir, "#{uuid}.doentry")
    +  Doing.logger.debug('Day One Export:', "Exporting to #{entry_file}")
    +  File.open(entry_file, 'w') do |f|
    +    f.puts plist.to_plist
    +  end
    +
    +  Doing.logger.count(:exported, level: :info, count: items.count, message: '%count %items exported to Day One import folder')
    +end
    +
    @@ -280,9 +680,9 @@

    diff --git a/docs/doc/Doing/DoingExport.html b/docs/doc/Doing/DoingExport.html index a474788c..dd74702b 100644 --- a/docs/doc/Doing/DoingExport.html +++ b/docs/doc/Doing/DoingExport.html @@ -6,7 +6,7 @@ Class: Doing::DoingExport - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -176,7 +176,57 @@

    -

    + + + + + +
    +
    +
    +
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +
    +
    # File 'lib/doing/plugins/export/doing_export.rb', line 15
    +
    +def self.render(wwid, items, variables: {})
    +  return if items.nil?
    +
    +  content = Doing::Items.new
    +  items.each do |item|
    +    content.add_section(item.section, log: false)
    +    content.push(item)
    +  end
    +
    +  out = []
    +  content.sections.each do |section|
    +    out.push(section.original)
    +    is = content.in_section(section.title).sort_by { |i| [i.date, i.title] }
    +    is.reverse! if Doing.setting('doing_file_sort').normalize_order == :desc
    +    is.each { |item| out.push(item.to_s) }
    +  end
    +
    +  Doing::Pager.page out.join("\n")
    +end
    +
    @@ -188,7 +238,29 @@

    -

    + + + + + +
    +
    +
    +
    +9
    +10
    +11
    +12
    +13
    +
    +
    # File 'lib/doing/plugins/export/doing_export.rb', line 9
    +
    +def self.settings
    +  {
    +    trigger: 'doing'
    +  }
    +end
    +
    @@ -196,9 +268,9 @@

    diff --git a/docs/doc/Doing/DoingImport.html b/docs/doc/Doing/DoingImport.html index b3db861f..dda0b124 100644 --- a/docs/doc/Doing/DoingImport.html +++ b/docs/doc/Doing/DoingImport.html @@ -6,7 +6,7 @@ Class: Doing::DoingImport - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -259,7 +259,33 @@

    - + + + + + +
    +
    +
    +
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +
    +
    # File 'lib/doing/plugins/import/doing_import.rb', line 112
    +
    +def self.duplicate?(item)
    +  @old_items.each do |oi|
    +    return true if item.equal?(oi)
    +  end
    +
    +  false
    +end
    +
    @@ -349,7 +375,189 @@

    -

    + + + + + +
    +
    +
    +
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +
    +
    # File 'lib/doing/plugins/import/doing_import.rb', line 26
    +
    +def self.import(wwid, path, options: {})
    +  exit_now! 'Path to Doing file required' if path.nil?
    +
    +  exit_now! 'File not found' unless File.exist?(File.expand_path(path))
    +
    +  options[:no_overlap] ||= false
    +
    +  options[:autotag] ||= Doing.auto_tag
    +
    +  tags = options[:tag] ? options[:tag].split(/[ ,]+/).map { |t| t.sub(/^@?/, '') } : []
    +  prefix = options[:prefix] || ''
    +
    +  @old_items = wwid.content.dup
    +
    +  new_items = read_doing_file(path)
    +
    +  total = new_items.count
    +
    +  options[:count] = 0
    +  new_items = wwid.filter_items(new_items, opt: options)
    +
    +  skipped = total - new_items.count
    +  Doing.logger.debug('Skipped:', %(#{skipped} items that didn't match filter criteria)) if skipped.positive?
    +
    +  imported = []
    +  updated = 0
    +
    +  new_items.each do |item|
    +    next if duplicate?(item)
    +
    +    title = "#{prefix} #{item.title}"
    +    tags.each do |tag|
    +      if title =~ /\b#{tag}\b/i
    +        title.sub!(/\b#{tag}\b/i, "@#{tag}")
    +      else
    +        title += " @#{tag}"
    +      end
    +    end
    +    title = wwid.autotag(title) if options[:autotag]
    +    title.gsub!(/ +/, ' ')
    +    title.strip!
    +    section = options[:section] || item.section
    +    section ||= Doing.setting('current_section')
    +
    +    new_item = Item.new(item.date, title, section, item.note, item.id)
    +
    +    is_match = true
    +
    +    if options[:search]
    +      is_match = new_item.search(options[:search], case_type: options[:case], negate: options[:not])
    +    end
    +
    +    if is_match && options[:date_filter]
    +      is_match = new_item.date > options[:date_filter][0] && new_item.date < options[:date_filter][1]
    +      is_match = options[:not] ? !is_match : is_match
    +    end
    +
    +    if wwid.content.find_id(new_item.id)
    +      old_index = wwid.content.index_for_id(new_item.id)
    +      old_item = wwid.content[old_index].clone
    +      wwid.content[old_index] = new_item
    +      Hooks.trigger :post_entry_updated, self, new_item, old_item
    +      updated += 1
    +    else
    +      imported.push(new_item) if is_match
    +    end
    +  end
    +
    +  dups = new_items.count - imported.count
    +  Doing.logger.info('Skipped:', %(#{dups} duplicate items)) if dups.positive?
    +
    +  imported = wwid.dedup(imported, no_overlap: options[:no_overlap])
    +  overlaps = new_items.count - imported.count - dups
    +  Doing.logger.debug('Skipped:', "#{overlaps} items with overlapping times") if overlaps.positive?
    +
    +  imported.each do |item|
    +    wwid.content.add_section(item.section) unless wwid.content.section?(item.section)
    +    Hooks.trigger :pre_entry_add, self, item
    +    wwid.content.push(item)
    +    Hooks.trigger :post_entry_added, self, item
    +  end
    +
    +  Doing.logger.info('Updated:', %(#{updated} items))
    +  Doing.logger.info('Imported:', "#{imported.count} items")
    +end
    +
    @@ -361,7 +569,105 @@

    -

    +

    + + + + +
    +
    +
    +
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +153
    +154
    +155
    +156
    +157
    +158
    +159
    +160
    +161
    +162
    +
    +
    # File 'lib/doing/plugins/import/doing_import.rb', line 120
    +
    +def self.read_doing_file(path)
    +  doing_file = File.expand_path(path)
    +
    +  return nil unless File.exist?(doing_file) && File.file?(doing_file) && File.stat(doing_file).size.positive?
    +
    +  input = IO.read(doing_file)
    +  input = input.force_encoding('utf-8') if input.respond_to? :force_encoding
    +
    +  lines = input.split(/[\n\r]/)
    +  current = 0
    +
    +  items = []
    +  section = ''
    +
    +  lines.each do |line|
    +    next if line =~ /^\s*$/
    +
    +    case line
    +    when /^(\S[\S ]+):(\s+@[\w\-_.]+(?= |$))*\s*$/
    +      section = Regexp.last_match(1)
    +      current = 0
    +    when /^\s*- (\d{4}-\d\d-\d\d \d\d:\d\d) \| (.*?)(?: <([a-z0-9]{32})>)? *$/
    +      date = Regexp.last_match(1).strip
    +      title = Regexp.last_match(2).strip
    +      id = Regexp.last_match(3)
    +      item = Item.new(date, title, section, nil, id)
    +      items.push(item)
    +      current += 1
    +    when /^\S/
    +      next
    +    else
    +      next if current.zero?
    +
    +      prev_item = items[current - 1]
    +      prev_item.note = Note.new unless prev_item.note
    +
    +      prev_item.note.add(line)
    +      # end
    +    end
    +  end
    +
    +  items
    +end
    +
    @@ -373,7 +679,29 @@

    -

    + + + + + +
    +
    +
    +
    +11
    +12
    +13
    +14
    +15
    +
    +
    # File 'lib/doing/plugins/import/doing_import.rb', line 11
    +
    +def self.settings
    +  {
    +    trigger: 'doing'
    +  }
    +end
    +
    @@ -381,9 +709,9 @@

    diff --git a/docs/doc/Doing/Entry.html b/docs/doc/Doing/Entry.html index 59f420cc..ab73f5ec 100644 --- a/docs/doc/Doing/Entry.html +++ b/docs/doc/Doing/Entry.html @@ -6,7 +6,7 @@ Class: Doing::Entry - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -329,7 +329,29 @@

    -
    + + + + + +
    +
    +
    +
    +10
    +11
    +12
    +13
    +14
    +
    +
    # File 'lib/doing/changelog/entry.rb', line 10
    +
    +def initialize(string, type, prefix: false)
    +  @string = string
    +  @type = type
    +  @prefix = prefix
    +end
    +
    @@ -377,7 +399,25 @@

    - + + + + + +
    +
    +
    +
    +8
    +9
    +10
    +
    +
    # File 'lib/doing/changelog/entry.rb', line 8
    +
    +def prefix=(value)
    +  @prefix = value
    +end
    +
    @@ -401,7 +441,25 @@

    -
    + + + + + +
    +
    +
    +
    +6
    +7
    +8
    +
    +
    # File 'lib/doing/changelog/entry.rb', line 6
    +
    +def string
    +  @string
    +end
    +
    @@ -425,7 +483,25 @@

    -
    + + + + + +
    +
    +
    +
    +6
    +7
    +8
    +
    +
    # File 'lib/doing/changelog/entry.rb', line 6
    +
    +def type
    +  @type
    +end
    +
    @@ -444,7 +520,25 @@

    -

    + + + + + +
    +
    +
    +
    +16
    +17
    +18
    +
    +
    # File 'lib/doing/changelog/entry.rb', line 16
    +
    +def clean(string)
    +  string.gsub(/\|/, '\|')
    +end
    +
    @@ -456,7 +550,25 @@ + + + + + +
    +
    +
    +
    +20
    +21
    +22
    +
    +
    # File 'lib/doing/changelog/entry.rb', line 20
    +
    +def print_prefix
    +  @prefix ? "#{@type}: " : ''
    +end
    +
    @@ -468,7 +580,25 @@

    -

    + + + + + +
    +
    +
    +
    +24
    +25
    +26
    +
    +
    # File 'lib/doing/changelog/entry.rb', line 24
    +
    +def to_s
    +  "- #{print_prefix}#{clean(@string)}"
    +end
    +
    @@ -476,9 +606,9 @@

    diff --git a/docs/doc/Doing/Errors.html b/docs/doc/Doing/Errors.html index e568de46..2075465e 100644 --- a/docs/doc/Doing/Errors.html +++ b/docs/doc/Doing/Errors.html @@ -6,7 +6,7 @@ Module: Doing::Errors - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -181,9 +181,9 @@

    diff --git a/docs/doc/Doing/Errors/DoingNoTraceError.html b/docs/doc/Doing/Errors/DoingNoTraceError.html index fc756be4..6f818322 100644 --- a/docs/doc/Doing/Errors/DoingNoTraceError.html +++ b/docs/doc/Doing/Errors/DoingNoTraceError.html @@ -6,7 +6,7 @@ Exception: Doing::Errors::DoingNoTraceError - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -171,7 +171,37 @@

    -
    + + + + + +
    +
    +
    +
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +
    +
    # File 'lib/doing/errors.rb', line 6
    +
    +def initialize(msg = nil, level: nil, topic: 'Error:', exit_code: 1)
    +  level ||= :error
    +  Doing.logger.output_results
    +  if msg
    +    Doing.logger.log_now(level, topic, msg)
    +  end
    +
    +  Process.exit exit_code
    +end
    +
    @@ -180,9 +210,9 @@

    diff --git a/docs/doc/Doing/Errors/DoingRuntimeError.html b/docs/doc/Doing/Errors/DoingRuntimeError.html index 9a4d0282..21d1561e 100644 --- a/docs/doc/Doing/Errors/DoingRuntimeError.html +++ b/docs/doc/Doing/Errors/DoingRuntimeError.html @@ -6,7 +6,7 @@ Exception: Doing::Errors::DoingRuntimeError - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -171,7 +171,31 @@

    -
    + + + + + +
    +
    +
    +
    +44
    +45
    +46
    +47
    +48
    +49
    +
    +
    # File 'lib/doing/errors.rb', line 44
    +
    +def initialize(msg = 'Runtime Error', exit_code = nil, topic: 'Error:')
    +  Doing.logger.output_results
    +  Doing.logger.log_now(:error, topic, msg)
    +
    +  Process.exit exit_code if exit_code
    +end
    +
    @@ -180,9 +204,9 @@

    diff --git a/docs/doc/Doing/Errors/DoingStandardError.html b/docs/doc/Doing/Errors/DoingStandardError.html index 0fb212e7..b92b581a 100644 --- a/docs/doc/Doing/Errors/DoingStandardError.html +++ b/docs/doc/Doing/Errors/DoingStandardError.html @@ -6,7 +6,7 @@ Exception: Doing::Errors::DoingStandardError - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -167,7 +167,29 @@

    -
    + + + + + +
    +
    +
    +
    +30
    +31
    +32
    +33
    +34
    +
    +
    # File 'lib/doing/errors.rb', line 30
    +
    +def initialize(msg = '')
    +  Doing.logger.output_results
    +
    +  super(msg)
    +end
    +
    @@ -176,9 +198,9 @@

    diff --git a/docs/doc/Doing/Errors/EmptyInput.html b/docs/doc/Doing/Errors/EmptyInput.html index 4acb6fd7..a8b2653d 100644 --- a/docs/doc/Doing/Errors/EmptyInput.html +++ b/docs/doc/Doing/Errors/EmptyInput.html @@ -6,7 +6,7 @@ Exception: Doing::Errors::EmptyInput - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -175,7 +175,25 @@

    -
    + + + + + +
    +
    +
    +
    +24
    +25
    +26
    +
    +
    # File 'lib/doing/errors.rb', line 24
    +
    +def initialize(msg = 'No input', topic = 'Exited:')
    +  super(msg, level: :warn, topic: topic, exit_code: 6)
    +end
    +
    @@ -184,9 +202,9 @@

    diff --git a/docs/doc/Doing/Errors/HistoryLimitError.html b/docs/doc/Doing/Errors/HistoryLimitError.html index d1ad106a..2c22ea11 100644 --- a/docs/doc/Doing/Errors/HistoryLimitError.html +++ b/docs/doc/Doing/Errors/HistoryLimitError.html @@ -6,7 +6,7 @@ Exception: Doing::Errors::HistoryLimitError - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -175,7 +175,25 @@

    -
    + + + + + +
    +
    +
    +
    +60
    +61
    +62
    +
    +
    # File 'lib/doing/errors.rb', line 60
    +
    +def initialize(msg, exit_code = 24)
    +  super(msg, level: :error, topic: 'History:', exit_code: exit_code)
    +end
    +
    @@ -184,9 +202,9 @@

    diff --git a/docs/doc/Doing/Errors/InvalidPlugin.html b/docs/doc/Doing/Errors/InvalidPlugin.html index 1cdbd3b7..55ca3197 100644 --- a/docs/doc/Doing/Errors/InvalidPlugin.html +++ b/docs/doc/Doing/Errors/InvalidPlugin.html @@ -6,7 +6,7 @@ Exception: Doing::Errors::InvalidPlugin - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -175,7 +175,25 @@

    -
    + + + + + +
    +
    +
    +
    +72
    +73
    +74
    +
    +
    # File 'lib/doing/errors.rb', line 72
    +
    +def initialize(kind = 'output', msg = nil)
    +  super(%(Invalid #{kind} type (#{msg})), 128, topic: 'Plugin:')
    +end
    +
    @@ -184,9 +202,9 @@

    diff --git a/docs/doc/Doing/Errors/MissingBackupFile.html b/docs/doc/Doing/Errors/MissingBackupFile.html index 59f51c6a..52e2b88f 100644 --- a/docs/doc/Doing/Errors/MissingBackupFile.html +++ b/docs/doc/Doing/Errors/MissingBackupFile.html @@ -6,7 +6,7 @@ Exception: Doing::Errors::MissingBackupFile - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -175,7 +175,25 @@

    -
    + + + + + +
    +
    +
    +
    +66
    +67
    +68
    +
    +
    # File 'lib/doing/errors.rb', line 66
    +
    +def initialize(msg, exit_code = 26)
    +  super(msg, level: :error, topic: 'History:', exit_code: exit_code)
    +end
    +
    @@ -184,9 +202,9 @@

    diff --git a/docs/doc/Doing/Errors/NoResults.html b/docs/doc/Doing/Errors/NoResults.html index bed3a94a..bba2387b 100644 --- a/docs/doc/Doing/Errors/NoResults.html +++ b/docs/doc/Doing/Errors/NoResults.html @@ -6,7 +6,7 @@ Exception: Doing::Errors::NoResults - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -175,7 +175,27 @@

    -
    + + + + + +
    +
    +
    +
    +53
    +54
    +55
    +56
    +
    +
    # File 'lib/doing/errors.rb', line 53
    +
    +def initialize(msg = 'No results', topic = 'Exited:')
    +  super(msg, level: :warn, topic: topic, exit_code: 0)
    +
    +end
    +
    @@ -184,9 +204,9 @@

    diff --git a/docs/doc/Doing/Errors/PluginException.html b/docs/doc/Doing/Errors/PluginException.html index 24761333..88153e58 100644 --- a/docs/doc/Doing/Errors/PluginException.html +++ b/docs/doc/Doing/Errors/PluginException.html @@ -6,7 +6,7 @@ Exception: Doing::Errors::PluginException - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -200,7 +200,65 @@

    -
    + + + + + +
    +
    +
    +
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +
    +
    # File 'lib/doing/errors.rb', line 80
    +
    +def initialize(msg = 'Plugin error', type = nil, plugin = nil)
    +  @plugin = plugin || 'Unknown Plugin'
    +
    +  type ||= 'Unknown'
    +  @type = case type.to_s
    +          when /^i/
    +            'Import plugin'
    +          when /^e/
    +            'Export plugin'
    +          when /^h/
    +            'Hook'
    +          when /^u/
    +            'Unrecognized'
    +          else
    +            type.to_s
    +          end
    +
    +  msg = "(#{@type}: #{@plugin}) #{msg}"
    +
    +  Doing.logger.log_now(:error, 'Plugin:', msg)
    +
    +  super(msg)
    +end
    +
    @@ -229,7 +287,25 @@

    -
    + + + + + +
    +
    +
    +
    +78
    +79
    +80
    +
    +
    # File 'lib/doing/errors.rb', line 78
    +
    +def plugin
    +  @plugin
    +end
    +
    @@ -238,9 +314,9 @@

    diff --git a/docs/doc/Doing/Errors/UserCancelled.html b/docs/doc/Doing/Errors/UserCancelled.html index 22041000..88d9b67f 100644 --- a/docs/doc/Doing/Errors/UserCancelled.html +++ b/docs/doc/Doing/Errors/UserCancelled.html @@ -6,7 +6,7 @@ Exception: Doing::Errors::UserCancelled - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -175,7 +175,25 @@

    -
    + + + + + +
    +
    +
    +
    +18
    +19
    +20
    +
    +
    # File 'lib/doing/errors.rb', line 18
    +
    +def initialize(msg = 'Cancelled', topic = 'Exited:')
    +  super(msg, level: :warn, topic: topic, exit_code: 1)
    +end
    +
    @@ -184,9 +202,9 @@

    diff --git a/docs/doc/Doing/Errors/WrongCommand.html b/docs/doc/Doing/Errors/WrongCommand.html index a4f64ac2..e768bc79 100644 --- a/docs/doc/Doing/Errors/WrongCommand.html +++ b/docs/doc/Doing/Errors/WrongCommand.html @@ -6,7 +6,7 @@ Exception: Doing::Errors::WrongCommand - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -175,7 +175,25 @@

    -
    + + + + + +
    +
    +
    +
    +38
    +39
    +40
    +
    +
    # File 'lib/doing/errors.rb', line 38
    +
    +def initialize(msg = 'wrong command', topic: 'Error:')
    +  super(msg, level: :warn, topic: topic, exit_code: 2)
    +end
    +
    @@ -184,9 +202,9 @@

    diff --git a/docs/doc/Doing/HTMLExport.html b/docs/doc/Doing/HTMLExport.html index 9cc8b143..1432cbab 100644 --- a/docs/doc/Doing/HTMLExport.html +++ b/docs/doc/Doing/HTMLExport.html @@ -6,7 +6,7 @@ Class: Doing::HTMLExport - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -214,7 +214,119 @@

    -

    + + + + + +
    +
    +
    +
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +
    +
    # File 'lib/doing/plugins/export/html_export.rb', line 29
    +
    +def self.render(wwid, items, variables: {})
    +  return if items.nil?
    +
    +  opt = variables[:options]
    +
    +  items_out = []
    +  items.each do |i|
    +    # if i.has_key?('note')
    +    #   note = '<span class="note">' + i.note.map{|n| n.strip }.join('<br>') + '</span>'
    +    # else
    +    #   note = ''
    +    # end
    +    if String.method_defined? :force_encoding
    +      title = i.title.force_encoding('utf-8').link_urls
    +      note = i.note.map { |line| line.force_encoding('utf-8').strip.link_urls } if i.note
    +    else
    +      title = i.title.link_urls
    +      note = i.note.map { |line| line.strip.link_urls } if i.note
    +    end
    +
    +    interval = wwid.get_interval(i) if i.title =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
    +    interval ||= false
    +
    +    items_out << {
    +      date: i.date.strftime('%a %-I:%M%p'),
    +      title: title.gsub(/(@[^ (]+(\(.*?\))?)/im, '<span class="tag">\1</span>').strip, #+ " #{note}"
    +      note: note,
    +      time: interval,
    +      section: i.section
    +    }
    +  end
    +
    +  template = if Doing.setting('export_templates.haml') && File.exist?(File.expand_path(Doing.setting('export_templates.haml')))
    +               IO.read(File.expand_path(Doing.setting('export_templates.haml')))
    +             else
    +               self.template('html')
    +             end
    +
    +  style = if Doing.setting('export_templates.css') && File.exist?(File.expand_path(Doing.setting('export_templates.css')))
    +            IO.read(File.expand_path(Doing.setting('export_templates.css')))
    +          else
    +            self.template('css')
    +          end
    +
    +  totals = opt[:totals] ? wwid.tag_times(format: :html, sort_by: opt[:sort_tags], sort_order: opt[:tag_order]) : ''
    +  engine = Haml::Engine.new(template)
    +  Doing.logger.debug('HTML Export:', "#{items_out.count} items output to HTML")
    +  @out = engine.render(Object.new,
    +                     { :@items => items_out, :@page_title => variables[:page_title], :@style => style, :@totals => totals })
    +end
    +
    @@ -226,7 +338,37 @@

    -

    + + + + + +
    +
    +
    +
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +
    +
    # File 'lib/doing/plugins/export/html_export.rb', line 11
    +
    +def self.settings
    +  {
    +    trigger: 'html?|web(?:page)?',
    +    templates: [
    +      { name: 'html', trigger: 'h[ta]ml?|web', format: 'haml', filename: 'html_export.haml' },
    +      { name: 'html_style', trigger: 'css|styl(?:e|us)', format: 'css', filename: 'html_export.css' }
    +    ]
    +  }
    +end
    +
    @@ -238,7 +380,33 @@

    -

    + + + + + +
    +
    +
    +
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +
    +
    # File 'lib/doing/plugins/export/html_export.rb', line 21
    +
    +def self.template(trigger)
    +  if trigger =~ /^(css|sty)/
    +    IO.read(File.join(File.dirname(__FILE__), '../../../templates/doing.css'))
    +  else
    +    IO.read(File.join(File.dirname(__FILE__), '../../../templates/doing.haml'))
    +  end
    +end
    +
    @@ -246,9 +414,9 @@

    diff --git a/docs/doc/Doing/Hooks.html b/docs/doc/Doing/Hooks.html index 7e842219..8ebabd5e 100644 --- a/docs/doc/Doing/Hooks.html +++ b/docs/doc/Doing/Hooks.html @@ -6,7 +6,7 @@ Module: Doing::Hooks - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -255,7 +255,27 @@

    -

    + + + + + +
    +
    +
    +
    +53
    +54
    +55
    +56
    +
    +
    # File 'lib/doing/hooks.rb', line 53
    +
    +def self.insert_hook(event, priority, &block)
    +  @hook_priority[block] = [-priority, @hook_priority.size]
    +  @registry[event] << block
    +end
    +
    @@ -277,7 +297,29 @@

    -
    +

    + + + + +
    +
    +
    +
    +34
    +35
    +36
    +37
    +38
    +
    +
    # File 'lib/doing/hooks.rb', line 34
    +
    +def self.priority_value(priority)
    +  return priority if priority.is_a?(Integer)
    +
    +  PRIORITY_MAP[priority] || DEFAULT_PRIORITY
    +end
    +
    @@ -299,7 +341,33 @@

    -
    +

    + + + + +
    +
    +
    +
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +
    +
    # File 'lib/doing/hooks.rb', line 25
    +
    +def self.register(event, priority: DEFAULT_PRIORITY, &block)
    +  if event.is_a?(Array)
    +    event.each { |ev| register_one(ev, priority_value(priority), &block) }
    +  else
    +    register_one(event, priority_value(priority), &block)
    +  end
    +end
    +
    @@ -334,7 +402,41 @@

    -

    + + + + + +
    +
    +
    +
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +
    +
    # File 'lib/doing/hooks.rb', line 41
    +
    +def self.register_one(event, priority, &block)
    +  unless @registry[event]
    +    raise Doing::Errors::HookUnavailable.new("Invalid hook. Doing only supports #{@registry.keys.inspect}", 'hook', event)
    +  end
    +
    +  raise Doing::Errors::PluginUncallable.new('Hooks must respond to :call', 'hook', event) unless block.respond_to? :call
    +
    +  Doing.logger.debug('Hook Manager:', "Registered #{event} hook") if ENV['DOING_PLUGIN_DEBUG']
    +
    +  insert_hook event, priority, &block
    +end
    +
    @@ -346,7 +448,37 @@

    -

    + + + + + +
    +
    +
    +
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +
    +
    # File 'lib/doing/hooks.rb', line 58
    +
    +def self.trigger(event, *args)
    +  hooks = @registry[event]
    +  return unless hooks.good?
    +
    +  # sort and call hooks according to priority and load order
    +  hooks.sort_by { |h| @hook_priority[h] }.each do |hook|
    +    hook.call(*args)
    +  end
    +end
    +
    @@ -354,9 +486,9 @@

    diff --git a/docs/doc/Doing/Item.html b/docs/doc/Doing/Item.html index b2aae81c..865d354e 100644 --- a/docs/doc/Doing/Item.html +++ b/docs/doc/Doing/Item.html @@ -6,7 +6,7 @@ Class: Doing::Item - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -388,7 +388,7 @@

  • - #to_pretty(elements: %i[date title section]) ⇒ Object + #to_pretty(elements: %i[date title section])) ⇒ Object @@ -597,7 +597,33 @@

    - + + + + + +
    +
    +
    +
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +
    +
    # File 'lib/doing/item/item.rb', line 36
    +
    +def initialize(date, title, section, note = nil, id = nil)
    +  @date = date.is_a?(Time) ? date : Time.parse(date)
    +  @title = title
    +  @section = section
    +  @note = Note.new(note)
    +  @id = id&.valid_id? ? id.strip : gen_id
    +end
    +
    @@ -626,7 +652,25 @@

    -
    + + + + + +
    +
    +
    +
    +18
    +19
    +20
    +
    +
    # File 'lib/doing/item/item.rb', line 18
    +
    +def date
    +  @date
    +end
    +
    @@ -650,7 +694,25 @@

    -
    + + + + + +
    +
    +
    +
    +18
    +19
    +20
    +
    +
    # File 'lib/doing/item/item.rb', line 18
    +
    +def id
    +  @id
    +end
    +
    @@ -674,7 +736,25 @@

    -
    + + + + + +
    +
    +
    +
    +18
    +19
    +20
    +
    +
    # File 'lib/doing/item/item.rb', line 18
    +
    +def note
    +  @note
    +end
    +
    @@ -698,7 +778,25 @@

    -
    + + + + + +
    +
    +
    +
    +18
    +19
    +20
    +
    +
    # File 'lib/doing/item/item.rb', line 18
    +
    +def section
    +  @section
    +end
    +
    @@ -722,7 +820,25 @@

    -
    + + + + + +
    +
    +
    +
    +18
    +19
    +20
    +
    +
    # File 'lib/doing/item/item.rb', line 18
    +
    +def title
    +  @title
    +end
    +
    @@ -741,7 +857,25 @@

    -

    +
  • + + + + +
    +
    +
    +
    +123
    +124
    +125
    +
    +
    # File 'lib/doing/item/item.rb', line 123
    +
    +def clone
    +  Marshal.load(Marshal.dump(self))
    +end
    +
    @@ -816,7 +950,41 @@

    -

    + + + + + +
    +
    +
    +
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +
    +
    # File 'lib/doing/item/item.rb', line 56
    +
    +def equal?(other, match_section: false)
    +  return false if @title.strip != other.title.strip
    +
    +  return false if @date != other.date
    +
    +  return false unless @note.equal?(other.note)
    +
    +  return false if match_section && !@section.equal?(other.section)
    +
    +  true
    +end
    +
    @@ -828,7 +996,25 @@

    -

    + + + + + +
    +
    +
    +
    +44
    +45
    +46
    +
    +
    # File 'lib/doing/item/item.rb', line 44
    +
    +def gen_id
    +  Digest::MD5.hexdigest(to_s)
    +end
    +
    @@ -922,13 +1108,47 @@

    -

    + + + + + +
    +
    +
    +
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +
    +
    # File 'lib/doing/item/item.rb', line 79
    +
    +def move_to(new_section, label: true, log: true)
    +  from = @section
    +
    +  tag('from', rename_to: 'from', value: from, force: true) if label
    +  @section = new_section
    +
    +  Doing.logger.count(@section == 'Archive' ? :archived : :moved) if log
    +  Doing.logger.debug("#{@section == 'Archive' ? 'Archived' : 'Moved'}:",
    +                     "#{@title.trunc(60)} from #{from} to #{@section}")
    +  self
    +end
    +

    - #to_pretty(elements: %i[date title section]) ⇒ Object + #to_pretty(elements: %i[date title section])) ⇒ Object @@ -961,7 +1181,49 @@

    -

    + + + + + +
    +
    +
    +
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +
    +
    # File 'lib/doing/item/item.rb', line 101
    +
    +def to_pretty(elements: %i[date title section])
    +  output = []
    +  elements.each do |e|
    +    case e
    +    when :date
    +      output << format('%13s |', @date.relative_date).cyan
    +    when :section
    +      output << "#{magenta}(#{white(@section)}#{magenta})"
    +    when :title
    +      output << @title.white.highlight_tags('cyan')
    +    end
    +  end
    +
    +  output.join(' ')
    +end
    +
    @@ -983,7 +1245,25 @@

    -
    +

    + + + + +
    +
    +
    +
    +92
    +93
    +94
    +
    +
    # File 'lib/doing/item/item.rb', line 92
    +
    +def to_s
    +  "\t- #{@date.strftime('%Y-%m-%d %H:%M')} | #{@title} <#{@id}>#{@note.good? ? "\n#{@note}" : ''}"
    +end
    +
    @@ -991,9 +1271,9 @@

    diff --git a/docs/doc/Doing/ItemDates.html b/docs/doc/Doing/ItemDates.html index 0421c76c..06548c72 100644 --- a/docs/doc/Doing/ItemDates.html +++ b/docs/doc/Doing/ItemDates.html @@ -6,7 +6,7 @@ Module: Doing::ItemDates - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -281,7 +281,55 @@

    -

    + + + + + +
    +
    +
    +
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +
    +
    # File 'lib/doing/item/dates.rb', line 35
    +
    +def calculate_end_date(opt)
    +  if opt[:took]
    +    if @date + opt[:took] > Time.now
    +      @date = Time.now - opt[:took]
    +      Time.now
    +    else
    +      @date + opt[:took]
    +    end
    +  elsif opt[:back]
    +    if opt[:back].is_a? Integer
    +      @date + opt[:back]
    +    else
    +      @date + (opt[:back] - @date)
    +    end
    +  else
    +    Time.now
    +  end
    +end
    +
    @@ -303,7 +351,33 @@

    -
    +

    + + + + +
    +
    +
    +
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +
    +
    # File 'lib/doing/item/dates.rb', line 8
    +
    +def duration
    +  return nil unless should_time? && should_finish?
    +
    +  return nil if @title =~ /(?<=^| )@done\b/
    +
    +  return Time.now - @date
    +end
    +
    @@ -342,7 +416,25 @@

    -

    + + + + + +
    +
    +
    +
    +31
    +32
    +33
    +
    +
    # File 'lib/doing/item/dates.rb', line 31
    +
    +def end_date
    +  @end_date ||= Time.parse(Regexp.last_match(1)) if @title =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
    +end
    +
    @@ -389,7 +481,25 @@

    -

    + + + + + +
    +
    +
    +
    +94
    +95
    +96
    +
    +
    # File 'lib/doing/item/dates.rb', line 94
    +
    +def expand_date_tags(additional_tags = nil)
    +  @title.expand_date_tags(additional_tags)
    +end
    +
    @@ -429,7 +539,25 @@

    -

    + + + + + +
    +
    +
    +
    +22
    +23
    +24
    +
    +
    # File 'lib/doing/item/dates.rb', line 22
    +
    +def interval
    +  @interval ||= calc_interval
    +end
    +
    @@ -488,7 +616,41 @@

    -

    + + + + + +
    +
    +
    +
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +
    +
    # File 'lib/doing/item/dates.rb', line 73
    +
    +def overlapping_time?(item_b)
    +  return true if same_time?(item_b)
    +
    +  start_a = date
    +  a_interval = interval
    +  end_a = a_interval ? start_a + a_interval.to_i : start_a
    +  start_b = item_b.date
    +  b_interval = item_b.interval
    +  end_b = b_interval ? start_b + b_interval.to_i : start_b
    +  (start_a >= start_b && start_a <= end_b) || (end_a >= start_b && end_a <= end_b) || (start_a < start_b && end_a > end_b)
    +end
    +
    @@ -546,7 +708,25 @@

    -

    + + + + + +
    +
    +
    +
    +61
    +62
    +63
    +
    +
    # File 'lib/doing/item/dates.rb', line 61
    +
    +def same_time?(item_b)
    +  date == item_b.date ? interval == item_b.interval : false
    +end
    +
    @@ -554,9 +734,9 @@

    diff --git a/docs/doc/Doing/ItemQuery.html b/docs/doc/Doing/ItemQuery.html index 81c69c3b..4e0144f7 100644 --- a/docs/doc/Doing/ItemQuery.html +++ b/docs/doc/Doing/ItemQuery.html @@ -6,7 +6,7 @@ Module: Doing::ItemQuery - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -268,7 +268,73 @@

    -

    + + + + + +
    +
    +
    +
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +
    +
    # File 'lib/doing/item/query.rb', line 72
    +
    +def highlight_search(search, distance: nil, negate: false, case_type: nil)
    +  prefs = Doing.setting('search', {})
    +  matching = prefs.fetch('matching', 'pattern').normalize_matching
    +  distance ||= prefs.fetch('distance', 3).to_i
    +  case_type ||= prefs.fetch('case', 'smart').normalize_case
    +  new_note = Note.new
    +
    +  if search.rx? || matching == :fuzzy
    +    rx = search.to_rx(distance: distance, case_type: case_type)
    +    new_title = @title.gsub(rx) { |m| yellow(m) }
    +    new_note.add(@note.to_s.gsub(rx) { |m| yellow(m) })
    +  else
    +    query = search.strip.to_phrase_query
    +
    +    if query[:must].nil? && query[:must_not].nil?
    +      query[:must] = query[:should]
    +      query[:should] = []
    +    end
    +    query[:must].concat(query[:should]).each do |s|
    +      rx = Regexp.new(s.wildcard_to_rx, ignore_case(s, case_type))
    +      new_title = @title.gsub(rx) { |m| yellow(m) }
    +      new_note.add(@note.to_s.gsub(rx) { |m| yellow(m) })
    +    end
    +  end
    +
    +  Item.new(@date, new_title, @section, new_note)
    +end
    +
    @@ -341,7 +407,25 @@

    -

    + + + + + +
    +
    +
    +
    +68
    +69
    +70
    +
    +
    # File 'lib/doing/item/query.rb', line 68
    +
    +def ignore_case(search, case_type)
    +  (case_type == :smart && search !~ /[A-Z]/) || case_type == :ignore
    +end
    +
    @@ -399,7 +483,257 @@

    -

    + + + + + +
    +
    +
    +
    +162
    +163
    +164
    +165
    +166
    +167
    +168
    +169
    +170
    +171
    +172
    +173
    +174
    +175
    +176
    +177
    +178
    +179
    +180
    +181
    +182
    +183
    +184
    +185
    +186
    +187
    +188
    +189
    +190
    +191
    +192
    +193
    +194
    +195
    +196
    +197
    +198
    +199
    +200
    +201
    +202
    +203
    +204
    +205
    +206
    +207
    +208
    +209
    +210
    +211
    +212
    +213
    +214
    +215
    +216
    +217
    +218
    +219
    +220
    +221
    +222
    +223
    +224
    +225
    +226
    +227
    +228
    +229
    +230
    +231
    +232
    +233
    +234
    +235
    +236
    +237
    +238
    +239
    +240
    +241
    +242
    +243
    +244
    +245
    +246
    +247
    +248
    +249
    +250
    +251
    +252
    +253
    +254
    +255
    +256
    +257
    +258
    +259
    +260
    +261
    +262
    +263
    +264
    +265
    +266
    +267
    +268
    +269
    +270
    +271
    +272
    +273
    +274
    +275
    +276
    +277
    +278
    +279
    +280
    +
    +
    # File 'lib/doing/item/query.rb', line 162
    +
    +def keep_item?(opt)
    +  item = dup
    +  time_rx = /^(\d{1,2}+(:\d{1,2}+)?( *(am|pm))?|midnight|noon)$/i
    +
    +  keep = true
    +  if opt[:unfinished]
    +    finished = item.tags?('done', :and)
    +    finished = opt[:not] ? !finished : finished
    +    keep = false if finished
    +  end
    +
    +  if keep && opt[:val]&.count&.positive?
    +    bool = opt[:bool].normalize_bool if opt[:bool]
    +    bool ||= :and
    +    bool = :and if bool == :pattern
    +
    +    val_match = opt[:val].nil? || opt[:val].empty? ? true : item.tag_values?(opt[:val], bool)
    +    keep = false unless val_match
    +    keep = opt[:not] ? !keep : keep
    +  end
    +
    +  if keep && opt[:tag]
    +    opt[:tag_bool] = opt[:bool].normalize_bool if opt[:bool]
    +    opt[:tag_bool] ||= :and
    +    tag_match = opt[:tag].nil? || opt[:tag].empty? ? true : item.tags?(opt[:tag], opt[:tag_bool])
    +    keep = false unless tag_match
    +    keep = opt[:not] ? !keep : keep
    +  end
    +
    +  if keep && opt[:search]
    +    search_match = if opt[:search].nil? || opt[:search].empty?
    +                     true
    +                   else
    +                     item.search(opt[:search], case_type: opt[:case].normalize_case)
    +                   end
    +
    +    keep = false unless search_match
    +    keep = opt[:not] ? !keep : keep
    +  end
    +
    +  if keep && opt[:date_filter]&.length == 2
    +    start_date = opt[:date_filter][0]
    +    end_date = opt[:date_filter][1]
    +
    +    in_date_range = if end_date
    +                      item.date >= start_date && item.date <= end_date
    +                    else
    +                      item.date.strftime('%F') == start_date.strftime('%F')
    +                    end
    +    keep = false unless in_date_range
    +    keep = opt[:not] ? !keep : keep
    +  end
    +
    +  if keep && opt[:time_filter][0] || opt[:time_filter][1]
    +    opt[:time_filter].map! { |v| v =~ /(12 *am|midnight)/i ? '00:00' : v }
    +
    +    start_string = if opt[:time_filter][0].nil?
    +                     "#{item.date.strftime('%Y-%m-%d')} 00:00"
    +                   else
    +                     "#{item.date.strftime('%Y-%m-%d')} #{opt[:time_filter][0]}"
    +                   end
    +    start_time = start_string.chronify(guess: :begin)
    +
    +    end_string = if opt[:time_filter][1].nil?
    +                   "#{item.date.to_datetime.next_day.strftime('%Y-%m-%d')} 00:00"
    +                 else
    +                   "#{item.date.strftime('%Y-%m-%d')} #{opt[:time_filter][1]}"
    +                 end
    +    end_time = end_string.chronify(guess: :end) || Time.now
    +
    +    in_time_range = item.date >= start_time && item.date <= end_time
    +
    +    keep = false unless in_time_range
    +    keep = opt[:not] ? !keep : keep
    +  end
    +
    +  keep = false if keep && opt[:only_timed] && !item.interval
    +
    +  if keep && opt[:tag_filter]
    +    keep = item.tags?(opt[:tag_filter]['tags'], opt[:tag_filter]['bool'])
    +    keep = opt[:not] ? !keep : keep
    +  end
    +
    +  if keep && opt[:before]
    +    before = opt[:before]
    +    cutoff = if before.is_a?(String) && before =~ time_rx
    +               "#{item.date.strftime('%Y-%m-%d')} #{before}".chronify(guess: :begin)
    +             elsif before.is_a?(String)
    +               before.chronify(guess: :begin)
    +             else
    +               before
    +             end
    +    keep = cutoff && item.date <= cutoff
    +    keep = opt[:not] ? !keep : keep
    +  end
    +
    +  if keep && opt[:after]
    +    after = opt[:after]
    +    cutoff = if after.is_a?(String) && after =~ time_rx
    +               "#{item.date.strftime('%Y-%m-%d')} #{after}".chronify(guess: :end)
    +             elsif after.is_a?(String)
    +               after.chronify(guess: :end)
    +             else
    +               after
    +             end
    +    keep = cutoff && item.date >= cutoff
    +    keep = opt[:not] ? !keep : keep
    +  end
    +
    +  if keep && opt[:today]
    +    keep = item.date >= Date.today.to_time && item.date < Date.today.next_day.to_time
    +    keep = opt[:not] ? !keep : keep
    +  elsif keep && opt[:yesterday]
    +    keep = item.date >= Date.today.prev_day.to_time && item.date < Date.today.to_time
    +    keep = opt[:not] ? !keep : keep
    +  end
    +
    +  keep
    +end
    +
    @@ -493,7 +827,105 @@

    -

    + + + + + +
    +
    +
    +
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +153
    +
    +
    # File 'lib/doing/item/query.rb', line 111
    +
    +def search(search, distance: nil, negate: false, case_type: nil)
    +  prefs = Doing.setting('search', {})
    +  matching = prefs.fetch('matching', 'pattern').normalize_matching
    +  distance ||= prefs.fetch('distance', 3).to_i
    +  case_type ||= prefs.fetch('case', 'smart').normalize_case
    +
    +  if search.rx? || matching == :fuzzy
    +    matches = @title + @note.to_s =~ search.to_rx(distance: distance, case_type: case_type)
    +  else
    +    query = search.strip.to_phrase_query
    +
    +    if query[:must].nil? && query[:must_not].nil?
    +      query[:must] = query[:should]
    +      query[:should] = []
    +    end
    +    matches = no_searches?(query[:must_not], case_type: case_type)
    +    matches &&= all_searches?(query[:must], case_type: case_type)
    +    matches &&= any_searches?(query[:should], case_type: case_type)
    +  end
    +  # if search =~ /(?<=\A| )[+-]\S/
    +  # else
    +  #   text = @title + @note.to_s
    +  #   matches = text =~ search.to_rx(distance: distance, case_type: case_type)
    +  # end
    +
    +  # if search.rx? || !fuzzy
    +  #   matches = text =~ search.to_rx(distance: distance, case_type: case_type)
    +  # else
    +  #   distance = 0.25 if distance > 1
    +  #   score = if (case_type == :smart && search !~ /[A-Z]/) || case_type == :ignore
    +  #             text.downcase.pair_distance_similar(search.downcase)
    +  #           else
    +  #             score = text.pair_distance_similar(search)
    +  #           end
    +
    +  #   if score >= distance
    +  #     matches = true
    +  #     Doing.logger.debug('Fuzzy Match:', %(#{@title}, "#{search}" #{score}))
    +  #   end
    +  # end
    +
    +  negate ? !matches : matches
    +end
    +
    @@ -585,7 +1017,45 @@

    -

    + + + + + +
    +
    +
    +
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +
    +
    # File 'lib/doing/item/query.rb', line 46
    +
    +def tag_values?(queries, bool = :and, negate: false)
    +  bool = bool.normalize_bool
    +
    +  matches = case bool
    +            when :and
    +              all_values?(queries)
    +            when :not
    +              no_values?(queries)
    +            else
    +              any_values?(queries)
    +            end
    +  negate ? !matches : matches
    +end
    +
    @@ -677,7 +1147,61 @@

    -

    + + + + + +
    +
    +
    +
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +
    +
    # File 'lib/doing/item/query.rb', line 15
    +
    +def tags?(tags, bool = :and, negate: false)
    +  if bool == :pattern
    +    tags = tags.to_tags.tags_to_array.join(' ')
    +    matches = tag_pattern?(tags)
    +
    +    return negate ? !matches : matches
    +  end
    +
    +  tags = split_tags(tags)
    +  bool = bool.normalize_bool
    +
    +  matches = case bool
    +            when :and
    +              all_tags?(tags)
    +            when :not
    +              no_tags?(tags)
    +            else
    +              any_tags?(tags)
    +            end
    +  negate ? !matches : matches
    +end
    +
    @@ -685,9 +1209,9 @@

    diff --git a/docs/doc/Doing/ItemState.html b/docs/doc/Doing/ItemState.html index ee30dd3d..64afad1a 100644 --- a/docs/doc/Doing/ItemState.html +++ b/docs/doc/Doing/ItemState.html @@ -6,7 +6,7 @@ Module: Doing::ItemState - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -250,7 +250,25 @@

    - + + + + + +
    +
    +
    +
    +9
    +10
    +11
    +
    +
    # File 'lib/doing/item/state.rb', line 9
    +
    +def finished?
    +  tags?('done')
    +end
    +
    @@ -290,7 +308,25 @@

    -

    + + + + + +
    +
    +
    +
    +28
    +29
    +30
    +
    +
    # File 'lib/doing/item/state.rb', line 28
    +
    +def should_finish?
    +  should?('never_finish')
    +end
    +
    @@ -330,7 +366,25 @@

    -

    + + + + + +
    +
    +
    +
    +38
    +39
    +40
    +
    +
    # File 'lib/doing/item/state.rb', line 38
    +
    +def should_time?
    +  should?('never_time')
    +end
    +
    @@ -369,7 +423,25 @@

    -

    + + + + + +
    +
    +
    +
    +18
    +19
    +20
    +
    +
    # File 'lib/doing/item/state.rb', line 18
    +
    +def unfinished?
    +  tags?('done', negate: true)
    +end
    +
    @@ -377,9 +449,9 @@

    diff --git a/docs/doc/Doing/ItemTags.html b/docs/doc/Doing/ItemTags.html index b816f418..cc9c9e62 100644 --- a/docs/doc/Doing/ItemTags.html +++ b/docs/doc/Doing/ItemTags.html @@ -6,7 +6,7 @@ Module: Doing::ItemTags - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -362,7 +362,83 @@

    - + + + + + +
    +
    +
    +
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +
    +
    # File 'lib/doing/item/tags.rb', line 20
    +
    +def tag(tags, **options)
    +  added = []
    +  removed = []
    +
    +  date = options.fetch(:date, false)
    +  options[:value] ||= date ? Time.now.strftime('%F %R') : nil
    +  options.delete(:date)
    +
    +  single = options.fetch(:single, false)
    +  options.delete(:single)
    +
    +  tags = tags.to_tags if tags.is_a? ::String
    +
    +  remove = options.fetch(:remove, false)
    +  tags.each do |tag|
    +    if tag =~ /^(\S+)\((.*?)\)$/
    +      m = Regexp.last_match
    +      tag = m[1]
    +      options[:value] ||= m[2]
    +    end
    +
    +    bool = remove ? :and : :not
    +    if tags?(tag, bool) || options[:value]
    +      @title = @title.tag(tag, **options).strip
    +      remove ? removed.push(tag) : added.push(tag)
    +    end
    +  end
    +
    +  Doing.logger.log_change(tags_added: added, tags_removed: removed, count: 1, item: self, single: single)
    +
    +  self
    +end
    +
    @@ -401,7 +477,25 @@

    -

    + + + + + +
    +
    +
    +
    +77
    +78
    +79
    +
    +
    # File 'lib/doing/item/tags.rb', line 77
    +
    +def tag_array
    +  tags.tags_to_array
    +end
    +
    @@ -440,7 +534,25 @@

    -

    + + + + + +
    +
    +
    +
    +58
    +59
    +60
    +
    +
    # File 'lib/doing/item/tags.rb', line 58
    +
    +def tags
    +  @title.scan(/(?<= |\A)@([^\s(]+)/).map { |tag| tag[0] }.sort.uniq
    +end
    +
    @@ -480,7 +592,25 @@

    -

    + + + + + +
    +
    +
    +
    +68
    +69
    +70
    +
    +
    # File 'lib/doing/item/tags.rb', line 68
    +
    +def tags_with_values
    +  @title.scan(/(?<= |\A)@([^\s(]+)(?:\((.*?)\))?/).map { |tag| [tag[0], tag[1]] }.sort.uniq
    +end
    +
    @@ -488,9 +618,9 @@

    diff --git a/docs/doc/Doing/Items.html b/docs/doc/Doing/Items.html index afb62557..4e92f5b1 100644 --- a/docs/doc/Doing/Items.html +++ b/docs/doc/Doing/Items.html @@ -6,7 +6,7 @@ Class: Doing::Items - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -692,7 +692,27 @@

    -
    + + + + + +
    +
    +
    +
    +13
    +14
    +15
    +16
    +
    +
    # File 'lib/doing/items/items.rb', line 13
    +
    +def initialize
    +  super
    +  @sections = []
    +end
    +
    @@ -721,7 +741,25 @@

    -
    + + + + + +
    +
    +
    +
    +11
    +12
    +13
    +
    +
    # File 'lib/doing/items/items.rb', line 11
    +
    +def sections
    +  @sections
    +end
    +
    @@ -809,7 +847,35 @@

    - + + + + + +
    +
    +
    +
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +
    +
    # File 'lib/doing/items/sections.rb', line 59
    +
    +def add_section(section, log: false)
    +  section = section.is_a?(Section) ? section : Section.new(section.cap_first)
    +
    +  return if section?(section)
    +
    +  @sections.push(section)
    +  Doing.logger.info('New section:', %("#{section}" added)) if log
    +end
    +
    @@ -848,7 +914,29 @@

    -

    + + + + + +
    +
    +
    +
    +26
    +27
    +28
    +29
    +30
    +
    +
    # File 'lib/doing/items/util.rb', line 26
    +
    +def all_tags
    +  each_with_object([]) do |entry, tags|
    +    tags.concat(entry.tags).sort!.uniq!
    +  end
    +end
    +
    @@ -925,7 +1013,29 @@

    -

    + + + + + +
    +
    +
    +
    +62
    +63
    +64
    +65
    +66
    +
    +
    # File 'lib/doing/items/filter.rb', line 62
    +
    +def between_dates(start, finish)
    +  start = start.chronify(guess: :begin, future: false) if start.is_a?(String)
    +  finish = finish.chronify(guess: :end) if finish.is_a?(String)
    +  WWID.new.filter_items(self, opt: { date_filter: [start, finish] })
    +end
    +
    @@ -964,7 +1074,35 @@

    -

    + + + + + +
    +
    +
    +
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +
    +
    # File 'lib/doing/items/util.rb', line 60
    +
    +def dedup(match_section: true)
    +  unique = Items.new
    +  each do |item|
    +    unique.push(item) unless unique.include?(item, match_section: match_section)
    +  end
    +
    +  unique
    +end
    +
    @@ -992,7 +1130,25 @@

    -

    + + + + + +
    +
    +
    +
    +70
    +71
    +72
    +
    +
    # File 'lib/doing/items/util.rb', line 70
    +
    +def dedup!(match_section: true)
    +  replace dedup(match_section: match_section)
    +end
    +
    @@ -1018,7 +1174,39 @@

    Create a deep copy of Items

    -
    +
    + + + + +
    +
    +
    +
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +
    +
    # File 'lib/doing/items/util.rb', line 10
    +
    +def delete(item)
    +  deleted = nil
    +  each_with_index do |i, idx|
    +    if i.equal?(item, match_section: true)
    +      deleted = delete_at(idx)
    +      break
    +    end
    +  end
    +  deleted
    +end
    +
    @@ -1059,7 +1247,31 @@

    -

    + + + + + +
    +
    +
    +
    +10
    +11
    +12
    +13
    +14
    +15
    +
    +
    # File 'lib/doing/items/modify.rb', line 10
    +
    +def delete_item(item, single: false)
    +  deleted = delete(item)
    +  Doing.logger.count(:deleted)
    +  Doing.logger.info('Entry deleted:', deleted.title) if single
    +  deleted
    +end
    +
    @@ -1093,7 +1305,47 @@

    -

    + + + + + +
    +
    +
    +
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +
    +
    # File 'lib/doing/items/sections.rb', line 68
    +
    +def delete_section(section, log: false)
    +  return unless section?(section)
    +
    +  raise DoingRuntimeError, 'Section not empty' if in_section(section).count.positive?
    +
    +  @sections.each do |sect|
    +    next unless sect.title == section && in_section(sect).count.zero?
    +
    +    @sections.delete(sect)
    +    Doing.logger.info('Removed section:', %("#{section}" removed)) if log
    +  end
    +
    +  Doing.logger.error('Not found:', %("#{section}" not found))
    +end
    +
    @@ -1152,7 +1404,47 @@

    -

    + + + + + +
    +
    +
    +
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +
    +
    # File 'lib/doing/items/util.rb', line 40
    +
    +def diff(items)
    +  a = clone
    +  b = items.clone
    +
    +  a.delete_if do |item|
    +    if b.include?(item)
    +      b.delete(item)
    +      true
    +    else
    +      false
    +    end
    +  end
    +  { added: b, deleted: a }
    +end
    +
    @@ -1193,7 +1485,25 @@

    -

    + + + + + +
    +
    +
    +
    +42
    +43
    +44
    +
    +
    # File 'lib/doing/items/items.rb', line 42
    +
    +def find_id(id)
    +  select { |item| item.id == id }[0]
    +end
    +
    @@ -1268,7 +1578,45 @@

    -

    + + + + + +
    +
    +
    +
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +
    +
    # File 'lib/doing/items/sections.rb', line 32
    +
    +def guess_section(frag, distance: 2)
    +  section = nil
    +  re = frag.to_rx(distance: distance, case_type: :ignore)
    +  @sections.each do |sect|
    +    next unless sect.title =~ /#{re}/i
    +
    +    Doing.logger.debug('Match:', %(Assuming "#{sect.title}" from "#{frag}"))
    +    section = sect
    +    break
    +  end
    +
    +  section
    +end
    +
    @@ -1327,7 +1675,39 @@

    -

    + + + + + +
    +
    +
    +
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +
    +
    # File 'lib/doing/items/filter.rb', line 12
    +
    +def in_section(section)
    +  sect = section.is_a?(Section) ? section.title : section
    +  if sect =~ /^all$/i
    +    dup
    +  else
    +    items = Items.new.concat(select { |item| !item.nil? && item.section == section })
    +    items.add_section(section, log: false)
    +    items
    +  end
    +end
    +
    @@ -1402,7 +1782,41 @@

    -

    + + + + + +
    +
    +
    +
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +
    +
    # File 'lib/doing/items/items.rb', line 26
    +
    +def include?(item, match_section: true)
    +  includes = false
    +  each do |other_item|
    +    if other_item.equal?(item, match_section: match_section)
    +      includes = true
    +      break
    +    end
    +  end
    +
    +  includes
    +end
    +
    @@ -1443,7 +1857,39 @@

    -

    + + + + + +
    +
    +
    +
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +
    +
    # File 'lib/doing/items/items.rb', line 51
    +
    +def index_for_id(id)
    +  i = nil
    +  each_with_index do |item, idx|
    +    if item.id == id
    +      i = idx
    +      break
    +    end
    +  end
    +  i
    +end
    +
    @@ -1519,7 +1965,25 @@

    -

    + + + + + +
    +
    +
    +
    +32
    +33
    +34
    +
    +
    # File 'lib/doing/items/filter.rb', line 32
    +
    +def search(query, case_type: :smart)
    +  WWID.new.fuzzy_filter_items(self, query, case_type: case_type)
    +end
    +
    @@ -1577,7 +2041,27 @@

    -

    + + + + + +
    +
    +
    +
    +19
    +20
    +21
    +22
    +
    +
    # File 'lib/doing/items/sections.rb', line 19
    +
    +def section?(section)
    +  section = section.is_a?(Section) ? section.title.downcase : section.downcase
    +  @sections.map { |i| i.title.downcase }.include?(section)
    +end
    +
    @@ -1616,7 +2100,25 @@

    -

    + + + + + +
    +
    +
    +
    +9
    +10
    +11
    +
    +
    # File 'lib/doing/items/sections.rb', line 9
    +
    +def section_titles
    +  @sections.map(&:title)
    +end
    +
    @@ -1693,7 +2195,25 @@

    -

    + + + + + +
    +
    +
    +
    +46
    +47
    +48
    +
    +
    # File 'lib/doing/items/filter.rb', line 46
    +
    +def tagged(tags, bool: :and)
    +  WWID.new.filter_items(self, opt: { tag: tags, bool: bool })
    +end
    +
    @@ -1715,7 +2235,41 @@

    -
    +

    + + + + +
    +
    +
    +
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +
    +
    # File 'lib/doing/items/items.rb', line 63
    +
    +def to_s
    +  out = []
    +  @sections.each do |section|
    +    out.push(section.original)
    +    items = in_section(section.title).sort_by { |i| [i.date, i.title] }
    +    items.reverse! if Doing.setting('doing_file_sort').normalize_order == :desc
    +    items.each { |item| out.push(item.to_s) }
    +  end
    +
    +  out.join("\n")
    +end
    +
    @@ -1784,7 +2338,43 @@

    -

    + + + + + +
    +
    +
    +
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +
    +
    # File 'lib/doing/items/modify.rb', line 23
    +
    +def update_item(old_item, new_item)
    +  s_idx = index { |item| item.equal?(old_item) }
    +
    +  raise ItemNotFound, 'Unable to find item in index, did it mutate?' unless s_idx
    +
    +  return if fetch(s_idx).equal?(new_item)
    +
    +  self[s_idx] = new_item
    +  Doing.logger.count(:updated)
    +  Doing.logger.info('Entry updated:', self[s_idx].title.trunc(60))
    +  new_item
    +end
    +
    @@ -1792,9 +2382,9 @@

    diff --git a/docs/doc/Doing/JSONExport.html b/docs/doc/Doing/JSONExport.html index 90575b01..f2fd06a0 100644 --- a/docs/doc/Doing/JSONExport.html +++ b/docs/doc/Doing/JSONExport.html @@ -6,7 +6,7 @@ Class: Doing::JSONExport - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -192,7 +192,283 @@

    -

    + + + + + +
    +
    +
    +
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +
    +
    # File 'lib/doing/plugins/export/json_export.rb', line 17
    +
    +def self.render(wwid, items, variables: {})
    +  if items.nil? || items.empty?
    +    return case variables[:options][:output]
    +           when 'json'
    +             {
    +               'section' => '',
    +               'items' => [],
    +               'timers' => ""
    +             }.to_json
    +           when 'timeline'
    +            "<html></html>"
    +           end
    +  end
    +
    +  opt = variables[:options]
    +  opt[:output] =  case opt[:output]
    +                  when /^t/
    +                    'timeline'
    +                  else
    +                    'json'
    +                  end
    +  items_out = []
    +
    +  last_date = items[-1].date + (60 * 60 * 24)
    +  max = last_date.strftime('%F')
    +  min = items[0].date.strftime('%F')
    +  items.each_with_index do |i, index|
    +    title = i.title.utf8
    +    note = i.note.utf8
    +
    +    end_date = i.end_date || ''
    +    interval = wwid.get_interval(i, formatted: false) || 0
    +    duration = i.duration || 0
    +    note ||= ''
    +
    +    tags = []
    +    attributes = {}
    +    skip_tags = %w[meanwhile done cancelled flagged]
    +    i.title.scan(/@([^(\s]+)(?:\((.*?)\))?/).each do |tag|
    +      tags.push(tag[0]) unless skip_tags.include?(tag[0])
    +      attributes[tag[0]] = tag[1] if tag[1]
    +    end
    +
    +    case opt[:output]
    +    when 'json'
    +      i = {
    +        date: i.date,
    +        end_date: end_date,
    +        title: title.strip, #+ " #{note}"
    +        section: i.section,
    +        note: note.to_s(prefix: ''),
    +        time: interval.time_string(format: :clock),
    +        duration: duration.time_string(format: :clock),
    +        tags: tags,
    +        id: i.id
    +      }
    +
    +      attributes.each { |attr, val| i[attr.to_sym] = val }
    +
    +      items_out << i
    +
    +    when 'timeline'
    +      new_item = {
    +        'id' => index + 1,
    +        'content' => title.strip, #+ " #{note}"
    +        'title' => title.strip + " (#{interval.time_string(format: :clock)})",
    +        'start' => i.date.strftime('%F %T'),
    +        'type' => 'box',
    +        'style' => 'color:#4c566b;background-color:#d8dee9;'
    +      }
    +
    +      if interval.to_i&.positive?
    +        new_item['end'] = end_date.strftime('%F %T')
    +        if interval.to_i > 3600
    +          new_item['type'] = 'range'
    +          new_item['style'] = 'color:white;background-color:#a2bf8a;'
    +        end
    +      end
    +      new_item['style'] = 'color:white;background-color:#f7921e;' if i.tags?(Doing.setting('marker_tag'))
    +      items_out.push(new_item)
    +    end
    +  end
    +  case opt[:output]
    +  when 'json'
    +    Doing.logger.debug('JSON Export:', "#{items_out.count} items output to JSON")
    +    JSON.pretty_generate({
    +                           'section' => variables[:page_title],
    +                           'items' => items_out,
    +                           'timers' => wwid.tag_times(format: :json,
    +                                                      sort_by: opt[:sort_tags],
    +                                                      sort_order: opt[:tag_order])
    +                         })
    +  when 'timeline'
    +    template = <<~EOTEMPLATE
    +                <!doctype html>
    +                <html>
    +                <head>
    +                  <link href="https://unpkg.com/vis-timeline@7.4.9/dist/vis-timeline-graph2d.min.css" rel="stylesheet" type="text/css" />
    +                  <script src="https://unpkg.com/vis-timeline@7.4.9/dist/vis-timeline-graph2d.min.js"></script>
    +                </head>
    +                <body>
    +                  <div id="mytimeline"></div>
    +      #{'          '}
    +                  <script type="text/javascript">
    +                    // DOM element where the Timeline will be attached
    +                    var container = document.getElementById('mytimeline');
    +      #{'          '}
    +                    // Create a DataSet with data (enables two way data binding)
    +                    var data = new vis.DataSet(#{items_out.to_json});
    +      #{'          '}
    +                    // Configuration for the Timeline
    +                    var options = {
    +                      width: '100%',
    +                      height: '800px',
    +                      margin: {
    +                        item: 20
    +                      },
    +                      stack: true,
    +                      min: '#{min}',
    +                      max: '#{max}'
    +                    };
    +      #{'          '}
    +                    // Create a Timeline
    +                    var timeline = new vis.Timeline(container, data, options);
    +                  </script>
    +                </body>
    +                </html>
    +    EOTEMPLATE
    +    Doing.logger.debug('Timeline Export:', "#{items_out.count} items output to Timeline")
    +    template
    +  end
    +end
    +
    @@ -204,7 +480,29 @@

    -

    + + + + + +
    +
    +
    +
    +11
    +12
    +13
    +14
    +15
    +
    +
    # File 'lib/doing/plugins/export/json_export.rb', line 11
    +
    +def self.settings
    +  {
    +    trigger: 'json|time(?:line)?'
    +  }
    +end
    +
    @@ -212,9 +510,9 @@

    diff --git a/docs/doc/Doing/JSONImport.html b/docs/doc/Doing/JSONImport.html index e5dfdaf8..811b88ee 100644 --- a/docs/doc/Doing/JSONImport.html +++ b/docs/doc/Doing/JSONImport.html @@ -6,7 +6,7 @@ Class: Doing::JSONImport - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -265,7 +265,149 @@

    - + + + + + +
    +
    +
    +
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +
    +
    # File 'lib/doing/plugins/import/json_import.rb', line 26
    +
    +def self.import(wwid, path, options: {})
    +  exit_now! 'Path to JSON export required' if path.nil?
    +  options[:no_overlap] ||= false
    +  options[:autotag] ||= Doing.auto_tag
    +
    +  exit_now! 'File not found' unless File.exist?(File.expand_path(path))
    +
    +  updated = 0
    +  added = 0
    +  skipped = 0
    +
    +  data = JSON.parse(IO.read(File.expand_path(path)))
    +  new_items = []
    +  new_section = options[:section] || Doing.setting('current_section')
    +
    +  data['items'].each do |entry|
    +    title = entry['title']
    +    date = Time.parse(entry['date'])
    +    date ||= entry['date'].chronify
    +    note = Doing::Note.new(entry['note'])
    +    section = if entry['section'].empty?
    +                new_section
    +              else
    +                entry['section']
    +              end
    +    id = entry.key?('id') ? entry['id'] : nil
    +
    +    new_item = Doing::Item.new(date, title, section, note, id)
    +
    +    is_match = true
    +
    +    if options[:search]
    +      is_match = new_item.search(options[:search], case_type: options[:case], negate: options[:not])
    +    end
    +
    +    if is_match && options[:date_filter]
    +      is_match = start_time > options[:date_filter][0] && start_time < options[:date_filter][1]
    +      is_match = options[:not] ? !is_match : is_match
    +    end
    +
    +    unless is_match
    +      skipped += 1
    +      next
    +
    +    end
    +
    +    if wwid.content.find_id(new_item.id)
    +      old_index = wwid.content.index_for_id(entry['id'])
    +      old_item = wwid.content[old_index].clone
    +      wwid.content[old_index] = new_item
    +      Hooks.trigger :post_entry_updated, self, new_item, old_item
    +      updated += 1
    +    else
    +      Hooks.trigger :pre_entry_add, self, item
    +      wwid.content << new_entry
    +      Hooks.trigger :post_entry_added, self, item
    +      added += 1
    +    end
    +  end
    +  total = new_items.count
    +  skipped = data.count - total
    +  Doing.logger.debug('Skipped:', %(#{skipped} items)) if skipped.positive?
    +  Doing.logger.info('Updated:', %(#{updated} items))
    +  Doing.logger.info('Imported:', %(#{added} new items to #{new_section}))
    +end
    +
    @@ -277,7 +419,29 @@

    -

    +

    + + + + +
    +
    +
    +
    +12
    +13
    +14
    +15
    +16
    +
    +
    # File 'lib/doing/plugins/import/json_import.rb', line 12
    +
    +def self.settings
    +  {
    +    trigger: 'json'
    +  }
    +end
    +
    @@ -285,9 +449,9 @@

    diff --git a/docs/doc/Doing/Logger.html b/docs/doc/Doing/Logger.html index 91555d3c..b272a1d3 100644 --- a/docs/doc/Doing/Logger.html +++ b/docs/doc/Doing/Logger.html @@ -6,7 +6,7 @@ Class: Doing::Logger - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -137,19 +137,19 @@

    %i[
    -  added
    -  added_tags
    -  archived
    -  autotag
    -  completed
    -  completed_archived
    -  deleted
    -  moved
    -  removed_tags
    -  rotated
    -  skipped
    -  updated
    -  exported
    +  added
    +  added_tags
    +  archived
    +  autotag
    +  completed
    +  completed_archived
    +  deleted
    +  moved
    +  removed_tags
    +  rotated
    +  skipped
    +  updated
    +  exported
     ].freeze
    @@ -771,7 +771,39 @@

    - + + + + + +
    +
    +
    +
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +
    +
    # File 'lib/doing/logger.rb', line 49
    +
    +def initialize(level = :info)
    +  @messages = []
    +  @counters = {}
    +  COUNT_KEYS.each { |key| @counters[key] = { tag: [], count: 0 } }
    +  @results = []
    +  @logdev = $stderr
    +  @max_length = TTY::Screen.columns - 5 || 85
    +  self.log_level = level
    +  @prev_level = level
    +end
    +
    @@ -800,7 +832,25 @@

    -
    + + + + + +
    +
    +
    +
    +15
    +16
    +17
    +
    +
    # File 'lib/doing/logger.rb', line 15
    +
    +def level
    +  @level
    +end
    +
    @@ -824,7 +874,25 @@

    -
    + + + + + +
    +
    +
    +
    +9
    +10
    +11
    +
    +
    # File 'lib/doing/logger.rb', line 9
    +
    +def logdev=(value)
    +  @logdev = value
    +end
    +
    @@ -848,7 +916,25 @@

    -
    + + + + + +
    +
    +
    +
    +12
    +13
    +14
    +
    +
    # File 'lib/doing/logger.rb', line 12
    +
    +def max_length=(value)
    +  @max_length = value
    +end
    +
    @@ -872,7 +958,25 @@

    -
    + + + + + +
    +
    +
    +
    +17
    +18
    +19
    +
    +
    # File 'lib/doing/logger.rb', line 17
    +
    +def messages
    +  @messages
    +end
    +
    @@ -896,7 +1000,25 @@

    -
    + + + + + +
    +
    +
    +
    +17
    +18
    +19
    +
    +
    # File 'lib/doing/logger.rb', line 17
    +
    +def results
    +  @results
    +end
    +
    @@ -982,7 +1104,27 @@

    - + + + + + +
    +
    +
    +
    +187
    +188
    +189
    +190
    +
    +
    # File 'lib/doing/logger.rb', line 187
    +
    +def abort_with(topic, message = nil, &block)
    +  error(topic, message, &block)
    +  abort
    +end
    +
    @@ -994,7 +1136,41 @@

    -

    +

    + + + + +
    +
    +
    +
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +
    +
    # File 'lib/doing/logger.rb', line 100
    +
    +def adjust_verbosity(options = {})
    +  if options[:log_level]
    +    self.log_level = options[:log_level].to_sym
    +  elsif options[:quiet]
    +    self.log_level = :error
    +  elsif options[:verbose] || options[:debug]
    +    self.log_level = :debug
    +  end
    +  log_now :debug, 'Logging at level:', @level.to_s
    +  # log_now :debug, 'Doing Version:', Doing::VERSION
    +end
    +
    @@ -1006,7 +1182,33 @@

    -

    + + + + + +
    +
    +
    +
    +270
    +271
    +272
    +273
    +274
    +275
    +276
    +
    +
    # File 'lib/doing/logger.rb', line 270
    +
    +def benchmark(key, state)
    +  return unless ENV['DOING_BENCHMARK']
    +
    +  @benchmarks ||= {}
    +  @benchmarks[key] ||= { start: nil, finish: nil }
    +  @benchmarks[key][state] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
    +end
    +
    @@ -1040,7 +1242,35 @@

    -

    + + + + + +
    +
    +
    +
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +
    +
    # File 'lib/doing/logger.rb', line 112
    +
    +def count(key, level: :info, count: 1, tag: nil, message: nil)
    +  raise ArgumentError, 'invalid counter key' unless COUNT_KEYS.include?(key)
    +
    +  @counters[key][:count] += count
    +  @counters[key][:tag].concat(tag).sort.uniq unless tag.nil?
    +  @counters[key][:level] ||= level
    +  @counters[key][:message] ||= message
    +end
    +
    @@ -1115,7 +1345,25 @@

    -

    + + + + + +
    +
    +
    +
    +129
    +130
    +131
    +
    +
    # File 'lib/doing/logger.rb', line 129
    +
    +def debug(topic, message = nil, &block)
    +  write(:debug, topic, message, &block)
    +end
    +
    @@ -1192,7 +1440,25 @@

    -

    + + + + + +
    +
    +
    +
    +171
    +172
    +173
    +
    +
    # File 'lib/doing/logger.rb', line 171
    +
    +def error(topic, message = nil, &block)
    +  write(:error, topic, message, &block)
    +end
    +
    @@ -1269,7 +1535,37 @@

    -

    + + + + + +
    +
    +
    +
    +202
    +203
    +204
    +205
    +206
    +207
    +208
    +209
    +210
    +
    +
    # File 'lib/doing/logger.rb', line 202
    +
    +def formatted_topic(topic, colon: false)
    +  if colon
    +    "#{topic}: ".rjust(TOPIC_WIDTH)
    +  elsif topic =~ /:$/
    +    "#{topic} ".rjust(TOPIC_WIDTH)
    +  else
    +    "#{topic} "
    +  end
    +end
    +
    @@ -1346,7 +1642,25 @@

    -

    + + + + + +
    +
    +
    +
    +143
    +144
    +145
    +
    +
    # File 'lib/doing/logger.rb', line 143
    +
    +def info(topic, message = nil, &block)
    +  write(:info, topic, message, &block)
    +end
    +
    @@ -1358,7 +1672,115 @@

    -

    + + + + + +
    +
    +
    +
    +278
    +279
    +280
    +281
    +282
    +283
    +284
    +285
    +286
    +287
    +288
    +289
    +290
    +291
    +292
    +293
    +294
    +295
    +296
    +297
    +298
    +299
    +300
    +301
    +302
    +303
    +304
    +305
    +306
    +307
    +308
    +309
    +310
    +311
    +312
    +313
    +314
    +315
    +316
    +317
    +318
    +319
    +320
    +321
    +322
    +323
    +324
    +325
    +
    +
    # File 'lib/doing/logger.rb', line 278
    +
    +def log_benchmarks
    +  if ENV['DOING_BENCHMARK']
    +
    +    output = []
    +    beginning = @benchmarks[:total][:start]
    +    ending = @benchmarks[:total][:finish]
    +    total = ending - beginning
    +    factor = TTY::Screen.columns / total
    +
    +    cols = Array.new(TTY::Screen.columns)
    +
    +    colors = %w[bgred bggreen bgyellow bgblue bgmagenta bgcyan bgwhite boldbgred boldbggreen boldbgyellow boldbgblue boldbgwhite]
    +    idx = 0
    +    # @benchmarks.delete(:total)
    +
    +    @benchmarks.sort_by { |_, timers| [timers[:start], timers[:finish]] }.each do |k, timers|
    +      if timers[:finish] && timers[:start]
    +        color = colors[idx % colors.count]
    +        fg = if idx < 7
    +               Color.boldblack
    +             else
    +               Color.boldwhite
    +             end
    +        color = Color.send(color) + fg
    +
    +        start = ((timers[:start] - beginning) * factor).floor
    +        finish = ((timers[:finish] - beginning) * factor).ceil
    +
    +        cols.fill("#{color}-", start..finish)
    +        cols[start] = "#{color}|"
    +        cols[finish] = "#{color}|"
    +        output << "#{color}#{k}#{Color.default}: #{timers[:finish] - timers[:start]}"
    +      else
    +        output << "#{k}: error"
    +      end
    +
    +      idx += 1
    +    end
    +
    +    output.each do |msg|
    +      $stdout.puts color_message(:debug, 'Benchmark:', msg)
    +    end
    +
    +    $stdout.puts color_message(:debug, 'Benchmark:', "Total: #{total}")
    +
    +    $stdout.puts cols[0..TTY::Screen.columns-1].join + Color.reset
    +  end
    +end
    +
    @@ -1370,7 +1792,81 @@

    -

    + + + + + +
    +
    +
    +
    +327
    +328
    +329
    +330
    +331
    +332
    +333
    +334
    +335
    +336
    +337
    +338
    +339
    +340
    +341
    +342
    +343
    +344
    +345
    +346
    +347
    +348
    +349
    +350
    +351
    +352
    +353
    +354
    +355
    +356
    +357
    +
    +
    # File 'lib/doing/logger.rb', line 327
    +
    +def log_change(tags_added: [], tags_removed: [], count: 1, item: nil, single: false)
    +  if tags_added.empty? && tags_removed.empty?
    +    count(:skipped, level: :debug, message: '%count %items with no change', count: count)
    +  else
    +    if tags_added.empty?
    +      count(:skipped, level: :debug, message: 'no tags added to %count %items')
    +    elsif single && item
    +      elapsed = if item && tags_added.include?('done')
    +                  item.interval ? " (#{item.interval&.time_string(format: :dhm)})" : ''
    +                else
    +                  ''
    +                end
    +
    +      added = tags_added.log_tags
    +      info('Tagged:',
    +           %(added #{tags_added.count == 1 ? 'tag' : 'tags'} #{added}#{elapsed} to #{item.title}))
    +    else
    +      count(:added_tags, level: :info, tag: tags_added, message: '%tags added to %count %items')
    +    end
    +
    +    if tags_removed.empty?
    +      count(:skipped, level: :debug, message: 'no tags removed from %count %items')
    +    elsif single && item
    +      removed = tags_removed.log_tags
    +      info('Untagged:',
    +           %(removed #{tags_removed.count == 1 ? 'tag' : 'tags'} #{removed} from #{item.title}))
    +    else
    +      count(:removed_tags, level: :info, tag: tags_removed, message: '%tags removed from %count %items')
    +    end
    +  end
    +end
    +
    @@ -1430,7 +1926,51 @@

    -

    + + + + + +
    +
    +
    +
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +
    +
    # File 'lib/doing/logger.rb', line 67
    +
    +def log_level=(level = 'info')
    +  level = level.to_s
    +
    +  level = case level
    +          when /^[e0]/i
    +            :error
    +          when /^[w1]/i
    +            :warn
    +          when /^[d3]/i
    +            :debug
    +          else
    +            :info
    +          end
    +
    +  @level = level
    +end
    +
    @@ -1518,7 +2058,37 @@

    -

    + + + + + +
    +
    +
    +
    +241
    +242
    +243
    +244
    +245
    +246
    +247
    +248
    +249
    +
    +
    # File 'lib/doing/logger.rb', line 241
    +
    +def log_now(level, topic, message = nil, &block)
    +  return false unless write_message?(level)
    +
    +  if @logdev == $stdout
    +    @logdev.puts message(topic, message, &block)
    +  else
    +    @logdev.puts color_message(level, topic, message, &block)
    +  end
    +end
    +
    @@ -1557,7 +2127,45 @@

    -

    + + + + + +
    +
    +
    +
    +256
    +257
    +258
    +259
    +260
    +261
    +262
    +263
    +264
    +265
    +266
    +267
    +268
    +
    +
    # File 'lib/doing/logger.rb', line 256
    +
    +def output_results
    +  total_counters
    +  results = @results.select { |msg| write_message?(msg[:level]) }.uniq
    +
    +  if @logdev == $stdout
    +    $stdout.print results.map { |res| res[:message].uncolor }.join("\n")
    +    $stdout.puts
    +  else
    +    results.each do |msg|
    +      @logdev.puts color_message(msg[:level], msg[:message])
    +    end
    +  end
    +end
    +
    @@ -1579,7 +2187,31 @@

    -
    +

    + + + + +
    +
    +
    +
    +93
    +94
    +95
    +96
    +97
    +98
    +
    +
    # File 'lib/doing/logger.rb', line 93
    +
    +def restore_level
    +  return if @prev_level.nil? || @prev_level == @log_level
    +
    +  self.log_level = @prev_level
    +  @prev_level = nil
    +end
    +
    @@ -1601,7 +2233,31 @@

    -
    +

    + + + + +
    +
    +
    +
    +85
    +86
    +87
    +88
    +89
    +90
    +
    +
    # File 'lib/doing/logger.rb', line 85
    +
    +def temp_level(level)
    +  return if level.nil? || level.to_sym == @log_level
    +
    +  @prev_level = log_level.dup
    +  @log_level = level.to_sym
    +end
    +
    @@ -1678,7 +2334,25 @@

    -

    + + + + + +
    +
    +
    +
    +157
    +158
    +159
    +
    +
    # File 'lib/doing/logger.rb', line 157
    +
    +def warn(topic, message = nil, &block)
    +  write(:warn, topic, message, &block)
    +end
    +
    @@ -1789,7 +2463,27 @@

    -

    + + + + + +
    +
    +
    +
    +228
    +229
    +230
    +231
    +
    +
    # File 'lib/doing/logger.rb', line 228
    +
    +def write(level_of_message, topic, message = nil, &block)
    +  @results << { level: level_of_message, message: message(topic, message, &block) }
    +  true
    +end
    +
    @@ -1797,9 +2491,9 @@

    diff --git a/docs/doc/Doing/MarkdownExport.html b/docs/doc/Doing/MarkdownExport.html index 5514058f..cf7862eb 100644 --- a/docs/doc/Doing/MarkdownExport.html +++ b/docs/doc/Doing/MarkdownExport.html @@ -6,7 +6,7 @@ Class: Doing::MarkdownExport - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -224,7 +224,117 @@

    -

    + + + + + +
    +
    +
    +
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +
    +
    # File 'lib/doing/plugins/export/markdown_export.rb', line 38
    +
    +def self.render(wwid, items, variables: {})
    +  return if items.nil?
    +
    +  opt = variables[:options]
    +
    +  all_items = []
    +  items.each do |i|
    +    if String.method_defined? :force_encoding
    +      title = i.title.force_encoding('utf-8').link_urls(format: :markdown)
    +      note = i.note.map { |line| line.force_encoding('utf-8').strip.link_urls(format: :markdown) } if i.note
    +    else
    +      title = i.title.link_urls(format: :markdown)
    +      note = i.note.map { |line| line.strip.link_urls(format: :markdown) } if i.note
    +    end
    +
    +    title = "#{title} @section(#{i.section})" unless variables[:is_single]
    +
    +    interval = wwid.get_interval(i, record: true) if i.title =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
    +    interval ||= false
    +
    +    finished = i.title =~ /(?<= |^)@done/ ? true : false
    +    done = finished ? 'x' : ' '
    +
    +    all_items << {
    +      date: i.date.strftime('%a %-I:%M%p'),
    +      shortdate: i.date.relative_date,
    +      flagged: i.title =~ /(?<= |^)@#{Doing.setting('marker_tag')}/,
    +      done: done,
    +      finished: finished,
    +      note: note,
    +      section: i.section,
    +      time: interval,
    +      title: title.strip
    +    }
    +  end
    +
    +  template = if Doing.setting('export_templates.markdown') && File.exist?(File.expand_path(Doing.setting('export_templates.markdown')))
    +               IO.read(File.expand_path(Doing.setting('export_templates.markdown')))
    +             else
    +               self.template(nil)
    +             end
    +
    +  totals = opt[:totals] ? wwid.tag_times(format: :markdown, sort_by: opt[:sort_tags], sort_order: opt[:tag_order]) : ''
    +
    +  mdx = MarkdownRenderer.new(variables[:page_title], all_items, totals)
    +  Doing.logger.debug('Markdown Export:', "#{all_items.count} items output to Markdown")
    +  engine = ERB.new(template)
    +  @out = engine.result(mdx.get_binding)
    +end
    +
    @@ -236,7 +346,31 @@

    -

    + + + + + +
    +
    +
    +
    +27
    +28
    +29
    +30
    +31
    +32
    +
    +
    # File 'lib/doing/plugins/export/markdown_export.rb', line 27
    +
    +def self.settings
    +  {
    +    trigger: 'markdown|mk?d|gfm',
    +    templates: [{ name: 'markdown', trigger: 'mk?d|markdown', format: 'erb', filename: 'doing-markdown.erb' }]
    +  }
    +end
    +
    @@ -248,7 +382,25 @@

    -

    + + + + + +
    +
    +
    +
    +34
    +35
    +36
    +
    +
    # File 'lib/doing/plugins/export/markdown_export.rb', line 34
    +
    +def self.template(_trigger)
    +  IO.read(File.join(File.dirname(__FILE__), '../../../templates/doing-markdown.erb'))
    +end
    +
    @@ -256,9 +408,9 @@

    diff --git a/docs/doc/Doing/Note.html b/docs/doc/Doing/Note.html index 46c4c8dc..dde4c8ae 100644 --- a/docs/doc/Doing/Note.html +++ b/docs/doc/Doing/Note.html @@ -6,7 +6,7 @@ Class: Doing::Note - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -405,7 +405,29 @@

    - + + + + + +
    +
    +
    +
    +14
    +15
    +16
    +17
    +18
    +
    +
    # File 'lib/doing/note.rb', line 14
    +
    +def initialize(note = [])
    +  super()
    +
    +  add(note) if note
    +end
    +
    @@ -472,7 +494,37 @@

    - + + + + + +
    +
    +
    +
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +
    +
    # File 'lib/doing/note.rb', line 28
    +
    +def add(note, replace: false)
    +  clear if replace
    +  case note
    +  when String
    +    append_string(note)
    +  when Array
    +    append(note)
    +  end
    +end
    +
    @@ -511,7 +563,25 @@

    -

    + + + + + +
    +
    +
    +
    +43
    +44
    +45
    +
    +
    # File 'lib/doing/note.rb', line 43
    +
    +def compress
    +  delete_if { |l| l =~ /^\s*$/ || l =~ /^#/ }
    +end
    +
    @@ -523,7 +593,25 @@

    -

    +

    + + + + +
    +
    +
    +
    +47
    +48
    +49
    +
    +
    # File 'lib/doing/note.rb', line 47
    +
    +def compress!
    +  replace compress
    +end
    +
    @@ -582,7 +670,29 @@

    -

    + + + + + +
    +
    +
    +
    +99
    +100
    +101
    +102
    +103
    +
    +
    # File 'lib/doing/note.rb', line 99
    +
    +def equal?(other)
    +  return false unless other.is_a?(Note)
    +
    +  to_s == other.to_s
    +end
    +
    @@ -622,7 +732,25 @@

    -

    + + + + + +
    +
    +
    +
    +57
    +58
    +59
    +
    +
    # File 'lib/doing/note.rb', line 57
    +
    +def strip_lines
    +  Note.new(map(&:strip))
    +end
    +
    @@ -634,7 +762,25 @@

    -

    + + + + + +
    +
    +
    +
    +61
    +62
    +63
    +
    +
    # File 'lib/doing/note.rb', line 61
    +
    +def strip_lines!
    +  replace strip_lines
    +end
    +
    @@ -696,7 +842,25 @@

    -

    + + + + + +
    +
    +
    +
    +83
    +84
    +85
    +
    +
    # File 'lib/doing/note.rb', line 83
    +
    +def to_line(separator: ' ')
    +  compress.strip_lines.join(separator)
    +end
    +
    @@ -739,7 +903,25 @@

    -

    + + + + + +
    +
    +
    +
    +70
    +71
    +72
    +
    +
    # File 'lib/doing/note.rb', line 70
    +
    +def to_s(prefix: "\t\t")
    +  compress.strip_lines.map { |l| "#{prefix}#{l}" }.join("\n")
    +end
    +
    @@ -747,9 +929,9 @@

    diff --git a/docs/doc/Doing/Pager.html b/docs/doc/Doing/Pager.html index bd3e990f..02004c3d 100644 --- a/docs/doc/Doing/Pager.html +++ b/docs/doc/Doing/Pager.html @@ -6,7 +6,7 @@ Module: Doing::Pager - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -225,7 +225,97 @@

    - + + + + + +
    +
    +
    +
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +
    +
    # File 'lib/doing/pager.rb', line 26
    +
    +def page(text)
    +  unless @paginate
    +    puts text
    +    return
    +  end
    +
    +  pager = which_pager
    +  Doing.logger.debug('Pager:', "Using #{pager}")
    +
    +  read_io, write_io = IO.pipe
    +
    +  input = $stdin
    +
    +  pid = Kernel.fork do
    +    write_io.close
    +    input.reopen(read_io)
    +    read_io.close
    +
    +    # Wait until we have input before we start the pager
    +    IO.select [input]
    +
    +    begin
    +      exec(pager)
    +    rescue SystemCallError => e
    +      raise Errors::DoingStandardError, "Pager error, #{e}"
    +    end
    +  end
    +
    +  begin
    +    read_io.close
    +    write_io.write(text)
    +    write_io.close
    +  rescue SystemCallError # => e
    +    # raise Errors::DoingStandardError, "Pager error, #{e}"
    +  end
    +
    +  _, status = Process.waitpid2(pid)
    +  status.success?
    +end
    +
    @@ -247,7 +337,25 @@

    -
    +

    + + + + +
    +
    +
    +
    +10
    +11
    +12
    +
    +
    # File 'lib/doing/pager.rb', line 10
    +
    +def paginate
    +  @paginate ||= false
    +end
    +
    @@ -288,7 +396,25 @@

    -

    + + + + + +
    +
    +
    +
    +17
    +18
    +19
    +
    +
    # File 'lib/doing/pager.rb', line 17
    +
    +def paginate=(should_paginate)
    +  @paginate = should_paginate
    +end
    +
    @@ -296,9 +422,9 @@

    diff --git a/docs/doc/Doing/Plugins.html b/docs/doc/Doing/Plugins.html index 2e39aecc..0a44eeef 100644 --- a/docs/doc/Doing/Plugins.html +++ b/docs/doc/Doing/Plugins.html @@ -6,7 +6,7 @@ Module: Doing::Plugins - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -496,7 +496,27 @@

    - + + + + + +
    +
    +
    +
    +182
    +183
    +184
    +185
    +
    +
    # File 'lib/doing/plugin_manager.rb', line 182
    +
    +def available_plugins(type: :export)
    +  type = valid_type(type)
    +  plugins[type].keys.sort
    +end
    +
    @@ -572,7 +592,51 @@

    -

    + + + + + +
    +
    +
    +
    +158
    +159
    +160
    +161
    +162
    +163
    +164
    +165
    +166
    +167
    +168
    +169
    +170
    +171
    +172
    +173
    +
    +
    # File 'lib/doing/plugin_manager.rb', line 158
    +
    +def list_plugins(options = {})
    +  separator = options[:column] ? "\n" : "\t"
    +  type = options[:type].nil? || options[:type] =~ /all/i ? 'all' : valid_type(options[:type])
    +
    +  case type
    +  when :import
    +    puts plugin_names(type: :import, separator: separator)
    +  when :export
    +    puts plugin_names(type: :export, separator: separator)
    +  else
    +    print 'Import plugins: '
    +    puts plugin_names(type: :import, separator: ', ')
    +    print 'Export plugins: '
    +    puts plugin_names(type: :export, separator: ', ')
    +  end
    +end
    +
    @@ -594,7 +658,59 @@

    -
    +

    + + + + +
    +
    +
    +
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +
    +
    # File 'lib/doing/plugin_manager.rb', line 28
    +
    +def load_plugins(add_dir = nil)
    +  plugins_path(add_dir).each do |plugin_search_path|
    +    Dir.glob(File.join(plugin_search_path, '**', '*.rb')).sort.each do |plugin|
    +      require plugin
    +    end
    +  end
    +
    +  Gem.find_latest_files('doing-plugin-*', true).sort.each do |plugin|
    +    load plugin
    +  end
    +
    +  # Gem.path.each do |path|
    +  #   $LOAD_PATH.unshift path
    +  #   Dir.glob(File.join(path, 'gems', 'doing-plugin-*', 'lib', '*.rb')).sort.each do |plugin|
    +  #     require plugin.sub(%r{#{path}/gems/(.*?)-[\d.]+$}, '\1')
    +  #   end
    +  # end
    +
    +  plugins
    +end
    +
    @@ -671,7 +787,27 @@

    -

    + + + + + +
    +
    +
    +
    +195
    +196
    +197
    +198
    +
    +
    # File 'lib/doing/plugin_manager.rb', line 195
    +
    +def plugin_names(type: :export, separator: '|')
    +  type = valid_type(type)
    +  available_plugins(type: type).join(separator)
    +end
    +
    @@ -733,7 +869,35 @@

    -

    + + + + + +
    +
    +
    +
    +209
    +210
    +211
    +212
    +213
    +214
    +215
    +216
    +
    +
    # File 'lib/doing/plugin_manager.rb', line 209
    +
    +def plugin_regex(type: :export)
    +  type = valid_type(type)
    +  pattern = []
    +  plugins[type].each do |_, options|
    +    pattern << options[:trigger].normalize_trigger
    +  end
    +  Regexp.new("^(?:#{pattern.sort.uniq.join('|')})$", true)
    +end
    +
    @@ -794,7 +958,47 @@

    -

    + + + + + +
    +
    +
    +
    +226
    +227
    +228
    +229
    +230
    +231
    +232
    +233
    +234
    +235
    +236
    +237
    +238
    +239
    +
    +
    # File 'lib/doing/plugin_manager.rb', line 226
    +
    +def plugin_templates(type: :export)
    +  type = valid_type(type)
    +  templates = []
    +  plugs = plugins[type].clone
    +  plugs.delete_if { |_t, o| o[:templates].nil? }.each do |_, options|
    +    options[:templates].each do |t|
    +      out = t[:name]
    +      out += " (#{t[:format]})" if t.key?(:format)
    +      templates << out
    +    end
    +  end
    +
    +  templates.sort.uniq
    +end
    +
    @@ -835,7 +1039,31 @@

    -

    + + + + + +
    +
    +
    +
    +18
    +19
    +20
    +21
    +22
    +23
    +
    +
    # File 'lib/doing/plugin_manager.rb', line 18
    +
    +def plugins
    +  @plugins ||= {
    +    import: {},
    +    export: {}
    +  }
    +end
    +
    @@ -896,7 +1124,29 @@

    -

    + + + + + +
    +
    +
    +
    +56
    +57
    +58
    +59
    +60
    +
    +
    # File 'lib/doing/plugin_manager.rb', line 56
    +
    +def plugins_path(add_dir = nil)
    +  paths = Array(File.join(File.dirname(__FILE__), 'plugins'))
    +  paths << File.join(add_dir) if add_dir
    +  paths.map { |d| File.expand_path(d) }
    +end
    +
    @@ -987,7 +1237,73 @@

    -

    + + + + + +
    +
    +
    +
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +
    +
    # File 'lib/doing/plugin_manager.rb', line 74
    +
    +def register(title, type, klass)
    +  type = validate_plugin(title, type, klass)
    +  return unless type
    +
    +  if title.is_a?(Array)
    +    title.each { |t| register(t, type, klass) }
    +    return
    +  end
    +
    +  settings = if klass.respond_to? :settings
    +               klass.settings
    +             else
    +               { trigger: title.normalize_trigger, config: {} }
    +             end
    +
    +  plugins[type] ||= {}
    +  plugins[type][title] = {
    +    trigger: settings[:trigger].normalize_trigger || title.normalize_trigger,
    +    class: klass,
    +    templates: settings[:templates] || nil,
    +    config: settings[:config] || {}
    +  }
    +
    +  return unless ENV['DOING_PLUGIN_DEBUG']
    +
    +  Doing.logger.debug('Plugin Manager:', "Registered #{type} plugin \"#{title}\"")
    +end
    +
    @@ -1097,7 +1413,49 @@

    -

    + + + + + +
    +
    +
    +
    +276
    +277
    +278
    +279
    +280
    +281
    +282
    +283
    +284
    +285
    +286
    +287
    +288
    +289
    +290
    +
    +
    # File 'lib/doing/plugin_manager.rb', line 276
    +
    +def template_for_trigger(trigger, type: :export, save_to: nil)
    +  plugins[valid_type(type)].clone.delete_if { |_t, o| o[:templates].nil? }.each do |_, options|
    +    options[:templates].each do |t|
    +      next unless trigger =~ /^(?:#{t[:trigger].normalize_trigger})$/
    +
    +      tpl = options[:class].template(trigger)
    +      return tpl unless save_to
    +
    +      raise PluginException.new('No default filename defined', :export, t[:name]) unless t.key?(:filename)
    +
    +      return save_template(tpl, save_to, t[:filename])
    +    end
    +  end
    +  raise Errors::InvalidArgument, "No template type matched \"#{trigger}\""
    +end
    +
    @@ -1159,7 +1517,41 @@

    -

    + + + + + +
    +
    +
    +
    +250
    +251
    +252
    +253
    +254
    +255
    +256
    +257
    +258
    +259
    +260
    +
    +
    # File 'lib/doing/plugin_manager.rb', line 250
    +
    +def template_regex(type: :export)
    +  type = valid_type(type)
    +  pattern = []
    +  plugs = plugins[type].clone
    +  plugs.delete_if { |_, o| o[:templates].nil? }.each do |_, options|
    +    options[:templates].each do |t|
    +      pattern << t[:trigger].normalize_trigger
    +    end
    +  end
    +  Regexp.new("^(?:#{pattern.join('|')})$", true)
    +end
    +
    @@ -1181,7 +1573,25 @@

    -
    +

    + + + + +
    +
    +
    +
    +8
    +9
    +10
    +
    +
    # File 'lib/doing/plugin_manager.rb', line 8
    +
    +def user_home
    +  @user_home ||= Util.user_home
    +end
    +
    @@ -1257,7 +1667,49 @@

    -

    + + + + + +
    +
    +
    +
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +
    +
    # File 'lib/doing/plugin_manager.rb', line 135
    +
    +def valid_type(type, default: nil)
    +  type ||= default
    +
    +  t = type.to_s
    +  type = case t
    +         when /^i(m(p(o(r(t)?)?)?)?)?$/
    +           :import
    +         when /^e(x(p(o(r(t)?)?)?)?)?$/
    +           :export
    +         else
    +           raise Errors::InvalidPluginType.new('Invalid plugin type', 'unrecognized')
    +         end
    +
    +  type.to_sym
    +end
    +
    @@ -1332,7 +1784,43 @@

    -

    + + + + + +
    +
    +
    +
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +
    +
    # File 'lib/doing/plugin_manager.rb', line 113
    +
    +def validate_plugin(title, type, klass)
    +  type = valid_type(type)
    +  if type == :import && !klass.respond_to?(:import)
    +    raise Errors::PluginUncallable.new('Import plugins must respond to :import', type, title)
    +  end
    +
    +  if type == :export && !klass.respond_to?(:render)
    +    raise Errors::PluginUncallable.new('Export plugins must respond to :render', type, title)
    +  end
    +
    +  type
    +end
    +
    @@ -1340,9 +1828,9 @@

    diff --git a/docs/doc/Doing/Prompt.html b/docs/doc/Doing/Prompt.html index e1da0840..4fbd73fe 100644 --- a/docs/doc/Doing/Prompt.html +++ b/docs/doc/Doing/Prompt.html @@ -6,7 +6,7 @@ Module: Doing::Prompt - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -276,7 +276,25 @@

    - + + + + + +
    +
    +
    +
    +36
    +37
    +38
    +
    +
    # File 'lib/doing/prompt/prompt.rb', line 36
    +
    +def default_answer
    +  @default_answer ||= false
    +end
    +
    @@ -317,7 +335,25 @@

    - + + + + + +
    +
    +
    +
    +27
    +28
    +29
    +
    +
    # File 'lib/doing/prompt/prompt.rb', line 27
    +
    +def force_answer
    +  @force_answer ||= nil
    +end
    +
    @@ -326,9 +362,9 @@

    diff --git a/docs/doc/Doing/PromptChoose.html b/docs/doc/Doing/PromptChoose.html index 6714811c..4bd29e5e 100644 --- a/docs/doc/Doing/PromptChoose.html +++ b/docs/doc/Doing/PromptChoose.html @@ -6,7 +6,7 @@ Module: Doing::PromptChoose - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -291,7 +291,57 @@

    - + + + + + +
    +
    +
    +
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +
    +
    # File 'lib/doing/prompt/choose.rb', line 17
    +
    +def choose_from(options, prompt: 'Make a selection: ', multiple: false, sorted: true, fzf_args: [])
    +  return nil unless $stdout.isatty
    +
    +  # fzf_args << '-1' # User is expecting a menu, and even if only one it serves as confirmation
    +  default_args = []
    +  default_args << %(--prompt="#{prompt}")
    +  default_args << "--height=#{options.count + 2}"
    +  default_args << '--info=inline'
    +  default_args << '--multi' if multiple
    +  header = "esc: cancel,#{multiple ? ' tab: multi-select, ctrl-a: select all,' : ''} return: confirm"
    +  default_args << %(--header="#{header}")
    +  default_args.concat(fzf_args)
    +  options.sort! if sorted
    +
    +  res = `echo #{Shellwords.escape(options.join("\n"))}|#{fzf} #{default_args.join(' ')}`
    +  return false if res.strip.size.zero?
    +
    +  res
    +end
    +
    @@ -466,7 +516,147 @@

    -

    + + + + + +
    +
    +
    +
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +
    +
    # File 'lib/doing/prompt/choose.rb', line 53
    +
    +def choose_from_items(items, **opt)
    +  return items unless $stdout.isatty
    +
    +  return nil unless items.count.positive?
    +
    +  case_sensitive = opt.fetch(:case, :smart).normalize_case
    +  header = opt.fetch(:header, 'Arrows: navigate, tab: mark for selection, ctrl-a: select all, enter: commit')
    +  prompt = opt.fetch(:prompt, 'Select entries to act on > ')
    +  query = opt.fetch(:query) { opt.fetch(:search, '') }
    +  include_section = opt.fetch(:include_section, false)
    +
    +  pad = items.length.to_s.length
    +  options = items.map.with_index do |item, i|
    +    out = [
    +      format("%#{pad}d", i),
    +      ') ',
    +      format('%16s', item.date.strftime('%Y-%m-%d %H:%M')),
    +      ' | ',
    +      item.title
    +    ]
    +    if include_section
    +      out.concat([
    +        ' (',
    +        item.section,
    +        ') '
    +      ])
    +    end
    +    out.join('')
    +  end
    +
    +  fzf_args = [
    +    %(--header="#{header}"),
    +    %(--prompt="#{prompt.sub(/ *$/, ' ')}"),
    +    opt.fetch(:multiple) ? '--multi' : '--no-multi',
    +    '-0',
    +    '--bind ctrl-a:select-all',
    +    %(-q "#{query}"),
    +    '--info=inline'
    +  ]
    +  fzf_args.push('-1') unless opt.fetch(:show_if_single, false)
    +  fzf_args << case case_sensitive
    +              when :sensitive
    +                '+i'
    +              when :ignore
    +                '-i'
    +              end
    +  fzf_args << '-e' if opt.fetch(:exact, false)
    +
    +
    +  unless opt.fetch(:menu)
    +    raise InvalidArgument, "Can't skip menu when no query is provided" unless query && !query.empty?
    +
    +    fzf_args.concat([%(--filter="#{query}"), opt.fetch(:sort) ? '' : '--no-sort'])
    +  end
    +  res = `echo #{Shellwords.escape(options.join("\n"))}|#{fzf} #{fzf_args.join(' ')}`
    +
    +  selected = []
    +  res.split(/\n/).each do |item|
    +    idx = item.match(/^ *(\d+)\)/)[1].to_i
    +    selected.push(items[idx])
    +  end
    +
    +  opt.fetch(:multiple) ? selected : selected[0]
    +end
    +
    @@ -474,9 +664,9 @@

    diff --git a/docs/doc/Doing/PromptFZF.html b/docs/doc/Doing/PromptFZF.html index 47529245..e81c9bdd 100644 --- a/docs/doc/Doing/PromptFZF.html +++ b/docs/doc/Doing/PromptFZF.html @@ -6,7 +6,7 @@ Module: Doing::PromptFZF - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -250,7 +250,25 @@

    - + + + + + +
    +
    +
    +
    +11
    +12
    +13
    +
    +
    # File 'lib/doing/prompt/fzf.rb', line 11
    +
    +def fzf
    +  @fzf ||= install_fzf
    +end
    +
    @@ -312,7 +330,91 @@

    -

    + + + + + +
    +
    +
    +
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +
    +
    # File 'lib/doing/prompt/fzf.rb', line 47
    +
    +def install_fzf(force: false)
    +  if force
    +    uninstall_fzf
    +  elsif which_fzf
    +    return which_fzf
    +  end
    +
    +  fzf_dir = File.join(File.dirname(__FILE__), '../../helpers/fzf')
    +  FileUtils.mkdir_p(fzf_dir) unless File.directory?(fzf_dir)
    +  fzf_bin = File.join(fzf_dir, 'bin/fzf')
    +  return fzf_bin if File.exist?(fzf_bin)
    +
    +  prev_level = Doing.logger.level
    +  Doing.logger.adjust_verbosity({ log_level: :info })
    +  Doing.logger.log_now(:warn, 'fzf:', 'Compiling and installing fzf -- this will only happen once')
    +  Doing.logger.log_now(:warn, 'fzf:', 'fzf is copyright Junegunn Choi, MIT License <https://github.com/junegunn/fzf/blob/master/LICENSE>')
    +
    +  silence_std
    +  `'#{fzf_dir}/install' --bin --no-key-bindings --no-completion --no-update-rc --no-bash --no-zsh --no-fish &> /dev/null`
    +  unless File.exist?(fzf_bin)
    +    restore_std
    +    Doing.logger.log_now(:warn, 'Error installing, trying again as root')
    +    silence_std
    +    `sudo '#{fzf_dir}/install' --bin --no-key-bindings --no-completion --no-update-rc --no-bash --no-zsh --no-fish &> /dev/null`
    +  end
    +  restore_std
    +  unless File.exist?(fzf_bin)
    +    Doing.logger.error('fzf:', 'unable to install fzf. You can install manually and Doing will use the system version.')
    +    Doing.logger.error('fzf:', 'see https://github.com/junegunn/fzf#installation')
    +    raise RuntimeError.new('Error installing fzf, please report at https://github.com/ttscoff/doing/issues')
    +  end
    +
    +  Doing.logger.info('fzf:', "installed to #{fzf}")
    +  Doing.logger.adjust_verbosity({ log_level: prev_level })
    +  fzf_bin
    +end
    +
    @@ -334,7 +436,29 @@

    -
    +

    + + + + +
    +
    +
    +
    +18
    +19
    +20
    +21
    +22
    +
    +
    # File 'lib/doing/prompt/fzf.rb', line 18
    +
    +def uninstall_fzf
    +  fzf_bin = File.join(File.dirname(__FILE__), '../../helpers/fzf/bin/fzf')
    +  FileUtils.rm_f(fzf_bin) if File.exist?(fzf_bin)
    +  Doing.logger.warn('fzf:', "removed #{fzf_bin}")
    +end
    +
    @@ -373,7 +497,35 @@

    -

    + + + + + +
    +
    +
    +
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +
    +
    # File 'lib/doing/prompt/fzf.rb', line 29
    +
    +def which_fzf
    +  fzf_dir = File.join(File.dirname(__FILE__), '../../helpers/fzf')
    +  fzf_bin = File.join(fzf_dir, 'bin/fzf')
    +  return fzf_bin if File.exist?(fzf_bin)
    +
    +  Doing.logger.debug('fzf:', 'Using user-installed fzf')
    +  TTY::Which.which('fzf')
    +end
    +
    @@ -381,9 +533,9 @@

    diff --git a/docs/doc/Doing/PromptInput.html b/docs/doc/Doing/PromptInput.html index f2c3c47a..df8b560d 100644 --- a/docs/doc/Doing/PromptInput.html +++ b/docs/doc/Doing/PromptInput.html @@ -6,7 +6,7 @@ Module: Doing::PromptInput - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -291,7 +291,33 @@

    - + + + + + +
    +
    +
    +
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +
    +
    # File 'lib/doing/prompt/input.rb', line 19
    +
    +def enter_text(prompt, default_response: '')
    +  $stdin.reopen('/dev/tty')
    +  return default_response if @default_answer
    +
    +  print "#{yellow(prompt).sub(/:?$/, ':')} #{reset}"
    +  $stdin.gets.strip
    +end
    +
    @@ -390,7 +416,53 @@

    -

    + + + + + +
    +
    +
    +
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +
    +
    # File 'lib/doing/prompt/input.rb', line 41
    +
    +def read_line(prompt: 'Enter text', completions: [], default_response: '')
    +  $stdin.reopen('/dev/tty')
    +  return default_response if @default_answer
    +
    +  unless completions.empty?
    +    completions.sort!
    +    comp = proc { |s| completions.grep(/^#{Regexp.escape(s)}/) }
    +    Readline.completion_append_character = ' '
    +    Readline.completion_proc = comp
    +  end
    +
    +  begin
    +    Readline.readline("#{yellow(prompt).sub(/:?$/, ':')} #{reset}", true).strip
    +  rescue Interrupt
    +    raise UserCancelled
    +  end
    +end
    +
    @@ -489,7 +561,77 @@

    -

    + + + + + +
    +
    +
    +
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +
    +
    # File 'lib/doing/prompt/input.rb', line 73
    +
    +def read_lines(prompt: 'Enter text', completions: [], default_response: '')
    +  $stdin.reopen('/dev/tty')
    +  return default_response if @default_answer
    +
    +  completions.sort!
    +  comp = proc { |s| completions.grep(/^#{Regexp.escape(s)}/) }
    +  Readline.completion_append_character = ' '
    +  Readline.completion_proc = comp
    +  puts format(['%<promptcolor>s%<prompt>s %<textcolor>sEnter a blank line',
    +    '(%<keycolor>sreturn twice%<textcolor>s)',
    +    'to end editing and save,',
    +    '%<keycolor>sCTRL-C%<textcolor>s to cancel%<reset>s'].join(' '),
    +    { promptcolor: boldgreen, prompt: prompt.sub(/:?$/, ':'),
    +      textcolor: yellow, keycolor: boldwhite, reset: reset })
    +
    +  res = []
    +
    +  begin
    +    while (line = Readline.readline('> ', true))
    +      break if line.strip.empty?
    +
    +      res << line.chomp
    +    end
    +  rescue Interrupt
    +    return nil
    +  end
    +
    +  res.join("\n").strip
    +end
    +
    @@ -554,7 +696,49 @@

    -

    + + + + + +
    +
    +
    +
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +
    +
    # File 'lib/doing/prompt/input.rb', line 113
    +
    +def request_lines(prompt: 'Enter text', default_response: '')
    +  $stdin.reopen('/dev/tty')
    +  return default_response if @default_answer
    +
    +  ask_note = []
    +  reader = TTY::Reader.new(interrupt: -> { raise Errors::UserCancelled }, track_history: false)
    +  puts "#{boldgreen(prompt.sub(/:?$/, ':'))} #{yellow('Hit return for a new line, ')}#{boldwhite('enter a blank line (')}#{boldyellow('return twice')}#{boldwhite(') to end editing')}"
    +  loop do
    +    res = reader.read_line(green('> '))
    +    break if res.strip.empty?
    +
    +    ask_note.push(res)
    +  end
    +  ask_note.join("\n").strip
    +end
    +
    @@ -562,9 +746,9 @@

    diff --git a/docs/doc/Doing/PromptSTD.html b/docs/doc/Doing/PromptSTD.html index bab81600..cfe3dd71 100644 --- a/docs/doc/Doing/PromptSTD.html +++ b/docs/doc/Doing/PromptSTD.html @@ -6,7 +6,7 @@ Module: Doing::PromptSTD - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -210,7 +210,27 @@

    -
    + + + + + +
    +
    +
    +
    +9
    +10
    +11
    +12
    +
    +
    # File 'lib/doing/prompt/std.rb', line 9
    +
    +def clear_screen(msg = nil)
    +  puts "\e[H\e[2J" if $stdout.tty?
    +  puts msg if msg.good?
    +end
    +
    @@ -232,7 +252,27 @@

    -
    +

    + + + + +
    +
    +
    +
    +27
    +28
    +29
    +30
    +
    +
    # File 'lib/doing/prompt/std.rb', line 27
    +
    +def restore_std
    +  $stdout = STDOUT
    +  $stderr = STDERR
    +end
    +
    @@ -275,7 +315,27 @@

    -

    + + + + + +
    +
    +
    +
    +19
    +20
    +21
    +22
    +
    +
    # File 'lib/doing/prompt/std.rb', line 19
    +
    +def silence_std(file = '/dev/null')
    +  $stdout = File.new(file, 'w')
    +  $stderr = File.new(file, 'w')
    +end
    +
    @@ -283,9 +343,9 @@

    diff --git a/docs/doc/Doing/PromptYN.html b/docs/doc/Doing/PromptYN.html index 3ff28f21..158d0b9b 100644 --- a/docs/doc/Doing/PromptYN.html +++ b/docs/doc/Doing/PromptYN.html @@ -6,7 +6,7 @@ Module: Doing::PromptYN - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -219,7 +219,113 @@

    - + + + + + +
    +
    +
    +
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +
    +
    # File 'lib/doing/prompt/yn.rb', line 16
    +
    +def yn(question, default_response: false)
    +  return @force_answer == :yes ? true : false unless @force_answer.nil?
    +
    +  $stdin.reopen('/dev/tty')
    +
    +  default = if default_response.is_a?(String)
    +              default_response =~ /y/i ? true : false
    +            else
    +              default_response
    +            end
    +
    +  # if global --default is set, answer default
    +  return default if @default_answer
    +
    +  # if this isn't an interactive shell, answer default
    +  return default unless $stdout.isatty
    +
    +  # clear the buffer
    +  if ARGV&.length
    +    ARGV.length.times do
    +      ARGV.shift
    +    end
    +  end
    +  system 'stty cbreak'
    +
    +  cw = white
    +  cbw = boldwhite
    +  cbg = boldgreen
    +  cd = Color.default
    +
    +  options = unless default.nil?
    +              "#{cw}[#{default ? "#{cbg}Y#{cw}/#{cbw}n" : "#{cbw}y#{cw}/#{cbg}N"}#{cw}]#{cd}"
    +            else
    +              "#{cw}[#{cbw}y#{cw}/#{cbw}n#{cw}]#{cd}"
    +            end
    +  $stdout.syswrite "#{cbw}#{question.sub(/\?$/, '')} #{options}#{cbw}?#{cd} "
    +  res = $stdin.sysread 1
    +  puts
    +  system 'stty cooked'
    +
    +  res.chomp!
    +  res.downcase!
    +
    +  return default if res.empty?
    +
    +  res =~ /y/i ? true : false
    +end
    +
    @@ -227,9 +333,9 @@

    diff --git a/docs/doc/Doing/Section.html b/docs/doc/Doing/Section.html index 27d89e47..fe6b7c63 100644 --- a/docs/doc/Doing/Section.html +++ b/docs/doc/Doing/Section.html @@ -6,7 +6,7 @@ Class: Doing::Section - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -276,7 +276,41 @@

    -
    + + + + + +
    +
    +
    +
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +
    +
    # File 'lib/doing/section.rb', line 8
    +
    +def initialize(title, original: nil)
    +  super()
    +
    +  @title = title
    +
    +  @original = if original.nil?
    +                "#{title}:"
    +              else
    +                original =~ /:(\s+@[^ (]+(\([^)]*\))?)*?$/ ? original : "#{original}:"
    +              end
    +end
    +
    @@ -305,7 +339,25 @@

    -
    + + + + + +
    +
    +
    +
    +6
    +7
    +8
    +
    +
    # File 'lib/doing/section.rb', line 6
    +
    +def original
    +  @original
    +end
    +
    @@ -329,7 +381,25 @@

    -
    + + + + + +
    +
    +
    +
    +6
    +7
    +8
    +
    +
    # File 'lib/doing/section.rb', line 6
    +
    +def title
    +  @title
    +end
    +
    @@ -370,7 +440,25 @@

    - + + + + + +
    +
    +
    +
    +20
    +21
    +22
    +
    +
    # File 'lib/doing/section.rb', line 20
    +
    +def equal?(other)
    +  @title == other.title
    +end
    +
    @@ -392,7 +480,25 @@

    -
    +

    + + + + +
    +
    +
    +
    +25
    +26
    +27
    +
    +
    # File 'lib/doing/section.rb', line 25
    +
    +def to_s
    +  @title
    +end
    +
    @@ -400,9 +506,9 @@

    diff --git a/docs/doc/Doing/StringHighlight.html b/docs/doc/Doing/StringHighlight.html index 76e85bae..48e62541 100644 --- a/docs/doc/Doing/StringHighlight.html +++ b/docs/doc/Doing/StringHighlight.html @@ -6,7 +6,7 @@ Module: Doing::StringHighlight - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -288,7 +288,79 @@

    -

    + + + + + +
    +
    +
    +
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +
    +
    # File 'lib/doing/string/highlight.rb', line 31
    +
    +def highlight_search(search, distance: nil, negate: false, case_type: nil)
    +  out = dup
    +  matching = Doing.setting('search.matching', 'pattern').normalize_matching
    +  distance ||= Doing.setting('search.distance', 3).to_i
    +  case_type ||= Doing.setting('search.case', 'smart').normalize_case
    +
    +  if search.rx? || matching == :fuzzy
    +    rx = search.to_rx(distance: distance, case_type: case_type)
    +    out.gsub!(rx) { |m| m.bgyellow.black }
    +  else
    +    query = search.strip.to_phrase_query
    +
    +    if query[:must].nil? && query[:must_not].nil?
    +      query[:must] = query[:should]
    +      query[:should] = []
    +    end
    +    qs = []
    +    qs.concat(query[:must]) if query[:must]
    +    qs.concat(query[:should]) if query[:should]
    +    qs.each do |s|
    +      rx = Regexp.new(s.wildcard_to_rx, ignore_case(s, case_type))
    +      out.gsub!(rx) do
    +        m = Regexp.last_match
    +        last = m.pre_match.last_color_code
    +        "#{m[0].bgyellow.black}#{last}"
    +      end
    +    end
    +  end
    +  out
    +end
    +
    @@ -300,7 +372,25 @@

    -

    + + + + + +
    +
    +
    +
    +27
    +28
    +29
    +
    +
    # File 'lib/doing/string/highlight.rb', line 27
    +
    +def highlight_search!(search, distance: nil, negate: false, case_type: nil)
    +  replace highlight_search(search, distance: distance, negate: negate, case_type: case_type)
    +end
    +
    @@ -360,7 +450,35 @@

    -

    + + + + + +
    +
    +
    +
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +
    +
    # File 'lib/doing/string/highlight.rb', line 18
    +
    +def highlight_tags(color = 'yellow', last_color: nil)
    +  unless last_color
    +    color = color.split(' ') unless color.is_a?(Array)
    +    tag_color = color.each_with_object([]) { |c, arr| arr << Doing::Color.send(c) }.join('')
    +    last_color = last_color_code
    +  end
    +  gsub(/(\s|m)(@[^ ("']+)/, "\\1#{tag_color}\\2#{last_color}")
    +end
    +
    @@ -402,7 +520,25 @@

    -

    + + + + + +
    +
    +
    +
    +7
    +8
    +9
    +
    +
    # File 'lib/doing/string/highlight.rb', line 7
    +
    +def highlight_tags!(color = 'yellow', last_color: nil)
    +  replace highlight_tags(color)
    +end
    +
    @@ -448,7 +584,25 @@

    -

    + + + + + +
    +
    +
    +
    +73
    +74
    +75
    +
    +
    # File 'lib/doing/string/highlight.rb', line 73
    +
    +def last_color
    +  scan(/\e\[[\d;]+m/).join('')
    +end
    +
    @@ -487,7 +641,25 @@

    -

    + + + + + +
    +
    +
    +
    +82
    +83
    +84
    +
    +
    # File 'lib/doing/string/highlight.rb', line 82
    +
    +def uncolor
    +  gsub(/\e\[[\d;]+m/, '')
    +end
    +
    @@ -515,7 +687,25 @@

    -

    + + + + + +
    +
    +
    +
    +89
    +90
    +91
    +
    +
    # File 'lib/doing/string/highlight.rb', line 89
    +
    +def uncolor!
    +  replace uncolor
    +end
    +
    @@ -523,9 +713,9 @@

    diff --git a/docs/doc/Doing/StringNormalize.html b/docs/doc/Doing/StringNormalize.html index 9a57c0e1..f441a006 100644 --- a/docs/doc/Doing/StringNormalize.html +++ b/docs/doc/Doing/StringNormalize.html @@ -6,7 +6,7 @@ Module: Doing::StringNormalize - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -518,7 +518,39 @@

    - + + + + + +
    +
    +
    +
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +
    +
    # File 'lib/doing/normalize.rb', line 54
    +
    +def normalize_age(default = :newest)
    +  case self
    +  when /^o/i
    +    :oldest
    +  when /^n/i
    +    :newest
    +  else
    +    default.is_a?(Symbol) ? default : default.normalize_age
    +  end
    +end
    +
    @@ -546,7 +578,25 @@

    -

    + + + + + +
    +
    +
    +
    +66
    +67
    +68
    +
    +
    # File 'lib/doing/normalize.rb', line 66
    +
    +def normalize_age!(default = :newest)
    +  replace normalize_age(default)
    +end
    +
    @@ -585,7 +635,47 @@

    -

    + + + + + +
    +
    +
    +
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +
    +
    # File 'lib/doing/normalize.rb', line 118
    +
    +def normalize_bool(default = :and)
    +  case self
    +  when /(and|all)/i
    +    :and
    +  when /(any|or)/i
    +    :or
    +  when /(not|none)/i
    +    :not
    +  when /^p/i
    +    :pattern
    +  else
    +    default.is_a?(Symbol) ? default : default.normalize_bool
    +  end
    +end
    +
    @@ -613,7 +703,25 @@

    -

    + + + + + +
    +
    +
    +
    +134
    +135
    +136
    +
    +
    # File 'lib/doing/normalize.rb', line 134
    +
    +def normalize_bool!(default = :and)
    +  replace normalize_bool(default)
    +end
    +
    @@ -652,7 +760,43 @@

    -

    + + + + + +
    +
    +
    +
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +
    +
    # File 'lib/doing/normalize.rb', line 95
    +
    +def normalize_case(default = :smart)
    +  case self
    +  when /^(c|sens)/i
    +    :sensitive
    +  when /^i/i
    +    :ignore
    +  when /^s/i
    +    :smart
    +  else
    +    default.is_a?(Symbol) ? default : default.normalize_case
    +  end
    +end
    +
    @@ -680,7 +824,25 @@

    -

    + + + + + +
    +
    +
    +
    +109
    +110
    +111
    +
    +
    # File 'lib/doing/normalize.rb', line 109
    +
    +def normalize_case!(default = :smart)
    +  replace normalize_case(default)
    +end
    +
    @@ -692,7 +854,43 @@

    -

    +

    + + + + +
    +
    +
    +
    +179
    +180
    +181
    +182
    +183
    +184
    +185
    +186
    +187
    +188
    +189
    +190
    +
    +
    # File 'lib/doing/normalize.rb', line 179
    +
    +def normalize_change_type
    +  case self
    +  when /^c/i
    +    :changed
    +  when /^i/i
    +    :improved
    +  when /^f/i
    +    :fixed
    +  when /^n/i
    +    :new
    +  end
    +end
    +
    @@ -752,7 +950,43 @@

    -

    + + + + + +
    +
    +
    +
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +
    +
    # File 'lib/doing/normalize.rb', line 36
    +
    +def normalize_list_style(default = :space)
    +  case self
    +  when /^c(om|sv)/i
    +    :comma
    +  when /^c/i
    +    :column
    +  when /^t/i
    +    :tab
    +  else
    +    default.is_a?(Symbol) ? default : default.normalize_list_style
    +  end
    +end
    +
    @@ -814,7 +1048,43 @@

    -

    + + + + + +
    +
    +
    +
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +153
    +154
    +155
    +156
    +157
    +
    +
    # File 'lib/doing/normalize.rb', line 146
    +
    +def normalize_matching(default = :pattern)
    +  case self
    +  when /^f/i
    +    :fuzzy
    +  when /^p/i
    +    :pattern
    +  when /^e/i
    +    :exact
    +  else
    +    default.is_a?(Symbol) ? default : default.normalize_matching
    +  end
    +end
    +
    @@ -842,7 +1112,25 @@

    -

    + + + + + +
    +
    +
    +
    +160
    +161
    +162
    +
    +
    # File 'lib/doing/normalize.rb', line 160
    +
    +def normalize_matching!(default = :pattern)
    +  replace normalize_bool(default)
    +end
    +
    @@ -854,7 +1142,39 @@

    -

    + + + + + +
    +
    +
    +
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +
    +
    # File 'lib/doing/normalize.rb', line 79
    +
    +def normalize_order(default = :asc)
    +  case self
    +  when /^a/i
    +    :asc
    +  when /^d/i
    +    :desc
    +  else
    +    default.is_a?(Symbol) ? default : default.normalize_order
    +  end
    +end
    +
    @@ -893,7 +1213,25 @@

    -

    + + + + + +
    +
    +
    +
    +75
    +76
    +77
    +
    +
    # File 'lib/doing/normalize.rb', line 75
    +
    +def normalize_order!(default = :asc)
    +  replace normalize_order(default)
    +end
    +
    @@ -932,7 +1270,39 @@

    -

    + + + + + +
    +
    +
    +
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +
    +
    # File 'lib/doing/normalize.rb', line 13
    +
    +def normalize_tag_sort(default = :name)
    +  case self
    +  when /^n/i
    +    :name
    +  when /^t/i
    +    :time
    +  else
    +    default.is_a?(Symbol) ? default : default.normalize_tag_sort
    +  end
    +end
    +
    @@ -960,7 +1330,25 @@

    -

    + + + + + +
    +
    +
    +
    +25
    +26
    +27
    +
    +
    # File 'lib/doing/normalize.rb', line 25
    +
    +def normalize_tag_sort!(default = :name)
    +  replace normalize_tag_sort(default)
    +end
    +
    @@ -1000,7 +1388,25 @@

    -

    + + + + + +
    +
    +
    +
    +170
    +171
    +172
    +
    +
    # File 'lib/doing/normalize.rb', line 170
    +
    +def normalize_trigger
    +  gsub(/\((?!\?:)/, '(?:').downcase
    +end
    +
    @@ -1028,7 +1434,25 @@

    -

    + + + + + +
    +
    +
    +
    +175
    +176
    +177
    +
    +
    # File 'lib/doing/normalize.rb', line 175
    +
    +def normalize_trigger!
    +  replace normalize_trigger
    +end
    +
    @@ -1036,9 +1460,9 @@

    diff --git a/docs/doc/Doing/StringQuery.html b/docs/doc/Doing/StringQuery.html index 2d24d0ee..74c42e01 100644 --- a/docs/doc/Doing/StringQuery.html +++ b/docs/doc/Doing/StringQuery.html @@ -6,7 +6,7 @@ Module: Doing::StringQuery - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -365,7 +365,27 @@

    - + + + + + +
    +
    +
    +
    +24
    +25
    +26
    +27
    +
    +
    # File 'lib/doing/string/query.rb', line 24
    +
    +def ignore?
    +  line = self
    +  line =~ /^#/ || line =~ /^\s*$/
    +end
    +
    @@ -439,7 +459,25 @@

    -

    + + + + + +
    +
    +
    +
    +15
    +16
    +17
    +
    +
    # File 'lib/doing/string/query.rb', line 15
    +
    +def ignore_case(search, case_type)
    +  (case_type == :smart && search !~ /[A-Z]/) || case_type == :ignore
    +end
    +
    @@ -478,7 +516,25 @@

    -

    + + + + + +
    +
    +
    +
    +34
    +35
    +36
    +
    +
    # File 'lib/doing/string/query.rb', line 34
    +
    +def rx?
    +  self =~ %r{(^/.*?/$|^')}
    +end
    +
    @@ -517,7 +573,35 @@

    -

    + + + + + +
    +
    +
    +
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +
    +
    # File 'lib/doing/string/query.rb', line 130
    +
    +def to_bool
    +  case self
    +  when /^[yt1]/i
    +    true
    +  else
    +    false
    +  end
    +end
    +
    @@ -556,7 +640,31 @@

    -

    + + + + + +
    +
    +
    +
    +93
    +94
    +95
    +96
    +97
    +98
    +
    +
    # File 'lib/doing/string/query.rb', line 93
    +
    +def to_phrase_query
    +  parser = PhraseParser::QueryParser.new
    +  transformer = PhraseParser::QueryTransformer.new
    +  parse_tree = parser.parse(self)
    +  transformer.apply(parse_tree).to_elasticsearch
    +end
    +
    @@ -595,7 +703,31 @@

    -

    + + + + + +
    +
    +
    +
    +105
    +106
    +107
    +108
    +109
    +110
    +
    +
    # File 'lib/doing/string/query.rb', line 105
    +
    +def to_query
    +  parser = BooleanTermParser::QueryParser.new
    +  transformer = BooleanTermParser::QueryTransformer.new
    +  parse_tree = parser.parse(self)
    +  transformer.apply(parse_tree).to_elasticsearch
    +end
    +
    @@ -619,7 +751,7 @@

    -

    Examples:

    +

    Examples:

    "this word".to_rx(3)
    @@ -684,7 +816,67 @@ 

    -

    +
    + + + + +
    +
    +
    +
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +
    +
    # File 'lib/doing/string/query.rb', line 63
    +
    +def to_rx(distance: nil, case_type: nil)
    +  distance ||= Doing.config.fetch('search', 'distance', 3).to_i
    +  case_type ||= Doing.config.fetch('search', 'case', 'smart')&.normalize_case
    +  case_sensitive = case case_type
    +                   when :smart
    +                     self =~ /[A-Z]/ ? true : false
    +                   when :sensitive
    +                     true
    +                   else
    +                     false
    +                   end
    +
    +  pattern = case dup.strip
    +            when %r{^/.*?/$}
    +              sub(%r{/(.*?)/}, '\1')
    +            when /^'/
    +              sub(/^'(.*?)'?$/, '\1')
    +            else
    +              split(/ +/).map do |w|
    +                w.split('').join(".{0,#{distance}}").gsub(/\+/, '\+').wildcard_to_rx
    +              end.join('.*?')
    +            end
    +  Regexp.new(pattern, !case_sensitive)
    +end
    +

    @@ -723,7 +915,33 @@

    -

    + + + + + +
    +
    +
    +
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +
    +
    # File 'lib/doing/string/query.rb', line 117
    +
    +def truthy?
    +  if self =~ /^(0|f(alse)?|n(o)?)$/i
    +    false
    +  else
    +    true
    +  end
    +end
    +
    @@ -763,7 +981,25 @@

    -

    + + + + + +
    +
    +
    +
    +44
    +45
    +46
    +
    +
    # File 'lib/doing/string/query.rb', line 44
    +
    +def wildcard_to_rx
    +  gsub(/\?/, '\S').gsub(/\*/, '\S*?').gsub(/\]\]/, '--')
    +end
    +
    @@ -771,9 +1007,9 @@

    diff --git a/docs/doc/Doing/StringTags.html b/docs/doc/Doing/StringTags.html index 8e030d4b..f115ed0e 100644 --- a/docs/doc/Doing/StringTags.html +++ b/docs/doc/Doing/StringTags.html @@ -6,7 +6,7 @@ Module: Doing::StringTags - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -386,7 +386,25 @@

    - + + + + + +
    +
    +
    +
    +11
    +12
    +13
    +
    +
    # File 'lib/doing/string/tags.rb', line 11
    +
    +def add_at
    +  strip.sub(/^([+-]*)@?/, '\1@')
    +end
    +
    @@ -461,7 +479,31 @@

    -

    + + + + + +
    +
    +
    +
    +59
    +60
    +61
    +62
    +63
    +64
    +
    +
    # File 'lib/doing/string/tags.rb', line 59
    +
    +def add_tags(tags, remove: false)
    +  title = dup
    +  tags = tags.to_tags
    +  tags.each { |tag| title.tag!(tag, remove: remove) }
    +  title
    +end
    +
    @@ -489,7 +531,25 @@

    -

    + + + + + +
    +
    +
    +
    +67
    +68
    +69
    +
    +
    # File 'lib/doing/string/tags.rb', line 67
    +
    +def add_tags!(tags, remove: false)
    +  replace add_tags(tags, remove: remove)
    +end
    +
    @@ -528,7 +588,51 @@

    -

    + + + + + +
    +
    +
    +
    +159
    +160
    +161
    +162
    +163
    +164
    +165
    +166
    +167
    +168
    +169
    +170
    +171
    +172
    +173
    +174
    +
    +
    # File 'lib/doing/string/tags.rb', line 159
    +
    +def dedup_tags
    +  title = dup
    +  tags = title.scan(/(?<=\A| )(@(\S+?)(\([^)]+\))?)(?= |\Z)/).uniq
    +  tags.each do |tag|
    +    found = false
    +    title.gsub!(/( |^)#{Regexp.escape(tag[1])}(\([^)]+\))?(?= |$)/) do |m|
    +      if found
    +        ''
    +      else
    +        found = true
    +        m
    +      end
    +    end
    +  end
    +  title
    +end
    +
    @@ -556,7 +660,25 @@

    -

    + + + + + +
    +
    +
    +
    +177
    +178
    +179
    +
    +
    # File 'lib/doing/string/tags.rb', line 177
    +
    +def dedup_tags!
    +  replace dedup_tags
    +end
    +
    @@ -595,7 +717,25 @@

    -

    + + + + + +
    +
    +
    +
    +20
    +21
    +22
    +
    +
    # File 'lib/doing/string/tags.rb', line 20
    +
    +def remove_at
    +  strip.sub(/^([+-]*)@?/, '\1')
    +end
    +
    @@ -636,7 +776,25 @@

    -

    + + + + + +
    +
    +
    +
    +31
    +32
    +33
    +
    +
    # File 'lib/doing/string/tags.rb', line 31
    +
    +def split_tags
    +  gsub(/ *, */, ' ').scan(/(@?(?:\S+(?:\(.+\)))|@?(?:\S+))/).map(&:first).map(&:remove_at).sort.uniq
    +end
    +
    @@ -796,7 +954,139 @@

    -

    + + + + + +
    +
    +
    +
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +
    +
    # File 'lib/doing/string/tags.rb', line 93
    +
    +def tag(tag, value: nil, remove: false, rename_to: nil, regex: false, single: false, force: false)
    +  log_level = single ? :info : :debug
    +  title = dup
    +  title.chomp!
    +  tag = tag.sub(/^@?/, '')
    +  case_sensitive = tag !~ /[A-Z]/
    +
    +  rx_tag = if regex
    +             tag.gsub(/\./, '\S')
    +           else
    +             tag.gsub(/\?/, '.').gsub(/\*/, '\S*?')
    +           end
    +
    +  if remove || rename_to
    +    rx = Regexp.new("(?<=^| )@#{rx_tag}(?<parens>\\((?<value>[^)]*)\\))?(?= |$)", case_sensitive)
    +    m = title.match(rx)
    +
    +    if m.nil? && rename_to && force
    +      title.tag!(rename_to, value: value, single: single)
    +    elsif m
    +      title.gsub!(rx) do
    +        rename_to ? "@#{rename_to}#{value.nil? ? m['parens'] : "(#{value})"}" : ''
    +      end
    +
    +      title.dedup_tags!
    +      title.chomp!
    +
    +      if rename_to
    +        f = "@#{tag}".cyan
    +        t = "@#{rename_to}".cyan
    +        Doing.logger.write(log_level, 'Tag:', %(renamed #{f} to #{t} in "#{title}"))
    +      else
    +        f = "@#{tag}".cyan
    +        Doing.logger.write(log_level, 'Tag:', %(removed #{f} from "#{title}"))
    +      end
    +    else
    +      Doing.logger.debug('Skipped:', "not tagged #{"@#{tag}".cyan}")
    +    end
    +  elsif title =~ /@#{tag}(?=[ (]|$)/ && !value.good?
    +    Doing.logger.debug('Skipped:', "already tagged #{"@#{tag}".cyan}")
    +    return title
    +  else
    +    add = tag
    +    add += "(#{value})" unless value.nil?
    +
    +    title.chomp!
    +
    +    if value && title =~ /@#{tag}(?=[ (]|$)/
    +      title.sub!(/@#{tag}(\(.*?\))?/, "@#{add}")
    +    else
    +      title += " @#{add}"
    +    end
    +
    +    title.dedup_tags!
    +    title.chomp!
    +    Doing.logger.write(log_level, 'Tag:', %(added #{('@' + tag).cyan} to "#{title}"))
    +  end
    +
    +  title.gsub(/ +/, ' ')
    +end
    +
    @@ -825,7 +1115,25 @@

    -

    + + + + + +
    +
    +
    +
    +76
    +77
    +78
    +
    +
    # File 'lib/doing/string/tags.rb', line 76
    +
    +def tag!(tag, **options)
    +  replace tag(tag, **options)
    +end
    +
    @@ -866,7 +1174,35 @@

    -

    + + + + + +
    +
    +
    +
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +
    +
    # File 'lib/doing/string/tags.rb', line 42
    +
    +def to_tags
    +  arr = split_tags.map(&:add_at)
    +  if block_given?
    +    yield arr
    +  else
    +    arr
    +  end
    +end
    +
    @@ -874,9 +1210,9 @@

    diff --git a/docs/doc/Doing/StringTransform.html b/docs/doc/Doing/StringTransform.html index 8707c976..547d0b24 100644 --- a/docs/doc/Doing/StringTransform.html +++ b/docs/doc/Doing/StringTransform.html @@ -6,7 +6,7 @@ Module: Doing::StringTransform - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -339,7 +339,29 @@

    - + + + + + +
    +
    +
    +
    +102
    +103
    +104
    +105
    +106
    +
    +
    # File 'lib/doing/string/transform.rb', line 102
    +
    +def cap_first
    +  sub(/^\w/) do |m|
    +    m.upcase
    +  end
    +end
    +
    @@ -361,7 +383,25 @@

    -
    +

    + + + + +
    +
    +
    +
    +9
    +10
    +11
    +
    +
    # File 'lib/doing/string/transform.rb', line 9
    +
    +def compress
    +  gsub(/ +/, ' ').strip
    +end
    +
    @@ -373,7 +413,25 @@

    -

    +

    + + + + +
    +
    +
    +
    +13
    +14
    +15
    +
    +
    # File 'lib/doing/string/transform.rb', line 13
    +
    +def compress!
    +  replace compress
    +end
    +
    @@ -440,7 +498,93 @@

    -

    + + + + + +
    +
    +
    +
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +153
    +154
    +155
    +156
    +157
    +158
    +159
    +160
    +161
    +162
    +163
    +164
    +165
    +166
    +
    +
    # File 'lib/doing/string/transform.rb', line 130
    +
    +def set_type(kind = nil)
    +  if kind
    +    case kind.to_s
    +    when /^a/i
    +      gsub(/^\[ *| *\]$/, '').split(/ *, */)
    +    when /^i/i
    +      to_i
    +    when /^(fa|tr)/i
    +      to_bool
    +    when /^f/i
    +      to_f
    +    when /^sy/i
    +      sub(/^:/, '').to_sym
    +    when /^b/i
    +      self =~ /^(true|yes)$/ ? true : false
    +    else
    +      to_s
    +    end
    +  else
    +    case self
    +    when /(^\[.*?\]$| *, *)/
    +      gsub(/^\[ *| *\]$/, '').split(/ *, */)
    +    when /^[0-9]+$/
    +      to_i
    +    when /^[0-9]+\.[0-9]+$/
    +      to_f
    +    when /^:\w+/
    +      sub(/^:/, '').to_sym
    +    when /^(true|yes)$/i
    +      true
    +    when /^(false|no)$/i
    +      false
    +    else
    +      to_s
    +    end
    +  end
    +end
    +
    @@ -452,7 +596,63 @@

    -

    +

    + + + + +
    +
    +
    +
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +
    +
    # File 'lib/doing/string/transform.rb', line 17
    +
    +def simple_wrap(width)
    +  str = gsub(/@\S+\(.*?\)/) { |tag| tag.gsub(/\s/, '%%%%') }
    +  words = str.split(/ /).map { |word| word.gsub(/%%%%/, ' ') }
    +  out = []
    +  line = []
    +
    +  words.each do |word|
    +    if word.uncolor.length >= width
    +      chars = word.uncolor.split('')
    +      out << chars.slice!(0, width - 1).join('') while chars.count >= width
    +      line << chars.join('')
    +      next
    +    elsif line.join(' ').uncolor.length + word.uncolor.length + 1 > width
    +      out.push(line.join(' '))
    +      line.clear
    +    end
    +
    +    line << word.uncolor
    +  end
    +  out.push(line.join(' '))
    +  out.join("\n")
    +end
    +
    @@ -464,7 +664,29 @@

    -

    + + + + + +
    +
    +
    +
    +168
    +169
    +170
    +171
    +172
    +
    +
    # File 'lib/doing/string/transform.rb', line 168
    +
    +def titlecase
    +  tr('_', ' ').
    +  gsub(/\s+/, ' ').
    +  gsub(/\b\w/){ $`[-1,1] == "'" ? $& : $&.upcase }
    +end
    +
    @@ -506,7 +728,25 @@

    -

    + + + + + +
    +
    +
    +
    +114
    +115
    +116
    +
    +
    # File 'lib/doing/string/transform.rb', line 114
    +
    +def to_p(number)
    +  number == 1 ? self : "#{self}s"
    +end
    +
    @@ -581,7 +821,117 @@

    -

    + + + + + +
    +
    +
    +
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +
    +
    # File 'lib/doing/string/transform.rb', line 47
    +
    +def wrap(len, pad: 0, indent: '  ', offset: 0, prefix: '', color: '', after: '', reset: '', pad_first: false)
    +  last_color = color.empty? ? '' : after.last_color
    +  note_rx = /(?mi)(?<!\\)%(?<width>-?\d+)?(?:\^(?<mchar>.))?(?:(?<ichar>[ _t]|[^a-z0-9])(?<icount>\d+))?(?<prefix>.[ _t]?)?note/
    +  note = ''
    +  after = after.dup if after.frozen?
    +  after.sub!(note_rx) do
    +    note = Regexp.last_match(0)
    +    ''
    +  end
    +
    +  left_pad = ' ' * offset
    +  left_pad += indent
    +
    +  # return "#{left_pad}#{prefix}#{color}#{self}#{last_color} #{note}" unless len.positive?
    +
    +  # Don't break inside of tag values
    +  str = gsub(/@\S+\(.*?\)/) { |tag| tag.gsub(/\s/, '%%%%') }.gsub(/\n/, ' ')
    +
    +  words = str.split(/ /).map { |word| word.gsub(/%%%%/, ' ') }
    +  out = []
    +  line = []
    +
    +  words.each do |word|
    +    if word.uncolor.length >= len
    +      chars = word.uncolor.split('')
    +      out << chars.slice!(0, len - 1).join('') while chars.count >= len
    +      line << chars.join('')
    +      next
    +    elsif line.join(' ').uncolor.length + word.uncolor.length + 1 > len
    +      out.push(line.join(' '))
    +      line.clear
    +    end
    +
    +    line << word.uncolor
    +  end
    +  out.push(line.join(' '))
    +
    +  last_color = ''
    +  out[0] = format("%-#{pad}s%s%s", out[0], last_color, after)
    +
    +  out.map.with_index { |l, idx|
    +    if !pad_first && idx == 0
    +      "#{color}#{prefix}#{l}#{last_color}"
    +    else
    +      "#{left_pad}#{color}#{prefix}#{l}#{last_color}"
    +    end
    +  }.join("\n") + " #{note}".chomp
    +  # res.join("\n").strip + last_color + " #{note}".chomp
    +end
    +
    @@ -589,9 +939,9 @@

    diff --git a/docs/doc/Doing/StringTruncate.html b/docs/doc/Doing/StringTruncate.html index 2d98730b..b517e2d6 100644 --- a/docs/doc/Doing/StringTruncate.html +++ b/docs/doc/Doing/StringTruncate.html @@ -6,7 +6,7 @@ Module: Doing::StringTruncate - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -295,7 +295,47 @@

    - + + + + + +
    +
    +
    +
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +
    +
    # File 'lib/doing/string/truncate.rb', line 13
    +
    +def trunc(len, ellipsis: '...')
    +  return self if length <= len
    +
    +  total = 0
    +  res = []
    +
    +  split(/ /).each do |word|
    +    break if total + 1 + word.length > len
    +
    +    total += 1 + word.length
    +    res.push(word)
    +  end
    +  res.join(' ') + ellipsis
    +end
    +
    @@ -307,7 +347,25 @@

    -

    +

    + + + + +
    +
    +
    +
    +28
    +29
    +30
    +
    +
    # File 'lib/doing/string/truncate.rb', line 28
    +
    +def trunc!(len, ellipsis: '...')
    +  replace trunc(len, ellipsis: ellipsis)
    +end
    +
    @@ -348,7 +406,47 @@

    -

    + + + + + +
    +
    +
    +
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +
    +
    # File 'lib/doing/string/truncate.rb', line 37
    +
    +def truncend(len, ellipsis: '...')
    +  return self if length <= len
    +
    +  total = 0
    +  res = []
    +
    +  split(/ /).reverse.each do |word|
    +    break if total + 1 + word.length > len
    +
    +    total += 1 + word.length
    +    res.unshift(word)
    +  end
    +  ellipsis + res.join(' ')
    +end
    +
    @@ -360,7 +458,25 @@

    -

    + + + + + +
    +
    +
    +
    +52
    +53
    +54
    +
    +
    # File 'lib/doing/string/truncate.rb', line 52
    +
    +def truncend!(len, ellipsis: '...')
    +  replace truncend(len, ellipsis: ellipsis)
    +end
    +
    @@ -418,7 +534,35 @@

    -

    + + + + + +
    +
    +
    +
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +
    +
    # File 'lib/doing/string/truncate.rb', line 62
    +
    +def truncmiddle(len, ellipsis: '...')
    +  return self if length <= len
    +  len -= (ellipsis.length / 2).to_i
    +  half = (len / 2).to_i
    +  start = trunc(half, ellipsis: ellipsis)
    +  finish = truncend(half, ellipsis: '')
    +  start + finish
    +end
    +
    @@ -430,7 +574,25 @@

    -

    + + + + + +
    +
    +
    +
    +71
    +72
    +73
    +
    +
    # File 'lib/doing/string/truncate.rb', line 71
    +
    +def truncmiddle!(len, ellipsis: '...')
    +  replace truncmiddle(len, ellipsis: ellipsis)
    +end
    +
    @@ -438,9 +600,9 @@

    diff --git a/docs/doc/Doing/StringURL.html b/docs/doc/Doing/StringURL.html index 5cb528a5..4b44bf4f 100644 --- a/docs/doc/Doing/StringURL.html +++ b/docs/doc/Doing/StringURL.html @@ -6,7 +6,7 @@ Module: Doing::StringURL - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -255,7 +255,39 @@

    -
    + + + + + +
    +
    +
    +
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +
    +
    # File 'lib/doing/string/url.rb', line 71
    +
    +def clean_unlinked_urls
    +  gsub(/<(\w+:.*?)>/) do |match|
    +    m = Regexp.last_match
    +    if m[1] =~ /<a href/
    +      match
    +    else
    +      %(<a href="#{m[1]}" title="Link to #{m[1]}">[link]</a>)
    +    end
    +  end
    +end
    +
    @@ -319,7 +351,39 @@
    + + + + + +
    +
    +
    +
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +
    +
    # File 'lib/doing/string/url.rb', line 16
    +
    +def link_urls(**opt)
    +  fmt = opt.fetch(:format, :html)
    +  return self unless fmt
    +
    +  str = dup
    +
    +  str = str.remove_self_links if fmt == :markdown
    +
    +  str.replace_qualified_urls(format: fmt).clean_unlinked_urls
    +end
    +
    @@ -347,7 +411,27 @@
    + + + + + +
    +
    +
    +
    +28
    +29
    +30
    +31
    +
    +
    # File 'lib/doing/string/url.rb', line 28
    +
    +def link_urls!(**opt)
    +  fmt = opt.fetch(:format, :html)
    +  replace link_urls(format: fmt)
    +end
    +
    @@ -369,7 +453,39 @@
    + + + + +
    +
    +
    +
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +
    +
    # File 'lib/doing/string/url.rb', line 34
    +
    +def remove_self_links
    +  gsub(/<(.*?)>/) do |match|
    +    m = Regexp.last_match
    +    if m[1] =~ /^https?:/
    +      m[1]
    +    else
    +      match
    +    end
    +  end
    +end
    +
    @@ -391,7 +507,65 @@

    -
    +

    + + + + +
    +
    +
    +
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +
    +
    # File 'lib/doing/string/url.rb', line 46
    +
    +def replace_qualified_urls(**options)
    +  fmt = options.fetch(:format, :html)
    +  gsub(%r{(?mi)(?x:
    +  (?<!["'\[(\\])
    +  (?<protocol>(?:http|https)://)
    +  (?<domain>[\w\-]+(?:\.[\w\-]+)+)
    +  (?<path>[\w\-.,@?^=%&;:/~+#]*[\w\-@^=%&;/~+#])?
    +  )}) do |_match|
    +    m = Regexp.last_match
    +    url = "#{m['domain']}#{m['path']}"
    +    proto = m['protocol'].nil? ? 'http://' : m['protocol']
    +    case fmt
    +    when :terminal
    +      TTY::Link.link_to("#{proto}#{url}", "#{proto}#{url}")
    +    when :html
    +      %(<a href="#{proto}#{url}" title="Link to #{m['domain']}">[#{url}]</a>)
    +    when :markdown
    +      "[#{url}](#{proto}#{url})"
    +    else
    +      m[0]
    +    end
    +  end
    +end
    +
    @@ -399,9 +573,9 @@

    diff --git a/docs/doc/Doing/SymbolNormalize.html b/docs/doc/Doing/SymbolNormalize.html index 878b285d..2353ee07 100644 --- a/docs/doc/Doing/SymbolNormalize.html +++ b/docs/doc/Doing/SymbolNormalize.html @@ -6,7 +6,7 @@ Module: Doing::SymbolNormalize - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -263,7 +263,25 @@

    -

    + + + + + +
    +
    +
    +
    +205
    +206
    +207
    +
    +
    # File 'lib/doing/normalize.rb', line 205
    +
    +def normalize_age(default = :newest)
    +  to_s.normalize_age(default)
    +end
    +
    @@ -275,7 +293,25 @@

    -

    + + + + + +
    +
    +
    +
    +201
    +202
    +203
    +
    +
    # File 'lib/doing/normalize.rb', line 201
    +
    +def normalize_bool(default = :and)
    +  to_s.normalize_bool(default)
    +end
    +
    @@ -287,7 +323,25 @@

    -

    + + + + + +
    +
    +
    +
    +213
    +214
    +215
    +
    +
    # File 'lib/doing/normalize.rb', line 213
    +
    +def normalize_case(default = :smart)
    +  to_s.normalize_case(default)
    +end
    +
    @@ -299,7 +353,25 @@

    -

    + + + + + +
    +
    +
    +
    +217
    +218
    +219
    +
    +
    # File 'lib/doing/normalize.rb', line 217
    +
    +def normalize_matching(default = :pattern)
    +  to_s.normalize_matching(default)
    +end
    +
    @@ -311,7 +383,25 @@

    -

    + + + + + +
    +
    +
    +
    +209
    +210
    +211
    +
    +
    # File 'lib/doing/normalize.rb', line 209
    +
    +def normalize_order(default = :asc)
    +  to_s.normalize_order(default)
    +end
    +
    @@ -323,7 +413,25 @@

    -

    + + + + + +
    +
    +
    +
    +197
    +198
    +199
    +
    +
    # File 'lib/doing/normalize.rb', line 197
    +
    +def normalize_tag_sort(default = :name)
    +  to_s.normalize_tag_sort
    +end
    +
    @@ -331,9 +439,9 @@

    diff --git a/docs/doc/Doing/TaskPaperExport.html b/docs/doc/Doing/TaskPaperExport.html index 1e1ba0e5..310657f1 100644 --- a/docs/doc/Doing/TaskPaperExport.html +++ b/docs/doc/Doing/TaskPaperExport.html @@ -6,7 +6,7 @@ Class: Doing::TaskPaperExport - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -192,7 +192,47 @@

    -

    + + + + + +
    +
    +
    +
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +
    +
    # File 'lib/doing/plugins/export/taskpaper_export.rb', line 17
    +
    +def self.render(wwid, items, variables: {})
    +  return if items.nil?
    +
    +  options = variables[:options]
    +
    +  options[:highlight] = false
    +  options[:wrap_width] = 0
    +  options[:tags_color] = false
    +  options[:output] = 'template'
    +  options[:template] = '- %title @date(%date)%note'
    +
    +  Doing.logger.debug('TaskPaper Export:', "#{items.count} items output to TaskPaper format")
    +  @out = wwid.list_section(options)
    +end
    +
    @@ -204,7 +244,29 @@

    -

    + + + + + +
    +
    +
    +
    +11
    +12
    +13
    +14
    +15
    +
    +
    # File 'lib/doing/plugins/export/taskpaper_export.rb', line 11
    +
    +def self.settings
    +  {
    +    trigger: 'task(?:paper)?|tp'
    +  }
    +end
    +
    @@ -212,9 +274,9 @@

    diff --git a/docs/doc/Doing/TemplateExport.html b/docs/doc/Doing/TemplateExport.html index 5815360b..51339674 100644 --- a/docs/doc/Doing/TemplateExport.html +++ b/docs/doc/Doing/TemplateExport.html @@ -6,7 +6,7 @@ Class: Doing::TemplateExport - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -219,7 +219,271 @@

    -

    + + + + + +
    +
    +
    +
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +
    +
    # File 'lib/doing/plugins/export/template_export.rb', line 19
    +
    +def self.render(wwid, items, variables: {})
    +  Doing.logger.benchmark(:template_render, :start)
    +  return if items.nil?
    +
    +  opt = variables[:options]
    +
    +  out = ''
    +  items.each do |item|
    +    if opt[:highlight] && item.title =~ /@#{Doing.setting('marker_tag')}\b/i
    +      flag = Doing::Color.send(Doing.setting('marker_color'))
    +      reset = Doing::Color.reset + Doing::Color.default
    +    else
    +      flag = ''
    +      reset = ''
    +    end
    +
    +    placeholders = {}
    +
    +    if !item.note.empty? && Doing.setting('include_notes')
    +      note = item.note.map(&:strip).delete_if(&:empty?)
    +      note.map! { |line| "#{line.sub(/^\t*/, '')}  " }
    +
    +      if opt[:wrap_width]&.positive?
    +        width = opt[:wrap_width]
    +        note.map! do |line|
    +          line.simple_wrap(width)
    +          # line.chomp.gsub(/(.{1,#{width}})(\s+|\Z)/, "\\1\n")
    +        end
    +        note.delete_if(&:empty?)
    +      end
    +    else
    +      note = []
    +    end
    +
    +    placeholders['id'] = item.id
    +
    +    placeholders['tags'] = item.tags
    +
    +    placeholders['date'] = item.date.strftime(opt[:format])
    +
    +    interval = wwid.get_interval(item, record: true, formatted: false) if opt[:times]
    +    if interval
    +      interval = case opt[:interval_format].to_sym
    +                 when :human
    +                   interval.time_string(format: :hm)
    +                 when :text
    +                   interval.time_string(format: :clock)
    +                 else
    +                   interval.time_string(format: opt[:interval_format].to_sym)
    +                 end
    +    end
    +
    +    interval ||= ''
    +    placeholders['interval'] = interval
    +
    +    duration = item.duration if opt[:duration]
    +    if duration
    +      duration = case opt[:interval_format].to_sym
    +                 when :human
    +                   duration.time_string(format: :hm)
    +                 when :text
    +                   duration.time_string(format: :clock)
    +                 else
    +                   duration.time_string(format: opt[:interval_format].to_sym)
    +                 end
    +    end
    +    duration ||= ''
    +    placeholders['duration'] = duration
    +
    +    placeholders['shortdate'] = format('%13s', item.date.relative_date)
    +    placeholders['section'] = item.section || ''
    +    placeholders['title'] = item.title
    +    placeholders['note'] = note
    +    placeholders['idnote'] = note.empty? ? '' : "\n#{note.map { |l| "\t\t#{l.strip}  " }.join("\n")}"
    +    placeholders['odnote'] = note.empty? ? '' : "\n#{note.map { |l| "#{l.strip}  " }.join("\n")}"
    +
    +    chompnote = []
    +    unless note.empty?
    +      chompnote = note.map do |l|
    +        l.gsub(/\n+/, ' ').gsub(/(^\s*|\s*$)/, '').gsub(/\s+/, ' ')
    +      end
    +    end
    +    placeholders['chompnote'] = chompnote.join(' ')
    +
    +    template = opt[:template].dup
    +    note_rx = /(?i-m)(?x:^([\s\S]*?)
    +                (%(?:[io]d|(?:\^[\s\S])?
    +                (?:(?:[ _t]|[^a-z0-9])?\d+)?
    +                (?:[\s\S][ _t]?)?)?note)
    +                ([\s\S]*?)$)/
    +    template.sub!(note_rx, '\1\3\2')
    +    output = Doing::TemplateString.new(template,
    +                                       color: flag,
    +                                       placeholders: placeholders,
    +                                       reset: reset,
    +                                       tags_color: opt[:tags_color],
    +                                       wrap_width: opt[:wrap_width]).colored
    +
    +    output.gsub!(/(?<!\\)%(\S)?hr(_under)?/) do
    +      o = ''
    +      TTY::Screen.columns.to_i.times do
    +        char = Regexp.last_match(2).nil? ? '-' : '_'
    +        char = Regexp.last_match(1).nil? ? char : Regexp.last_match(1)
    +        o += char
    +      end
    +      o
    +    end
    +    output.gsub!(/(?<!\\)%n/, "\n")
    +    output.gsub!(/(?<!\\)%t/, "\t")
    +
    +    output.gsub!(/\\%/, '%')
    +
    +    output.highlight_search!(opt[:search]) if opt[:output] =~ /^temp/ && opt[:search] && !opt[:not] && opt[:hilite]
    +
    +    out += "#{output}\n"
    +  end
    +
    +  # Doing.logger.debug('Template Export:', "#{items.count} items output to template #{opt[:output]}")
    +  if opt[:totals]
    +    out += wwid.tag_times(format: Doing.setting('timer_format').to_sym,
    +                          sort_by: opt[:sort_tags],
    +                          sort_order: opt[:tag_order])
    +  end
    +  Doing.logger.benchmark(:template_render, :finish)
    +  out
    +end
    +
    @@ -231,7 +495,29 @@

    -

    + + + + + +
    +
    +
    +
    +13
    +14
    +15
    +16
    +17
    +
    +
    # File 'lib/doing/plugins/export/template_export.rb', line 13
    +
    +def self.settings
    +  {
    +    trigger: 'template'
    +  }
    +end
    +
    @@ -239,9 +525,9 @@

    diff --git a/docs/doc/Doing/TemplateString.html b/docs/doc/Doing/TemplateString.html index 35e966fa..41f610b6 100644 --- a/docs/doc/Doing/TemplateString.html +++ b/docs/doc/Doing/TemplateString.html @@ -6,7 +6,7 @@ Class: Doing::TemplateString - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -522,7 +522,35 @@

    -
    + + + + + +
    +
    +
    +
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +
    +
    # File 'lib/doing/template_string.rb', line 11
    +
    +def initialize(string, placeholders: {}, force_color: false, wrap_width: 0, color: '', tags_color: '', reset: '')
    +  Color.coloring = true if force_color
    +  @colors = nil
    +  @original = string
    +  super(Color.reset + string)
    +
    +  placeholders.each { |k, v| fill(k, v, wrap_width: wrap_width, color: color, tags_color: tags_color) }
    +end
    +
    @@ -551,7 +579,25 @@

    -
    + + + + + +
    +
    +
    +
    +8
    +9
    +10
    +
    +
    # File 'lib/doing/template_string.rb', line 8
    +
    +def original
    +  @original
    +end
    +
    @@ -601,7 +647,35 @@

    - + + + + + +
    +
    +
    +
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +
    +
    # File 'lib/doing/template_string.rb', line 88
    +
    +def apply_colors(color_array)
    +  str = dup
    +  color_array.reverse.each do |color|
    +    c = color[:color].empty? ? Color.send(color[:name]) : color[:color]
    +    str.insert(color[:index], c)
    +  end
    +  str
    +end
    +
    @@ -640,7 +714,27 @@

    -

    + + + + + +
    +
    +
    +
    +41
    +42
    +43
    +44
    +
    +
    # File 'lib/doing/template_string.rb', line 41
    +
    +def colored
    +  reparse
    +  parsed_colors[:string].apply_colors(parsed_colors[:colors])
    +end
    +
    @@ -679,7 +773,31 @@

    -

    + + + + + +
    +
    +
    +
    +25
    +26
    +27
    +28
    +29
    +30
    +
    +
    # File 'lib/doing/template_string.rb', line 25
    +
    +def colors?
    +  scan(/%([a-z]+)/).each do
    +    return true if Regexp.last_match(1).validate_color
    +  end
    +  false
    +end
    +
    @@ -691,7 +809,191 @@

    -

    +

    + + + + +
    +
    +
    +
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +153
    +154
    +155
    +156
    +157
    +158
    +159
    +160
    +161
    +162
    +163
    +164
    +165
    +166
    +167
    +168
    +169
    +170
    +171
    +172
    +173
    +174
    +175
    +176
    +177
    +178
    +179
    +180
    +181
    +182
    +
    +
    # File 'lib/doing/template_string.rb', line 97
    +
    +def fill(placeholder, value, wrap_width: 0, color: '', tags_color: '', reset: '')
    +  reparse
    +  rx = /(?mi)(?<!\\)%(?<width>-?\d+)?(?:\^(?<mchar>.))?(?:(?<ichar>[ _t]|[^a-z0-9])(?<icount>\d+))?(?<prefix>.[ _t]?)?#{placeholder.sub(/^%/, '')}(?<after>.*?)$/
    +  ph = raw.match(rx)
    +
    +  return unless ph
    +  placeholder_offset = ph.begin(0)
    +  last_colors = parsed_colors[:colors].select { |v| v[:index] <= placeholder_offset + 4 }
    +
    +  last_color = last_colors.map { |v| v[:color] }.pop(3).join('')
    +
    +  sub!(rx) do
    +    m = Regexp.last_match
    +
    +    after = m['after']
    +
    +    if !value.good?
    +      after
    +    else
    +      pad = m['width'].to_i
    +      mark = m['mchar'] || ''
    +      if placeholder == 'shortdate' && m['width'].nil?
    +        fmt_string = Doing.setting('shortdate_format.older', '%m/%d/%y %_I:%M%P', exact: true)
    +        pad = Date.today.strftime(fmt_string).length
    +      end
    +      indent = nil
    +      if m['ichar']
    +        char = m['ichar'] =~ /t/ ? "\t" : ' '
    +        indent = char * m['icount'].to_i
    +      end
    +      indent ||= placeholder =~ /^title/ ? '' : "\t"
    +      prefix = m['prefix']
    +
    +      if placeholder =~ /^tags/
    +        prefix ||= ''
    +        value = value.map { |t| "#{prefix}#{t.sub(/^#{prefix}?/, '')}" }.join(' ')
    +        prefix = ''
    +      end
    +
    +      if placeholder =~ /^title/
    +        color = last_color + color
    +
    +        if wrap_width.positive? || pad.positive?
    +          width = pad.positive? ? pad : wrap_width
    +
    +          out = value.gsub(/%/, '\%').strip.wrap(width,
    +                                                 pad: pad,
    +                                                 indent: indent,
    +                                                 offset: placeholder_offset,
    +                                                 prefix: prefix,
    +                                                 color: color,
    +                                                 after: after,
    +                                                 reset: reset,
    +                                                 pad_first: false)
    +          out.highlight_tags!(tags_color, last_color: color) if tags_color && !tags_color.empty?
    +          out
    +        else
    +          out = format("%s%s%#{pad}s%s", prefix, color, value.gsub(/%/, '\%').sub(/\s*$/, ''), after)
    +          out.highlight_tags!(tags_color, last_color: color) if tags_color && !tags_color.empty?
    +          out
    +        end
    +      elsif placeholder =~ /^note/
    +        if wrap_width.positive? || pad.positive?
    +          width = pad.positive? ? pad : wrap_width
    +          outstring = value.map do |l|
    +            if l.empty?
    +              '  '
    +            else
    +              line = l.gsub(/%/, '\%').strip.wrap(width, pad: pad, indent: indent, offset: 0, prefix: prefix, color: last_color, after: after, reset: reset, pad_first: true)
    +              line.highlight_tags!(tags_color, last_color: last_color) unless !tags_color || !tags_color.good?
    +              "#{line}  "
    +            end
    +          end.join("\n")
    +          "\n#{last_color}#{mark}#{outstring}  "
    +        else
    +          out = format("\n%s%s%s%#{pad}s%s", indent, prefix, last_color, value.join("\n#{indent}#{prefix}").gsub(/%/, '\%').sub(/\s*$/, ''), after)
    +          out.highlight_tags!(tags_color, last_color: last_color) if tags_color && !tags_color.empty?
    +          out
    +        end
    +      else
    +        format("%s%#{pad}s%s", prefix, value.gsub(/%/, '\%').sub(/\s*$/, ''), after)
    +      end
    +    end
    +  end
    +  @parsed_colors = parse_colors
    +end
    +
    @@ -731,7 +1033,51 @@

    -

    + + + + + +
    +
    +
    +
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +
    +
    # File 'lib/doing/template_string.rb', line 64
    +
    +def parse_colors
    +  working = dup
    +  color_array = []
    +
    +  scan(/(?<!\\)(%((?:[fb]g?)?#[a-fA-F0-9]{6}|[a-z]+))/).each do |color|
    +    valid_color = color[1].validate_color
    +    next unless valid_color
    +
    +    idx = working.match(/(?<!\\)%#{valid_color}/).begin(0)
    +    color = Color.attributes.include?(valid_color.to_sym) ? Color.send(valid_color) : Color.rgb(valid_color)
    +    color_array.push({ name: valid_color, color: color, index: idx })
    +    working.sub!(/(?<!\\)%#{valid_color}/, '')
    +  end
    +
    +  { string: working, colors: color_array }
    +end
    +
    @@ -743,7 +1089,25 @@

    -

    + + + + + +
    +
    +
    +
    +55
    +56
    +57
    +
    +
    # File 'lib/doing/template_string.rb', line 55
    +
    +def parsed_colors
    +  @parsed_colors ||= parse_colors
    +end
    +
    @@ -782,7 +1146,25 @@

    -

    + + + + + +
    +
    +
    +
    +51
    +52
    +53
    +
    +
    # File 'lib/doing/template_string.rb', line 51
    +
    +def raw
    +  parsed_colors[:string].uncolor
    +end
    +
    @@ -794,7 +1176,25 @@

    -

    + + + + + +
    +
    +
    +
    +32
    +33
    +34
    +
    +
    # File 'lib/doing/template_string.rb', line 32
    +
    +def reparse
    +  @parsed_colors = nil
    +end
    +
    @@ -802,9 +1202,9 @@

    diff --git a/docs/doc/Doing/TimingImport.html b/docs/doc/Doing/TimingImport.html index 638126ca..2efc9dcd 100644 --- a/docs/doc/Doing/TimingImport.html +++ b/docs/doc/Doing/TimingImport.html @@ -6,7 +6,7 @@ Class: Doing::TimingImport - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -255,7 +255,169 @@

    - + + + + + +
    +
    +
    +
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +
    +
    # File 'lib/doing/plugins/import/timing_import.rb', line 25
    +
    +def self.import(wwid, path, options: {})
    +  exit_now! 'Path to JSON report required' if path.nil?
    +  section = options[:section] || Doing.setting('current_section')
    +  options[:no_overlap] ||= false
    +  options[:autotag] ||= Doing.auto_tag
    +  wwid.content.add_section(section) unless wwid.content.section?(section)
    +
    +  add_tags = options[:tag] ? options[:tag].split(/[ ,]+/).map { |t| t.sub(/^@?/, '') } : []
    +  prefix = options[:prefix] || '[Timing.app]'
    +  exit_now! 'File not found' unless File.exist?(File.expand_path(path))
    +
    +  data = JSON.parse(IO.read(File.expand_path(path)))
    +  new_items = []
    +  data.each do |entry|
    +    # Only process task entries
    +    next if entry.key?('activityType') && entry['activityType'] != 'Task'
    +    # Only process entries with a start and end date
    +    next unless entry.key?('startDate') && entry.key?('endDate')
    +
    +    # Round down seconds and convert UTC to local time
    +    start_time = Time.parse(entry['startDate'].sub(/:\d\dZ$/, ':00Z')).getlocal
    +    end_time = Time.parse(entry['endDate'].sub(/:\d\dZ$/, ':00Z')).getlocal
    +    next unless start_time && end_time
    +
    +    tags = entry['project'].split(//).map { |proj| proj.gsub(/[^a-z0-9]+/i, '').downcase }
    +    tags.concat(add_tags)
    +    title = "#{prefix} "
    +    title += entry.key?('activityTitle') && entry['activityTitle'] != '(Untitled Task)' ? entry['activityTitle'] : 'Working on'
    +    tags.each do |tag|
    +      if title =~ /\b#{tag}\b/i
    +        title.sub!(/\b#{tag}\b/i, "@#{tag}")
    +      else
    +        title += " @#{tag}"
    +      end
    +    end
    +    title = wwid.autotag(title) if options[:autotag]
    +    title += " @done(#{end_time.strftime('%Y-%m-%d %H:%M')})"
    +    title.gsub!(/ +/, ' ')
    +    title.strip!
    +    new_item = Item.new(start_time, title, section)
    +    new_item.note.add(entry['notes']) if entry.key?('notes')
    +
    +    is_match = true
    +
    +    if options[:search]
    +      is_match = new_item.search(options[:search], case_type: options[:case], negate: options[:not])
    +    end
    +
    +    if is_match && options[:date_filter]
    +      is_match = start_time > options[:date_filter][0] && start_time < options[:date_filter][1]
    +      is_match = options[:not] ? !is_match : is_match
    +    end
    +
    +    new_items.push(new_item) if is_match
    +  end
    +  total = new_items.count
    +  skipped = data.count - total
    +  Doing.logger.debug('Skipped:' , %(#{skipped} items, invalid type or no time interval)) if skipped.positive?
    +
    +  new_items = wwid.filter_items(new_items, opt: options)
    +  filtered = skipped - new_items.count
    +  Doing.logger.debug('Skipped:' , %(#{filtered} items that didn't match filter criteria)) if filtered.positive?
    +
    +  new_items = wwid.dedup(new_items, no_overlap: options[:no_overlap])
    +  dups = filtered - new_items.count
    +  Doing.logger.debug('Skipped:' , %(#{dups} items with overlapping times)) if dups.positive?
    +
    +  new_items.map { |item| Hooks.trigger :pre_entry_add, self, item }
    +
    +  wwid.content.concat(new_items)
    +
    +  new_items.map { |item| Hooks.trigger :post_entry_added, self, item }
    +
    +  Doing.logger.info('Imported:', %(#{new_items.count} items to #{section}))
    +end
    +
    @@ -267,7 +429,29 @@

    -

    +

    + + + + +
    +
    +
    +
    +11
    +12
    +13
    +14
    +15
    +
    +
    # File 'lib/doing/plugins/import/timing_import.rb', line 11
    +
    +def self.settings
    +  {
    +    trigger: 'tim(?:ing)?'
    +  }
    +end
    +
    @@ -275,9 +459,9 @@

    diff --git a/docs/doc/Doing/Types.html b/docs/doc/Doing/Types.html index 2bbdabe1..a06da3e3 100644 --- a/docs/doc/Doing/Types.html +++ b/docs/doc/Doing/Types.html @@ -6,7 +6,7 @@ Module: Doing::Types - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -241,9 +241,9 @@

    diff --git a/docs/doc/Doing/Util.html b/docs/doc/Doing/Util.html index 3c3e4075..22def539 100644 --- a/docs/doc/Doing/Util.html +++ b/docs/doc/Doing/Util.html @@ -6,7 +6,7 @@ Module: Doing::Util - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -505,7 +505,45 @@

    -

    + + + + + +
    +
    +
    +
    +149
    +150
    +151
    +152
    +153
    +154
    +155
    +156
    +157
    +158
    +159
    +160
    +161
    +
    +
    # File 'lib/doing/util.rb', line 149
    +
    +def args_for_editor(editor)
    +  return editor if editor =~ /-\S/
    +
    +  args = case editor
    +         when /^(subl|code|mate)$/
    +           ['-w']
    +         when /^(vim|mvim)$/
    +           ['-f']
    +         else
    +           []
    +         end
    +  "#{editor} #{args.join(' ')}"
    +end
    +
    @@ -585,7 +623,25 @@

    -

    + + + + + +
    +
    +
    +
    +58
    +59
    +60
    +
    +
    # File 'lib/doing/util.rb', line 58
    +
    +def deep_merge_hashes(master_hash, other_hash)
    +  deep_merge_hashes!(master_hash.clone, other_hash)
    +end
    +
    @@ -666,7 +722,33 @@

    -

    + + + + + +
    +
    +
    +
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +
    +
    # File 'lib/doing/util.rb', line 77
    +
    +def deep_merge_hashes!(target, overwrite)
    +  merge_values(target, overwrite)
    +  merge_default_proc(target, overwrite)
    +  duplicate_frozen_values(target)
    +
    +  target
    +end
    +
    @@ -678,7 +760,25 @@

    -

    + + + + + +
    +
    +
    +
    +141
    +142
    +143
    +
    +
    # File 'lib/doing/util.rb', line 141
    +
    +def default_editor
    +  @default_editor ||= find_default_editor
    +end
    +
    @@ -712,7 +812,35 @@

    -

    + + + + + +
    +
    +
    +
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +
    +
    # File 'lib/doing/util.rb', line 85
    +
    +def duplicable?(obj)
    +  case obj
    +  when nil, false, true, Symbol, Numeric
    +    false
    +  else
    +    true
    +  end
    +end
    +
    @@ -724,7 +852,29 @@

    -

    + + + + + +
    +
    +
    +
    +44
    +45
    +46
    +47
    +48
    +
    +
    # File 'lib/doing/util.rb', line 44
    +
    +def duplicate_frozen_values(target)
    +  target.each do |key, val|
    +    target[key] = val.dup if val.frozen? && duplicable?(val)
    +  end
    +end
    +
    @@ -736,7 +886,25 @@

    -

    + + + + + +
    +
    +
    +
    +145
    +146
    +147
    +
    +
    # File 'lib/doing/util.rb', line 145
    +
    +def editor_with_args
    +  args_for_editor(default_editor)
    +end
    +
    @@ -777,7 +945,29 @@

    -

    + + + + + +
    +
    +
    +
    +21
    +22
    +23
    +24
    +25
    +
    +
    # File 'lib/doing/util.rb', line 21
    +
    +def exec_available(cli)
    +  return false unless cli.good?
    +
    +  !TTY::Which.which(cli).nil?
    +end
    +
    @@ -789,7 +979,113 @@

    -

    + + + + + +
    +
    +
    +
    +163
    +164
    +165
    +166
    +167
    +168
    +169
    +170
    +171
    +172
    +173
    +174
    +175
    +176
    +177
    +178
    +179
    +180
    +181
    +182
    +183
    +184
    +185
    +186
    +187
    +188
    +189
    +190
    +191
    +192
    +193
    +194
    +195
    +196
    +197
    +198
    +199
    +200
    +201
    +202
    +203
    +204
    +205
    +206
    +207
    +208
    +209
    +
    +
    # File 'lib/doing/util.rb', line 163
    +
    +def find_default_editor(editor_for = 'default')
    +  # return nil unless $stdout.isatty || ENV['DOING_EDITOR_TEST']
    +
    +  return ENV['EDITOR'] if ENV['DOING_EDITOR_TEST']
    +
    +  editor_config = Doing.setting('editors')
    +
    +  if editor_config.is_a?(String)
    +    msg = "Please update your configuration, 'editors' should be a mapping."
    +    msg << ' Delete the key and run `doing config --update`.'
    +    Doing.logger.warn('Deprecated:', msg)
    +    return editor_config
    +  end
    +
    +  if editor_config[editor_for]
    +    editor = editor_config[editor_for]
    +    Doing.logger.debug('Editor:', "Using #{editor} from config 'editors.#{editor_for}' for #{editor_for}")
    +    return editor if editor.good?
    +  end
    +
    +  if editor_for != 'editor' && editor_config['default']
    +    editor = editor_config['default']
    +    Doing.logger.debug('Editor:', "Using #{editor} from config: 'editors.default' for #{editor_for}")
    +    return editor if editor.good?
    +  end
    +
    +  editor ||= ENV['DOING_EDITOR'] || ENV['GIT_EDITOR'] || ENV['EDITOR']
    +
    +  if editor.good?
    +    Doing.logger.debug('Editor:', "Found editor in environment variables: #{editor} for #{editor_for}")
    +    return editor
    +  end
    +
    +  Doing.logger.debug('Editor:', 'No EDITOR environment variable, testing available editors')
    +  editors = %w[vim vi code subl mate mvim nano emacs]
    +  editors.each do |ed|
    +    try = TTY::Which.which(ed)
    +    if try
    +      Doing.logger.debug('Editor:', "Using editor #{try} for #{editor_for}")
    +      return try
    +    end
    +
    +    Doing.logger.debug('Editor:', "#{ed} not available")
    +  end
    +
    +  nil
    +end
    +
    @@ -811,7 +1107,7 @@

    -

    Examples:

    +

    Examples:

    Doing::Util.first_available_exec('bat', 'less -Xr', 'more -r', 'cat')
    @@ -819,7 +1115,27 @@

    -
    +

    + + + + +
    +
    +
    +
    +33
    +34
    +35
    +36
    +
    +
    # File 'lib/doing/util.rb', line 33
    +
    +def first_available_exec(*commands)
    +  commands.compact.map(&:strip).reject(&:empty?).uniq
    +          .find { |cmd| exec_available(cmd.split.first) }
    +end
    +
    @@ -853,7 +1169,25 @@

    -

    + + + + + +
    +
    +
    +
    +94
    +95
    +96
    +
    +
    # File 'lib/doing/util.rb', line 94
    +
    +def mergable?(value)
    +  value.is_a?(Hash)
    +end
    +
    @@ -865,7 +1199,29 @@

    -

    + + + + + +
    +
    +
    +
    +38
    +39
    +40
    +41
    +42
    +
    +
    # File 'lib/doing/util.rb', line 38
    +
    +def merge_default_proc(target, overwrite)
    +  return unless target.is_a?(Hash) && overwrite.is_a?(Hash) && target.default_proc.nil?
    +
    +  target.default_proc = overwrite.default_proc
    +end
    +
    @@ -877,7 +1233,41 @@

    -

    + + + + + +
    +
    +
    +
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +
    +
    # File 'lib/doing/util.rb', line 98
    +
    +def merge_values(target, overwrite)
    +  target.merge!(overwrite) do |_key, old_val, new_val|
    +    if new_val.nil?
    +      old_val
    +    elsif mergable?(old_val) && mergable?(new_val)
    +      deep_merge_hashes(old_val, new_val)
    +    else
    +      new_val
    +    end
    +  end
    +end
    +
    @@ -889,7 +1279,25 @@

    -

    + + + + + +
    +
    +
    +
    +137
    +138
    +139
    +
    +
    # File 'lib/doing/util.rb', line 137
    +
    +def safe_load_file(filename)
    +  SafeYAML.load_file(filename) || {}
    +end
    +
    @@ -901,7 +1309,33 @@

    -

    + + + + + +
    +
    +
    +
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +
    +
    # File 'lib/doing/util.rb', line 8
    +
    +def user_home
    +  if Dir.respond_to?('home')
    +    Dir.home
    +  else
    +    File.expand_path('~')
    +  end
    +end
    +
    @@ -974,7 +1408,57 @@

    -

    + + + + + +
    +
    +
    +
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +133
    +134
    +135
    +
    +
    # File 'lib/doing/util.rb', line 117
    +
    +def write_to_file(file, content, backup: true)
    +  unless file
    +    puts content
    +    return
    +  end
    +  Doing.logger.benchmark(:write_file, :start)
    +  file = File.expand_path(file)
    +
    +  Backup.write_backup(file) if backup
    +
    +  File.open(file, 'w+') do |f|
    +    f.puts content
    +    Doing.logger.debug('Write:', "File written: #{file}")
    +  end
    +  Doing.logger.benchmark(:_post_write_hook, :start)
    +  Hooks.trigger :post_write, file
    +  Doing.logger.benchmark(:_post_write_hook, :finish)
    +  Doing.logger.benchmark(:write_file, :finish)
    +end
    +
    @@ -982,9 +1466,9 @@

    diff --git a/docs/doc/Doing/Util/Backup.html b/docs/doc/Doing/Util/Backup.html index 3627c2cf..2d66d1b4 100644 --- a/docs/doc/Doing/Util/Backup.html +++ b/docs/doc/Doing/Util/Backup.html @@ -6,7 +6,7 @@ Module: Doing::Util::Backup - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -361,7 +361,31 @@

    - + + + + + +
    +
    +
    +
    +33
    +34
    +35
    +36
    +37
    +38
    +
    +
    # File 'lib/doing/util_backup.rb', line 33
    +
    +def last_backup(filename = nil, count: 1)
    +  filename ||= Doing.setting('doing_file')
    +
    +  backup = get_backups(filename).slice(count - 1)
    +  backup.nil? ? nil : File.join(backup_dir, backup)
    +end
    +
    @@ -404,7 +428,39 @@

    -

    + + + + + +
    +
    +
    +
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +
    +
    # File 'lib/doing/util_backup.rb', line 16
    +
    +def prune_backups(filename, limit = 10)
    +  backups = get_backups(filename)
    +  return unless backups.count > limit
    +
    +  backups[limit..-1].each do |file|
    +    FileUtils.rm(File.join(backup_dir, file))
    +  end
    +
    +  clear_redo(filename)
    +end
    +
    @@ -460,7 +516,65 @@

    -

    + + + + + +
    +
    +
    +
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +
    +
    # File 'lib/doing/util_backup.rb', line 67
    +
    +def redo_backup(filename = nil, count: 1)
    +  filename ||= Doing.setting('doing_file')
    +
    +  undones = Dir.glob("undone*#{File.basename(filename)}", base: backup_dir).sort.reverse
    +  total = undones.count
    +  count = total if count > total
    +
    +  skipped = undones.slice!(0, count)
    +  undone = skipped.pop
    +
    +  raise HistoryLimitError, 'End of redo history' if undone.nil?
    +
    +  redo_file = File.join(backup_dir, undone)
    +
    +  move_backup(redo_file, filename)
    +
    +  skipped.each do |f|
    +    FileUtils.mv(File.join(backup_dir, f), File.join(backup_dir, f.sub(/^undone/, '')))
    +  end
    +
    +  Doing.logger.warn('File update:', "restored undo step #{count}/#{total}")
    +  Doing.logger.debug('Backup:', "#{total - skipped.count - 1} redos remaining")
    +end
    +
    @@ -518,7 +632,47 @@

    -

    + + + + + +
    +
    +
    +
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +
    +
    # File 'lib/doing/util_backup.rb', line 47
    +
    +def restore_last_backup(filename = nil, count: 1)
    +  Doing.logger.benchmark(:restore_backup, :start)
    +  filename ||= Doing.setting('doing_file')
    +
    +  backup_file = last_backup(filename, count: count)
    +  raise HistoryLimitError, 'End of undo history' if backup_file.nil?
    +
    +  save_undone(filename)
    +  move_backup(backup_file, filename)
    +
    +  prune_backups_after(File.basename(backup_file))
    +  Doing.logger.warn('File update:', "restored from #{backup_file}")
    +  Doing.logger.benchmark(:restore_backup, :finish)
    +end
    +
    @@ -575,7 +729,53 @@

    -

    + + + + + +
    +
    +
    +
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +
    +
    # File 'lib/doing/util_backup.rb', line 135
    +
    +def select_backup(filename = nil)
    +  filename ||= Doing.setting('doing_file')
    +
    +  options = get_backups(filename).each_with_object([]) do |file, arr|
    +    d, _base = date_of_backup(file)
    +    next if d.nil?
    +    arr.push("#{d.time_ago}\t#{File.join(backup_dir, file)}")
    +  end
    +
    +  raise MissingBackupFile, 'No backup files to load' if options.empty?
    +
    +  backup_file = show_menu(options, filename)
    +  Util.write_to_file(File.join(backup_dir, "undone___#{File.basename(filename)}"), IO.read(filename), backup: false)
    +  move_backup(backup_file, filename)
    +  prune_backups_after(File.basename(backup_file))
    +  Doing.logger.warn('File update:', "restored from #{backup_file}")
    +end
    +
    @@ -632,7 +832,81 @@

    -

    + + + + + +
    +
    +
    +
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +
    +
    # File 'lib/doing/util_backup.rb', line 97
    +
    +def select_redo(filename = nil)
    +  filename ||= Doing.setting('doing_file')
    +
    +  undones = Dir.glob("undone*#{File.basename(filename)}", base: backup_dir).sort
    +  raise HistoryLimitError, 'End of redo history' if undones.empty?
    +
    +  total = undones.count
    +  options = undones.each_with_object([]) do |file, arr|
    +    d, _base = date_of_backup(file)
    +    next if d.nil?
    +
    +    arr.push("#{d.time_ago}\t#{File.join(backup_dir, file)}")
    +  end
    +  raise MissingBackupFile, 'No backup files to load' if options.empty?
    +
    +  backup_file = show_menu(options, filename)
    +  idx = undones.index(File.basename(backup_file))
    +  skipped = undones.slice!(idx, undones.count - idx)
    +  undone = skipped.shift
    +
    +  redo_file = File.join(backup_dir, undone)
    +
    +  move_backup(redo_file, filename)
    +
    +  skipped.each do |f|
    +    FileUtils.mv(File.join(backup_dir, f), File.join(backup_dir, f.sub(/^undone/, '')))
    +  end
    +
    +  Doing.logger.warn('File update:', "restored undo step #{idx}/#{total}")
    +  Doing.logger.debug('Backup:', "#{total - skipped.count - 1} redos remaining")
    +end
    +
    @@ -676,7 +950,61 @@

    -

    + + + + + +
    +
    +
    +
    +159
    +160
    +161
    +162
    +163
    +164
    +165
    +166
    +167
    +168
    +169
    +170
    +171
    +172
    +173
    +174
    +175
    +176
    +177
    +178
    +179
    +
    +
    # File 'lib/doing/util_backup.rb', line 159
    +
    +def write_backup(filename = nil)
    +  Doing.logger.benchmark(:_write_backup, :start)
    +  filename ||= Doing.setting('doing_file')
    +
    +  unless File.exist?(filename)
    +    Doing.logger.debug('Backup:', "original file doesn't exist (#{filename})")
    +    return
    +  end
    +
    +  backup_file = File.join(backup_dir, "#{timestamp_filename}___#{File.basename(filename)}")
    +  # compressed = Zlib::Deflate.deflate(content)
    +  # Zlib::GzipWriter.open(backup_file + '.gz') do |gz|
    +  #   gz.write(IO.read(filename))
    +  # end
    +
    +  FileUtils.cp(filename, backup_file)
    +
    +  prune_backups(filename, Doing.setting('history_size').to_i)
    +  clear_undone(filename)
    +  Doing.logger.benchmark(:_write_backup, :finish)
    +end
    +
    @@ -684,9 +1012,9 @@

    diff --git a/docs/doc/Doing/Version.html b/docs/doc/Doing/Version.html index aab21997..10138ec3 100644 --- a/docs/doc/Doing/Version.html +++ b/docs/doc/Doing/Version.html @@ -6,7 +6,7 @@ Class: Doing::Version - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -351,7 +351,25 @@

    -
    + + + + + +
    +
    +
    +
    +8
    +9
    +10
    +
    +
    # File 'lib/doing/changelog/version.rb', line 8
    +
    +def initialize(string)
    +  @maj, @min, @patch = version_to_a(string)
    +end
    +
    @@ -380,7 +398,25 @@

    -
    + + + + + +
    +
    +
    +
    +6
    +7
    +8
    +
    +
    # File 'lib/doing/changelog/version.rb', line 6
    +
    +def maj
    +  @maj
    +end
    +
    @@ -404,7 +440,25 @@

    -
    + + + + + +
    +
    +
    +
    +6
    +7
    +8
    +
    +
    # File 'lib/doing/changelog/version.rb', line 6
    +
    +def min
    +  @min
    +end
    +
    @@ -428,7 +482,25 @@

    -
    + + + + + +
    +
    +
    +
    +6
    +7
    +8
    +
    +
    # File 'lib/doing/changelog/version.rb', line 6
    +
    +def patch
    +  @patch
    +end
    +
    @@ -447,7 +519,139 @@

    -

    + + + + + +
    +
    +
    +
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +
    +
    # File 'lib/doing/changelog/version.rb', line 40
    +
    +def compare(other, comp, inclusive: false)
    +  case comp
    +  when :older
    +    if @maj <= other.maj
    +      if @maj < other.maj
    +        true
    +      elsif @maj == other.maj && (other.min.nil? || @min < other.min)
    +        true
    +      elsif @maj == other.maj && @min == other.min
    +        if other.patch.nil?
    +          false
    +        else
    +          inclusive ? @patch <= other.patch : @patch < other.patch
    +        end
    +      else
    +        false
    +      end
    +    else
    +      false
    +    end
    +  when :newer
    +    if @maj >= other.maj
    +      if @maj > other.maj
    +        true
    +      elsif @maj == other.maj && (other.min.nil? || @min > other.min)
    +        true
    +      elsif @maj == other.maj && @min == other.min
    +        if other.patch.nil?
    +          false
    +        else
    +          inclusive ? @patch >= other.patch : @patch > other.patch
    +        end
    +      else
    +        false
    +      end
    +    else
    +      false
    +    end
    +  when :equal
    +    if @maj == other.maj
    +      if other.min.nil?
    +        true
    +      elsif wild?(other.min)
    +        @min.to_s =~ /^#{other.min}/ ? true : false
    +      else
    +        if @min == other.min
    +          if other.patch.nil?
    +            true
    +          elsif wild?(other.patch)
    +            @patch.to_s =~ /^#{other.patch}/ ? true : false
    +          else
    +            @patch == other.patch
    +          end
    +        else
    +          false
    +        end
    +      end
    +    end
    +  end
    +end
    +
    @@ -459,7 +663,25 @@

    -

    + + + + + +
    +
    +
    +
    +101
    +102
    +103
    +
    +
    # File 'lib/doing/changelog/version.rb', line 101
    +
    +def to_s
    +  "#{@maj}.#{@min || 0}.#{@patch || 0}"
    +end
    +
    @@ -471,7 +693,63 @@

    -

    + + + + + +
    +
    +
    +
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +
    +
    # File 'lib/doing/changelog/version.rb', line 12
    +
    +def version_to_a(string)
    +  raise 'Version not a string' unless string.is_a?(String)
    +
    +  v = string.match(/(?<maj>\d+)(?:\.(?<min>[\d*?]+))?(?:\.(?<patch>[\d*?]+))?/)
    +
    +  raise 'Error parsing semantic version string' if v.nil?
    +
    +  maj = v['maj'].to_i
    +  min = case v['min']
    +        when /[*?]/
    +          v['min'].sub(/(\d+)?[^\d]/, '\1\d+')
    +        when /^[0-9]+$/
    +          v['min'].to_i
    +        end
    +  pat = case v['patch']
    +        when /[*?]/
    +          v['patch'].sub(/(\d+)?[^\d]/, '\1\d+')
    +        when /^[0-9]+$/
    +          v['patch'].to_i
    +        end
    +  [maj, min, pat]
    +end
    +
    @@ -505,7 +783,25 @@

    -

    + + + + + +
    +
    +
    +
    +35
    +36
    +37
    +
    +
    # File 'lib/doing/changelog/version.rb', line 35
    +
    +def wild?(val)
    +  val.is_a?(String)
    +end
    +
    @@ -513,9 +809,9 @@

    diff --git a/docs/doc/Doing/WWID.html b/docs/doc/Doing/WWID.html index 4230cc38..aeb49aa1 100644 --- a/docs/doc/Doing/WWID.html +++ b/docs/doc/Doing/WWID.html @@ -6,7 +6,7 @@ Class: Doing::WWID - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -1634,7 +1634,31 @@

    -
    + + + + + +
    +
    +
    +
    +55
    +56
    +57
    +58
    +59
    +60
    +
    +
    # File 'lib/doing/wwid/wwid.rb', line 55
    +
    +def initialize
    +  @timers = {}
    +  @recorded_items = []
    +  @content = Items.new
    +  Doing.auto_tag = true
    +end
    +
    @@ -1663,7 +1687,25 @@

    -
    + + + + + +
    +
    +
    +
    +27
    +28
    +29
    +
    +
    # File 'lib/doing/wwid/wwid.rb', line 27
    +
    +def additional_configs
    +  @additional_configs
    +end
    +
    @@ -1687,7 +1729,25 @@

    -
    + + + + + +
    +
    +
    +
    +42
    +43
    +44
    +
    +
    # File 'lib/doing/wwid/wwid.rb', line 42
    +
    +def config
    +  @config
    +end
    +
    @@ -1711,7 +1771,25 @@

    -
    + + + + + +
    +
    +
    +
    +45
    +46
    +47
    +
    +
    # File 'lib/doing/wwid/wwid.rb', line 45
    +
    +def config_file
    +  @config_file
    +end
    +
    @@ -1735,7 +1813,25 @@

    -
    + + + + + +
    +
    +
    +
    +36
    +37
    +38
    +
    +
    # File 'lib/doing/wwid/wwid.rb', line 36
    +
    +def content
    +  @content
    +end
    +
    @@ -1759,7 +1855,25 @@

    -
    + + + + + +
    +
    +
    +
    +30
    +31
    +32
    +
    +
    # File 'lib/doing/wwid/wwid.rb', line 30
    +
    +def current_section
    +  @current_section
    +end
    +
    @@ -1783,7 +1897,25 @@

    -
    + + + + + +
    +
    +
    +
    +48
    +49
    +50
    +
    +
    # File 'lib/doing/wwid/wwid.rb', line 48
    +
    +def default_option
    +  @default_option
    +end
    +
    @@ -1807,7 +1939,25 @@

    -
    + + + + + +
    +
    +
    +
    +33
    +34
    +35
    +
    +
    # File 'lib/doing/wwid/wwid.rb', line 33
    +
    +def doing_file
    +  @doing_file
    +end
    +
    @@ -1831,7 +1981,25 @@

    -
    + + + + + +
    +
    +
    +
    +39
    +40
    +41
    +
    +
    # File 'lib/doing/wwid/wwid.rb', line 39
    +
    +def initial_content
    +  @initial_content
    +end
    +
    @@ -2007,7 +2175,487 @@

    - + + + + + +
    +
    +
    +
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +153
    +154
    +155
    +156
    +157
    +158
    +159
    +160
    +161
    +162
    +163
    +164
    +165
    +166
    +167
    +168
    +169
    +170
    +171
    +172
    +173
    +174
    +175
    +176
    +177
    +178
    +179
    +180
    +181
    +182
    +183
    +184
    +185
    +186
    +187
    +188
    +189
    +190
    +191
    +192
    +193
    +194
    +195
    +196
    +197
    +198
    +199
    +200
    +201
    +202
    +203
    +204
    +205
    +206
    +207
    +208
    +209
    +210
    +211
    +212
    +213
    +214
    +215
    +216
    +217
    +218
    +219
    +220
    +221
    +222
    +223
    +224
    +225
    +226
    +227
    +228
    +229
    +230
    +231
    +232
    +233
    +234
    +235
    +236
    +237
    +238
    +239
    +240
    +241
    +242
    +243
    +244
    +245
    +246
    +247
    +248
    +249
    +250
    +251
    +252
    +253
    +254
    +255
    +256
    +257
    +258
    +259
    +260
    +261
    +262
    +263
    +264
    +265
    +266
    +267
    +268
    +269
    +270
    +271
    +272
    +273
    +274
    +275
    +276
    +277
    +278
    +279
    +280
    +281
    +282
    +283
    +284
    +285
    +286
    +287
    +288
    +289
    +290
    +291
    +292
    +293
    +294
    +295
    +296
    +297
    +298
    +299
    +
    +
    # File 'lib/doing/wwid/interactive.rb', line 66
    +
    +def act_on(items, opt)
    +  opt ||= {}
    +  actions = %i[editor delete tag flag finish cancel archive output save_to again resume]
    +  has_action = false
    +  single = items.count == 1
    +
    +  actions.each do |a|
    +    if opt[a]
    +      has_action = true
    +      break
    +    end
    +  end
    +
    +  unless has_action
    +    actions = [
    +      'add tag',
    +      'remove tag',
    +      'autotag',
    +      'cancel',
    +      'delete',
    +      'finish',
    +      'flag',
    +      'archive',
    +      'move',
    +      'edit',
    +      'output formatted'
    +    ]
    +
    +    actions.concat(['resume/repeat', 'begin/reset']) if items.count == 1
    +
    +    choice = Prompt.choose_from(actions.map(&:titlecase),
    +                                prompt: 'What do you want to do with the selected items? > ',
    +                                multiple: true,
    +                                sorted: false,
    +                                fzf_args: ["--height=#{actions.count + 3}", '--tac', '--no-sort', '--info=hidden'])
    +    return unless choice
    +
    +    to_do = choice.strip.split(/\n/).map(&:downcase)
    +
    +    to_do.each do |action|
    +      action = 'resume' if action =~ /^resume/i
    +      action = 'reset' if action =~ /^begin/i
    +
    +      case action
    +      when /(resume|reset|autotag|archive|delete|finish|cancel|flag)/
    +        opt[action.to_sym] = true
    +      when /edit/
    +        opt[:editor] = true
    +      when /(add|remove) tag/
    +        type = action =~ /^add/ ? 'add' : 'remove'
    +        raise InvalidArgument, "'add tag' and 'remove tag' can not be used together" if opt[:tag]
    +
    +        tags = type == 'add' ? all_tags(@content) : all_tags(items)
    +
    +        add_msg = type == 'add' ? ', include values with tag(value)' : ''
    +        puts "#{yellow}Separate multiple tags with spaces, hit tab to complete known tags#{add_msg}"
    +        puts "#{boldgreen}Available tags: #{boldwhite}#{tags.sort.map(&:add_at).join(', ')}" if type == 'remove'
    +        tag = Prompt.read_line(prompt: "Tags to #{type}", completions: tags)
    +
    +        # print "#{yellow("Tag to #{type}: ")}#{reset}"
    +        # tag = $stdin.gets
    +        next if tag =~ /^ *$/
    +
    +        opt[:tag] = tag.strip.sub(/^@/, '')
    +        opt[:remove] = true if type == 'remove'
    +      when /output formatted/
    +        plugins = Plugins.available_plugins(type: :export).sort
    +        output_format = Prompt.choose_from(plugins,
    +                                           prompt: 'Which output format? > ',
    +                                           fzf_args: [
    +                                             "--height=#{plugins.count + 3}",
    +                                             '--tac',
    +                                             '--no-sort',
    +                                             '--info=hidden'
    +                                           ])
    +        next if output_format =~ /^ *$/
    +
    +        raise UserCancelled unless output_format
    +
    +        opt[:output] = output_format.strip
    +        res = opt[:force] ? false : Prompt.yn('Save to file?', default_response: 'n')
    +        if res
    +          # print "#{yellow('File path/name: ')}#{reset}"
    +          # filename = $stdin.gets.strip
    +          filename = Prompt.read_line(prompt: 'File path/name')
    +          next if filename.empty?
    +
    +          opt[:save_to] = filename
    +        end
    +      when /move/
    +        section = choose_section.strip
    +        opt[:move] = section.strip unless section =~ /^ *$/
    +      end
    +    end
    +  end
    +
    +  if opt[:resume] || opt[:reset]
    +    raise InvalidArgument, 'resume and restart can only be used on a single entry' if items.count > 1
    +
    +    item = items[0]
    +    if opt[:resume] && !opt[:reset]
    +      repeat_item(item, { editor: opt[:editor] }) # hooked
    +    elsif opt[:reset]
    +      res = Prompt.enter_text('Start date (blank for current time)', default_response: '')
    +      date = if res =~ /^ *$/
    +               Time.now
    +             else
    +               res.chronify(guess: :begin)
    +             end
    +
    +      res = if item.tags?('done', :and) && !opt[:resume]
    +              opt[:force] ? true : Prompt.yn('Remove @done tag?', default_response: 'y')
    +            else
    +              opt[:resume]
    +            end
    +      old_item = item.clone
    +      new_entry = reset_item(item, date: date, resume: res)
    +      @content.update_item(item, new_entry)
    +      Hooks.trigger :post_entry_updated, self, new_entry, old_item
    +    end
    +    write(@doing_file)
    +
    +    return
    +  end
    +
    +  if opt[:delete]
    +    delete_items(items, force: opt[:force]) # hooked
    +    write(@doing_file)
    +
    +    return
    +  end
    +
    +  if opt[:flag]
    +    tag = Doing.setting('marker_tag', 'flagged')
    +    items.map! do |i|
    +      old_item = i.clone
    +      i.tag(tag, date: false, remove: opt[:remove], single: single)
    +      Hooks.trigger :post_entry_updated, self, i, old_item
    +    end
    +  end
    +
    +  if opt[:finish] || opt[:cancel]
    +    tag = 'done'
    +    items.map! do |i|
    +      next unless i.should_finish?
    +
    +      old_item = i.clone
    +      should_date = !opt[:cancel] && i.should_time?
    +      i.tag(tag, date: should_date, remove: opt[:remove], single: single)
    +      Hooks.trigger :post_entry_updated, self, i, old_item
    +    end
    +  end
    +
    +  if opt[:autotag]
    +    items.map! do |i|
    +      new_title = autotag(i.title)
    +      if new_title == i.title
    +        logger.count(:skipped, level: :debug, message: '%count unchaged %items')
    +        # logger.debug('Autotag:', 'No changes')
    +      else
    +        logger.count(:added_tags)
    +        logger.write(items.count == 1 ? :info : :debug, 'Tagged:', new_title)
    +        old_item = i.clone
    +        i.title = new_title
    +        Hooks.trigger :post_entry_updated, self, i, old_item
    +      end
    +    end
    +  end
    +
    +  if opt[:tag]
    +    tag = opt[:tag]
    +    items.map! do |i|
    +      old_item = i.clone
    +      i.tag(tag, date: false, remove: opt[:remove], single: single)
    +      i.expand_date_tags(Doing.setting('date_tags'))
    +      Hooks.trigger :post_entry_updated, self, i, old_item
    +    end
    +  end
    +
    +  if opt[:archive] || opt[:move]
    +    section = opt[:archive] ? 'Archive' : guess_section(opt[:move])
    +    items.map! do |i|
    +      old_item = i.clone
    +      i.move_to(section, label: true)
    +      Hooks.trigger :post_entry_updated, self, i, old_item
    +    end
    +  end
    +
    +  write(@doing_file)
    +
    +  if opt[:editor]
    +    sleep 2 # This seems to be necessary between running fzf
    +            # and forking the editor, otherwise vim gets all
    +            # screwy and I can't figure out why
    +    edit_items(items) # hooked
    +
    +    write(@doing_file)
    +  end
    +
    +  return unless opt[:output]
    +
    +  items.each { |i| i.title = "#{i.title} @section(#{i.section})" }
    +
    +  export_items = Items.new
    +  export_items.concat(items)
    +  export_items.add_section(Section.new('Export'), log: false)
    +  options = { section: 'All' }
    +
    +  if opt[:output] =~ /doing/
    +    options[:output] = 'template'
    +    options[:template] = '- %date | %title%note'
    +  else
    +    options[:output] = opt[:output]
    +    options[:template] = opt[:template] || nil
    +  end
    +
    +  output = list_section(options, items: export_items) # hooked
    +
    +  if opt[:save_to]
    +    file = File.expand_path(opt[:save_to])
    +    if File.exist?(file)
    +      # Create a backup copy for the undo command
    +      FileUtils.cp(file, "#{file}~")
    +    end
    +
    +    File.open(file, 'w+') do |f|
    +      f.puts output
    +    end
    +
    +    logger.warn('File written:', file)
    +  else
    +    Doing::Pager.page output
    +  end
    +end
    +
    @@ -2153,7 +2801,119 @@

    -

    + + + + + +
    +
    +
    +
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +
    +
    # File 'lib/doing/wwid/modify.rb', line 18
    +
    +def add_item(title, section = nil, opt)
    +  opt ||= {}
    +  section ||= Doing.setting('current_section')
    +  @content.add_section(section, log: false)
    +  opt[:back] ||= opt[:date] ? opt[:date] : Time.now
    +  opt[:date] ||= Time.now
    +  note = Note.new
    +  opt[:timed] ||= false
    +
    +  note.add(opt[:note]) if opt[:note]
    +
    +  title = [title.strip.cap_first]
    +  title = title.join(' ')
    +
    +  if Doing.auto_tag
    +    title = autotag(title)
    +    title.add_tags!(Doing.setting('default_tags')) if Doing.setting('default_tags').good?
    +  end
    +
    +  title.compress!
    +  entry = Item.new(opt[:back], title.strip, section)
    +
    +  if opt[:done] && entry.should_finish?
    +    if entry.should_time?
    +      finish = opt[:done].is_a?(String) ? opt[:done].chronify(guess: :end, context: :today) : opt[:done]
    +      entry.tag('done', value: finish)
    +    else
    +      entry.tag('done')
    +    end
    +  end
    +
    +  entry.note = note
    +
    +  if opt[:timed]
    +    last_item = last_entry({ section: section })
    +    if last_item.tags?(['done'], :not)
    +      finish_date = verify_duration(last_item.date, opt[:back], title: last_item.title)
    +      last_item.tag('done', value: finish_date.strftime('%F %R'))
    +    end
    +  end
    +
    +  Hooks.trigger :pre_entry_add, self, entry
    +
    +  @content.push(entry)
    +  # logger.count(:added, level: :debug)
    +  logger.info('New entry:', %(added "#{entry.date.relative_date}: #{entry.title}" to #{section}))
    +
    +  Hooks.trigger :post_entry_added, self, entry
    +  entry
    +end
    +
    @@ -2187,7 +2947,61 @@

    -

    + + + + + +
    +
    +
    +
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +
    +
    # File 'lib/doing/wwid/editor.rb', line 103
    +
    +def add_with_editor(**options)
    +  raise MissingEditor, 'No EDITOR variable defined in environment' if Util.default_editor.nil?
    +
    +  input = options[:date].strftime('%F %R | ')
    +  input += options[:title]
    +  input += "\n#{options[:note]}" if options[:note]
    +  input = fork_editor(input).strip
    +
    +  d, title, note = format_input(input)
    +  raise EmptyInput, 'No content' if title.empty?
    +
    +  if options[:ask]
    +    ask_note = Doing::Prompt.read_lines(prompt: 'Add a note')
    +    note.add(ask_note) unless ask_note.empty?
    +  end
    +
    +  date = d.nil? ? options[:date] : d
    +  finish = options[:finish_last] || false
    +  add_item(title.cap_first, options[:section], { note: note, back: date, timed: finish })
    +  write(@doing_file)
    +end
    +
    @@ -2295,7 +3109,59 @@

    -

    + + + + + +
    +
    +
    +
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +
    +
    # File 'lib/doing/wwid/tags.rb', line 18
    +
    +def all_tags(items, opt: {}, counts: false)
    +  if counts
    +    all_tags = {}
    +    items.each do |item|
    +      item.tags.each do |tag|
    +        if all_tags.key?(tag.downcase)
    +          all_tags[tag.downcase] += 1
    +        else
    +          all_tags[tag.downcase] = 1
    +        end
    +      end
    +    end
    +
    +    all_tags.sort_by { |_, count| count }
    +  else
    +    all_tags = []
    +    items.each { |item| all_tags.concat(item.tags.map(&:downcase)).uniq! }
    +    all_tags.sort
    +  end
    +end
    +
    @@ -2354,7 +3220,67 @@

    -

    + + + + + +
    +
    +
    +
    +417
    +418
    +419
    +420
    +421
    +422
    +423
    +424
    +425
    +426
    +427
    +428
    +429
    +430
    +431
    +432
    +433
    +434
    +435
    +436
    +437
    +438
    +439
    +440
    +
    +
    # File 'lib/doing/wwid/modify.rb', line 417
    +
    +def archive(section = Doing.setting('current_section'), options)
    +  options ||= {}
    +  count       = options[:keep] || 0
    +  destination = options[:destination] || 'Archive'
    +  tags        = options[:tags] || []
    +  bool        = options[:bool] || :and
    +
    +  section = section[0] if section.is_a?(Array) && section.count == 1
    +  section = choose_section if section.nil? || section.empty? || section.is_a?(String) && section =~ /choose/i
    +  archive_all = section =~ /^all$/i # && !(tags.nil? || tags.empty?)
    +  section = guess_section(section) unless archive_all
    +
    +  @content.add_section(destination, log: true)
    +  # add_section(Section.new('Archive')) if destination =~ /^archive$/i && !@content.section?('Archive')
    +
    +  destination = guess_section(destination)
    +
    +  if @content.section?(destination) && (@content.section?(section) || archive_all)
    +    do_archive(section, destination, { count: count, tags: tags, bool: bool, search: options[:search], label: options[:label], before: options[:before], after: options[:after], from: options[:from] })
    +    write(doing_file)
    +  else
    +    raise InvalidArgument, 'Either source or destination does not exist'
    +  end
    +end
    +
    @@ -2397,7 +3323,217 @@

    -

    + + + + + +
    +
    +
    +
    +449
    +450
    +451
    +452
    +453
    +454
    +455
    +456
    +457
    +458
    +459
    +460
    +461
    +462
    +463
    +464
    +465
    +466
    +467
    +468
    +469
    +470
    +471
    +472
    +473
    +474
    +475
    +476
    +477
    +478
    +479
    +480
    +481
    +482
    +483
    +484
    +485
    +486
    +487
    +488
    +489
    +490
    +491
    +492
    +493
    +494
    +495
    +496
    +497
    +498
    +499
    +500
    +501
    +502
    +503
    +504
    +505
    +506
    +507
    +508
    +509
    +510
    +511
    +512
    +513
    +514
    +515
    +516
    +517
    +518
    +519
    +520
    +521
    +522
    +523
    +524
    +525
    +526
    +527
    +528
    +529
    +530
    +531
    +532
    +533
    +534
    +535
    +536
    +537
    +538
    +539
    +540
    +541
    +542
    +543
    +544
    +545
    +546
    +547
    +
    +
    # File 'lib/doing/wwid/modify.rb', line 449
    +
    +def autotag(string)
    +  return unless string
    +  return string unless Doing.auto_tag
    +
    +  original = string.dup
    +  text = string.dup
    +
    +  current_tags = text.scan(/@\w+/).map { |t| t.sub(/^@/, '') }
    +  tagged = {
    +    whitelisted: [],
    +    synonyms: [],
    +    transformed: [],
    +    replaced: []
    +  }
    +
    +  Doing.setting('autotag.whitelist').each do |tag|
    +    next if text =~ /@#{tag}\b/i
    +
    +    text.sub!(/(?<= |\A)(#{tag.strip})(?= |\Z)/i) do |m|
    +      m.downcase! unless tag =~ /[A-Z]/
    +      tagged[:whitelisted].push(m)
    +      "@#{m}"
    +    end
    +  end
    +
    +  Doing.setting('autotag.synonyms').each do |tag, v|
    +    v.each do |word|
    +      word = word.wildcard_to_rx
    +      next unless text =~ /\b#{word}\b/i
    +
    +      unless current_tags.include?(tag) || tagged[:whitelisted].include?(tag)
    +        tagged[:synonyms].push(tag)
    +        tagged[:synonyms] = tagged[:synonyms].uniq
    +      end
    +    end
    +  end
    +
    +  if Doing.setting('autotag.transform')
    +    Doing.setting('autotag.transform').each do |tag|
    +      next unless tag =~ /\S+:\S+/
    +
    +      if tag =~ /::/
    +        rx, r = tag.split(/::/)
    +      else
    +        rx, r = tag.split(/:/)
    +      end
    +
    +      flag_rx = %r{/([r]+)$}
    +      if r =~ flag_rx
    +        flags = r.match(flag_rx)[1].split(//)
    +        r.sub!(flag_rx, '')
    +      end
    +      r.gsub!(/\$/, '\\')
    +      rx.sub!(/^@?/, '@')
    +      regex = Regexp.new("(?<= |\\A)#{rx}(?= |\\Z)")
    +
    +      text.sub!(regex) do
    +        m = Regexp.last_match
    +        new_tag = r
    +
    +        m.to_a.slice(1, m.length - 1).each_with_index do |v, idx|
    +          next if v.nil?
    +
    +          new_tag.gsub!("\\#{idx + 1}", v)
    +        end
    +        # Replace original tag if /r
    +        if flags&.include?('r')
    +          tagged[:replaced].concat(new_tag.split(/ /).map { |t| t.sub(/^@/, '') })
    +          new_tag.split(/ /).map { |t| t.sub(/^@?/, '@') }.join(' ')
    +        else
    +          tagged[:transformed].concat(new_tag.split(/ /).map { |t| t.sub(/^@/, '') })
    +          tagged[:transformed] = tagged[:transformed].uniq
    +          m[0]
    +        end
    +      end
    +    end
    +  end
    +
    +  logger.debug('Autotag:', "whitelisted tags: #{tagged[:whitelisted].log_tags}") unless tagged[:whitelisted].empty?
    +  logger.debug('Autotag:', "synonyms: #{tagged[:synonyms].log_tags}") unless tagged[:synonyms].empty?
    +  logger.debug('Autotag:', "transforms: #{tagged[:transformed].log_tags}") unless tagged[:transformed].empty?
    +  logger.debug('Autotag:', "transform replaced: #{tagged[:replaced].log_tags}") unless tagged[:replaced].empty?
    +
    +  tail_tags = tagged[:synonyms].concat(tagged[:transformed])
    +  tail_tags.sort!
    +  tail_tags.uniq!
    +
    +  text.add_tags!(tail_tags) unless tail_tags.empty?
    +
    +  if text == original
    +    logger.debug('Autotag:', "no change to \"#{text.strip}\"")
    +  else
    +    new_tags = tagged[:whitelisted].concat(tail_tags).concat(tagged[:replaced])
    +    logger.debug('Autotag:', "added #{new_tags.log_tags} to \"#{text.strip}\"")
    +    logger.count(:autotag, level: :info, count: 1, message: 'autotag updated %count %items')
    +  end
    +
    +  text.dedup_tags
    +end
    +
    @@ -2438,7 +3574,25 @@

    -

    + + + + + +
    +
    +
    +
    +97
    +98
    +99
    +
    +
    # File 'lib/doing/wwid/wwidutil.rb', line 97
    +
    +def changes
    +  @content.diff(@initial_content)
    +end
    +
    @@ -2477,7 +3631,31 @@

    -

    + + + + + +
    +
    +
    +
    +306
    +307
    +308
    +309
    +310
    +311
    +
    +
    # File 'lib/doing/wwid/interactive.rb', line 306
    +
    +def choose_section(include_all: false)
    +  options = @content.section_titles.sort
    +  options.unshift('All') if include_all
    +  choice = Prompt.choose_from(options, prompt: 'Choose a section > ', fzf_args: ['--height=60%'])
    +  choice ? choice.strip : choice
    +end
    +
    @@ -2516,7 +3694,31 @@

    -

    + + + + + +
    +
    +
    +
    +335
    +336
    +337
    +338
    +339
    +340
    +
    +
    # File 'lib/doing/wwid/interactive.rb', line 335
    +
    +def 
    +  options = @content.section_titles.sort
    +  options.concat(@content.all_tags.sort.map { |t| "@#{t}" })
    +  choice = Prompt.choose_from(options, prompt: 'Choose a section or tag > ', fzf_args: ['--height=60%'])
    +  choice ? choice.strip : choice
    +end
    +
    @@ -2555,7 +3757,41 @@

    -

    + + + + + +
    +
    +
    +
    +318
    +319
    +320
    +321
    +322
    +323
    +324
    +325
    +326
    +327
    +328
    +
    +
    # File 'lib/doing/wwid/interactive.rb', line 318
    +
    +def choose_tag(section = 'All', items: nil, include_all: false)
    +  items ||= @content.in_section(section)
    +  tags = all_tags(items, counts: true).map { |t, c| "@#{t} (#{c})" }
    +  tags.unshift('No tag filter') if include_all
    +  choice = Prompt.choose_from(tags,
    +                              sorted: false,
    +                              multiple: true,
    +                              prompt: 'Choose tag(s) > ',
    +                              fzf_args: ['--height=60%'])
    +  choice ? choice.split(/\n/).map { |t| t.strip.sub(/ \(.*?\)$/, '') }.join(' ') : choice
    +end
    +
    @@ -2594,7 +3830,27 @@

    -

    + + + + + +
    +
    +
    +
    +347
    +348
    +349
    +350
    +
    +
    # File 'lib/doing/wwid/interactive.rb', line 347
    +
    +def choose_view
    +  choice = Prompt.choose_from(views.sort, prompt: 'Choose a view > ', fzf_args: ['--height=60%'])
    +  choice ? choice.strip : choice
    +end
    +
    @@ -2639,7 +3895,45 @@

    -

    + + + + + +
    +
    +
    +
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +
    +
    # File 'lib/doing/wwid/wwidutil.rb', line 56
    +
    +def configure(filename = nil)
    +  logger.benchmark(:configure, :start)
    +
    +  if filename
    +    Doing.config_with(filename, { ignore_local: true })
    +  elsif ENV['DOING_CONFIG']
    +    Doing.config_with(ENV['DOING_CONFIG'], { ignore_local: true })
    +  end
    +
    +  logger.benchmark(:configure, :finish)
    +
    +  Doing.set('backup_dir', ENV['DOING_BACKUP_DIR']) if ENV['DOING_BACKUP_DIR']
    +end
    +
    @@ -2661,7 +3955,39 @@

    -
    +

    + + + + +
    +
    +
    +
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +
    +
    # File 'lib/doing/wwid/filetools.rb', line 75
    +
    +def create(filename = nil)
    +  filename = @doing_file if filename.nil?
    +  return if File.exist?(filename) && File.stat(filename).size.positive?
    +
    +  FileUtils.mkdir_p(File.dirname(filename)) unless File.directory?(File.dirname(filename))
    +
    +  File.open(filename, 'w+') do |f|
    +    f.puts "#{Doing.setting('current_section')}:"
    +  end
    +end
    +
    @@ -2722,7 +4048,43 @@

    -

    + + + + + +
    +
    +
    +
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +
    +
    # File 'lib/doing/wwid/wwidutil.rb', line 14
    +
    +def dedup(items, no_overlap: false)
    +  items.delete_if do |item|
    +    duped = false
    +    @content.each do |comp|
    +      duped = no_overlap ? item.overlapping_time?(comp) : item.same_time?(comp)
    +      break if duped
    +    end
    +    logger.count(:skipped, level: :debug, message: '%count overlapping %items') if duped
    +    # logger.log_now(:debug, 'Skipped:', "overlapping entry: #{item.title}") if duped
    +    duped
    +  end
    +end
    +
    @@ -2780,7 +4142,39 @@

    -

    + + + + + +
    +
    +
    +
    +399
    +400
    +401
    +402
    +403
    +404
    +405
    +406
    +407
    +408
    +
    +
    # File 'lib/doing/wwid/modify.rb', line 399
    +
    +def delete_items(items, force: false)
    +  items.slice(0, 5).each { |i| puts i.to_pretty } unless force
    +  puts softpurple("+ #{items.size - 5} additional #{'item'.to_p(items.size - 5)}") if items.size > 5 && !force
    +
    +  res = force ? true : Prompt.yn("Delete #{items.size} #{'item'.to_p(items.size)}?", default_response: 'y')
    +  return unless res
    +
    +  items.each { |i| Hooks.trigger :post_entry_removed, self, @content.delete_item(i, single: items.count == 1) }
    +  # write(@doing_file)
    +end
    +
    @@ -2792,7 +4186,117 @@

    -

    +

    + + + + +
    +
    +
    +
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +153
    +154
    +155
    +156
    +157
    +158
    +159
    +160
    +161
    +162
    +163
    +164
    +165
    +166
    +167
    +168
    +169
    +170
    +171
    +172
    +173
    +
    +
    # File 'lib/doing/wwid/editor.rb', line 125
    +
    +def edit_items(items)
    +  items.sort_by! { |i| i.date }
    +  editable_items = []
    +
    +  items.each do |i|
    +    editable = "#{i.date.strftime('%F %R')} | #{i.title}"
    +    old_note = i.note ? i.note.strip_lines.join("\n") : nil
    +    editable += "\n#{old_note}" unless old_note.nil?
    +    editable_items << editable
    +  end
    +  divider = "-----------"
    +  notice =<<~EONOTICE
    +
    +  # - You may delete entries, but leave all divider lines (---) in place.
    +  # - Start and @done dates replaced with a time string (yesterday 3pm) will
    +  #   be parsed automatically. Do not delete the pipe (|) between start date
    +  #   and entry title.
    +  EONOTICE
    +  input =  "#{editable_items.map(&:strip).join("\n#{divider}\n")}\n"
    +
    +  new_items = fork_editor(input, message: notice).split(/^#{divider}/).map(&:strip)
    +
    +  new_items.each_with_index do |new_item, i|
    +    input_lines = new_item.split(/[\n\r]+/).delete_if(&:ignore?)
    +    first_line = input_lines[0]&.strip
    +
    +    if first_line.nil? || first_line =~ /^#{divider.strip}$/ || first_line.strip.empty?
    +      deleted = @content.delete_item(items[i], single: new_items.count == 1)
    +      Hooks.trigger :post_entry_removed, self, deleted
    +      Doing.logger.info('Deleted:', deleted.title)
    +    else
    +      date, title, note = format_input(new_item)
    +
    +      note.map!(&:strip)
    +      note.delete_if(&:ignore?)
    +      item = items[i]
    +      old_item = item.clone
    +      item.date = date || items[i].date
    +      item.title = title
    +      item.note = note
    +      if (item.equal?(old_item))
    +        Doing.logger.count(:skipped, level: :debug)
    +      else
    +        Doing.logger.count(:updated)
    +        Hooks.trigger :post_entry_updated, self, item, old_item
    +      end
    +    end
    +  end
    +end
    +
    @@ -2848,7 +4352,85 @@

    -

    + + + + + +
    +
    +
    +
    +180
    +181
    +182
    +183
    +184
    +185
    +186
    +187
    +188
    +189
    +190
    +191
    +192
    +193
    +194
    +195
    +196
    +197
    +198
    +199
    +200
    +201
    +202
    +203
    +204
    +205
    +206
    +207
    +208
    +209
    +210
    +211
    +212
    +
    +
    # File 'lib/doing/wwid/editor.rb', line 180
    +
    +def edit_last(section: 'All', options: {})
    +  options[:section] = guess_section(section)
    +
    +  item = last_entry(options)
    +
    +  if item.nil?
    +    logger.debug('Skipped:', 'No entries found')
    +    return
    +  end
    +
    +  old_item = item.clone
    +  content = ["#{item.date.strftime('%F %R')} | #{item.title.dup}"]
    +  content << item.note.strip_lines.join("\n") unless item.note.empty?
    +  new_item = fork_editor(content.join("\n"))
    +  raise UserCancelled, 'No change' if new_item.strip == content.join("\n").strip
    +
    +  date, title, note = format_input(new_item)
    +  date ||= item.date
    +
    +  if title.nil? || title.empty?
    +    logger.debug('Skipped:', 'No content provided')
    +  elsif title == item.title && note.equal?(item.note) && date.equal?(item.date)
    +    logger.debug('Skipped:', 'No change in content')
    +  else
    +    item.date = date unless date.nil?
    +    item.title = title
    +    item.note.add(note, replace: true)
    +    logger.info('Edited:', item.title)
    +    Hooks.trigger :post_entry_updated, self, item, old_item
    +
    +    write(@doing_file)
    +  end
    +end
    +
    @@ -3120,7 +4702,129 @@

    -

    + + + + + +
    +
    +
    +
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +
    +
    # File 'lib/doing/wwid/filter.rb', line 61
    +
    +def filter_items(items = Items.new, opt: {})
    +  logger.benchmark(:filter_items, :start)
    +  time_rx = /^(\d{1,2}+(:\d{1,2}+)?( *(am|pm))?|midnight|noon)$/i
    +
    +  if items.nil? || items.empty?
    +    section = !opt[:section] || opt[:section].empty? ? 'All' : guess_section(opt[:section])
    +    if section.is_a?(Array)
    +      section.each do |s|
    +        s = s[0] if s.is_a?(Array)
    +        items.concat(s =~ /^all$/i ? @content.clone : @content.in_section(s))
    +      end
    +    else
    +      items = section =~ /^all$/i ? @content.clone : @content.in_section(section)
    +    end
    +  end
    +
    +  unless opt[:time_filter]
    +    opt[:time_filter] = [nil, nil]
    +    if opt[:from] && !opt[:date_filter]
    +      if opt[:from][0].is_a?(String) && opt[:from][0] =~ time_rx
    +        opt[:time_filter] = opt[:from]
    +      elsif opt[:from][0].is_a?(Time)
    +        opt[:date_filter] = opt[:from]
    +      end
    +    end
    +  end
    +
    +  if opt[:before].is_a?(String) && opt[:before] =~ time_rx
    +    opt[:time_filter][1] = opt[:before]
    +    opt[:before] = nil
    +  end
    +
    +  if opt[:after].is_a?(String) && opt[:after] =~ time_rx
    +    opt[:time_filter][0] = opt[:after]
    +    opt[:after] = nil
    +  end
    +
    +  items.sort_by! { |item| [item.date, item.title.downcase] }.reverse
    +
    +  filtered_items = items.select { |item| item.keep_item?(opt) }
    +
    +  count = opt[:count].to_i&.positive? ? opt[:count].to_i : filtered_items.count
    +
    +  output = Items.new
    +
    +  if opt[:age] && opt[:age].normalize_age == :oldest
    +    output.concat(filtered_items.slice(0, count).reverse)
    +  else
    +    output.concat(filtered_items.reverse.slice(0, count))
    +  end
    +
    +  logger.benchmark(:filter_items, :finish)
    +
    +  output
    +end
    +
    @@ -3176,7 +4880,103 @@

    -

    + + + + + +
    +
    +
    +
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +
    +
    # File 'lib/doing/wwid/editor.rb', line 10
    +
    +def fork_editor(input = '', message: :default)
    +  # raise NonInteractive, 'Non-interactive terminal' unless $stdout.isatty || ENV['DOING_EDITOR_TEST']
    +
    +  raise MissingEditor, 'No EDITOR variable defined in environment' if Util.default_editor.nil?
    +
    +  tmpfile = Tempfile.new(['doing_temp', '.doing'])
    +
    +  File.open(tmpfile.path, 'w+') do |f|
    +    f.puts input
    +    unless message.nil?
    +      f.puts message == :default ? '# First line is the entry title, lines after are added as a note' : message
    +    end
    +  end
    +
    +  pid = Process.fork { system("#{Util.editor_with_args} #{tmpfile.path}") }
    +
    +  trap('INT') do
    +    begin
    +      Process.kill(9, pid)
    +    rescue StandardError
    +      Errno::ESRCH
    +    end
    +    tmpfile.unlink
    +    tmpfile.close!
    +    exit 0
    +  end
    +
    +  Process.wait(pid)
    +
    +  begin
    +    if $?.exitstatus == 0
    +      input = IO.read(tmpfile.path)
    +    else
    +      exit_now! 'Cancelled'
    +    end
    +  ensure
    +    tmpfile.close
    +    tmpfile.unlink
    +  end
    +
    +  input.split(/\n/).delete_if(&:ignore?).join("\n")
    +end
    +
    @@ -3247,7 +5047,103 @@

    -

    + + + + + +
    +
    +
    +
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +
    +
    # File 'lib/doing/wwid/editor.rb', line 60
    +
    +def format_input(input)
    +  raise EmptyInput, 'No content in entry' if input.nil? || input.strip.empty?
    +
    +  input_lines = input.split(/[\n\r]+/).delete_if(&:ignore?)
    +  title = input_lines[0]&.strip
    +  raise EmptyInput, 'No content in first line' if title.nil? || title.strip.empty?
    +
    +  date = nil
    +  iso_rx = /\d{4}-\d\d-\d\d \d\d:\d\d/
    +  date_rx = /^(?:\s*- )?(?<date>.*?) \| (?=\S)/
    +
    +  raise EmptyInput, 'No content' if title.sub(/^.*?\| */, '').strip.empty?
    +
    +  title.expand_date_tags(Doing.setting('date_tags'))
    +
    +  if title =~ date_rx
    +    m = title.match(date_rx)
    +    d = m['date']
    +    date = if d =~ iso_rx
    +             Time.parse(d)
    +           else
    +             d.chronify(guess: :begin)
    +           end
    +    title.sub!(date_rx, '').strip!
    +  end
    +
    +  note = Note.new
    +  note.add(input_lines[1..-1]) if input_lines.length > 1
    +  # If title line ends in a parenthetical, use that as the note
    +  if note.empty? && title =~ /\s+\(.*?\)$/
    +    title.sub!(/\s+\((?<note>.*?)\)$/) do
    +      m = Regexp.last_match
    +      note.add(m['note'])
    +      ''
    +    end
    +  end
    +
    +  note.strip_lines!
    +  note.compress
    +
    +  [date, title, note]
    +end
    +
    @@ -3259,7 +5155,43 @@

    -

    + + + + + +
    +
    +
    +
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +
    +
    # File 'lib/doing/wwid/filter.rb', line 27
    +
    +def fuzzy_filter_args(query, case_type)
    +  fzf_args = ['--multi', %(--filter="#{query.sub(/^'?/, "'")}"), '--no-sort', '-d "\|"', '--nth=1']
    +  fzf_args << case case_type.normalize_case
    +              when :smart
    +                query =~ /[A-Z]/ ? '+i' : '-i'
    +              when :sensitive
    +                '+i'
    +              when :ignore
    +                '-i'
    +              end
    +  fzf_args
    +end
    +
    @@ -3351,7 +5283,41 @@

    -

    + + + + + +
    +
    +
    +
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +
    +
    # File 'lib/doing/wwid/filter.rb', line 15
    +
    +def fuzzy_filter_items(items, query, case_type: :smart)
    +  scannable = items.map.with_index { |item, idx| "#{item.title} #{item.note.join(' ')}".gsub(/[|*?!]/, '') + "|#{idx}" }.join("\n")
    +
    +  res = `echo #{Shellwords.escape(scannable)}|#{Prompt.fzf} #{fuzzy_filter_args(query, case_type).join(' ')}`
    +  selected = Items.new
    +  res.split(/\n/).each do |item|
    +    idx = item.match(/\|(\d+)$/)[1].to_i
    +    selected.push(items[idx])
    +  end
    +  selected
    +end
    +
    @@ -3407,7 +5373,47 @@

    -

    + + + + + +
    +
    +
    +
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +
    +
    # File 'lib/doing/wwid/wwidutil.rb', line 75
    +
    +def get_diff(filename = nil)
    +  configure if Doing.settings.nil?
    +
    +  filename ||= Doing.setting('doing_file')
    +  init_doing_file(filename)
    +  current_content = @content.clone
    +  backup_file = Util::Backup.last_backup(filename, count: 1)
    +  raise DoingRuntimeError, 'No undo history to diff' if backup_file.nil?
    +
    +  backup = WWID.new
    +  backup.config = Doing.settings
    +  backup.init_doing_file(backup_file)
    +  current_content.diff(backup.content)
    +end
    +
    @@ -3504,7 +5510,41 @@

    -

    + + + + + +
    +
    +
    +
    +154
    +155
    +156
    +157
    +158
    +159
    +160
    +161
    +162
    +163
    +164
    +
    +
    # File 'lib/doing/wwid/timers.rb', line 154
    +
    +def get_interval(item, formatted: true, record: true)
    +  if item.interval
    +    seconds = item.interval
    +    record_tag_times(item, seconds) if record
    +    return seconds.positive? ? seconds : false unless formatted
    +
    +    return seconds.positive? ? seconds.time_string(format: :clock) : false
    +  end
    +
    +  false
    +end
    +
    @@ -3545,7 +5585,25 @@

    -

    + + + + + +
    +
    +
    +
    +103
    +104
    +105
    +
    +
    # File 'lib/doing/wwid/wwid.rb', line 103
    +
    +def get_view(title, fallback: nil)
    +  Doing.setting(['views', title], fallback)
    +end
    +
    @@ -3603,7 +5661,113 @@

    -

    + + + + + +
    +
    +
    +
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +
    +
    # File 'lib/doing/wwid/guess.rb', line 11
    +
    +def guess_section(frag, guessed: false, suggest: false)
    +  if frag.is_a?(Array) && frag.count == 1
    +    frag = frag[0]
    +  end
    +
    +  frag = frag.split(/ *, */).map(&:strip) if frag.is_a?(String) && frag =~ /,/
    +
    +  return frag.map { |s| guess_section(s, guessed: guessed, suggest: suggest) } if frag.is_a?(Array)
    +
    +  return 'All' if frag.empty? || frag.nil? || frag =~ /^all$/i
    +
    +  frag ||= Doing.setting('current_section')
    +
    +  return frag.cap_first if @content.section?(frag)
    +
    +  found = @content.guess_section(frag, distance: 2)
    +
    +  section = found ? found.title : nil
    +
    +  if section && suggest
    +    Doing.logger.debug('Match:', %(Assuming "#{section}" from "#{frag}"))
    +    return section
    +  end
    +
    +  unless section || guessed
    +    alt = guess_view(frag, guessed: true, suggest: true)
    +    if alt
    +      prompt = Color.template("{bw}Did you mean `{xy}doing {by}view {xy}#{alt}`{bw}?{x}")
    +      meant_view = Prompt.yn(prompt, default_response: 'n')
    +
    +      msg = format('%<y>srun with `%<w>sdoing view %<alt>s%<y>s`', w: boldwhite, y: yellow, alt: alt)
    +      raise Errors::WrongCommand.new(msg, topic: 'Try again:') if meant_view
    +
    +    end
    +
    +    res = Prompt.yn("#{boldwhite}Section #{frag.yellow}#{boldwhite} not found, create it", default_response: 'n')
    +
    +    if res
    +      @content.add_section(frag.cap_first, log: true)
    +      write(@doing_file)
    +      return frag.cap_first
    +    end
    +
    +    raise Errors::InvalidSection.new("unknown section #{frag.bold.white}", topic: 'Missing:')
    +  end
    +  section ? section.cap_first : nil
    +end
    +
    @@ -3661,7 +5825,79 @@

    -

    + + + + + +
    +
    +
    +
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +
    +
    # File 'lib/doing/wwid/guess.rb', line 65
    +
    +def guess_view(frag, guessed: false, suggest: false)
    +  views.each { |view| return view if frag.downcase == view.downcase }
    +  view = nil
    +  re = frag.to_rx(distance: 2, case_type: :ignore)
    +  views.each do |v|
    +    next unless v =~ /#{re}/i
    +
    +    logger.debug('Match:', %(Assuming "#{v}" from "#{frag}"))
    +    view = v
    +    break
    +  end
    +  unless view || guessed
    +    alt = guess_section(frag, guessed: true, suggest: true)
    +
    +    raise Errors::InvalidView.new(%(unknown view #{frag.bold.white}), topic: 'Missing:') unless alt
    +
    +    prompt = Color.template("{bw}Did you mean `{xy}doing {by}show {xy}#{alt}`{bw}?{x}")
    +    meant_view = Prompt.yn(prompt, default_response: 'n')
    +
    +    if meant_view
    +      msg = format('%<y>srun with `%<w>sdoing show %<alt>s%<y>s`', w: boldwhite, y: yellow, alt: alt)
    +      raise Errors::WrongCommand.new(msg, topic: 'Try again:')
    +
    +    end
    +
    +    raise Errors::InvalidView.new(%(unknown view #{alt.bold.white}), topic: 'Missing:')
    +
    +  end
    +  view
    +end
    +
    @@ -3717,7 +5953,49 @@

    -

    + + + + + +
    +
    +
    +
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +
    +
    # File 'lib/doing/wwid/wwidutil.rb', line 33
    +
    +def import(paths, opt)
    +  opt ||= {}
    +  Plugins.plugins[:import].each do |_, options|
    +    next unless opt[:type] =~ /^(#{options[:trigger].normalize_trigger})$/i
    +
    +    if paths.count.positive?
    +      paths.each do |path|
    +        options[:class].import(self, path, options: opt)
    +      end
    +    else
    +      options[:class].import(self, nil, options: opt)
    +    end
    +    break
    +  end
    +end
    +
    @@ -3760,7 +6038,141 @@

    -

    + + + + + +
    +
    +
    +
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +
    +
    # File 'lib/doing/wwid/filetools.rb', line 10
    +
    +def init_doing_file(path = nil)
    +  @doing_file =  File.expand_path(Doing.setting('doing_file'))
    +
    +  if path.nil?
    +    create(@doing_file) unless File.exist?(@doing_file)
    +    input = IO.read(@doing_file)
    +    input = input.force_encoding('utf-8') if input.respond_to? :force_encoding
    +    logger.debug('Read:', "read file #{@doing_file}")
    +  elsif File.exist?(File.expand_path(path)) && File.file?(File.expand_path(path)) && File.stat(File.expand_path(path)).size.positive?
    +    @doing_file = File.expand_path(path)
    +    input = IO.read(File.expand_path(path))
    +    input = input.force_encoding('utf-8') if input.respond_to? :force_encoding
    +    logger.debug('Read:', "read file #{File.expand_path(path)}")
    +  elsif path.length < 256
    +    @doing_file = File.expand_path(path)
    +    create(path)
    +    input = IO.read(File.expand_path(path))
    +    input = input.force_encoding('utf-8') if input.respond_to? :force_encoding
    +    logger.debug('Read:', "read file #{File.expand_path(path)}")
    +  end
    +
    +  @other_content_top = []
    +  @other_content_bottom = []
    +
    +  section = nil
    +  lines = input.split(/[\n\r]/)
    +
    +  lines.each do |line|
    +    next if line =~ /^\s*$/
    +
    +    if line =~ /^(\S[\S ]+):\s*(@[\w\-_.]+\s*)*$/
    +      section = Regexp.last_match(1)
    +      @content.add_section(Section.new(section, original: line), log: false)
    +    elsif line =~ /^\s*- (\d{4}-\d\d-\d\d \d\d:\d\d) \| (.*?)(?: +<([a-z0-9]{32})>)? *$/
    +      if section.nil?
    +        section = 'Uncategorized'
    +        @content.add_section(Section.new(section, original: 'Uncategorized:'), log: false)
    +      end
    +
    +      date = Regexp.last_match(1).strip
    +      title = Regexp.last_match(2).strip
    +      id = Regexp.last_match(3) || nil
    +      item = Item.new(date, title, section, [], id)
    +      @content.push(item)
    +    elsif @content.count.zero?
    +      # if content[section].items.length - 1 == current
    +      @other_content_top.push(line)
    +    elsif line =~ /^\S/
    +      @other_content_bottom.push(line)
    +    else
    +      prev_item = @content.last
    +      prev_item.note = Note.new unless prev_item.note
    +
    +      prev_item.note.add(line)
    +      # end
    +    end
    +  end
    +
    +  Hooks.trigger :post_read, self
    +  @initial_content = @content.clone
    +end
    +
    @@ -3816,7 +6228,83 @@

    -

    + + + + + +
    +
    +
    +
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +
    +
    # File 'lib/doing/wwid/interactive.rb', line 12
    +
    +def interactive(opt)
    +  opt ||= {}
    +  opt[:section] = opt[:section] ? guess_section(opt[:section]) : 'All'
    +
    +  search = nil
    +
    +  if opt[:search]
    +    search = opt[:search]
    +    search.sub!(/^'?/, "'") if opt[:exact]
    +    opt[:search] = search
    +  end
    +
    +  # opt[:query] = opt[:search] if opt[:search] && !opt[:query]
    +  opt[:query] = "!#{opt[:query]}" if opt[:query] && opt[:not]
    +  opt[:multiple] = true
    +  opt[:show_if_single] = true
    +  filter_options = %i[after before case date_filter from fuzzy not search section val].each_with_object({}) {
    +    |k, hsh| hsh[k] = opt[k]
    +  }
    +  items = filter_items(Items.new, opt: filter_options)
    +
    +  menu_options = %i[search query exact multiple show_if_single menu sort case].each_with_object({}) {
    +    |k, hsh| hsh[k] = opt[k]
    +  }
    +  include_section = (opt[:section].is_a?(Array) && opt[:section][0] =~ /^all$/i) || (opt[:section].is_a?(String) && opt[:section] =~ /^all$/i)
    +
    +  selection = Prompt.choose_from_items(items, include_section: include_section, **menu_options)
    +
    +  raise NoResults, 'no items selected' if selection.nil? || selection.empty?
    +
    +  act_on(selection, opt)
    +end
    +
    @@ -3876,7 +6364,103 @@

    -

    + + + + + +
    +
    +
    +
    +258
    +259
    +260
    +261
    +262
    +263
    +264
    +265
    +266
    +267
    +268
    +269
    +270
    +271
    +272
    +273
    +274
    +275
    +276
    +277
    +278
    +279
    +280
    +281
    +282
    +283
    +284
    +285
    +286
    +287
    +288
    +289
    +290
    +291
    +292
    +293
    +294
    +295
    +296
    +297
    +298
    +299
    +
    +
    # File 'lib/doing/wwid/display.rb', line 258
    +
    +def last(times: true, section: nil, options: {})
    +  section = section[0] if section.is_a?(Array) && section.count == 1
    +  section = section.nil? ? 'All' : guess_section(section)
    +  cfg = Doing.setting(['templates', options[:config_template]]).deep_merge(Doing.setting('templates.default'), { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
    +    'wrap_width' => Doing.setting('wrap_width', 0),
    +    'date_format' => Doing.setting('default_date_format'),
    +    'order' => Doing.setting('order', :asc),
    +    'tags_color' => Doing.setting('tags_color'),
    +    'duration' => Doing.setting('duration'),
    +    'interval_format' => Doing.setting('interval_format')
    +  }, { extend_existing_arrays: true, sort_merged_arrays: true })
    +  options[:duration] ||= cfg['duration'] || false
    +  options[:interval_format] ||= cfg['interval_format'] || 'text'
    +
    +  opts = {
    +    case: options[:case],
    +    config_template: options[:config_template] || 'last',
    +    count: 1,
    +    delete: options[:delete],
    +    duration: options[:duration],
    +    format: cfg['date_format'],
    +    interval_format: options[:interval_format],
    +    not: options[:negate],
    +    output: options[:output],
    +    section: section,
    +    template: options[:template] || cfg['template'],
    +    times: times,
    +    val: options[:val],
    +    wrap_width: cfg['wrap_width']
    +  }
    +
    +  if options[:tag]
    +    opts[:tag_filter] = {
    +      'tags' => options[:tag],
    +      'bool' => options[:tag_bool]
    +    }
    +  end
    +
    +  opts[:search] = options[:search] if options[:search]
    +
    +  list_section(opts)
    +end
    +
    @@ -3917,7 +6501,67 @@

    -

    + + + + + +
    +
    +
    +
    +325
    +326
    +327
    +328
    +329
    +330
    +331
    +332
    +333
    +334
    +335
    +336
    +337
    +338
    +339
    +340
    +341
    +342
    +343
    +344
    +345
    +346
    +347
    +348
    +
    +
    # File 'lib/doing/wwid/display.rb', line 325
    +
    +def last_entry(opt)
    +  opt ||= {}
    +  opt[:tag_bool] ||= :and
    +  opt[:section] ||= Doing.setting('current_section')
    +
    +  items = filter_items(Items.new, opt: opt)
    +
    +  logger.debug('Filtered:', "Parameters matched #{items.count} entries")
    +
    +  if opt[:interactive]
    +    last_entry = Prompt.choose_from_items(items, include_section: opt[:section] =~ /^all$/i,
    +      menu: true,
    +      header: '',
    +      prompt: 'Select an entry > ',
    +      multiple: false,
    +      sort: false,
    +      show_if_single: true
    +     )
    +  else
    +    last_entry = items.max_by { |item| item.date }
    +  end
    +
    +  last_entry
    +end
    +
    @@ -3974,7 +6618,43 @@

    -

    + + + + + +
    +
    +
    +
    +307
    +308
    +309
    +310
    +311
    +312
    +313
    +314
    +315
    +316
    +317
    +318
    +
    +
    # File 'lib/doing/wwid/display.rb', line 307
    +
    +def last_note(section = 'All')
    +  section = guess_section(section)
    +
    +  last_item = last_entry({ section: section })
    +
    +  raise NoEntryError, 'No entry found' unless last_item
    +
    +  logger.log_now(:info, 'Edit note:', last_item.title)
    +
    +  note = last_item.note&.to_s || ''
    +  "#{last_item.title}\n# EDIT BELOW THIS LINE ------------\n#{note}"
    +end
    +
    @@ -4079,7 +6759,63 @@

    -

    + + + + + +
    +
    +
    +
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +
    +
    # File 'lib/doing/wwid/display.rb', line 107
    +
    +def list_date(dates, section, times = nil, output = nil, opt)
    +  opt ||= {}
    +  opt[:totals] ||= false
    +  opt[:sort_tags] ||= false
    +  section = guess_section(section)
    +  # :date_filter expects an array with start and end date
    +  dates = dates.split_date_range if dates.instance_of?(String)
    +
    +  opt[:section] = section
    +  opt[:count] = 0
    +  opt[:order] = :asc
    +  opt[:date_filter] = dates
    +  opt[:times] = times
    +  opt[:output] = output
    +
    +  time_rx = /^(\d{1,2}+(:\d{1,2}+)?( *(am|pm))?|midnight|noon)$/
    +  if opt[:from] && opt[:from][0].is_a?(String) && opt[:from][0] =~ time_rx
    +    opt[:time_filter] = opt[:from]
    +  end
    +
    +  list_section(opt)
    +end
    +
    @@ -4120,7 +6856,193 @@

    -

    + + + + + +
    +
    +
    +
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +
    +
    # File 'lib/doing/wwid/display.rb', line 10
    +
    +def list_section(opt, items: Items.new)
    +  logger.benchmark(:list_section, :start)
    +  opt[:config_template] ||= 'default'
    +
    +  tpl_cfg = Doing.setting(['templates', opt[:config_template]])
    +
    +  cfg = if opt[:view_template]
    +          Doing.setting(['views', opt[:view_template]]).deep_merge(tpl_cfg, { extend_existing_arrays: true, sort_merged_arrays: true })
    +        else
    +          tpl_cfg
    +        end
    +
    +  cfg.deep_merge({
    +                   'wrap_width' => Doing.setting('wrap_width') || 0,
    +                   'date_format' => Doing.setting('default_date_format'),
    +                   'order' => Doing.setting('order') || :asc,
    +                   'tags_color' => Doing.setting('tags_color'),
    +                   'duration' => Doing.setting('duration'),
    +                   'interval_format' => Doing.setting('interval_format')
    +                 }, { extend_existing_arrays: true, sort_merged_arrays: true })
    +
    +  opt[:duration] ||= cfg['duration'] || false
    +  opt[:interval_format] ||= cfg['interval_format'] || 'text'
    +  opt[:count] ||= 0
    +  opt[:age] ||= :newest
    +  opt[:age] = opt[:age].normalize_age
    +  opt[:format] ||= cfg['date_format']
    +  opt[:order] ||= cfg['order'] || :asc
    +  opt[:tag_order] ||= :asc
    +  opt[:tags_color] = cfg['tags_color'] || false if opt[:tags_color].nil?
    +  opt[:template] ||= cfg['template']
    +  opt[:sort_tags] ||= opt[:tag_sort]
    +
    +  # opt[:highlight] ||= true
    +  title = ''
    +  is_single = true
    +  if opt[:section].nil?
    +    opt[:section] = choose_section
    +    title = opt[:section]
    +  elsif opt[:section].is_a?(Array)
    +    title = opt[:section].join(', ')
    +  elsif opt[:section].is_a?(String)
    +    title = if opt[:section] =~ /^all$/i
    +              if opt[:page_title]
    +                opt[:page_title]
    +              elsif opt[:tag_filter] && opt[:tag_filter]['bool'].normalize_bool != :not
    +                opt[:tag_filter]['tags'].map { |tag| "@#{tag}" }.join(' + ')
    +              else
    +                'doing'
    +              end
    +            else
    +              guess_section(opt[:section])
    +            end
    +  end
    +
    +  items = filter_items(items, opt: opt)
    +
    +  items.reverse! unless opt[:order].normalize_order == :desc
    +
    +  if opt[:delete]
    +    delete_items(items, force: opt[:force])
    +
    +    write(@doing_file)
    +    return
    +  elsif opt[:editor]
    +    edit_items(items)
    +
    +    write(@doing_file)
    +    return
    +  elsif opt[:interactive]
    +    opt[:menu] = !opt[:force]
    +    opt[:query] = '' # opt[:search]
    +    opt[:multiple] = true
    +    selected = Prompt.choose_from_items(items.reverse, include_section: opt[:section] =~ /^all$/i, **opt)
    +
    +    raise NoResults, 'no items selected' if selected.nil? || selected.empty?
    +
    +    act_on(selected, opt)
    +    return
    +  end
    +
    +  opt[:output] ||= 'template'
    +  opt[:wrap_width] ||= Doing.setting('templates.default.wrap_width', 0)
    +
    +  logger.benchmark(:list_section, :finish)
    +  output(items, title, is_single, opt)
    +end
    +
    @@ -4148,7 +7070,25 @@

    -
    +

    + + + + +
    +
    +
    +
    +76
    +77
    +78
    +
    +
    # File 'lib/doing/wwid/wwid.rb', line 76
    +
    +def logger
    +  @logger ||= Doing.logger
    +end
    +
    @@ -4223,7 +7163,77 @@

    -

    + + + + + +
    +
    +
    +
    +222
    +223
    +224
    +225
    +226
    +227
    +228
    +229
    +230
    +231
    +232
    +233
    +234
    +235
    +236
    +237
    +238
    +239
    +240
    +241
    +242
    +243
    +244
    +245
    +246
    +247
    +248
    +249
    +250
    +
    +
    # File 'lib/doing/wwid/display.rb', line 222
    +
    +def recent(count = 10, section = nil, opt)
    +  opt ||= {}
    +  opt[:times] ||= false
    +  opt[:totals] ||= false
    +  opt[:sort_tags] ||= false
    +
    +  cfg = Doing.setting('templates.recent').deep_merge(Doing.setting('templates.default'), { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
    +    'wrap_width' => Doing.setting('wrap_width') || 0,
    +    'date_format' => Doing.setting('default_date_format'),
    +    'order' => Doing.setting('order') || :asc,
    +    'tags_color' => Doing.setting('tags_color'),
    +    'duration' => Doing.setting('duration'),
    +    'interval_format' => Doing.setting('interval_format')
    +  }, { extend_existing_arrays: true, sort_merged_arrays: true })
    +  opt[:duration] ||= cfg['duration'] || false
    +  opt[:interval_format] ||= cfg['interval_format'] || 'text'
    +
    +  section ||= Doing.setting('current_section')
    +  section = guess_section(section)
    +
    +  opt[:section] = section
    +  opt[:wrap_width] = cfg['wrap_width']
    +  opt[:count] = count
    +  opt[:format] = cfg['date_format']
    +  opt[:template] = opt[:template] || cfg['template']
    +  opt[:order] = :asc
    +
    +  list_section(opt)
    +end
    +
    @@ -4235,7 +7245,41 @@

    -

    + + + + + +
    +
    +
    +
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +
    +
    # File 'lib/doing/wwid/wwid.rb', line 107
    +
    +def rename_view_keys(view)
    +  options = view.symbolize_keys
    +  # options.rename_key(:tags, :tag, keep: true)
    +  options.rename_key(:output_format, :output)
    +  options.rename_key(:tags_bool, :bool)
    +  options.rename_key(:tag_sort, :sort_tags)
    +  options.rename_key(:negate, :not)
    +  options.rename_key(:order, :sort)
    +
    +  options
    +end
    +
    @@ -4367,7 +7411,95 @@

    -

    + + + + + +
    +
    +
    +
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +
    +
    # File 'lib/doing/wwid/modify.rb', line 100
    +
    +def repeat_item(item, opt)
    +  opt ||= {}
    +  old_item = item.clone
    +  if item.unfinished? && item.should_finish?
    +    if item.should_time?
    +      finish_date = verify_duration(item.date, Time.now, title: item.title)
    +      item.title.tag!('done', value: finish_date.strftime('%F %R'))
    +    else
    +      item.title.tag!('done')
    +    end
    +    Hooks.trigger :post_entry_updated, self, item, old_item
    +  end
    +
    +  # Remove @done tag
    +  title = item.title.sub(/\s*@done(\(.*?\))?/, '').chomp
    +  section = opt[:in].nil? ? item.section : guess_section(opt[:in])
    +  Doing.auto_tag = false
    +
    +  note = opt[:note] || Note.new
    +
    +  if opt[:editor]
    +    start = opt[:date] ? opt[:date] : Time.now
    +    to_edit = "#{start.strftime('%F %R')} | #{title}"
    +    to_edit += "\n#{note.strip_lines.join("\n")}" unless note.empty?
    +    new_item = fork_editor(to_edit)
    +    date, title, note = format_input(new_item)
    +
    +    opt[:date] = date unless date.nil?
    +
    +    if title.nil? || title.empty?
    +      logger.warn('Skipped:', 'No content provided')
    +      return
    +    end
    +  end
    +
    +  # @content.update_item(original, item)
    +  add_item(title, section, { note: note, back: opt[:date], timed: false })
    +end
    +
    @@ -4408,7 +7540,53 @@

    -

    + + + + + +
    +
    +
    +
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +153
    +154
    +155
    +156
    +157
    +158
    +159
    +160
    +
    +
    # File 'lib/doing/wwid/modify.rb', line 144
    +
    +def repeat_last(opt)
    +  opt ||= {}
    +  opt[:section] ||= 'all'
    +  opt[:section] = guess_section(opt[:section])
    +  opt[:note] ||= []
    +  opt[:tag] ||= []
    +  opt[:tag_bool] ||= :and
    +
    +  last = last_entry(opt)
    +  if last.nil?
    +    logger.warn('Skipped:', 'No previous entry found')
    +    return
    +  end
    +
    +  repeat_item(last, opt)
    +  write(@doing_file)
    +end
    +
    @@ -4467,7 +7645,43 @@

    -

    + + + + + +
    +
    +
    +
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +
    +
    # File 'lib/doing/wwid/modify.rb', line 75
    +
    +def reset_item(item, date: nil, finish_date: nil, resume: false)
    +  date ||= Time.now
    +  item.date = date
    +  if finish_date
    +    item.tag('done', remove: true)
    +    item.tag('done', value: finish_date.strftime('%F %R'))
    +  else
    +    item.tag('done', remove: true) if resume
    +  end
    +  logger.info('Reset:', %(Reset #{resume ? 'and resumed ' : ''} "#{item.title}" in #{item.section}))
    +  item
    +end
    +
    @@ -4489,7 +7703,135 @@

    -
    +

    + + + + +
    +
    +
    +
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +153
    +154
    +155
    +156
    +157
    +158
    +159
    +160
    +161
    +162
    +
    +
    # File 'lib/doing/wwid/filetools.rb', line 105
    +
    +def rotate(opt)
    +  opt ||= {}
    +  keep = opt[:keep] || 0
    +  tags = []
    +  tags.concat(opt[:tag].split(/ *, */).map { |t| t.sub(/^@/, '').strip }) if opt[:tag]
    +  bool  = opt[:bool] || :and
    +
    +  sect = opt[:section] !~ /^all$/i ? guess_section(opt[:section]) : 'all'
    +
    +  section = guess_section(sect)
    +
    +  section_items = @content.in_section(section)
    +  max = section_items.count - keep.to_i
    +
    +  counter = 0
    +  new_content = Items.new
    +
    +  section_items.each do |item|
    +    break if counter >= max
    +    if opt[:before]
    +      time_string = opt[:before]
    +      cutoff = time_string.chronify(guess: :begin)
    +    end
    +
    +    unless ((!tags.empty? && !item.tags?(tags, bool)) || (opt[:search] && !item.search(opt[:search].to_s)) || (opt[:before] && item.date >= cutoff))
    +      new_item = @content.delete(item)
    +      Hooks.trigger :post_entry_removed, self, item.clone
    +      raise DoingRuntimeError, "Error deleting item: #{item}" if new_item.nil?
    +
    +      new_content.add_section(new_item.section, log: false)
    +      new_content.push(new_item)
    +      counter += 1
    +    end
    +  end
    +
    +  if counter.positive?
    +    logger.count(:rotated,
    +                 level: :info,
    +                 count: counter,
    +                 message: "Rotated %count %items")
    +  else
    +    logger.info('Skipped:', 'No items were rotated')
    +  end
    +
    +  write(@doing_file)
    +
    +  file = @doing_file.sub(/(\.\w+)$/, "_#{Time.now.strftime('%Y-%m-%d')}\\1")
    +  if File.exist?(file)
    +    init_doing_file(file)
    +    @content.concat(new_content).uniq!
    +    logger.warn('File update:', "added entries to existing file: #{file}")
    +  else
    +    @content = new_content
    +    logger.warn('File update:', "created new file: #{file}")
    +  end
    +
    +  write(file, backup: false)
    +end
    +
    @@ -4528,7 +7870,25 @@

    -

    + + + + + +
    +
    +
    +
    +85
    +86
    +87
    +
    +
    # File 'lib/doing/wwid/wwid.rb', line 85
    +
    +def sections
    +  @content.section_titles
    +end
    +
    @@ -4659,7 +8019,121 @@

    -

    + + + + + +
    +
    +
    +
    +341
    +342
    +343
    +344
    +345
    +346
    +347
    +348
    +349
    +350
    +351
    +352
    +353
    +354
    +355
    +356
    +357
    +358
    +359
    +360
    +361
    +362
    +363
    +364
    +365
    +366
    +367
    +368
    +369
    +370
    +371
    +372
    +373
    +374
    +375
    +376
    +377
    +378
    +379
    +380
    +381
    +382
    +383
    +384
    +385
    +386
    +387
    +388
    +389
    +390
    +391
    +
    +
    # File 'lib/doing/wwid/modify.rb', line 341
    +
    +def stop_start(target_tag, opt)
    +  opt ||= {}
    +  tag = target_tag.dup
    +  opt[:section] ||= Doing.setting('current_section')
    +  opt[:archive] ||= false
    +  opt[:back] ||= Time.now
    +  opt[:new_item] ||= false
    +  opt[:note] ||= false
    +
    +  opt[:section] = guess_section(opt[:section])
    +
    +  tag.sub!(/^@/, '')
    +
    +  found_items = 0
    +
    +  @content.each_with_index do |item, i|
    +    old_item = i.clone
    +    next unless item.section == opt[:section] || opt[:section] =~ /all/i
    +
    +    next unless item.title =~ /@#{tag}/
    +
    +    item.title.add_tags!([tag, 'done'], remove: true)
    +    item.tag('done', value: opt[:back].strftime('%F %R'))
    +
    +    found_items += 1
    +
    +    if opt[:archive] && opt[:section] != 'Archive'
    +      item.title = item.title.sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{item.section})")
    +      item.move_to('Archive', label: false, log: false)
    +      logger.count(:completed_archived)
    +      logger.info('Completed/archived:', item.title)
    +    else
    +      logger.count(:completed)
    +      logger.info('Completed:', item.title)
    +    end
    +    Hooks.trigger :post_entry_updated, self, item, old_item
    +  end
    +
    +
    +  logger.debug('Skipped:', "No active @#{tag} tasks found.") if found_items.zero?
    +
    +  if opt[:new_item]
    +    date, title, note = format_input(opt[:new_item])
    +    opt[:back] = date unless date.nil?
    +    note.add(opt[:note]) if opt[:note]
    +    title.tag!(tag)
    +    add_item(title.cap_first, opt[:section], { note: note, back: opt[:back] })
    +  end
    +
    +  write(@doing_file)
    +end
    +
    @@ -4671,7 +8145,41 @@

    -

    + + + + + +
    +
    +
    +
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +
    +
    # File 'lib/doing/wwid/tags.rb', line 39
    +
    +def tag_groups(items, opt: {})
    +  all_items = filter_items(items, opt: opt)
    +  tags = all_tags(all_items, opt: {})
    +  groups = {}
    +  tags.each do |tag|
    +    groups[tag] ||= []
    +    groups[tag] = filter_items(all_items, opt: { tag: tag, tag_bool: :or })
    +  end
    +
    +  groups
    +end
    +
    @@ -4736,7 +8244,327 @@

    filter_items for filtering

    -
    + + + + + +
    +
    +
    +
    +171
    +172
    +173
    +174
    +175
    +176
    +177
    +178
    +179
    +180
    +181
    +182
    +183
    +184
    +185
    +186
    +187
    +188
    +189
    +190
    +191
    +192
    +193
    +194
    +195
    +196
    +197
    +198
    +199
    +200
    +201
    +202
    +203
    +204
    +205
    +206
    +207
    +208
    +209
    +210
    +211
    +212
    +213
    +214
    +215
    +216
    +217
    +218
    +219
    +220
    +221
    +222
    +223
    +224
    +225
    +226
    +227
    +228
    +229
    +230
    +231
    +232
    +233
    +234
    +235
    +236
    +237
    +238
    +239
    +240
    +241
    +242
    +243
    +244
    +245
    +246
    +247
    +248
    +249
    +250
    +251
    +252
    +253
    +254
    +255
    +256
    +257
    +258
    +259
    +260
    +261
    +262
    +263
    +264
    +265
    +266
    +267
    +268
    +269
    +270
    +271
    +272
    +273
    +274
    +275
    +276
    +277
    +278
    +279
    +280
    +281
    +282
    +283
    +284
    +285
    +286
    +287
    +288
    +289
    +290
    +291
    +292
    +293
    +294
    +295
    +296
    +297
    +298
    +299
    +300
    +301
    +302
    +303
    +304
    +305
    +306
    +307
    +308
    +309
    +310
    +311
    +312
    +313
    +314
    +315
    +316
    +317
    +318
    +319
    +320
    +321
    +322
    +323
    +324
    +
    +
    # File 'lib/doing/wwid/modify.rb', line 171
    +
    +def tag_last(opt) # hooked
    +  opt ||= {}
    +  opt[:count] ||= 1
    +  opt[:archive] ||= false
    +  opt[:tags] ||= ['done']
    +  opt[:sequential] ||= false
    +  opt[:date] ||= false
    +  opt[:remove] ||= false
    +  opt[:update] ||= false
    +  opt[:autotag] ||= false
    +  opt[:back] ||= false
    +  opt[:unfinished] ||= false
    +  opt[:section] = opt[:section] ? guess_section(opt[:section]) : 'All'
    +
    +  items = filter_items(Items.new, opt: opt)
    +
    +  if opt[:interactive]
    +    items = Prompt.choose_from_items(items, include_section: opt[:section] =~ /^all$/i, menu: true,
    +                                header: '',
    +                                prompt: 'Select entries to tag > ',
    +                                multiple: true,
    +                                sort: true,
    +                                show_if_single: true)
    +
    +    raise NoResults, 'no items selected' if items.empty?
    +
    +  end
    +
    +  raise NoResults, 'no items matched your search' if items.empty?
    +
    +  if opt[:tags].empty? && !opt[:autotag]
    +    completions = opt[:remove] ? all_tags(items) : all_tags(@content)
    +    if opt[:remove]
    +      puts "#{yellow}Available tags: #{boldwhite}#{completions.map(&:add_at).join(', ')}"
    +    else
    +      puts "#{yellow}Use tab to complete known tags"
    +    end
    +    opt[:tags] = Doing::Prompt.read_line(prompt: "Enter tag(s) to #{opt[:remove] ? 'remove' : 'add'}",
    +                                         completions: completions,
    +                                         default_response: '').to_tags
    +    raise UserCancelled, 'No tags provided' if opt[:tags].empty?
    +  end
    +
    +  items.each do |item|
    +    old_item = item.clone
    +    added = []
    +    removed = []
    +
    +    item.date = opt[:start_date] if opt[:start_date]
    +
    +    if opt[:autotag]
    +      new_title = autotag(item.title) if Doing.auto_tag
    +      if new_title == item.title
    +        logger.count(:skipped, level: :debug, message: '%count unchaged %items')
    +        # logger.debug('Autotag:', 'No changes')
    +      else
    +        logger.count(:added_tags)
    +        logger.write(items.count == 1 ? :info : :debug, 'Tagged:', new_title)
    +        item.title = new_title
    +      end
    +    else
    +      if opt[:done_date]
    +        done_date = opt[:done_date]
    +      elsif opt[:sequential]
    +        next_entry = next_item(item)
    +
    +        done_date = if next_entry.nil?
    +                      Time.now
    +                    else
    +                      next_entry.date - 60
    +                    end
    +      else
    +        done_date = item.calculate_end_date(opt)
    +      end
    +
    +      opt[:tags].each do |tag|
    +        if tag == 'done' && !item.should_finish?
    +
    +          Doing.logger.debug('Skipped:', "Item in never_finish: #{item.title}")
    +          logger.count(:skipped, level: :debug)
    +          next
    +        end
    +
    +        tag = tag.strip
    +
    +        if tag =~ /^(\S+)\((.*?)\)$/
    +          m = Regexp.last_match
    +          tag = m[1]
    +          opt[:value] ||= m[2]
    +        end
    +
    +        if tag =~ /^done$/ && opt[:date] && item.should_time?
    +          max_elapsed = Doing.setting('interaction.confirm_longer_than', 0)
    +          max_elapsed = max_elapsed.chronify_qty if max_elapsed.is_a?(String)
    +          elapsed = done_date - item.date
    +
    +          if max_elapsed.positive? && (elapsed > max_elapsed) && !opt[:took]
    +            puts boldwhite(item.title)
    +            human = elapsed.time_string(format: :natural)
    +            res = Prompt.yn(yellow("Did this actually take #{human}"), default_response: true)
    +            unless res
    +              new_elapsed = Prompt.enter_text('How long did it take?').chronify_qty
    +              raise InvalidTimeExpression, 'Unrecognized time span entry' unless new_elapsed > 0
    +
    +              opt[:took] = new_elapsed
    +              done_date = item.calculate_end_date(opt) if opt[:took]
    +            end
    +          end
    +        end
    +
    +        if opt[:remove] || opt[:rename] || opt[:value]
    +          rename_to = nil
    +
    +          if opt[:value]
    +            rename_to = tag
    +          elsif opt[:rename]
    +            rename_to = tag
    +            tag = opt[:rename]
    +          end
    +          old_title = item.title.dup
    +          force = opt[:value].nil? ? false : true
    +          item.title.tag!(tag, remove: opt[:remove], rename_to: rename_to, regex: opt[:regex], value: opt[:value], force: force)
    +          if old_title != item.title
    +            removed << tag
    +            added << rename_to if rename_to
    +          else
    +            logger.count(:skipped, level: :debug)
    +          end
    +        else
    +          old_title = item.title.dup
    +          should_date = opt[:date] && item.should_time?
    +          item.title.tag!('done', remove: true) if tag =~ /done/ && (!should_date || opt[:update])
    +          item.title.tag!(tag, value: should_date ? done_date.strftime('%F %R') : nil)
    +          added << tag if old_title != item.title
    +        end
    +      end
    +    end
    +
    +    logger.log_change(tags_added: added, tags_removed: removed, item: item, single: items.count == 1)
    +
    +    item.note.add(opt[:note]) if opt[:note]
    +
    +    if opt[:archive] && opt[:section] != 'Archive' && (opt[:count]).positive?
    +      item.move_to('Archive', label: true)
    +    elsif opt[:archive] && opt[:count].zero?
    +      logger.warn('Skipped:', 'Archiving is skipped when operating on all entries')
    +    end
    +
    +    item.expand_date_tags(Doing.setting('date_tags'))
    +    Hooks.trigger :post_entry_updated, self, item, old_item
    +  end
    +
    +  write(@doing_file)
    +end
    +
    @@ -4815,7 +8643,269 @@

    -

    + + + + + +
    +
    +
    +
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +78
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +130
    +131
    +132
    +133
    +134
    +135
    +136
    +137
    +138
    +
    +
    # File 'lib/doing/wwid/timers.rb', line 14
    +
    +def tag_times(format: :text, sort_by: :time, sort_order: :asc)
    +  return '' if @timers.empty?
    +
    +  @timers.delete('meanwhile')
    +
    +  max = @timers.keys.sort_by(&:length).reverse[0].length + 1
    +
    +  total = @timers.delete('All')
    +
    +  tags_data = @timers.delete_if { |_k, v| v.zero? }
    +  sorted_tags_data = if sort_by.normalize_tag_sort == :name
    +                       tags_data.sort_by { |k, _v| k }
    +                     else
    +                       tags_data.sort_by { |_k, v| v }
    +                     end
    +
    +  sorted_tags_data.reverse! if sort_order.normalize_order == :asc
    +  case format
    +  when :html
    +
    +    output = <<EOHEAD
    +      <table>
    +      <caption id="tagtotals">Tag Totals</caption>
    +      <colgroup>
    +      <col style="text-align:left;"/>
    +      <col style="text-align:left;"/>
    +      </colgroup>
    +      <thead>
    +      <tr>
    +        <th style="text-align:left;">project</th>
    +        <th style="text-align:left;">time</th>
    +      </tr>
    +      </thead>
    +      <tbody>
    +EOHEAD
    +    sorted_tags_data.reverse.each do |k, v|
    +      if v.positive?
    +        output += "<tr><td style='text-align:left;'>#{k}</td><td style='text-align:left;'>#{v.time_string(format: :clock)}</td></tr>\n"
    +      end
    +    end
    +    tail = <<EOTAIL
    +    <tr>
    +      <td style="text-align:left;" colspan="2"></td>
    +    </tr>
    +    </tbody>
    +    <tfoot>
    +    <tr>
    +      <td style="text-align:left;"><strong>Total</strong></td>
    +      <td style="text-align:left;">#{total.time_string(format: :clock)}</td>
    +    </tr>
    +    </tfoot>
    +    </table>
    +EOTAIL
    +    output + tail
    +  when :markdown
    +    pad = sorted_tags_data.map { |k, _| k }.group_by(&:size).max.last[0].length
    +    pad = 7 if pad < 7
    +    output = <<~EOHEADER
    +  | #{' ' * (pad - 7)}project | time     |
    +  | #{'-' * (pad - 1)}: | :------- |
    +    EOHEADER
    +    sorted_tags_data.reverse.each do |k, v|
    +      if v.positive?
    +        output += "| #{' ' * (pad - k.length)}#{k} | #{v.time_string(format: :clock)} |\n"
    +      end
    +    end
    +    tail = '[Tag Totals]'
    +    output + tail
    +  when :json
    +    output = []
    +    sorted_tags_data.reverse.each do |k, v|
    +      output << {
    +        'tag' => k,
    +        'seconds' => v,
    +        'formatted' => v.time_string(format: :clock)
    +      }
    +    end
    +    output
    +  when :human
    +    output = []
    +    sorted_tags_data.reverse.each do |k, v|
    +      spacer = ''
    +      (max - k.length).times do
    +        spacer += ' '
    +      end
    +      output.push("#{spacer}#{k}:#{v.time_string(format: :hm)}")
    +    end
    +
    +    header = '┏━━ Tag Totals '
    +    (max - 2).times { header += '' }
    +    header += ''
    +    footer = ''
    +    (max + 12).times { footer += '' }
    +    footer += ''
    +    divider = ''
    +    (max + 12).times { divider += '' }
    +    divider += ''
    +    output = output.empty? ? '' : "\n#{header}\n#{output.join("\n")}"
    +    output += "\n#{divider}"
    +    spacer = ''
    +    (max - 6).times do
    +      spacer += ' '
    +    end
    +    total_time = total.time_string(format: :hm)
    +    total = "#{spacer}total: "
    +    total += total_time
    +    total += ''
    +    output += "\n#{total}"
    +    output += "\n#{footer}"
    +    output
    +  else
    +    output = []
    +    sorted_tags_data.reverse.each do |k, v|
    +      spacer = ''
    +      (max - k.length).times do
    +        spacer += ' '
    +      end
    +      output.push("#{k}:#{spacer}#{v.time_string(format: :clock)}")
    +    end
    +
    +    output = output.empty? ? '' : "\n--- Tag Totals ---\n#{output.join("\n")}"
    +    output += "\n\nTotal tracked: #{total.time_string(format: :clock)}\n"
    +    output
    +  end
    +end
    +
    @@ -4890,7 +8980,117 @@

    -

    + + + + + +
    +
    +
    +
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +153
    +154
    +155
    +156
    +157
    +158
    +159
    +160
    +161
    +162
    +163
    +164
    +165
    +166
    +167
    +168
    +169
    +170
    +171
    +172
    +173
    +174
    +175
    +176
    +177
    +178
    +179
    +180
    +181
    +182
    +183
    +184
    +185
    +
    +
    # File 'lib/doing/wwid/display.rb', line 137
    +
    +def today(times = true, output = nil, opt)
    +  opt ||= {}
    +  opt[:totals] ||= false
    +  opt[:sort_tags] ||= false
    +
    +  cfg = Doing.setting('templates')
    +             .deep_merge(Doing.setting('templates.default'), {
    +                           extend_existing_arrays: true,
    +                           sort_merged_arrays: true
    +                         }).deep_merge({
    +                                         'wrap_width' => Doing.setting('wrap_width') || 0,
    +                                         'date_format' => Doing.setting('default_date_format'),
    +                                         'order' => Doing.setting('order') || :asc,
    +                                         'tags_color' => Doing.setting('tags_color'),
    +                                         'duration' => Doing.setting('duration'),
    +                                         'interval_format' => Doing.setting('interval_format')
    +                                       }, {
    +                                         extend_existing_arrays: true,
    +                                         sort_merged_arrays: true
    +                                       })
    +
    +  template = opt[:template] || cfg['template']
    +
    +  opt[:duration] ||= cfg['duration'] || false
    +  opt[:interval_format] ||= cfg['interval_format'] || 'text'
    +
    +  options = {
    +    after: opt[:after],
    +    before: opt[:before],
    +    count: 0,
    +    duration: opt[:duration],
    +    from: opt[:from],
    +    format: cfg['date_format'],
    +    interval_format: opt[:interval_format],
    +    only_timed: opt[:only_timed],
    +    order: cfg['order'] || :asc,
    +    output: output,
    +    section: opt[:section],
    +    sort_tags: opt[:sort_tags],
    +    template: template,
    +    times: times,
    +    today: true,
    +    totals: opt[:totals],
    +    wrap_width: cfg['wrap_width'],
    +    tags_color: cfg['tags_color'],
    +    config_template: opt[:config_template]
    +  }
    +  list_section(options)
    +end
    +
    @@ -4963,7 +9163,63 @@

    -

    + + + + + +
    +
    +
    +
    +359
    +360
    +361
    +362
    +363
    +364
    +365
    +366
    +367
    +368
    +369
    +370
    +371
    +372
    +373
    +374
    +375
    +376
    +377
    +378
    +379
    +380
    +
    +
    # File 'lib/doing/wwid/interactive.rb', line 359
    +
    +def verify_duration(date, finish_date, title: nil)
    +  max_elapsed = Doing.setting('interaction.confirm_longer_than', 0)
    +  max_elapsed = max_elapsed.chronify_qty if max_elapsed.is_a?(String)
    +  date = date.chronify(guess: :end, context: :today) if date.is_a?(String)
    +  finish_date = finish_date.chronify(guess: :end, context: :today) if finish_date.is_a?(String)
    +
    +  elapsed = finish_date - date
    +
    +  if max_elapsed.positive? && (elapsed > max_elapsed)
    +    puts boldwhite(title) if title
    +    human = elapsed.time_string(format: :natural)
    +    res = Prompt.yn(yellow("Did this entry actually take #{human}"), default_response: true)
    +    unless res
    +      new_elapsed = Prompt.enter_text('How long did it take?').chronify_qty
    +      raise InvalidTimeExpression, 'Unrecognized time span entry' unless new_elapsed.positive?
    +
    +      finish_date = date + new_elapsed if new_elapsed
    +    end
    +  end
    +
    +  finish_date
    +end
    +
    @@ -4975,7 +9231,33 @@

    -

    + + + + + +
    +
    +
    +
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +
    +
    # File 'lib/doing/wwid/wwid.rb', line 119
    +
    +def view_to_options(title)
    +  view = rename_view_keys(get_view(guess_view(title)))
    +  view.deep_merge(rename_view_keys(get_view(guess_view(view[:parent]), fallback: {}))) if view.key?(:parent)
    +  view.deep_merge(rename_view_keys(get_view(view[:config_template], fallback: {}))) if view.key?(:config_template)
    +  view.deep_merge(Doing.setting('templates.default').symbolize_keys)
    +  view
    +end
    +
    @@ -5014,7 +9296,25 @@

    -

    + + + + + +
    +
    +
    +
    +94
    +95
    +96
    +
    +
    # File 'lib/doing/wwid/wwid.rb', line 94
    +
    +def views
    +  Doing.setting('views') ? Doing.setting('views').keys : []
    +end
    +
    @@ -5057,7 +9357,39 @@

    -

    + + + + + +
    +
    +
    +
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +
    +
    # File 'lib/doing/wwid/filetools.rb', line 91
    +
    +def write(file = nil, backup: true)
    +  Hooks.trigger :pre_write, self, file
    +  output = combined_content
    +  if file.nil?
    +    $stdout.puts output
    +  else
    +    Util.write_to_file(file, output, backup: backup)
    +    run_after if Doing.setting('run_after')
    +  end
    +end
    +
    @@ -5147,7 +9479,57 @@

    -

    + + + + + +
    +
    +
    +
    +195
    +196
    +197
    +198
    +199
    +200
    +201
    +202
    +203
    +204
    +205
    +206
    +207
    +208
    +209
    +210
    +211
    +212
    +213
    +
    +
    # File 'lib/doing/wwid/display.rb', line 195
    +
    +def yesterday(section, times = nil, output = nil, opt)
    +  opt ||= {}
    +  opt[:totals] ||= false
    +  opt[:sort_tags] ||= false
    +  opt[:config_template] ||= 'today'
    +  opt[:yesterday] = true
    +
    +  section = guess_section(section)
    +  y = (Time.now - (60 * 60 * 24)).strftime('%Y-%m-%d')
    +  opt[:after] = "#{y} #{opt[:after]}" if opt[:after]
    +  opt[:before] = "#{y} #{opt[:before]}" if opt[:before]
    +
    +  opt[:output] = output
    +  opt[:section] = section
    +  opt[:times] = times
    +  opt[:count] = 0
    +
    +  list_section(opt)
    +end
    +
    @@ -5155,9 +9537,9 @@

    diff --git a/docs/doc/FalseClass.html b/docs/doc/FalseClass.html index 449f92a7..8618275a 100644 --- a/docs/doc/FalseClass.html +++ b/docs/doc/FalseClass.html @@ -6,7 +6,7 @@ Class: FalseClass - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -215,7 +215,25 @@

    - + + + + + +
    +
    +
    +
    +72
    +73
    +74
    +
    +
    # File 'lib/doing/good.rb', line 72
    +
    +def good?
    +  false
    +end
    +
    @@ -227,7 +245,25 @@

    -

    +

    + + + + +
    +
    +
    +
    +76
    +77
    +78
    +
    +
    # File 'lib/doing/good.rb', line 76
    +
    +def normalize_tag_sort
    +  :time
    +end
    +
    @@ -235,9 +271,9 @@

    diff --git a/docs/doc/GLI.html b/docs/doc/GLI.html index 264550af..0634bc69 100644 --- a/docs/doc/GLI.html +++ b/docs/doc/GLI.html @@ -6,7 +6,7 @@ Module: GLI - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -107,9 +107,9 @@

    Defined Under Namespace

    diff --git a/docs/doc/GLI/Commands.html b/docs/doc/GLI/Commands.html index 0591a825..97cd51d8 100644 --- a/docs/doc/GLI/Commands.html +++ b/docs/doc/GLI/Commands.html @@ -6,7 +6,7 @@ Module: GLI::Commands - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -107,9 +107,9 @@

    Defined Under Namespace

    diff --git a/docs/doc/GLI/Commands/Help.html b/docs/doc/GLI/Commands/Help.html index 67ee27de..bdc10dff 100644 --- a/docs/doc/GLI/Commands/Help.html +++ b/docs/doc/GLI/Commands/Help.html @@ -6,7 +6,7 @@ Class: GLI::Commands::Help - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -167,7 +167,65 @@

    -

    + + + + + +
    +
    +
    +
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +24
    +25
    +26
    +27
    +28
    +29
    +
    +
    # File 'lib/doing/help_monkey_patch.rb', line 7
    +
    +def show_help(global_options, options, arguments, out, error)
    +  Doing::Pager.paginate = true
    +
    +  command_finder = HelpModules::CommandFinder.new(@app, arguments, error)
    +  if options[:c]
    +    help_output = HelpModules::HelpCompletionFormat.new(@app, command_finder, arguments).format
    +    out.puts help_output unless help_output.nil?
    +  elsif arguments.empty? || options[:c]
    +    Doing::Pager.page HelpModules::GlobalHelpFormat.new(@app, @sorter, @text_wrapping_class).format
    +  else
    +    name = arguments.shift
    +    command = command_finder.find_command(name)
    +    unless command.nil?
    +      Doing::Pager.page HelpModules::CommandHelpFormat.new(
    +        command,
    +        @app,
    +        @sorter,
    +        @synopsis_formatter_class,
    +        @text_wrapping_class
    +      ).format
    +    end
    +  end
    +end
    +
    @@ -175,9 +233,9 @@

    diff --git a/docs/doc/GLI/Commands/MarkdownDocumentListener.html b/docs/doc/GLI/Commands/MarkdownDocumentListener.html index c069e656..005a83e0 100644 --- a/docs/doc/GLI/Commands/MarkdownDocumentListener.html +++ b/docs/doc/GLI/Commands/MarkdownDocumentListener.html @@ -6,7 +6,7 @@ Class: GLI::Commands::MarkdownDocumentListener - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -490,7 +490,41 @@

    -
    + + + + + +
    +
    +
    +
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +
    +
    # File 'lib/doing/markdown_document_listener.rb', line 9
    +
    +def initialize(_global_options, _options, _arguments, app)
    +  @exe = app.exe_name
    +  if File.exist?('COMMANDS.md') # Back up existing README
    +    FileUtils.mv('COMMANDS.md', 'COMMANDS.bak')
    +    $stderr.puts "Backing up existing COMMANDS.md"
    +  end
    +  @io = File.new('COMMANDS.md', 'w')
    +  @nest = '#'
    +  @arg_name_formatter = GLI::Commands::HelpModules::ArgNameFormatter.new
    +  @parent_command = []
    +end
    +
    @@ -509,7 +543,23 @@

    -

    + + + + + +
    +
    +
    +
    +21
    +22
    +
    +
    # File 'lib/doing/markdown_document_listener.rb', line 21
    +
    +def beginning
    +end
    +
    @@ -531,7 +581,43 @@

    -
    +

    + + + + +
    +
    +
    +
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +126
    +127
    +128
    +129
    +
    +
    # File 'lib/doing/markdown_document_listener.rb', line 118
    +
    +def command(name, aliases, desc, long_desc, arg_name, arg_options)
    +  @parent_command.push ([name] + aliases).join('|')
    +  arg_name_fmt = @arg_name_formatter.format(arg_name, arg_options, [])
    +  arg_name_fmt = " `#{arg_name_fmt.strip}`" if arg_name_fmt
    +  @io.puts header("`$ #{@exe}` <mark>`#{@parent_command.join(' ')}`</mark>#{arg_name_fmt}", 1)
    +  @io.puts
    +  @io.puts "*#{String(desc).strip}*"
    +  @io.puts
    +  cmd_desc = String(long_desc).strip.split("\n").map { |_| "> #{_}" }.join("\n")
    +  @io.puts "#{cmd_desc}\n\n" unless cmd_desc.length == 0
    +  increment_nest
    +end
    +
    @@ -543,7 +629,29 @@

    -

    + + + + + +
    +
    +
    +
    +111
    +112
    +113
    +114
    +115
    +
    +
    # File 'lib/doing/markdown_document_listener.rb', line 111
    +
    +def commands
    +  @io.puts header("Commands", 1)
    +  @io.puts
    +  increment_nest
    +end
    +
    @@ -565,7 +673,25 @@

    -
    +

    + + + + +
    +
    +
    +
    +139
    +140
    +141
    +
    +
    # File 'lib/doing/markdown_document_listener.rb', line 139
    +
    +def default_command(name)
    +  @io.puts "#### [Default Command] #{name}" unless name.nil?
    +end
    +
    @@ -587,7 +713,29 @@

    -
    +

    + + + + +
    +
    +
    +
    +132
    +133
    +134
    +135
    +136
    +
    +
    # File 'lib/doing/markdown_document_listener.rb', line 132
    +
    +def end_command(_name)
    +  @parent_command.pop
    +  decrement_nest
    +  @io.puts "* * * * * *\n\n" unless @nest.size > 2
    +end
    +
    @@ -599,7 +747,25 @@

    -

    + + + + + +
    +
    +
    +
    +143
    +144
    +145
    +
    +
    # File 'lib/doing/markdown_document_listener.rb', line 143
    +
    +def end_commands
    +  decrement_nest
    +end
    +
    @@ -611,7 +777,23 @@

    -

    + + + + + +
    +
    +
    +
    +108
    +109
    +
    +
    # File 'lib/doing/markdown_document_listener.rb', line 108
    +
    +def end_options
    +end
    +
    @@ -633,7 +815,59 @@

    -
    +

    + + + + +
    +
    +
    +
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +
    +
    # File 'lib/doing/markdown_document_listener.rb', line 25
    +
    +def ending
    +  if File.exist?('CREDITS.md')
    +    @io.puts IO.read('CREDITS.md')
    +    @io.puts
    +  end
    +
    +  if File.exist?('AUTHORS.md')
    +    @io.puts IO.read('AUTHORS.md')
    +    @io.puts
    +  end
    +
    +  if File.exist?('LICENSE.md')
    +    @io.puts IO.read('LICENSE.md')
    +    @io.puts
    +  end
    +  @io.puts
    +  @io.puts "Documentation generated #{Time.now.strftime('%Y-%m-%d %H:%M')}"
    +  @io.puts
    +  @io.close
    +end
    +
    @@ -655,7 +889,43 @@

    -
    +

    + + + + +
    +
    +
    +
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +
    +
    # File 'lib/doing/markdown_document_listener.rb', line 80
    +
    +def flag(name, aliases, desc, long_desc, default_value, arg_name, must_match, _type)
    +  invocations = ([name] + Array(aliases)).map { |_| "`" + add_dashes(_) + "`" }.join(' | ')
    +  usage = "#{invocations} #{arg_name || 'arg'}"
    +  @io.puts header(usage, 2)
    +  @io.puts
    +  @io.puts String(desc).strip
    +  @io.puts "\n*Default Value:* `#{default_value || 'None'}`\n" unless default_value.nil?
    +  @io.puts "\n*Must Match:* `#{must_match.to_s}`\n" unless must_match.nil?
    +  cmd_desc = String(long_desc).strip
    +  @io.puts "> #{cmd_desc}\n" unless cmd_desc.length == 0
    +  @io.puts
    +end
    +
    @@ -667,7 +937,35 @@

    -

    + + + + + +
    +
    +
    +
    +70
    +71
    +72
    +73
    +74
    +75
    +76
    +77
    +
    +
    # File 'lib/doing/markdown_document_listener.rb', line 70
    +
    +def options
    +  if @nest.size == 1
    +    @io.puts "## Global Options"
    +  else
    +    @io.puts header("Options", 1)
    +  end
    +  @io.puts
    +end
    +
    @@ -689,7 +987,31 @@

    -
    +

    + + + + +
    +
    +
    +
    +47
    +48
    +49
    +50
    +51
    +52
    +
    +
    # File 'lib/doing/markdown_document_listener.rb', line 47
    +
    +def program_desc(desc)
    +  @io.puts "# #{@exe} CLI"
    +  @io.puts
    +  @io.puts desc
    +  @io.puts
    +end
    +
    @@ -701,7 +1023,27 @@

    -

    + + + + + +
    +
    +
    +
    +54
    +55
    +56
    +57
    +
    +
    # File 'lib/doing/markdown_document_listener.rb', line 54
    +
    +def program_long_desc(desc)
    +  @io.puts "> #{desc}"
    +  @io.puts
    +end
    +
    @@ -723,7 +1065,45 @@

    -
    +

    + + + + +
    +
    +
    +
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +
    +
    # File 'lib/doing/markdown_document_listener.rb', line 94
    +
    +def switch(name, aliases, desc, long_desc, negatable)
    +  if negatable
    +    name = "[no-]#{name}" if name.to_s.length > 1
    +    aliases = aliases.map { |_|  _.to_s.length > 1 ? "[no-]#{_}" : _ }
    +  end
    +  invocations = ([name] + aliases).map { |_| "`" + add_dashes(_).strip + "`" }.join('|')
    +  @io.puts header("#{invocations}", 2)
    +  @io.puts
    +  @io.puts String(desc).strip
    +  cmd_desc = String(long_desc).strip
    +  @io.puts "\n> #{cmd_desc}\n" unless cmd_desc.length == 0
    +  @io.puts
    +end
    +
    @@ -745,7 +1125,37 @@

    -
    +

    + + + + +
    +
    +
    +
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +
    +
    # File 'lib/doing/markdown_document_listener.rb', line 60
    +
    +def version(version)
    +  @io.puts "*v#{version}*"
    +  @io.puts
    +  # Hacking in the overview file
    +  if File.exist?('OVERVIEW.md')
    +    @io.puts IO.read('OVERVIEW.md')
    +    @io.puts
    +  end
    +end
    +
    @@ -753,9 +1163,9 @@

    diff --git a/docs/doc/Hash.html b/docs/doc/Hash.html index 91f6dccc..4b5a6d7d 100644 --- a/docs/doc/Hash.html +++ b/docs/doc/Hash.html @@ -6,7 +6,7 @@ Class: Hash - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -482,7 +482,25 @@

    -

    + + + + + +
    +
    +
    +
    +37
    +38
    +39
    +
    +
    # File 'lib/doing/hash.rb', line 37
    +
    +def clone
    +  Marshal.load(Marshal.dump(self))
    +end
    +
    @@ -521,7 +539,35 @@

    -

    + + + + + +
    +
    +
    +
    +11
    +12
    +13
    +14
    +15
    +16
    +17
    +18
    +
    +
    # File 'lib/doing/hash.rb', line 11
    +
    +def deep_freeze
    +  chilled = {}
    +  each do |k, v|
    +    chilled[k] = v.is_a?(Hash) ? v.deep_freeze : v.freeze
    +  end
    +
    +  chilled.freeze
    +end
    +
    @@ -533,7 +579,25 @@

    -

    + + + + + +
    +
    +
    +
    +20
    +21
    +22
    +
    +
    # File 'lib/doing/hash.rb', line 20
    +
    +def deep_freeze!
    +  replace deep_thaw.deep_freeze
    +end
    +
    @@ -555,7 +619,7 @@

    -

    Examples:

    +

    Examples:

    {}.deep_set(['one', 'two'], 'value')
    @@ -598,7 +662,71 @@ 

    -

    +
    + + + + +
    +
    +
    +
    +100
    +101
    +102
    +103
    +104
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +118
    +119
    +120
    +121
    +122
    +123
    +124
    +125
    +
    +
    # File 'lib/doing/hash.rb', line 100
    +
    +def deep_set(path, value)
    +  if path.count == 1
    +    if value.nil? || (value.is_a?(String) && value =~ /^ *$/) || (value.is_a?(Array) && value.empty?)
    +      delete(path[0])
    +    else
    +      self[path[0]] = value
    +    end
    +  elsif value
    +    self.default_proc = ->(h, k) { h[k] = Hash.new(&h.default_proc) }
    +    dig(*path[0..-2])[path.fetch(-1)] = value
    +  else
    +    return self unless dig(*path)
    +
    +    dig(*path[0..-2]).delete(path.fetch(-1))
    +    path.pop
    +    cleaned = self
    +    path.each do |key|
    +      if cleaned[key].empty?
    +        cleaned.delete(key)
    +        break
    +      end
    +      cleaned = cleaned[key]
    +    end
    +    empty? ? nil : self
    +  end
    +end
    +

    @@ -610,7 +738,35 @@

    -

    + + + + + +
    +
    +
    +
    +24
    +25
    +26
    +27
    +28
    +29
    +30
    +31
    +
    +
    # File 'lib/doing/hash.rb', line 24
    +
    +def deep_thaw
    +  chilled = {}
    +  each do |k, v|
    +    chilled[k] = v.is_a?(Hash) ? v.deep_thaw : v.dup
    +  end
    +
    +  chilled.dup
    +end
    +
    @@ -622,7 +778,25 @@

    -

    + + + + + +
    +
    +
    +
    +33
    +34
    +35
    +
    +
    # File 'lib/doing/hash.rb', line 33
    +
    +def deep_thaw!
    +  replace deep_thaw
    +end
    +
    @@ -678,7 +852,29 @@

    -

    + + + + + +
    +
    +
    +
    +195
    +196
    +197
    +198
    +199
    +
    +
    # File 'lib/doing/hash.rb', line 195
    +
    +def delete_unless_key(key, to_delete)
    +  unless key?(key)
    +    to_delete.each { |k| delete(k) }
    +  end
    +end
    +
    @@ -700,7 +896,25 @@

    -
    +

    + + + + +
    +
    +
    +
    +155
    +156
    +157
    +
    +
    # File 'lib/doing/hash.rb', line 155
    +
    +def remove_empty
    +  delete_if { |k, v| !v.is_a?(FalseClass) && !v.good? }
    +end
    +
    @@ -774,7 +988,33 @@

    -

    + + + + + +
    +
    +
    +
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +
    +
    # File 'lib/doing/hash.rb', line 135
    +
    +def rename_key(old_key, new_key, keep: false)
    +  return unless key?(old_key)
    +
    +  self[new_key] = self[old_key]
    +  self[new_key.to_s] = self[old_key] if key?(new_key.to_s)
    +  delete(old_key) unless keep
    +end
    +
    @@ -815,7 +1055,25 @@

    -

    + + + + + +
    +
    +
    +
    +148
    +149
    +150
    +
    +
    # File 'lib/doing/hash.rb', line 148
    +
    +def rename_keys(*pairs)
    +  pairs.each { |p| rename_key(p[0], p[1]) }
    +end
    +
    @@ -858,7 +1116,33 @@

    -

    + + + + + +
    +
    +
    +
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +
    +
    # File 'lib/doing/hash.rb', line 49
    +
    +def stringify_keys
    +  each_with_object({}) do |(k, v), hsh|
    +    next if k.is_a?(Symbol) && key?(k.to_s)
    +
    +    hsh[k.to_s] = v.is_a?(Hash) ? v.stringify_keys : v
    +  end
    +end
    +
    @@ -898,7 +1182,41 @@

    -

    + + + + + +
    +
    +
    +
    +79
    +80
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +
    +
    # File 'lib/doing/hash.rb', line 79
    +
    +def stringify_values
    +  transform_values do |v|
    +    if v.is_a?(Hash)
    +      v.stringify_values
    +    elsif v.is_a?(Symbol)
    +      v.to_s
    +    else
    +      v
    +    end
    +  end
    +end
    +
    @@ -941,7 +1259,33 @@

    -

    + + + + + +
    +
    +
    +
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +
    +
    # File 'lib/doing/hash.rb', line 65
    +
    +def symbolize_keys
    +  each_with_object({}) do |(k, v), hsh|
    +    next if k.is_a?(String) && key?(k.to_sym)
    +
    +    hsh[k.to_sym] = v.is_a?(Hash) ? v.symbolize_keys : v
    +  end
    +end
    +
    @@ -953,7 +1297,37 @@

    -

    + + + + + +
    +
    +
    +
    +159
    +160
    +161
    +162
    +163
    +164
    +165
    +166
    +167
    +
    +
    # File 'lib/doing/hash.rb', line 159
    +
    +def tag_filter_to_options
    +  hsh = dup
    +  if hsh.key?(:tag_filter) && hsh[:tag_filter]
    +    hsh[:tags] = hsh[:tag_filter][:tags]
    +    hsh[:bool] = hsh[:tag_filter][:bool]
    +    hsh.delete(:tag_filter)
    +  end
    +  replace hsh
    +end
    +
    @@ -992,7 +1366,47 @@

    -

    + + + + + +
    +
    +
    +
    +174
    +175
    +176
    +177
    +178
    +179
    +180
    +181
    +182
    +183
    +184
    +185
    +186
    +187
    +
    +
    # File 'lib/doing/hash.rb', line 174
    +
    +def to_view
    +  hsh = symbolize_keys
    +  %i[x save c a s o h e editor m menu i interactive d delete t fuzzy time_filter sort_tags].each do |key|
    +    hsh.delete(key) if hsh.key?(key)
    +  end
    +
    +  hsh.delete_unless_key(:tag, %i[bool])
    +  hsh.delete_unless_key(:search, %i[exact case])
    +  hsh.rename_keys(%i[not negate], %i[tag tags])
    +  hsh.tag_filter_to_options
    +
    +  hsh = hsh.remove_empty.stringify_keys.stringify_values
    +  hsh.keys.sort.each_with_object({}) { |k, out| out[k] = hsh[k] }
    +end
    +
    @@ -1000,9 +1414,9 @@

    diff --git a/docs/doc/Numeric.html b/docs/doc/Numeric.html index 1d612d92..f84180ab 100644 --- a/docs/doc/Numeric.html +++ b/docs/doc/Numeric.html @@ -6,7 +6,7 @@ Class: Numeric - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -206,7 +206,25 @@

    - + + + + + +
    +
    +
    +
    +7
    +8
    +9
    +
    +
    # File 'lib/doing/good.rb', line 7
    +
    +def good?
    +  self >= 0
    +end
    +
    @@ -214,9 +232,9 @@

    diff --git a/docs/doc/Object.html b/docs/doc/Object.html index 871ce4d0..b7546148 100644 --- a/docs/doc/Object.html +++ b/docs/doc/Object.html @@ -6,7 +6,7 @@ Class: Object - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -185,7 +185,25 @@

    - + + + + + +
    +
    +
    +
    +20
    +21
    +22
    +
    +
    # File 'lib/doing/good.rb', line 20
    +
    +def good?
    +  !nil? && !self&.empty? || false
    +end
    +
    @@ -193,9 +211,9 @@

    diff --git a/docs/doc/PhraseParser.html b/docs/doc/PhraseParser.html index b5b57ade..39b26edf 100644 --- a/docs/doc/PhraseParser.html +++ b/docs/doc/PhraseParser.html @@ -6,7 +6,7 @@ Module: PhraseParser - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -105,9 +105,9 @@

    Defined Under Namespace

    diff --git a/docs/doc/PhraseParser/Operator.html b/docs/doc/PhraseParser/Operator.html index f5c0b6db..d734237b 100644 --- a/docs/doc/PhraseParser/Operator.html +++ b/docs/doc/PhraseParser/Operator.html @@ -6,7 +6,7 @@ Class: PhraseParser::Operator - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -154,7 +154,43 @@

    -

    + + + + + +
    +
    +
    +
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +
    +
    # File 'lib/doing/phrase_parser.rb', line 37
    +
    +def self.symbol(str)
    +  case str
    +  when '+'
    +    :must
    +  when '-'
    +    :must_not
    +  when nil
    +    :should
    +  else
    +    raise "Unknown operator: #{str}"
    +  end
    +end
    +
    @@ -162,9 +198,9 @@

    diff --git a/docs/doc/PhraseParser/PhraseClause.html b/docs/doc/PhraseParser/PhraseClause.html index 51f275f5..204b5ce0 100644 --- a/docs/doc/PhraseParser/PhraseClause.html +++ b/docs/doc/PhraseParser/PhraseClause.html @@ -6,7 +6,7 @@ Class: PhraseParser::PhraseClause - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -231,7 +231,27 @@

    -
    + + + + + +
    +
    +
    +
    +64
    +65
    +66
    +67
    +
    +
    # File 'lib/doing/phrase_parser.rb', line 64
    +
    +def initialize(operator, phrase)
    +  self.operator = Operator.symbol(operator)
    +  self.phrase = phrase
    +end
    +
    @@ -260,7 +280,25 @@

    -
    + + + + + +
    +
    +
    +
    +62
    +63
    +64
    +
    +
    # File 'lib/doing/phrase_parser.rb', line 62
    +
    +def operator
    +  @operator
    +end
    +
    @@ -284,7 +322,25 @@

    -
    + + + + + +
    +
    +
    +
    +62
    +63
    +64
    +
    +
    # File 'lib/doing/phrase_parser.rb', line 62
    +
    +def phrase
    +  @phrase
    +end
    +
    @@ -293,9 +349,9 @@

    diff --git a/docs/doc/PhraseParser/Query.html b/docs/doc/PhraseParser/Query.html index 60383514..90f345d6 100644 --- a/docs/doc/PhraseParser/Query.html +++ b/docs/doc/PhraseParser/Query.html @@ -6,7 +6,7 @@ Class: PhraseParser::Query - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -345,7 +345,31 @@

    -
    + + + + + +
    +
    +
    +
    +74
    +75
    +76
    +77
    +78
    +79
    +
    +
    # File 'lib/doing/phrase_parser.rb', line 74
    +
    +def initialize(clauses)
    +  grouped = clauses.chunk(&:operator).to_h
    +  self.should_clauses = grouped.fetch(:should, [])
    +  self.must_not_clauses = grouped.fetch(:must_not, [])
    +  self.must_clauses = grouped.fetch(:must, [])
    +end
    +
    @@ -374,7 +398,25 @@

    -
    + + + + + +
    +
    +
    +
    +72
    +73
    +74
    +
    +
    # File 'lib/doing/phrase_parser.rb', line 72
    +
    +def must_clauses
    +  @must_clauses
    +end
    +
    @@ -398,7 +440,25 @@

    -
    + + + + + +
    +
    +
    +
    +72
    +73
    +74
    +
    +
    # File 'lib/doing/phrase_parser.rb', line 72
    +
    +def must_not_clauses
    +  @must_not_clauses
    +end
    +
    @@ -422,7 +482,25 @@

    -
    + + + + + +
    +
    +
    +
    +72
    +73
    +74
    +
    +
    # File 'lib/doing/phrase_parser.rb', line 72
    +
    +def should_clauses
    +  @should_clauses
    +end
    +
    @@ -441,7 +519,39 @@

    -

    + + + + + +
    +
    +
    +
    +105
    +106
    +107
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +
    +
    # File 'lib/doing/phrase_parser.rb', line 105
    +
    +def clause_to_query(clause)
    +  case clause
    +  when TermClause
    +    match(clause.term)
    +  when PhraseClause
    +    match_phrase(clause.phrase)
    +  else
    +    raise "Unknown clause type: #{clause}"
    +  end
    +end
    +
    @@ -453,7 +563,25 @@

    -

    + + + + + +
    +
    +
    +
    +116
    +117
    +118
    +
    +
    # File 'lib/doing/phrase_parser.rb', line 116
    +
    +def match(term)
    +  term
    +end
    +
    @@ -465,7 +593,25 @@

    -

    + + + + + +
    +
    +
    +
    +120
    +121
    +122
    +
    +
    # File 'lib/doing/phrase_parser.rb', line 120
    +
    +def match_phrase(phrase)
    +  phrase
    +end
    +
    @@ -477,7 +623,65 @@

    -

    + + + + + +
    +
    +
    +
    +81
    +82
    +83
    +84
    +85
    +86
    +87
    +88
    +89
    +90
    +91
    +92
    +93
    +94
    +95
    +96
    +97
    +98
    +99
    +100
    +101
    +102
    +103
    +
    +
    # File 'lib/doing/phrase_parser.rb', line 81
    +
    +def to_elasticsearch
    +  query = {}
    +
    +  if should_clauses.any?
    +    query[:should] = should_clauses.map do |clause|
    +      clause_to_query(clause)
    +    end
    +  end
    +
    +  if must_clauses.any?
    +    query[:must] = must_clauses.map do |clause|
    +      clause_to_query(clause)
    +    end
    +  end
    +
    +  if must_not_clauses.any?
    +    query[:must_not] = must_not_clauses.map do |clause|
    +      clause_to_query(clause)
    +    end
    +  end
    +
    +  query
    +end
    +
    @@ -485,9 +689,9 @@

    diff --git a/docs/doc/PhraseParser/QueryParser.html b/docs/doc/PhraseParser/QueryParser.html index b8a66ec8..f17b80de 100644 --- a/docs/doc/PhraseParser/QueryParser.html +++ b/docs/doc/PhraseParser/QueryParser.html @@ -6,7 +6,7 @@ Class: PhraseParser::QueryParser - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -126,9 +126,9 @@

    Overview

    diff --git a/docs/doc/PhraseParser/QueryTransformer.html b/docs/doc/PhraseParser/QueryTransformer.html index c6c436d4..428b6d48 100644 --- a/docs/doc/PhraseParser/QueryTransformer.html +++ b/docs/doc/PhraseParser/QueryTransformer.html @@ -6,7 +6,7 @@ Class: PhraseParser::QueryTransformer - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -114,9 +114,9 @@ diff --git a/docs/doc/PhraseParser/TermClause.html b/docs/doc/PhraseParser/TermClause.html index e7e304d3..8a9763c9 100644 --- a/docs/doc/PhraseParser/TermClause.html +++ b/docs/doc/PhraseParser/TermClause.html @@ -6,7 +6,7 @@ Class: PhraseParser::TermClause - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -221,7 +221,27 @@

    -
    + + + + + +
    +
    +
    +
    +54
    +55
    +56
    +57
    +
    +
    # File 'lib/doing/phrase_parser.rb', line 54
    +
    +def initialize(operator, term)
    +  self.operator = Operator.symbol(operator)
    +  self.term = term
    +end
    +
    @@ -250,7 +270,25 @@

    -
    + + + + + +
    +
    +
    +
    +52
    +53
    +54
    +
    +
    # File 'lib/doing/phrase_parser.rb', line 52
    +
    +def operator
    +  @operator
    +end
    +
    @@ -274,7 +312,25 @@

    -
    + + + + + +
    +
    +
    +
    +52
    +53
    +54
    +
    +
    # File 'lib/doing/phrase_parser.rb', line 52
    +
    +def term
    +  @term
    +end
    +
    @@ -283,9 +339,9 @@

    diff --git a/docs/doc/Status.html b/docs/doc/Status.html index 3b6b6ab9..cb683476 100644 --- a/docs/doc/Status.html +++ b/docs/doc/Status.html @@ -6,7 +6,7 @@ Module: Status - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -248,7 +248,25 @@

    -

    + + + + + +
    +
    +
    +
    +25
    +26
    +27
    +
    +
    # File 'lib/doing/cli_status.rb', line 25
    +
    +def clear
    +  $stderr.print format("\r#{esc['kill']}")
    +end
    +
    @@ -260,7 +278,25 @@

    -

    + + + + + +
    +
    +
    +
    +2
    +3
    +4
    +
    +
    # File 'lib/doing/cli_status.rb', line 2
    +
    +def cols
    +  @cols ||= `tput cols`.strip.to_i
    +end
    +
    @@ -272,7 +308,109 @@

    -

    + + + + + +
    +
    +
    +
    +29
    +30
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +47
    +48
    +49
    +50
    +51
    +52
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +67
    +68
    +69
    +70
    +71
    +72
    +73
    +
    +
    # File 'lib/doing/cli_status.rb', line 29
    +
    +def esc
    +  e = {}
    +  e['kill'] = "\033[2K"
    +  e['reset'] = "\033[A\033[2K"
    +  e['black'] = "\033[0;0;30m"
    +  e['red'] = "\033[0;0;31m"
    +  e['green'] = "\033[0;0;32m"
    +  e['yellow'] = "\033[0;0;33m"
    +  e['blue'] = "\033[0;0;34m"
    +  e['magenta'] = "\033[0;0;35m"
    +  e['cyan'] = "\033[0;0;36m"
    +  e['white'] = "\033[0;0;37m"
    +  e['bgblack'] = "\033[40m"
    +  e['bgred'] = "\033[41m"
    +  e['bggreen'] = "\033[42m"
    +  e['bgyellow'] = "\033[43m"
    +  e['bgblue'] = "\033[44m"
    +  e['bgmagenta'] = "\033[45m"
    +  e['bgcyan'] = "\033[46m"
    +  e['bgwhite'] = "\033[47m"
    +  e['boldblack'] = "\033[1;30m"
    +  e['boldred'] = "\033[1;31m"
    +  e['boldgreen'] = "\033[0;1;32m"
    +  e['boldyellow'] = "\033[0;1;33m"
    +  e['boldblue'] = "\033[0;1;34m"
    +  e['boldmagenta'] = "\033[0;1;35m"
    +  e['boldcyan'] = "\033[0;1;36m"
    +  e['boldwhite'] = "\033[0;1;37m"
    +  e['boldbgblack'] = "\033[1;40m"
    +  e['boldbgred'] = "\033[1;41m"
    +  e['boldbggreen'] = "\033[1;42m"
    +  e['boldbgyellow'] = "\033[1;43m"
    +  e['boldbgblue'] = "\033[1;44m"
    +  e['boldbgmagenta'] = "\033[1;45m"
    +  e['boldbgcyan'] = "\033[1;46m"
    +  e['boldbgwhite'] = "\033[1;47m"
    +  e['softpurple'] = "\033[0;35;40m"
    +  e['hotpants'] = "\033[7;34;40m"
    +  e['knightrider'] = "\033[7;30;40m"
    +  e['flamingo'] = "\033[7;31;47m"
    +  e['yeller'] = "\033[1;37;43m"
    +  e['whiteboard'] = "\033[1;30;47m"
    +  e['default'] = "\033[0;39m"
    +  e
    +end
    +
    @@ -284,7 +422,25 @@

    -

    + + + + + +
    +
    +
    +
    +21
    +22
    +23
    +
    +
    # File 'lib/doing/cli_status.rb', line 21
    +
    +def msg(msg, reset: true, color: 'green', end_char: "\n")
    +  $stderr.print format("#{esc['kill']}#{esc[color]}%s#{esc['default']}%s", msg, reset ? "\r" : end_char)
    +end
    +
    @@ -296,7 +452,39 @@

    -

    + + + + + +
    +
    +
    +
    +6
    +7
    +8
    +9
    +10
    +11
    +12
    +13
    +14
    +15
    +
    +
    # File 'lib/doing/cli_status.rb', line 6
    +
    +def progress(msg, idx, total, tail = [])
    +  status_width = format("> %s [%#{total.to_s.length}d/%d]: ", msg, 0, total).length
    +  max_width = cols - status_width
    +  if tail.is_a? Array
    +    tail.shift while tail.join(', ').length + 3 > max_width
    +    tail = tail.join(', ')
    +  end
    +  tail.ltrunc!(max_width)
    +  $stderr.print format("#{esc['kill']}#{esc['boldyellow']}> #{esc['boldgreen']}%s #{esc['white']}[#{esc['boldwhite']}%#{@commands.count.to_s.length}d#{esc['boldblack']}/#{esc['boldyellow']}%d#{esc['white']}]: #{esc['boldcyan']}%s#{esc['default']}\r", msg, idx, total, tail)
    +end
    +
    @@ -308,7 +496,25 @@

    -

    + + + + + +
    +
    +
    +
    +17
    +18
    +19
    +
    +
    # File 'lib/doing/cli_status.rb', line 17
    +
    +def status(msg, reset: true, end_char: "\n")
    +  $stderr.print format("#{esc['kill']}#{esc['boldyellow']}> #{esc['whiteboard']}%s#{esc['default']}%s", msg, reset ? "\r" : end_char)
    +end
    +
    @@ -316,9 +522,9 @@

    diff --git a/docs/doc/String.html b/docs/doc/String.html index ea013de5..44d237ec 100644 --- a/docs/doc/String.html +++ b/docs/doc/String.html @@ -6,7 +6,7 @@ Class: String - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -457,7 +457,25 @@

    - + + + + + +
    +
    +
    +
    +46
    +47
    +48
    +
    +
    # File 'lib/doing/good.rb', line 46
    +
    +def good?
    +  !strip.empty?
    +end
    +
    @@ -497,7 +515,119 @@

    -

    + + + + + +
    +
    +
    +
    +135
    +136
    +137
    +138
    +139
    +140
    +141
    +142
    +143
    +144
    +145
    +146
    +147
    +148
    +149
    +150
    +151
    +152
    +153
    +154
    +155
    +156
    +157
    +158
    +159
    +160
    +161
    +162
    +163
    +164
    +165
    +166
    +167
    +168
    +169
    +170
    +171
    +172
    +173
    +174
    +175
    +176
    +177
    +178
    +179
    +180
    +181
    +182
    +183
    +184
    +
    +
    # File 'lib/doing/colors.rb', line 135
    +
    +def last_color_code
    +  m = scan(ESCAPE_REGEX)
    +
    +  em = ['0']
    +  fg = nil
    +  bg = nil
    +  rgbf = nil
    +  rgbb = nil
    +
    +  m.each do |c|
    +    case c
    +    when '0'
    +      em = ['0']
    +      fg, bg, rgbf, rgbb = nil
    +    when /^[34]8/
    +      case c
    +      when /^3/
    +        fg = nil
    +        rgbf = c
    +      when /^4/
    +        bg = nil
    +        rgbb = c
    +      end
    +    else
    +      c.split(/;/).each do |i|
    +        x = i.to_i
    +        if x <= 9
    +          em << x
    +        elsif x >= 30 && x <= 39
    +          rgbf = nil
    +          fg = x
    +        elsif x >= 40 && x <= 49
    +          rgbb = nil
    +          bg = x
    +        elsif x >= 90 && x <= 97
    +          rgbf = nil
    +          fg = x
    +        elsif x >= 100 && x <= 107
    +          rgbb = nil
    +          bg = x
    +        end
    +      end
    +    end
    +  end
    +
    +  escape = "\e[#{em.join(';')}m"
    +  escape += "\e[#{rgbb}m" if rgbb
    +  escape += "\e[#{rgbf}m" if rgbf
    +  escape + "\e[#{[fg, bg].delete_if(&:nil?).join(';')}m"
    +end
    +
    @@ -538,7 +668,25 @@

    -

    + + + + + +
    +
    +
    +
    +126
    +127
    +128
    +
    +
    # File 'lib/doing/colors.rb', line 126
    +
    +def normalize_color
    +  gsub(/_/, '').sub(/bright/i, 'bold').sub(/bgbold/, 'boldbg')
    +end
    +
    @@ -550,7 +698,25 @@

    -

    +

    + + + + +
    +
    +
    +
    +6
    +7
    +8
    +
    +
    # File 'lib/doing/completion/fig_completion.rb', line 6
    +
    +def sanitize
    +  gsub(/"/, '\"')
    +end
    +
    @@ -589,7 +755,33 @@

    -

    + + + + + +
    +
    +
    +
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +
    +
    # File 'lib/doing/string/string.rb', line 33
    +
    +def utf8
    +  if String.method_defined? :force_encoding
    +    dup.force_encoding('utf-8')
    +  else
    +    self
    +  end
    +end
    +
    @@ -628,7 +820,25 @@

    -

    + + + + + +
    +
    +
    +
    +24
    +25
    +26
    +
    +
    # File 'lib/doing/string/string.rb', line 24
    +
    +def valid_id?
    +  strip =~ /^[a-z0-9]{32}$/ ? true : false
    +end
    +
    @@ -671,7 +881,39 @@

    -

    + + + + + +
    +
    +
    +
    +108
    +109
    +110
    +111
    +112
    +113
    +114
    +115
    +116
    +117
    +
    +
    # File 'lib/doing/colors.rb', line 108
    +
    +def validate_color
    +  valid_color = nil
    +  compiled = ''
    +  normalize_color.split('').each do |char|
    +    compiled += char
    +    valid_color = compiled if Color.attributes.include?(compiled.to_sym) || compiled =~ /^([fb]g?)?#([a-f0-9]{6})$/i
    +  end
    +
    +  valid_color
    +end
    +
    @@ -679,9 +921,9 @@

    diff --git a/docs/doc/Symbol.html b/docs/doc/Symbol.html index 3aa78a9b..5b554852 100644 --- a/docs/doc/Symbol.html +++ b/docs/doc/Symbol.html @@ -6,7 +6,7 @@ Class: Symbol - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -127,9 +127,9 @@

    Methods included from - Generated on Tue Mar 26 11:00:42 2024 by + Generated on Sun Dec 1 11:01:05 2024 by yard - 0.9.36 (ruby-3.2.0). + 0.9.37 (ruby-3.3.0). diff --git a/docs/doc/Time.html b/docs/doc/Time.html index cd1fe20f..f282229b 100644 --- a/docs/doc/Time.html +++ b/docs/doc/Time.html @@ -6,7 +6,7 @@ Class: Time - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -264,7 +264,25 @@

    - + + + + + +
    +
    +
    +
    +33
    +34
    +35
    +
    +
    # File 'lib/doing/good.rb', line 33
    +
    +def good?
    +  !nil?
    +end
    +
    @@ -322,7 +340,51 @@

    -

    + + + + + +
    +
    +
    +
    +31
    +32
    +33
    +34
    +35
    +36
    +37
    +38
    +39
    +40
    +41
    +42
    +43
    +44
    +45
    +46
    +
    +
    # File 'lib/doing/time.rb', line 31
    +
    +def humanize(seconds)
    +  s = seconds
    +  m = (s / 60).floor
    +  s = (s % 60).floor
    +  h = (m / 60).floor
    +  m = (m % 60).floor
    +  d = (h / 24).floor
    +  h = h % 24
    +
    +  output = []
    +  output.push("#{d} #{'day'.to_p(d)}") if d.positive?
    +  output.push("#{h} #{'hour'.to_p(h)}") if h.positive?
    +  output.push("#{m} #{'minute'.to_p(m)}") if m.positive?
    +  output.push("#{s} #{'second'.to_p(s)}") if s.positive?
    +  output.join(', ')
    +end
    +
    @@ -364,7 +426,41 @@

    -

    + + + + + +
    +
    +
    +
    +13
    +14
    +15
    +16
    +17
    +18
    +19
    +20
    +21
    +22
    +23
    +
    +
    # File 'lib/doing/time.rb', line 13
    +
    +def relative_date
    +  if self > Date.today.to_time
    +    strftime(Doing.setting('shortdate_format.today', '%_I:%M%P', exact: true))
    +  elsif self > (Date.today - 6).to_time
    +    strftime(Doing.setting('shortdate_format.this_week', '%a %_I:%M%P', exact: true))
    +  elsif year == Date.today.year || (year + 1 == Date.today.year && month > Date.today.month)
    +    strftime(Doing.setting('shortdate_format.this_month', '%m/%d %_I:%M%P', exact: true))
    +  else
    +    strftime(Doing.setting('shortdate_format.older', '%m/%d/%y %_I:%M%P', exact: true))
    +  end
    +end
    +
    @@ -403,7 +499,47 @@

    -

    + + + + + +
    +
    +
    +
    +53
    +54
    +55
    +56
    +57
    +58
    +59
    +60
    +61
    +62
    +63
    +64
    +65
    +66
    +
    +
    # File 'lib/doing/time.rb', line 53
    +
    +def time_ago
    +  if self > Date.today.to_time
    +    output = humanize(Time.now - self)
    +    "#{output} ago"
    +  elsif self > (Date.today - 1).to_time
    +    "Yesterday at #{strftime('%_I:%M:%S%P')}"
    +  elsif self > (Date.today - 6).to_time
    +    strftime('%a %I:%M:%S%P')
    +  elsif self.year == Date.today.year
    +    strftime('%m/%d %I:%M:%S%P')
    +  else
    +    strftime('%m/%d/%Y %I:%M:%S%P')
    +  end
    +end
    +
    @@ -411,9 +547,9 @@

    diff --git a/docs/doc/TrueClass.html b/docs/doc/TrueClass.html index c46e4914..ff941f8f 100644 --- a/docs/doc/TrueClass.html +++ b/docs/doc/TrueClass.html @@ -6,7 +6,7 @@ Class: TrueClass - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -215,7 +215,25 @@

    - + + + + + +
    +
    +
    +
    +89
    +90
    +91
    +
    +
    # File 'lib/doing/good.rb', line 89
    +
    +def good?
    +  true
    +end
    +
    @@ -227,7 +245,25 @@

    -

    +

    + + + + +
    +
    +
    +
    +93
    +94
    +95
    +
    +
    # File 'lib/doing/good.rb', line 93
    +
    +def normalize_tag_sort
    +  :name
    +end
    +
    @@ -235,9 +271,9 @@

    diff --git a/docs/doc/_index.html b/docs/doc/_index.html index 2689f430..cbdbf4bd 100644 --- a/docs/doc/_index.html +++ b/docs/doc/_index.html @@ -4,7 +4,7 @@ - Documentation by YARD 0.9.36 + Documentation by YARD 0.9.37 @@ -52,7 +52,7 @@
    -

    Documentation by YARD 0.9.36

    +

    Documentation by YARD 0.9.37

    Alphabetic Index

    @@ -961,9 +961,9 @@

    Namespace Listing A-Z

    diff --git a/docs/doc/class_list.html b/docs/doc/class_list.html index f604d8de..3b4ef052 100644 --- a/docs/doc/class_list.html +++ b/docs/doc/class_list.html @@ -1,5 +1,5 @@ - + @@ -38,12 +38,15 @@

    Class List

    - + diff --git a/docs/doc/css/full_list.css b/docs/doc/css/full_list.css index fa359824..6eef5e4a 100644 --- a/docs/doc/css/full_list.css +++ b/docs/doc/css/full_list.css @@ -20,8 +20,8 @@ h1 { padding: 12px 10px; padding-bottom: 0; margin: 0; font-size: 1.4em; } #content.insearch #noresults { margin-left: 7px; } li.collapsed ul { display: none; } li a.toggle { cursor: default; position: relative; left: -5px; top: 4px; text-indent: -999px; width: 10px; height: 9px; margin-left: -10px; display: block; float: left; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK8AAACvABQqw0mAAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTM5jWRgMAAAAVdEVYdENyZWF0aW9uIFRpbWUAMy8xNC8wOeNZPpQAAAE2SURBVDiNrZTBccIwEEXfelIAHUA6CZ24BGaWO+FuzZAK4k6gg5QAdGAq+Bxs2Yqx7BzyL7Llp/VfzZeQhCTc/ezuGzKKnKSzpCxXJM8fwNXda3df5RZETlIt6YUzSQDs93sl8w3wBZxCCE10GM1OcWbWjB2mWgEH4Mfdyxm3PSepBHibgQE2wLe7r4HjEidpnXMYdQPKEMJcsZ4zs2POYQOcaPfwMVOo58zsAdMt18BuoVDPxUJRacELbXv3hUIX2vYmOUvi8C8ydz/ThjXrqKqqLbDIAdsCKBd+Wo7GWa7o9qzOQHVVVXeAbs+yHHCH4aTsaCOQqunmUy1yBUAXkdMIfMlgF5EXLo2OpV/c/Up7jG4hhHcYLgWzAZXUc2b2ixsfvc/RmNNfOXD3Q/oeL9axJE1yT9IOoUu6MGUkAAAAAElFTkSuQmCC) no-repeat bottom left; } -li.collapsed a.toggle { opacity: 0.5; cursor: default; background-position: top left; } -li { color: #888; cursor: pointer; } +li.collapsed a.toggle { cursor: default; background-position: top left; } +li { color: #666; cursor: pointer; } li.deprecated { text-decoration: line-through; font-style: italic; } li.odd { background: #f0f0f0; } li.even { background: #fafafa; } @@ -47,7 +47,7 @@ li small { display: block; font-size: 0.8em; } li small:before { content: ""; } li small:after { content: ""; } li small.search_info { display: none; } -#search { width: 170px; position: static; margin: 3px; margin-left: 10px; font-size: 0.9em; color: #888; padding-left: 0; padding-right: 24px; } +#search { width: 170px; position: static; margin: 3px; margin-left: 10px; font-size: 0.9em; color: #666; padding-left: 0; padding-right: 24px; } #content.insearch #search { background-position: center right; } #search input { width: 110px; } diff --git a/docs/doc/css/style.css b/docs/doc/css/style.css index eb0dbc86..f169a651 100644 --- a/docs/doc/css/style.css +++ b/docs/doc/css/style.css @@ -82,6 +82,11 @@ body { #search { display: none; } } +@media (max-width: 320px) { + body { height: 100%; overflow: hidden; overflow-wrap: break-word; } + #main { height: 100%; overflow: auto; } +} + #main img { max-width: 100%; } h1 { font-size: 25px; margin: 1em 0 0.5em; padding-top: 4px; border-top: 1px dotted #d5d5d5; } h1.noborder { border-top: 0px; margin-top: 0; padding-top: 4px; } @@ -106,6 +111,7 @@ h2 small a { position: relative; padding: 2px 7px; } +a { font-weight: 550; } .clear { clear: both; } .inline { display: inline; } .inline p:first-child { display: inline; } diff --git a/docs/doc/file.README.html b/docs/doc/file.README.html index c58f78ac..97f52f4a 100644 --- a/docs/doc/file.README.html +++ b/docs/doc/file.README.html @@ -6,7 +6,7 @@ File: README - — Documentation by YARD 0.9.36 + — Documentation by YARD 0.9.37 @@ -153,9 +153,9 @@

    Changelog

    diff --git a/docs/doc/file_list.html b/docs/doc/file_list.html index 2b6df404..05465dfd 100644 --- a/docs/doc/file_list.html +++ b/docs/doc/file_list.html @@ -1,5 +1,5 @@ - + @@ -38,7 +38,10 @@

    File List

    - +
      diff --git a/docs/doc/frames.html b/docs/doc/frames.html index 53734c22..6586005f 100644 --- a/docs/doc/frames.html +++ b/docs/doc/frames.html @@ -2,7 +2,7 @@ - Documentation by YARD 0.9.36 + Documentation by YARD 0.9.37