doing 2.1.30 → 2.1.31pre

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/.irbrc +1 -0
  3. data/.yardoc/checksums +9 -8
  4. data/.yardoc/object_types +0 -0
  5. data/.yardoc/objects/root.dat +0 -0
  6. data/CHANGELOG.md +4923 -0
  7. data/Gemfile.lock +1 -1
  8. data/README.md +1 -1
  9. data/bin/commands/again.rb +1 -1
  10. data/bin/commands/archive.rb +3 -3
  11. data/bin/commands/cancel.rb +1 -1
  12. data/bin/commands/commands.rb +8 -8
  13. data/bin/commands/completion.rb +61 -19
  14. data/bin/commands/config.rb +9 -9
  15. data/bin/commands/done.rb +1 -1
  16. data/bin/commands/flag.rb +1 -1
  17. data/bin/commands/grep.rb +5 -5
  18. data/bin/commands/last.rb +1 -1
  19. data/bin/commands/meanwhile.rb +1 -1
  20. data/bin/commands/now.rb +1 -1
  21. data/bin/commands/on.rb +1 -1
  22. data/bin/commands/open.rb +1 -1
  23. data/bin/commands/recent.rb +4 -4
  24. data/bin/commands/show.rb +8 -8
  25. data/bin/commands/since.rb +1 -1
  26. data/bin/commands/today.rb +1 -1
  27. data/bin/commands/view.rb +3 -3
  28. data/bin/commands/yesterday.rb +2 -2
  29. data/bin/doing +22 -133
  30. data/docs/doc/Array.html +1 -1
  31. data/docs/doc/BooleanTermParser/Clause.html +1 -1
  32. data/docs/doc/BooleanTermParser/Operator.html +1 -1
  33. data/docs/doc/BooleanTermParser/Query.html +1 -1
  34. data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
  35. data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
  36. data/docs/doc/BooleanTermParser.html +1 -1
  37. data/docs/doc/Doing/Color.html +1 -1
  38. data/docs/doc/Doing/Completion.html +324 -4
  39. data/docs/doc/Doing/Configuration.html +1 -1
  40. data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
  41. data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
  42. data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
  43. data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
  44. data/docs/doc/Doing/Errors/NoResults.html +1 -1
  45. data/docs/doc/Doing/Errors/PluginException.html +1 -1
  46. data/docs/doc/Doing/Errors/UserCancelled.html +1 -1
  47. data/docs/doc/Doing/Errors/WrongCommand.html +1 -1
  48. data/docs/doc/Doing/Errors.html +1 -1
  49. data/docs/doc/Doing/Hooks.html +1 -1
  50. data/docs/doc/Doing/Item.html +125 -1
  51. data/docs/doc/Doing/Items.html +1 -1
  52. data/docs/doc/Doing/LogAdapter.html +1 -1
  53. data/docs/doc/Doing/Note.html +109 -3
  54. data/docs/doc/Doing/Pager.html +1 -1
  55. data/docs/doc/Doing/Plugins.html +1 -1
  56. data/docs/doc/Doing/Prompt.html +1 -1
  57. data/docs/doc/Doing/Section.html +1 -1
  58. data/docs/doc/Doing/TemplateString.html +1 -1
  59. data/docs/doc/Doing/Types.html +1 -1
  60. data/docs/doc/Doing/Util/Backup.html +1 -1
  61. data/docs/doc/Doing/Util.html +1 -1
  62. data/docs/doc/Doing/WWID.html +6 -6
  63. data/docs/doc/Doing.html +2 -2
  64. data/docs/doc/FalseClass.html +1 -1
  65. data/docs/doc/GLI/Commands/Help.html +1 -1
  66. data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
  67. data/docs/doc/GLI/Commands.html +1 -1
  68. data/docs/doc/GLI.html +1 -1
  69. data/docs/doc/Hash.html +1 -1
  70. data/docs/doc/Object.html +1 -1
  71. data/docs/doc/PhraseParser/Operator.html +1 -1
  72. data/docs/doc/PhraseParser/PhraseClause.html +1 -1
  73. data/docs/doc/PhraseParser/Query.html +1 -1
  74. data/docs/doc/PhraseParser/QueryParser.html +1 -1
  75. data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
  76. data/docs/doc/PhraseParser/TermClause.html +1 -1
  77. data/docs/doc/PhraseParser.html +1 -1
  78. data/docs/doc/Status.html +1 -1
  79. data/docs/doc/String.html +1 -1
  80. data/docs/doc/Symbol.html +1 -1
  81. data/docs/doc/Time.html +1 -1
  82. data/docs/doc/TrueClass.html +1 -1
  83. data/docs/doc/_index.html +3 -1
  84. data/docs/doc/file.README.html +2 -2
  85. data/docs/doc/index.html +2 -2
  86. data/docs/doc/method_list.html +337 -241
  87. data/docs/doc/top-level-namespace.html +105 -1
  88. data/doing.rdoc +25 -9
  89. data/example_plugin.rb +7 -5
  90. data/lib/completion/_doing.zsh +2 -2
  91. data/lib/completion/doing.bash +2 -2
  92. data/lib/completion/doing.fish +2 -3
  93. data/lib/doing/add_options.rb +117 -0
  94. data/lib/doing/array/array.rb +16 -0
  95. data/lib/doing/completion/bash_completion.rb +12 -51
  96. data/lib/doing/completion/fish_completion.rb +16 -52
  97. data/lib/doing/completion/zsh_completion.rb +12 -56
  98. data/lib/doing/completion.rb +203 -17
  99. data/lib/doing/item.rb +21 -3
  100. data/lib/doing/items.rb +5 -5
  101. data/lib/doing/note.rb +24 -8
  102. data/lib/doing/plugins/export/dayone_export.rb +8 -6
  103. data/lib/doing/plugins/export/html_export.rb +4 -4
  104. data/lib/doing/plugins/export/json_export.rb +19 -20
  105. data/lib/doing/plugins/export/markdown_export.rb +2 -2
  106. data/lib/doing/plugins/export/template_export.rb +4 -4
  107. data/lib/doing/plugins/import/calendar_import.rb +1 -1
  108. data/lib/doing/plugins/import/doing_import.rb +1 -1
  109. data/lib/doing/plugins/import/timing_import.rb +1 -1
  110. data/lib/doing/string/highlight.rb +3 -4
  111. data/lib/doing/string/string.rb +8 -0
  112. data/lib/doing/util.rb +1 -1
  113. data/lib/doing/util_backup.rb +12 -12
  114. data/lib/doing/version.rb +1 -1
  115. data/lib/doing/wwid.rb +76 -77
  116. data/lib/doing.rb +57 -0
  117. data/lib/examples/commands/wiki.rb +27 -19
  118. data/scripts/setting_replace.rb +11 -0
  119. metadata +6 -4
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doing
2
4
  module Completion
