doing 2.0.25 → 2.1.0pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.yardoc/checksums +18 -15
  3. data/.yardoc/object_types +0 -0
  4. data/.yardoc/objects/root.dat +0 -0
  5. data/CHANGELOG.md +28 -0
  6. data/Gemfile.lock +8 -1
  7. data/README.md +1 -1
  8. data/Rakefile +23 -4
  9. data/bin/doing +205 -127
  10. data/doc/Array.html +354 -1
  11. data/doc/Doing/Color.html +104 -92
  12. data/doc/Doing/Completion.html +216 -0
  13. data/doc/Doing/Configuration.html +340 -5
  14. data/doc/Doing/Content.html +229 -0
  15. data/doc/Doing/Errors/DoingNoTraceError.html +1 -1
  16. data/doc/Doing/Errors/DoingRuntimeError.html +1 -1
  17. data/doc/Doing/Errors/DoingStandardError.html +1 -1
  18. data/doc/Doing/Errors/EmptyInput.html +1 -1
  19. data/doc/Doing/Errors/NoResults.html +1 -1
  20. data/doc/Doing/Errors/PluginException.html +1 -1
  21. data/doc/Doing/Errors/UserCancelled.html +1 -1
  22. data/doc/Doing/Errors/WrongCommand.html +1 -1
  23. data/doc/Doing/Errors.html +1 -1
  24. data/doc/Doing/Hooks.html +1 -1
  25. data/doc/Doing/Item.html +337 -49
  26. data/doc/Doing/Items.html +444 -35
  27. data/doc/Doing/LogAdapter.html +139 -51
  28. data/doc/Doing/Note.html +253 -22
  29. data/doc/Doing/Pager.html +74 -36
  30. data/doc/Doing/Plugins.html +1 -1
  31. data/doc/Doing/Prompt.html +674 -0
  32. data/doc/Doing/Section.html +354 -0
  33. data/doc/Doing/Util.html +57 -1
  34. data/doc/Doing/WWID.html +477 -670
  35. data/doc/Doing/WWIDFile.html +398 -0
  36. data/doc/Doing.html +5 -5
  37. data/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
  38. data/doc/GLI/Commands.html +1 -1
  39. data/doc/GLI.html +1 -1
  40. data/doc/Hash.html +97 -1
  41. data/doc/Status.html +37 -3
  42. data/doc/String.html +599 -23
  43. data/doc/Symbol.html +3 -3
  44. data/doc/Time.html +1 -1
  45. data/doc/_index.html +22 -1
  46. data/doc/class_list.html +1 -1
  47. data/doc/file.README.html +8 -2
  48. data/doc/index.html +8 -2
  49. data/doc/method_list.html +453 -173
  50. data/doc/top-level-namespace.html +1 -1
  51. data/doing.gemspec +3 -0
  52. data/doing.rdoc +40 -12
  53. data/example_plugin.rb +3 -3
  54. data/lib/completion/_doing.zsh +1 -1
  55. data/lib/completion/doing.bash +8 -8
  56. data/lib/completion/doing.fish +1 -1
  57. data/lib/doing/array.rb +36 -0
  58. data/lib/doing/colors.rb +70 -66
  59. data/lib/doing/completion.rb +6 -0
  60. data/lib/doing/configuration.rb +69 -28
  61. data/lib/doing/hash.rb +37 -0
  62. data/lib/doing/item.rb +77 -12
  63. data/lib/doing/items.rb +125 -0
  64. data/lib/doing/log_adapter.rb +55 -3
  65. data/lib/doing/note.rb +53 -1
  66. data/lib/doing/pager.rb +49 -38
  67. data/lib/doing/plugins/export/markdown_export.rb +4 -4
  68. data/lib/doing/plugins/export/template_export.rb +2 -2
  69. data/lib/doing/plugins/import/calendar_import.rb +4 -4
  70. data/lib/doing/plugins/import/doing_import.rb +5 -7
  71. data/lib/doing/plugins/import/timing_import.rb +3 -3
  72. data/lib/doing/prompt.rb +206 -0
  73. data/lib/doing/section.rb +30 -0
  74. data/lib/doing/string.rb +103 -27
  75. data/lib/doing/util.rb +14 -6
  76. data/lib/doing/version.rb +1 -1
  77. data/lib/doing/wwid.rb +306 -621
  78. data/lib/doing.rb +6 -2
  79. data/lib/examples/plugins/capture_thing_import.rb +162 -0
  80. metadata +73 -5
  81. data/lib/doing/wwidfile.rb +0 -117
