doing 2.0.25 → 2.1.0pre

Sign up to get free protection for your applications and to get access to all the features.
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