5
+ # Generate completions for zsh
3
6
  class ZshCompletions
4
-
5
7
  attr_accessor :commands, :global_options
6
8
 
7
9
  def generate_helpers
@@ -34,58 +36,11 @@ module Doing
34
36
  }
35
37
 
36
38
  EOFUNCTIONS
39
+ @bar.advance(status: '✅')
37
40
  @bar.finish
38
41
  out
39
42
  end
40
43
 
41
- def get_help_sections(command = '')
42
- res = `doing help #{command}`.strip
43
- scanned = res.scan(/(?m-i)^([A-Z ]+)\n([\s\S]*?)(?=\n+[A-Z]+|\Z)/)
44
- sections = {}
45
- scanned.each do |sect|
46
- title = sect[0].downcase.strip.gsub(/ +/, '_').to_sym
47
- content = sect[1].split(/\n/).map(&:strip).delete_if(&:empty?)
48
- sections[title] = content
49
- end
50
- sections
51
- end
52
-
53
- def parse_option(option)
54
- res = option.match(/(?:-(?<short>\w), )?(?:--(?:\[no-\])?(?<long>\w+)(?:=(?<arg>\w+))?)\s+- (?<desc>.*?)$/)
55
- return nil unless res
56
-
57
- {
58
- short: res['short'],
59
- long: res['long'],
60
- arg: res[:arg],
61
- description: res['desc'].short_desc
62
- }
63
- end
64
-
65
- def parse_options(options)
66
- options.map { |opt| parse_option(opt) }
67
- end
68
-
69
- def parse_command(command)
70
- res = command.match(/^(?<cmd>[^, \t]+)(?<alias>(?:, [^, \t]+)*)?\s+- (?<desc>.*?)$/)
71
- if res.nil?
72
- Doing.logger.error('Completion:', "Error parsing #{command}")
73
- return nil
74
-
75
- end
76
- commands = [res['cmd']]
77
- commands.concat(res['alias'].split(/, /).delete_if(&:empty?)) if res['alias']
78
-
79
- {
80
- commands: commands,
81
- description: res['desc'].short_desc
82
- }
83
- end
84
-
85
- def parse_commands(commands)
86
- commands.map { |cmd| parse_command(cmd) }
87
- end
88
-
89
44
  def generate_subcommand_completions