data/lib/doing/hash.rb CHANGED
@@ -27,5 +27,42 @@ module Doing
27
27
  def symbolize_keys
28
28
  each_with_object({}) { |(k, v), hsh| hsh[k.to_sym] = v.is_a?(Hash) ? v.symbolize_keys : v }
29
29
  end
30
+
31
+ # Set a nested hash value using an array
32
+ #
33
+ # @example `{}.deep_set(['one', 'two'], 'value')`
34
+ # @example `=> { 'one' => { 'two' => 'value' } }
35
+ #
36
+ # @param path [Array] key path
37
+ # @param value The value
38
+ #
39
+ def deep_set(path, value)
40
+ if path.count == 1
41
+ if value
42
+ self[path[0]] = value
43
+ else
44
+ delete(path[0])
45
+ end
46
+ else
47
+ if value
48
+ self.default_proc = ->(h, k) { h[k] = Hash.new(&h.default_proc) }
49
+ dig(*path[0..-2])[path.fetch(-1)] = value
50
+ else
51
+ return self unless dig(*path)
52
+
53
+ dig(*path[0..-2]).delete(path.fetch(-1))
54
+ path.pop
55
+ cleaned = self
56
+ path.each do |key|
57
+ if cleaned[key].empty?
58
+ cleaned.delete(key)
59
+ break
60
+ end
61
+ cleaned = cleaned[key]
62
+ end
63
+ empty? ? nil : self
64
+ end
65
+ end
66
+ end
30
67
  end
31
68
  end
data/lib/doing/item.rb CHANGED
@@ -5,10 +5,10 @@ module Doing
5
5
  ## This class describes a single WWID item
6
6
  ##
7
7
  class Item
8
- # include Amatch
9
-
10
8
  attr_accessor :date, :title, :section, :note
11
9
 
10
+ attr_reader :id
11
+
12
12
  ##
13
13
  ## Initialize an item with date, title, section, and
14
14
  ## optional note
@@ -50,8 +50,11 @@ module Doing
50
50
  @end_date ||= Time.parse(Regexp.last_match(1)) if @title =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
51
51
  end
52
52
 
53
- def hash_id
54
- (@date.to_s + @title + @section + @note.join(' ')).hash
53
+ # Generate a hash that represents the entry
54
+ #
55
+ # @return [String] entry hash
56
+ def id
57
+ @id ||= (@date.to_s + @title + @section).hash
55
58
  end
56
59
 
57
60
  ##
@@ -105,14 +108,42 @@ module Doing
105
108
  ##
106
109
  ## Add (or remove) tags from the title of the item
107
110
  ##
108
- ## @param tag [String] The tag to add
109
- ## @param value [String] A value to include as @tag(value)
110
- ## @param remove [Boolean] if true remove instead of adding
111
- ## @param rename_to [String] if not nil, rename target tag to this tag name
112
- ## @param regex [Boolean] treat target tag string as regex pattern
111
+ ## @param tags [Array] The tags to apply
112
+ ## @param **options Additional options
113
+ ##
114
+ ## @option options :date [Boolean] Include timestamp?
115
+ ## @option options :single [Boolean] Log as a single change?
116
+ ## @option options :value [String] A value to include as @tag(value)
117
+ ## @option options :remove [Boolean] if true remove instead of adding
118
+ ## @option options :rename_to [String] if not nil, rename target tag to this tag name
119
+ ## @option options :regex [Boolean] treat target tag string as regex pattern
120
+ ## @option options :force [Boolean] with rename_to, add tag if it doesn't exist
113
121
  ##