90
45
  out = []
91
46
  @commands.each_with_index do |cmd, i|
@@ -103,11 +58,11 @@ module Doing
103
58
  @commands.each_with_index do |cmd, i|
104
59
  @bar.advance(status: cmd[:commands].first)
105
60
 
106
- data = get_help_sections(cmd[:commands].first)
61
+ data = Completion.get_help_sections(cmd[:commands].first)
107
62
  option_arr = []
108
63
 
109
64
  if data[:command_options]
110
- parse_options(data[:command_options]).each do |option|
65
+ Completion.parse_options(data[:command_options]).each do |option|
111
66
  next if option.nil?
112
67
 
113
68
  arg = option[:arg] ? '=' : ''
@@ -129,11 +84,12 @@ module Doing
129
84
  end
130
85
 
131
86
  def initialize
132
- data = get_help_sections
133
- @global_options = parse_options(data[:global_options])
134
- @commands = parse_commands(data[:commands])
135
- @bar = TTY::ProgressBar.new(" \033[0;0;33mGenerating Zsh completions: \033[0;35;40m[:bar] :status\033[0m", total: @commands.count, bar_format: :blade, status: 'processing subcommands')
136
- @bar.resize(25)
87
+ data = Completion.get_help_sections
88
+ @global_options = Completion.parse_options(data[:global_options])
89
+ @commands = Completion.parse_commands(data[:commands])
90
+ @bar = TTY::ProgressBar.new(" \033[0;0;33mGenerating Zsh completions: \033[0;35;40m[:bar] :status\033[0m", total: @commands.count + 1, bar_format: :block, hide_cursor: true, status: 'processing subcommands')
91
+ width = TTY::Screen.columns - 45
92
+ @bar.resize(width)
137
93
  end
138
94
 
139
95
  def generate_completions
@@ -11,25 +11,118 @@ require 'bash_completion'
11
11
  module Doing
12
12
  # Completion script generator
13
13
  module Completion
14
+ OPTIONS_RX = /(?:-(?<short>\w), )?(?:--(?:\[no-\])?(?<long>\w+)(?:=(?<arg>\w+))?)\s+- (?<desc>.*?)$/.freeze
15
+ SECTIONS_RX = /(?m-i)^([A-Z ]+)\n([\s\S]*?)(?=\n+[A-Z]+|\Z)/.freeze
16
+ COMMAND_RX = /^(?<cmd>[^, \t]+)(?<alias>(?:, [^, \t]+)*)?\s+- (?<desc>.*?)$/.freeze
17
+
14
18
  class << self
19
+ def get_help_sections(command = '')
20
+ res = `doing help #{command}`.strip
21
+ scanned = res.scan(SECTIONS_RX)
22
+ sections = {}
23
+ scanned.each do |sect|
24
+ title = sect[0].downcase.strip.gsub(/ +/, '_').to_sym
25
+ content = sect[1].split(/\n/).map(&:strip).delete_if(&:empty?)
26
+ sections[title] = content
27
+ end
28
+ sections
29
+ end
30
+
31
+ def parse_option(option)
32
+ res = option.match(OPTIONS_RX)
33
+ return nil unless res
34
+
35
+ {
36
+ short: res['short'],
37
+ long: res['long'],
38
+ arg: res['arg'],
39
+ description: res['desc'].short_desc
40
+ }
41
+ end
42
+
43
+ def parse_options(options)
44
+ options.map { |opt| parse_option(opt) }
45
+ end
46
+
47
+ def parse_command(command)
48
+ res = command.match(COMMAND_RX)
49
+ commands = [res['cmd']]
50
+ commands.concat(res['alias'].split(/, /).delete_if(&:empty?)) if res['alias']
51
+
52
+ {
53
+ commands: commands,
54
+ description: res['desc'].short_desc
55
+ }
56
+ end
57
+
58
+ def parse_commands(commands)
59
+ commands.map { |cmd| parse_command(cmd) }
60
+ end
61
+
15
62
  # Generate a completion script and output to file or
16
63
  # stdout
17
64
  #
18
65
  # @param type [String] shell to generate for (zsh|bash|fish)
19
66
  # @param file [String] Path to save to, or 'stdout'
20
67
  #
21
- def generate_completion(type: 'zsh', file: 'stdout')
22
- if type =~ /^all$/i
23
- Doing.logger.log_now(:warn, 'Generating:', 'all completion types, will use default paths')
24
- generate_completion(type: 'fish', file: 'lib/completion/doing.fish')
25
- Doing.logger.warn('File written:', "fish completions written to lib/completion/doing.fish")
26
- generate_completion(type: 'zsh', file: 'lib/completion/_doing.zsh')
27
- Doing.logger.warn('File written:', "zsh completions written to lib/completion/_doing.zsh")
28
- generate_completion(type: 'bash', file: 'lib/completion/doing.bash')
29
- Doing.logger.warn('File written:', "bash completions written to lib/completion/doing.bash")
30
- return
68
+ def generate_completion(type: 'zsh', file: :default, link: true)
69
+ return generate_all if type =~ /^all$/i
70
+
71
+ file = file == :default ? default_file(type) : file
72
+ file = validate_target(file)
73
+ result = generate_type(type)
74
+
75
+ if file =~ /^stdout$/i
76
+ $stdout.puts result
77
+ else
78
+ File.open(file, 'w') { |f| f.puts result }
79
+ Doing.logger.warn('File written:', "#{type} completions written to #{file}")
80
+
81
+ link_completion_type(type, file) if link
31
82
  end
83
+ end
84
+
85
+ def link_default(type)
86
+ type = normalize_type(type)
87
+ raise InvalidArgument, 'Unrecognized shell specified' if type == :invalid
88
+
89
+ return %i[zsh bash fish].each { |t| link_default(t) } if type == :all
32
90
 
91
+ install_builtin(type)
92
+
93
+ link_completion_type(type, File.join(default_dir, default_filenames[type]))
94
+ end
95
+
96
+ def install_builtin(type)
97
+ FileUtils.mkdir_p(default_dir)
98
+ src = File.expand_path(File.join(File.dirname(__FILE__), '..', 'completion', default_filenames[type]))
99
+
100
+ if File.exist?(File.join(default_dir, default_filenames[type]))
101
+ return unless Doing::Prompt.yn("Update #{type} completion script", default_response: 'n')
102
+ end
103
+
104
+ FileUtils.cp(src, default_dir)
105
+ Doing.logger.warn('File written:', "#{type} completions saved to #{default_file(type)}")
106
+ end
107
+
108
+ def normalize_type(type)
109
+ case type.to_s
110
+ when /^f/i
111
+ :fish
112
+ when /^b/i
113
+ :bash
114
+ when /^z/i
115
+ :zsh
116
+ when /^a/i
117
+ :all
118
+ else
119
+ :invalid
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ def generate_type(type)
33
126
  generator = case type.to_s
34
127
  when /^f/i
35
128
  FishCompletions.new
@@ -39,16 +132,109 @@ module Doing
39
132
  ZshCompletions.new
40
133
  end
41
134
 
42
- result = generator.generate_completions
135
+ generator.generate_completions
136
+ end
43
137
 