114
- def tag(tag, value: nil, remove: false, rename_to: nil, regex: false)
115
- @title.tag!(tag, value: value, remove: remove, rename_to: rename_to, regex: regex).strip!
122
+ def tag(tags, **options)
123
+ added = []
124
+ removed = []
125
+
126
+ date = options.fetch(:date, false)
127
+ options[:value] ||= date ? Time.now.strftime('%F %R') : nil
128
+ options.delete(:date)
129
+
130
+ single = options.fetch(:single, false)
131
+ options.delete(:single)
132
+
133
+ tags = tags.to_tags if tags.is_a? ::String
134
+
135
+ remove = options.fetch(:remove, false)
136
+ tags.each do |tag|
137
+ bool = remove ? :and : :not
138
+ if tags?(tag, bool)
139
+ @title.tag!(tag, **options).strip!
140
+ remove ? removed.push(tag) : added.push(tag)
141
+ end
142
+ end
143
+
144
+ Doing.logger.log_change(tags_added: added, tags_removed: removed, count: 1, item: self, single: single)
145
+
146
+ self
116
147
  end
117
148
 
118
149
  ##
@@ -121,7 +152,7 @@ module Doing
121
152
  ## @return [Array] array of tags (no values)
122
153
  ##
123
154
  def tags
124
- @title.scan(/(?<= |\A)@([^\s(]+)/).map {|tag| tag[0]}.sort.uniq
155
+ @title.scan(/(?<= |\A)@([^\s(]+)/).map { |tag| tag[0] }.sort.uniq
125
156
  end
126
157
 
127
158
  ##
@@ -190,6 +221,40 @@ module Doing
190
221
  should?('never_time')
191
222
  end
192
223
 
224
+ ##
225
+ ## Move item from current section to destination section
226
+ ##
227
+ ## @param new_section [String] The destination
228
+ ## section
229
+ ## @param label [Boolean] add @from(original
230
+ ## section) tag
231
+ ## @param log [Boolean] log this action
232
+ ##
233
+ ## @return nothing
234
+ ##
235
+ def move_to(new_section, label: true, log: true)
236
+ from = @section
237
+
238
+ tag('from', rename_to: 'from', value: from, force: true) if label
239
+ @section = new_section
240
+
241
+ Doing.logger.count(@section == 'Archive' ? :archived : :moved) if log
242
+ Doing.logger.debug("#{@section == 'Archive' ? 'Archived' : 'Moved'}:",
243
+ "#{@title.truncate(60)} from #{from} to #{@section}")
244
+ self
245
+ end
246
+
247
+ # outputs item in Doing file format, including leading tab
248
+ def to_s
249
+ "\t- #{@date.strftime('%Y-%m-%d %H:%M')} | #{@title}#{@note.empty? ? '' : "\n#{@note}"}"
250
+ end
251
+
252
+ # @private
253
+ def inspect
254
+ # %(<Doing::Item @date=#{@date} @title="#{@title}" @section:"#{@section}" @note:#{@note.to_s}>)
255
+ %(<Doing::Item @date=#{@date}>)
256
+ end
257
+
193
258
  private
194
259
 
195
260
  def should?(key)
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doing
4
+ # Items Array
5
+ class Items < Array
6
+ attr_accessor :sections
7
+
8
+ def initialize
9
+ super
10
+ @sections = []
11
+ end
12
+
13
+ # List sections, title only
14
+ #
15
+ # @return [Array] section titles
16
+ #
17
+ def section_titles
18
+ @sections.map(&:title)
19
+ end
20
+
21
+ # Test if section already exists
22
+ #
23
+ # @param section [String] section title
24
+ #
25
+ # @return [Boolean] true if section exists
26
+ #
27
+ def section?(section)
28
+ has_section = false
29
+ section = section.is_a?(Section) ? section.title.downcase : section.downcase
30
+ @sections.each do |s|
31
+ if s.title.downcase == section
32
+ has_section = true
33
+ break
34
+ end
35
+ end
36
+ has_section
37
+ end
38
+
39
+ # Add a new section to the sections array. Accepts
40
+ # either a Section object, or a title string that will
41
+ # be converted into a Section.
42
+ #
43
+ # @param section [Section] The section to add. A
44
+ # String value will be converted to
45
+ # Section automatically.
46
+ # @param log [Boolean] Add a log message
47
+ # notifying the user about the
48
+ # creation of the section.
49
+ #
50
+ # @return nothing
51
+ #
52
+ def add_section(section, log: false)
53
+ section = section.is_a?(Section) ? section : Section.new(section.cap_first)
54
+
55
+ return if section?(section)
56
+
57
+ @sections.push(section)
58
+ Doing.logger.info('New section:', %("#{section}" added)) if log
59
+ end
60
+
61
+ # Get a new Items object containing only items in a
62
+ # specified section
63
+ #
64
+ # @param section [String] section title
65
+ #
66
+ # @return [Items] Array of items
67
+ #
68
+ def in_section(section)
69
+ if section =~ /^all$/i
70
+ dup
71
+ else
72
+ items = Items.new.concat(select { |item| item.section == section })
73
+ items.add_section(section, log: false)
74
+ items
75
+ end
76
+ end
77
+
78
+ ##
79
+ ## Delete an item from the index
80
+ ##
81
+ ## @param item The item
82
+ ##
83
+ def delete_item(item, single: false)
84
+ deleted = delete(item)
85
+ Doing.logger.count(:deleted)
86
+ Doing.logger.info('Entry deleted:', deleted.title) if single
87
+ end
88
+
89
+ ##
90
+ ## Update an item in the index with a modified item
91
+ ##
92
+ ## @param old_item The old item
93
+ ## @param new_item The new item
94
+ ##
95
+ def update_item(old_item, new_item)
96
+ s_idx = index { |item| item.equal?(old_item) }
97
+
98
+ raise ItemNotFound, 'Unable to find item in index, did it mutate?' unless s_idx
99
+
100
+ return if fetch(s_idx).equal?(new_item)
101
+
102
+ self[s_idx] = new_item
103
+ Doing.logger.count(:updated)
104
+ Doing.logger.info('Entry updated:', self[s_idx].title.truncate(60))
105
+ new_item
106
+ end
107
+
108
+ # Output sections and items in Doing file format
109
+ def to_s
110
+ out = []
111
+ @sections.each do |section|
112
+ out.push(section.original)
113
+ in_section(section.title).each { |item| out.push(item.to_s)}
114
+ end
115
+
116
+ out.join("\n")
117
+ end
118
+
119
+ # @private
120
+ def inspect
121
+ "#<Doing::Items #{count} items, #{@sections.count} sections: #{@sections.map { |s| "<Section:#{s.title} #{in_section(s.title).count} items>" }.join(', ')}>"
122
+ end
123
+
124
+ end
125
+ end
@@ -5,9 +5,16 @@ module Doing
5
5
  ## Log adapter
6
6
  ##
7
7
  class LogAdapter
8
- attr_writer :logdev, :max_length
8
+ # Sets the log device
9
+ attr_writer :logdev
9
10
 
10
- attr_reader :messages, :level, :results
11
+ # Max length of log messages (truncate in middle)
12
+ attr_writer :max_length
13
+
14
+ # Returns the current log level (debug, info, warn, error)
15
+ attr_reader :level
16
+
17
+ attr_reader :messages, :results
11
18
 
12
19
  TOPIC_WIDTH = 12
13
20
 
@@ -28,6 +35,7 @@ module Doing
28
35
  deleted
29
36
  moved
30
37
  removed_tags
38
+ rotated
31
39
  skipped
32
40
  updated
33
41
  ].freeze
@@ -45,6 +53,7 @@ module Doing
45
53
  @logdev = $stderr
46
54
  @max_length = `tput cols`.strip.to_i - 5 || 85
47
55
  self.log_level = level
56
+ @prev_level = level
48
57
  end
49
58
 
50
59
  #
@@ -71,6 +80,22 @@ module Doing
71
80
  @level = level
72
81
  end
73
82
 
83
+ # Set log level temporarily
84
+ def temp_level(level)
85
+ return if level.nil? || level.to_sym == @log_level
86
+
87
+ @prev_level = log_level.dup
88
+ @log_level = level.to_sym
89
+ end
90
+
91
+ # Restore temporary level
92
+ def restore_level
93
+ return if @prev_level.nil? || @prev_level == @log_level
94
+
95
+ self.log_level = @prev_level
96
+ @prev_level = nil
97
+ end
98
+
74
99
  def adjust_verbosity(options = {})
75
100
  if options[:log_level]
76
101
  self.log_level = options[:log_level].to_sym
@@ -229,7 +254,6 @@ module Doing
229
254
  ##
230
255
  def output_results
231
256
  total_counters
232
-
233
257
  results = @results.select { |msg| write_message?(msg[:level]) }.uniq
234
258
 
235
259
  if @logdev == $stdout
@@ -241,10 +265,38 @@ module Doing
241
265
  end
242
266
  end
243
267
 
268
+ def log_change(tags_added: [], tags_removed: [], count: 1, item: nil, single: false)
269
+ if tags_added.empty? && tags_removed.empty?
270
+ count(:skipped, level: :debug, message: '%count %items with no change', count: count)
271
+ else
272
+ if tags_added.empty?
273
+ count(:skipped, level: :debug, message: 'no tags added to %count %items')
274
+ elsif single && item
275
+ added = tags_added.log_tags
276
+ info('Tagged:',
277
+ %(added #{tags_added.count == 1 ? 'tag' : 'tags'} #{added} to #{item.title}))
278
+ else
279
+ count(:added_tags, level: :info, tag: tags_added, message: '%tags added to %count %items')
280
+ end
281
+
282
+ if tags_removed.empty?
283
+ count(:skipped, level: :debug, message: 'no tags removed from %count %items')
284
+ elsif single && item
285
+ added = tags_added.log_tags
286
+ info('Untagged:',
287
+ %(removed #{tags_removed.count == 1 ? 'tag' : 'tags'} #{added} from #{item.title}))
288
+ else
289
+ count(:removed_tags, level: :info, tag: tags_removed, message: '%tags removed from %count %items')
290
+ end
291
+ end
292
+ end
293
+
244
294
  private
245
295
 
246
296
  def format_counter(key, data)
247
297
  case key
298
+ when :rotated
299
+ ['Rotated:', data[:message] || 'rotated %count %items']
248
300
  when :autotag
249
301
  ['Autotag:', data[:message] || 'autotagged %count %items']
250
302
  when :added_tags
data/lib/doing/note.rb CHANGED
@@ -5,12 +5,27 @@ module Doing
5
5
  ## This class describes an item note.
6
6
  ##
7
7
  class Note < Array
8
+
9
+ ##
10
+ ## Initializes a new note
11
+ ##
12
+ ## @param note [Array] Initial note, can be string
13
+ ## or array
14
+ ##
8
15
  def initialize(note = [])
9
16
  super()
10
17
 
11
18
  add(note) if note
12
19
  end
13
20
 
21
+ ##
22
+ ## Add note contents, optionally replacing existing note
23
+ ##
24
+ ## @param note [Array] The note to add, can be
25
+ ## string or array (Note)
26
+ ## @param replace [Boolean] replace existing
27
+ ## content
28
+ ##
14
29
  def add(note, replace: false)
15
30
  clear if replace
16
31
  if note.is_a?(String)
@@ -20,11 +35,22 @@ module Doing
20
35
  end
21
36
  end
22
37
 
38
+ ##
39
+ ## Append an array of strings to note
40
+ ##
41
+ ## @param lines [Array] Array of strings
42
+ ##
23
43
  def append(lines)
24
44
  concat(lines)
25
45
  replace compress
26
46
  end
27
47
 
48
+ ##
49
+ ## Append a string to the note content
50
+ ##
51
+ ## @param input [String] The input string,
52
+ ## newlines will be split
53
+ ##
28
54
  def append_string(input)
29
55
  concat(input.split(/\n/).map(&:strip))
30
56
  replace compress
@@ -34,6 +60,11 @@ module Doing
34
60
  replace compress
35
61
  end
36
62
 
63
+ ##
64
+ ## Remove blank lines and comment lines (#)
65
+ ##
66
+ ## @return [Array] compressed array
67
+ ##
37
68
  def compress
38
69
  delete_if { |l| l =~ /^\s*$/ || l =~ /^#/ }
39
70
  end
@@ -42,14 +73,35 @@ module Doing
42
73
  replace strip_lines
43
74
  end
44
75
 
76
+ ##
77
+ ## Remove leading/trailing whitespace for
78
+ ## every line of note
79
+ ##
80
+ ## @return [Array] Stripped note
81
+ ##
45
82
  def strip_lines
46
83
  map(&:strip)
47
84
  end
48
85
 
86
+ ##
87
+ ## Note as multi-line string
49
88
  def to_s
50
- compress.strip_lines.join("\n")
89
+ compress.strip_lines.map { |l| "\t\t#{l}" }.join("\n")
90
+ end
91
+
92
+ # @private
93
+ def inspect
94
+ "<Doing::Note - characters:#{compress.strip_lines.join(' ').length} lines:#{count}>"
51
95
  end
52
96
 
97
+ ##
98
+ ## Test if a note is equal (compare string
99
+ ## representations)
100
+ ##
101
+ ## @param other [Note] The other Note
102
+ ##
103
+ ## @return [Boolean] true if equal
104
+ ##
53
105
  def equal?(other)
54
106
  return false unless other.is_a?(Note)
55
107
 
data/lib/doing/pager.rb CHANGED
@@ -1,23 +1,36 @@
1
1
  # frozen_string_literal: true
2
+ require 'pathname'
2
3
 
3
4
  module Doing
4
5
  # Pagination
5
6
  module Pager
6
7
  class << self
8
+ # Boolean determines whether output is paginated
7
9
  def paginate
8
10
  @paginate ||= false
9
11
  end
10
12
 
13
+ # Enable/disable pagination
14
+ #
15
+ # @param should_paginate [Boolean] true to paginate
11
16
  def paginate=(should_paginate)
12
17
  @paginate = should_paginate
13
18
  end
14
19
 
20
+ # Page output. If @paginate is false, just dump to
21
+ # STDOUT
22
+ #
23
+ # @param text [String] text to paginate
24
+ #
15
25
  def page(text)
16
26
  unless @paginate
17
27
  puts text
18
28
  return
19
29
  end
20
30
 
31
+ pager = which_pager
32
+ Doing.logger.debug('Pager:', "Using #{pager}")
33
+
21
34
  read_io, write_io = IO.pipe
22
35
 
23
36
  input = $stdin
@@ -30,10 +43,8 @@ module Doing
30
43
  # Wait until we have input before we start the pager
31
44
  IO.select [input]
32
45
 
33
- pager = which_pager
34
- Doing.logger.debug('Pager:', "Using #{pager}")
35
46
  begin
36
- exec(pager.join(' '))
47
+ exec(pager)
37
48
  rescue SystemCallError => e
38
49
  raise Errors::DoingStandardError, "Pager error, #{e}"
39
50
  end
@@ -51,44 +62,44 @@ module Doing
51
62
  status.success?
52
63
  end
53
64
 
54
- def which_pager
55
- pagers = [ENV['GIT_PAGER'], ENV['PAGER']]
56
-
57
- if Util.exec_available('git')
58
- git_pager = `git config --get-all core.pager || true`.split.first
59
- git_pager && pagers.push(git_pager)
60
- end
61
-
62
- pagers.concat(%w[bat less more pager])
63
-
64
- pagers.select! do |f|
65
- if f
66
- if f.strip =~ /[ |]/
67
- f
68
- elsif f == 'most'
69
- Doing.logger.warn('most not allowed as pager')
70
- false
71
- else
72
- system "which #{f}", out: File::NULL, err: File::NULL
73
- end
74
- else
75
- false
65
+ private
66
+
67
+ def command_exist?(command)
68
+ exts = ENV.fetch("PATHEXT", "").split(::File::PATH_SEPARATOR)
69
+ if Pathname.new(command).absolute?
70
+ ::File.exist?(command) ||
71
+ exts.any? { |ext| ::File.exist?("#{command}#{ext}")}
72
+ else
73
+ ENV.fetch("PATH", "").split(::File::PATH_SEPARATOR).any? do |dir|
74
+ file = ::File.join(dir, command)
75
+ ::File.exist?(file) ||
76
+ exts.any? { |ext| ::File.exist?("#{file}#{ext}") }
76
77
  end
77
78
  end
79
+ end
80
+
81
+ def git_pager
82
+ command_exist?("git") ? `git config --get-all core.pager` : nil
83
+ end
78
84
 
79
- pg = pagers.first
80
- args = case pg
81
- when /^more$/
82
- ' -r'
83
- when /^less$/
84
- ' -Xr'
85
- when /^bat$/
86
- ' -p --pager="less -Xr"'
87
- else
88
- ''
89
- end
90
-
91
- [pg, args]
85
+ def pagers
86
+ [ENV['GIT_PAGER'], ENV['PAGER'], git_pager,
87
+ 'bat -p --pager="less -Xr"', 'less -Xr', 'more -r'].compact
88
+ end
89
+
90
+ def find_executable(*commands)
91
+ execs = commands.empty? ? pagers : commands
92
+ execs
93
+ .compact.map(&:strip).reject(&:empty?).uniq
94
+ .find { |cmd| command_exist?(cmd.split.first) }
95
+ end
96
+
97
+ def exec_available?(*commands)
98
+ !find_executable(*commands).nil?
99
+ end
100
+
101
+ def which_pager
102
+ @which_pager ||= find_executable(*pagers)
92
103
  end
93
104
  end
94
105
  end
@@ -41,11 +41,11 @@ module Doing
41
41
  all_items = []
42
42
  items.each do |i|
43
43
  if String.method_defined? :force_encoding
44
- title = i.title.force_encoding('utf-8').link_urls({format: :markdown})
45
- note = i.note.map { |line| line.force_encoding('utf-8').strip.link_urls({format: :markdown}) } if i.note
44
+ title = i.title.force_encoding('utf-8').link_urls(format: :markdown)
45
+ note = i.note.map { |line| line.force_encoding('utf-8').strip.link_urls(format: :markdown) } if i.note
46
46
  else
47
- title = i.title.link_urls({format: :markdown})
48
- note = i.note.map { |line| line.strip.link_urls({format: :markdown}) } if i.note
47
+ title = i.title.link_urls(format: :markdown)
48
+ note = i.note.map { |line| line.strip.link_urls(format: :markdown) } if i.note
49
49
  end
50
50
 
51
51
  title = "#{title} @project(#{i.section})" unless variables[:is_single]
@@ -23,10 +23,10 @@ module Doing
23
23
  out = ''
24
24
  items.each do |item|
25
25
  if opt[:highlight] && item.title =~ /@#{wwid.config['marker_tag']}\b/i
26
- flag = Doing::Color.send(wwid.config['marker_color'])
26
+ # flag = Doing::Color.send(wwid.config['marker_color'])
27
27
  reset = Doing::Color.default
28
28
  else
29
- flag = ''
29
+ # flag = ''
30
30
  reset = ''
31
31
  end
32
32
 
@@ -27,7 +27,7 @@ module Doing
27
27
  options[:no_overlap] ||= false
28
28
  options[:autotag] ||= wwid.auto_tag
29
29
 
30
- wwid.add_section(section) unless wwid.content.key?(section)
30
+ wwid.content.add_section(section) unless wwid.content.section?(section)
31
31
 
32
32
  tags = options[:tag] ? options[:tag].split(/[ ,]+/).map { |t| t.sub(/^@?/, '') } : []
33
33
  prefix = options[:prefix] || '[Calendar.app]'
@@ -59,7 +59,7 @@ module Doing
59
59
  title += " @done(#{end_time.strftime('%Y-%m-%d %H:%M')})"
60
60
  title.gsub!(/ +/, ' ')
61
61
  title.strip!
62
- new_entry = { 'title' => title, 'date' => start_time, 'section' => section }
62
+ new_entry = Item.new(start_time, title, section)
63
63
  new_entry.note = entry['notes'].split(/\n/).map(&:chomp) if entry.key?('notes')
64
64
  new_items.push(new_entry)
65
65
  end
@@ -69,11 +69,11 @@ module Doing
69
69
  filtered = total - new_items.count
70
70
  Doing.logger.debug('Skipped:' , %(#{filtered} items that didn't match filter criteria)) if filtered.positive?
71
71
 
72
- new_items = wwid.dedup(new_items, options[:no_overlap])
72
+ new_items = wwid.dedup(new_items, no_overlap: options[:no_overlap])
73
73
  dups = filtered - new_items.count
74
74
  Doing.logger.info(%(Skipped #{dups} items with overlapping times)) if dups.positive?
75
75
 
76
- wwid.content[section][:items].concat(new_items)
76
+ wwid.content.concat(new_items)
77
77
  Doing.logger.info(%(Imported #{new_items.count} items to #{section}))
78
78
  end
79
79