44
- if file =~ /^stdout$/i
45
- $stdout.puts result
46
- else
47
- File.open(File.expand_path(file), 'w') do |f|
48
- f.puts result
138
+ def validate_target(file)
139
+ unless file =~ /stdout/i
140
+ file = validate_file(file)
141
+
142
+ validate_dir(file)
143
+ end
144
+
145
+ file
146
+ end
147
+
148
+ def default_dir
149
+ File.expand_path('~/.local/share/doing/completion')
150
+ end
151
+
152
+ def default_filenames
153
+ { zsh: '_doing.zsh', bash: 'doing.bash', fish: 'doing.fish' }
154
+ end
155
+
156
+ def default_file(type)
157
+ type = normalize_type(type)
158
+
159
+ File.join(default_dir, default_filenames[type])
160
+ end
161
+
162
+ def validate_file(file)
163
+ file = File.expand_path(file)
164
+ if File.exist?(file)
165
+ res = Doing::Prompt.yn("Overwrite #{file}", default_response: 'y')
166
+ raise UserCancelled unless res
167
+
168
+ FileUtils.rm(file) if res
169
+ end
170
+ file
171
+ end
172
+
173
+ def validate_dir(file)
174
+ dir = File.dirname(file)
175
+ unless File.directory?(dir)
176
+ res = Doing::Prompt.yn("#{dir} doesn't exist, create it", default_response: 'y')
177
+ raise UserCancelled unless res
178
+
179
+ FileUtils.mkdir_p(dir)
180
+ end
181
+ dir
182
+ end
183
+
184
+ def generate_all
185
+ Doing.logger.log_now(:warn, 'Generating:', 'all completion types, will use default paths')
186
+ generate_completion(type: 'fish', file: 'lib/completion/doing.fish', link: false)
187
+ Doing.logger.warn('File written:', 'fish completions written to lib/completion/doing.fish')
188
+ generate_completion(type: 'zsh', file: 'lib/completion/_doing.zsh', link: false)
189
+ Doing.logger.warn('File written:', 'zsh completions written to lib/completion/_doing.zsh')
190
+ generate_completion(type: 'bash', file: 'lib/completion/doing.bash', link: false)
191
+ Doing.logger.warn('File written:', 'bash completions written to lib/completion/doing.bash')
192
+ end
193
+
194
+ def link_completion_type(type, file)
195
+ dir = File.dirname(file)
196
+ case type.to_s
197
+ when /^b/i
198
+ unless dir =~ %r{(\.bash_it/completion|bash_completion/completions)}
199
+ link_completion(file, ['~/.bash_it/completion/enabled', '/usr/share/bash_completion/completions'], 'doing.bash')
49
200
  end
50
- Doing.logger.warn('File written:', "#{type} completions written to #{file}")
201
+ when /^f/i
202
+ link_completion(file, ['~/.config/fish/completions'], 'doing.fish') unless dir =~ %r{.config/fish/completions}
203
+ when /^z/i
204
+ unless dir =~ %r{(\.oh-my-zsh/completions|share/site-functions)}
205
+ link_completion(file, ['~/.oh-my-zsh/completions', '/usr/local/share/zsh/site-functions'], '_doing.zsh')
206
+ end
207
+ end
208
+ end
209
+
210
+ def link_completion(file, targets, filename)
211
+ return if targets.map { |t| File.expand_path(t) }.include?(File.dirname(file))
212
+
213
+ found = false
214
+ linked = false
215
+
216
+ targets.each do |target|
217
+ next unless File.directory?(File.expand_path(target))
218
+ found = true
219
+
220
+ target_file = File.join(File.expand_path(target), filename)
221
+ next unless Doing::Prompt.yn("Create link to #{target_file}", default_response: 'n')
222
+
223
+ FileUtils.ln_s(File.expand_path(file), target_file, force: true)
224
+ Doing.logger.warn('File linked:', "#{File.expand_path(file)} -> #{target_file}")
225
+ linked = true
226
+ break
227
+ end
228
+
229
+ return if linked
230
+
231
+ unless found
232
+ $stdout.puts 'No known auto-load directory found for specified shell'.red
233
+ $stdout.puts "Looked for #{targets.join(', ')}, found no existing directory".yellow
51
234
  end
235
+ $stdout.puts 'If you don\'t want to autoload completions'.yellow
236
+ $stdout.puts 'you can source the script directly in your shell\'s startup file:'.yellow
237
+ $stdout.puts %(source "#{file}").boldwhite
52
238
  end
53
239
  end
54
240
  end
data/lib/doing/item.rb CHANGED
@@ -272,7 +272,7 @@ module Doing
272
272
  end
273
273
 
274
274
  def highlight_search(search, distance: nil, negate: false, case_type: nil)
275
- prefs = Doing.config.settings['search'] || {}
275
+ prefs = Doing.setting('search', {})
276
276
  matching = prefs.fetch('matching', 'pattern').normalize_matching
277
277
  distance ||= prefs.fetch('distance', 3).to_i
278
278
  case_type ||= prefs.fetch('case', 'smart').normalize_case
@@ -311,7 +311,7 @@ module Doing
311
311
  ## @return [Boolean] matches search criteria
312
312
  ##
313
313
  def search(search, distance: nil, negate: false, case_type: nil)
314
- prefs = Doing.config.settings['search'] || {}
314
+ prefs = Doing.setting('search', {})
315
315
  matching = prefs.fetch('matching', 'pattern').normalize_matching
316
316
  distance ||= prefs.fetch('distance', 3).to_i
317
317
  case_type ||= prefs.fetch('case', 'smart').normalize_case
@@ -354,6 +354,24 @@ module Doing
354
354
  negate ? !matches : matches
355
355
  end
356
356
 
357
+ ##
358
+ ## Test if item has a @done tag
359
+ ##
360
+ ## @return [Boolean] true item has @done tag
361
+ ##
362
+ def finished?
363
+ tags?('done')
364
+ end
365
+
366
+ ##
367
+ ## Test if item does not contain @done tag
368
+ ##
369
+ ## @return [Boolean] true if item is missing @done tag
370
+ ##
371
+ def unfinished?
372
+ tags?('done', negate: true)
373
+ end
374
+
357
375
  ##
358
376
  ## Test if item is included in never_finish config and
359
377
  ## thus should not receive a @done tag
@@ -436,7 +454,7 @@ module Doing
436
454
  private
437
455
 
438
456
  def should?(key)
439
- config = Doing.config.settings
457
+ config = Doing.settings
440
458
  return true unless config[key].is_a?(Array)
441
459
 
442
460
  config[key].each do |tag|
data/lib/doing/items.rb CHANGED
@@ -131,9 +131,9 @@ module Doing
131
131
  out = []
132
132
  @sections.each do |section|
133
133
  out.push(section.original)
134
- items = in_section(section.title).sort_by { |i| i.date }
135
- items.reverse! if Doing.config.settings['doing_file_sort'].normalize_order == :desc
136
- items.each { |item| out.push(item.to_s)}
134
+ items = in_section(section.title).sort_by(&:date)
135
+ items.reverse! if Doing.setting('doing_file_sort').normalize_order == :desc
136
+ items.each { |item| out.push(item.to_s) }
137
137
  end
138
138
 
139
139
  out.join("\n")
@@ -141,8 +141,8 @@ module Doing
141
141
 
142
142
  # @private
143
143
  def inspect
144
- "#<Doing::Items #{count} items, #{@sections.count} sections: #{@sections.map { |s| "<Section:#{s.title} #{in_section(s.title).count} items>" }.join(', ')}>"
144
+ sections = @sections.map { |s| "<Section:#{s.title} #{in_section(s.title).count} items>" }.join(', ')
145
+ "#<Doing::Items #{count} items, #{@sections.count} sections: #{sections}>"
145
146
  end
146
-
147
147
  end
148
148
  end
data/lib/doing/note.rb CHANGED
@@ -5,7 +5,6 @@ module Doing
5
5
  ## This class describes an item note.
6
6
  ##
7
7
  class Note < Array
8
-
9
8
  ##
10
9
  ## Initializes a new note
11
10
  ##
@@ -28,9 +27,10 @@ module Doing
28
27
  ##
29
28
  def add(note, replace: false)
30
29
  clear if replace
31
- if note.is_a?(String)
30
+ case note
31
+ when String
32
32
  append_string(note)
33
- elsif note.is_a?(Array)
33
+ when Array
34
34
  append(note)
35
35
  end
36
36
  end
@@ -55,7 +55,7 @@ module Doing
55
55
  ## @return [Array] Stripped note
56
56
  ##
57
57
  def strip_lines
58
- map(&:strip)
58
+ Note.new(map(&:strip))
59
59
  end
60
60
 
61
61
  def strip_lines!
@@ -64,8 +64,24 @@ module Doing
64
64
 
65
65
  ##
66
66
  ## Note as multi-line string
67
- def to_s
68
- compress.strip_lines.map { |l| "\t\t#{l}" }.join("\n")
67
+ ##
68
+ ## @param prefix [String] prefix for each line (default two tabs, TaskPaper format)
69
+ ##
70
+ def to_s(prefix: "\t\t")
71
+ compress.strip_lines.map { |l| "#{prefix}#{l}" }.join("\n")
72
+ end
73
+
74
+ ##
75
+ ## Returns note as a single line, newlines separated by
76
+ ## space
77
+ ##
78
+ ## @return [String] Line representation of the Note.
79
+ ##
80
+ ## @param separator The separator with which to
81
+ ## join multiple lines
82
+ ##
83
+ def to_line(separator: ' ')
84
+ compress.strip_lines.join(separator)
69
85
  end
70
86
 
71
87
  # @private
@@ -94,7 +110,7 @@ module Doing
94
110
  ## @param lines [Array] Array of strings
95
111
  ##
96
112
  def append(lines)
97
- concat(lines)
113
+ concat(lines.utf8)
98
114
  replace compress
99
115
  end
100
116
 
@@ -105,7 +121,7 @@ module Doing
105
121
  ## newlines will be split
106
122
  ##
107
123
  def append_string(input)
108
- concat(input.split(/\n/).map(&:strip))
124
+ concat(input.utf8.split(/\n/).map(&:strip))
109
125
  replace compress
110
126
  end
111
127
  end
@@ -45,7 +45,9 @@ module Doing
45
45
 
46
46
  def self.render(wwid, items, variables: {})
47
47
 
48
- return if items.nil?
48
+ return unless items.good?
49
+
50
+ config = Doing.settings
49
51
 
50
52
  opt = variables[:options]
51
53
  trigger = opt[:output]
@@ -78,7 +80,7 @@ module Doing
78
80
  title = "#{title} @section(#{i.section})" unless variables[:is_single]
79
81
 
80
82
  tags.concat(i.tag_array).sort!.uniq!
81
- flagged = day_flagged = true if i.tags?(wwid.config['marker_tag'])
83
+ flagged = day_flagged = true if i.tags?(config['marker_tag'])
82
84
 
83
85
  interval = wwid.get_interval(i, record: true) if i.title =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
84
86
  interval ||= false
@@ -123,8 +125,8 @@ module Doing
123
125
  end
124
126
 
125
127
 
126
- template = if wwid.config['export_templates']['dayone'] && File.exist?(File.expand_path(wwid.config['export_templates']['dayone']))
127
- IO.read(File.expand_path(wwid.config['export_templates']['dayone']))
128
+ template = if config['export_templates']['dayone'] && File.exist?(File.expand_path(config['export_templates']['dayone']))
129
+ IO.read(File.expand_path(config['export_templates']['dayone']))
128
130
  else
129
131
  self.template('dayone')
130
132
  end
@@ -144,8 +146,8 @@ module Doing
144
146
  starred: hsh[:starred])
145
147
  end
146
148
  when :entries
147
- entry_template = if wwid.config['export_templates']['dayone_entry'] && File.exist?(File.expand_path(wwid.config['export_templates']['dayone_entry']))
148
- IO.read(File.expand_path(wwid.config['export_templates']['dayone_entry']))
149
+ entry_template = if config['export_templates']['dayone_entry'] && File.exist?(File.expand_path(config['export_templates']['dayone_entry']))
150
+ IO.read(File.expand_path(config['export_templates']['dayone_entry']))
149
151
  else
150
152
  self.template('dayone-entry')
151
153
  end
@@ -58,14 +58,14 @@ module Doing
58
58
  }
59
59
  end
60
60
 
61
- template = if wwid.config['export_templates']['haml'] && File.exist?(File.expand_path(wwid.config['export_templates']['haml']))
62
- IO.read(File.expand_path(wwid.config['export_templates']['haml']))
61
+ template = if Doing.setting('export_templates.haml') && File.exist?(File.expand_path(Doing.setting('export_templates.haml')))
62
+ IO.read(File.expand_path(Doing.setting('export_templates.haml')))
63
63
  else
64
64
  self.template('html')
65
65
  end
66
66
 
67
- style = if wwid.config['export_templates']['css'] && File.exist?(File.expand_path(wwid.config['export_templates']['css']))
68
- IO.read(File.expand_path(wwid.config['export_templates']['css']))
67
+ style = if Doing.setting('export_templates.css') && File.exist?(File.expand_path(Doing.setting('export_templates.css')))
68
+ IO.read(File.expand_path(Doing.setting('export_templates.css')))
69
69
  else
70
70
  self.template('css')
71
71
  end
@@ -29,16 +29,12 @@ module Doing
29
29
  max = last_date.strftime('%F')
30
30
  min = items[0].date.strftime('%F')
31
31
  items.each_with_index do |i, index|
32
- if String.method_defined? :force_encoding
33
- title = i.title.force_encoding('utf-8')
34
- note = i.note.map { |line| line.force_encoding('utf-8').strip } if i.note
35
- else
36
- title = i.title
37
- note = i.note.map { |line| line.strip } if i.note
38
- end
32
+ title = i.title.utf8
33
+ note = i.note.utf8
39
34
 
40
35
  end_date = i.end_date || ''
41
36
  interval = wwid.get_interval(i, formatted: false) || 0
37
+ duration = i.duration || 0
42
38
  note ||= ''
43
39
 
44
40
  tags = []
@@ -49,14 +45,15 @@ module Doing
49
45
  attributes[tag[0]] = tag[1] if tag[1]
50
46
  end
51
47
 
52
- if opt[:output] == 'json'
53
-
48
+ case opt[:output]
49
+ when 'json'
54
50
  i = {
55
51
  date: i.date,
56
52
  end_date: end_date,
57
53
  title: title.strip, #+ " #{note}"
58
- note: note.instance_of?(Array) ? note.to_s : note,
54
+ note: note.to_s(prefix: ''),
59
55
  time: interval.time_string(format: :clock),
56
+ duration: duration.time_string(format: :clock),
60
57
  tags: tags
61
58
  }
62
59
 
@@ -64,7 +61,7 @@ module Doing
64
61
 
65
62
  items_out << i
66
63
 
67
- elsif opt[:output] == 'timeline'
64
+ when 'timeline'
68
65
  new_item = {
69
66
  'id' => index + 1,
70
67
  'content' => title.strip, #+ " #{note}"
@@ -74,26 +71,28 @@ module Doing
74
71
  'style' => 'color:#4c566b;background-color:#d8dee9;'
75
72
  }
76
73
 
77
-
78
- if interval && interval.to_i > 0
74
+ if interval.to_i&.positive?
79
75
  new_item['end'] = end_date.strftime('%F %T')
80
76
  if interval.to_i > 3600
81
77
  new_item['type'] = 'range'
82
78
  new_item['style'] = 'color:white;background-color:#a2bf8a;'
83
79
  end
84
80
  end
85
- new_item['style'] = 'color:white;background-color:#f7921e;' if i.tags?(wwid.config['marker_tag'])
81
+ new_item['style'] = 'color:white;background-color:#f7921e;' if i.tags?(Doing.setting('marker_tag'))
86
82
  items_out.push(new_item)
87
83
  end
88
84
  end
89
- if opt[:output] == 'json'
85
+ case opt[:output]
86
+ when 'json'
90
87
  Doing.logger.debug('JSON Export:', "#{items_out.count} items output to JSON")
91
88
  JSON.pretty_generate({
92
- 'section' => variables[:page_title],
93
- 'items' => items_out,
94
- 'timers' => wwid.tag_times(format: :json, sort_by: opt[:sort_tags], sort_order: opt[:tag_order])
95
- })
96
- elsif opt[:output] == 'timeline'
89
+ 'section' => variables[:page_title],
90
+ 'items' => items_out,
91
+ 'timers' => wwid.tag_times(format: :json,
92
+ sort_by: opt[:sort_tags],
93
+ sort_order: opt[:tag_order])
94
+ })
95
+ when 'timeline'
97
96
  template = <<~EOTEMPLATE
98
97
  <!doctype html>
99
98
  <html>
@@ -66,8 +66,8 @@ module Doing
66
66
  }
67
67
  end
68
68
 
69
- template = if wwid.config['export_templates']['markdown'] && File.exist?(File.expand_path(wwid.config['export_templates']['markdown']))
70
- IO.read(File.expand_path(wwid.config['export_templates']['markdown']))
69
+ template = if Doing.setting('export_templates.markdown') && File.exist?(File.expand_path(Doing.setting('export_templates.markdown')))
70
+ IO.read(File.expand_path(Doing.setting('export_templates.markdown')))
71
71
  else
72
72
  self.template(nil)
73
73
  end