doing 2.1.2pre → 2.1.6pre

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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/.yardoc/checksums +19 -15
  3. data/.yardoc/object_types +0 -0
  4. data/.yardoc/objects/root.dat +0 -0
  5. data/.yardopts +1 -1
  6. data/CHANGELOG.md +62 -14
  7. data/Gemfile.lock +25 -1
  8. data/README.md +5 -1
  9. data/Rakefile +2 -0
  10. data/bin/doing +429 -142
  11. data/docs/_config.yml +1 -0
  12. data/{doc → docs/doc}/Array.html +63 -1
  13. data/docs/doc/BooleanTermParser/Clause.html +293 -0
  14. data/docs/doc/BooleanTermParser/Operator.html +172 -0
  15. data/docs/doc/BooleanTermParser/Query.html +417 -0
  16. data/docs/doc/BooleanTermParser/QueryParser.html +135 -0
  17. data/docs/doc/BooleanTermParser/QueryTransformer.html +124 -0
  18. data/docs/doc/BooleanTermParser.html +115 -0
  19. data/docs/doc/Doing/CLIFormat.html +131 -0
  20. data/{doc → docs/doc}/Doing/Color.html +2 -2
  21. data/{doc → docs/doc}/Doing/Completion.html +1 -1
  22. data/{doc → docs/doc}/Doing/Configuration.html +163 -69
  23. data/{doc → docs/doc}/Doing/Content.html +0 -0
  24. data/{doc → docs/doc}/Doing/Errors/DoingNoTraceError.html +1 -1
  25. data/{doc → docs/doc}/Doing/Errors/DoingRuntimeError.html +1 -1
  26. data/{doc → docs/doc}/Doing/Errors/DoingStandardError.html +1 -1
  27. data/{doc → docs/doc}/Doing/Errors/EmptyInput.html +1 -1
  28. data/{doc → docs/doc}/Doing/Errors/NoResults.html +1 -1
  29. data/{doc → docs/doc}/Doing/Errors/PluginException.html +1 -1
  30. data/{doc → docs/doc}/Doing/Errors/UserCancelled.html +1 -1
  31. data/{doc → docs/doc}/Doing/Errors/WrongCommand.html +1 -1
  32. data/{doc → docs/doc}/Doing/Errors.html +1 -1
  33. data/{doc → docs/doc}/Doing/Hooks.html +1 -1
  34. data/{doc → docs/doc}/Doing/Item.html +135 -89
  35. data/{doc → docs/doc}/Doing/Items.html +36 -2
  36. data/{doc → docs/doc}/Doing/LogAdapter.html +70 -1
  37. data/{doc → docs/doc}/Doing/Note.html +5 -134
  38. data/{doc → docs/doc}/Doing/Pager.html +1 -1
  39. data/{doc → docs/doc}/Doing/Plugins.html +431 -35
  40. data/{doc → docs/doc}/Doing/Prompt.html +70 -18
  41. data/{doc → docs/doc}/Doing/Section.html +1 -1
  42. data/docs/doc/Doing/TemplateString.html +713 -0
  43. data/docs/doc/Doing/Util/Backup.html +686 -0
  44. data/{doc → docs/doc}/Doing/Util.html +16 -4
  45. data/{doc → docs/doc}/Doing/WWID.html +133 -73
  46. data/{doc → docs/doc}/Doing/WWIDFile.html +0 -0
  47. data/{doc → docs/doc}/Doing.html +4 -4
  48. data/{doc → docs/doc}/GLI/Commands/MarkdownDocumentListener.html +1 -1
  49. data/{doc → docs/doc}/GLI/Commands.html +1 -1
  50. data/{doc → docs/doc}/GLI.html +1 -1
  51. data/{doc → docs/doc}/Hash.html +1 -1
  52. data/docs/doc/PhraseParser/Operator.html +172 -0
  53. data/docs/doc/PhraseParser/PhraseClause.html +303 -0
  54. data/docs/doc/PhraseParser/Query.html +495 -0
  55. data/docs/doc/PhraseParser/QueryParser.html +136 -0
  56. data/docs/doc/PhraseParser/QueryTransformer.html +124 -0
  57. data/docs/doc/PhraseParser/TermClause.html +293 -0
  58. data/docs/doc/PhraseParser.html +115 -0
  59. data/{doc → docs/doc}/Status.html +1 -1
  60. data/{doc → docs/doc}/String.html +319 -13
  61. data/{doc → docs/doc}/Symbol.html +35 -1
  62. data/{doc → docs/doc}/Time.html +70 -2
  63. data/{doc → docs/doc}/_index.html +132 -4
  64. data/docs/doc/class_list.html +51 -0
  65. data/{doc → docs/doc}/css/common.css +0 -0
  66. data/{doc → docs/doc}/css/full_list.css +0 -0
  67. data/{doc → docs/doc}/css/style.css +0 -0
  68. data/{doc → docs/doc}/file.README.html +6 -2
  69. data/{doc → docs/doc}/file_list.html +0 -0
  70. data/{doc → docs/doc}/frames.html +0 -0
  71. data/{doc → docs/doc}/index.html +6 -2
  72. data/{doc → docs/doc}/js/app.js +0 -0
  73. data/{doc → docs/doc}/js/full_list.js +0 -0
  74. data/{doc → docs/doc}/js/jquery.js +0 -0
  75. data/{doc → docs/doc}/method_list.html +684 -196
  76. data/{doc → docs/doc}/top-level-namespace.html +2 -2
  77. data/docs/index.md +60 -0
  78. data/doing.gemspec +3 -0
  79. data/doing.rdoc +222 -74
  80. data/example_plugin.rb +3 -1
  81. data/lib/completion/_doing.zsh +53 -41
  82. data/lib/completion/doing.bash +17 -6
  83. data/lib/completion/doing.fish +321 -2
  84. data/lib/doing/array.rb +9 -0
  85. data/lib/doing/boolean_term_parser.rb +86 -0
  86. data/lib/doing/completion/fish_completion.rb +46 -3
  87. data/lib/doing/completion/zsh_completion.rb +1 -1
  88. data/lib/doing/configuration.rb +48 -21
  89. data/lib/doing/item.rb +105 -10
  90. data/lib/doing/items.rb +6 -0
  91. data/lib/doing/log_adapter.rb +28 -0
  92. data/lib/doing/note.rb +31 -30
  93. data/lib/doing/phrase_parser.rb +124 -0
  94. data/lib/doing/plugin_manager.rb +84 -21
  95. data/lib/doing/plugins/export/dayone_export.rb +209 -0
  96. data/lib/doing/plugins/export/html_export.rb +2 -2
  97. data/lib/doing/plugins/export/json_export.rb +1 -0
  98. data/lib/doing/plugins/export/markdown_export.rb +1 -1
  99. data/lib/doing/plugins/export/template_export.rb +94 -86
  100. data/lib/doing/prompt.rb +26 -15
  101. data/lib/doing/string.rb +114 -29
  102. data/lib/doing/string_chronify.rb +5 -1
  103. data/lib/doing/symbol.rb +4 -0
  104. data/lib/doing/template_string.rb +197 -0
  105. data/lib/doing/time.rb +32 -0
  106. data/lib/doing/util.rb +6 -7
  107. data/lib/doing/util_backup.rb +287 -0
  108. data/lib/doing/version.rb +1 -1
  109. data/lib/doing/wwid.rb +105 -41
  110. data/lib/doing.rb +9 -0
  111. data/lib/examples/plugins/say_export.rb +1 -1
  112. data/lib/examples/plugins/wiki_export/wiki_export.rb +3 -3
  113. data/lib/templates/doing-dayone-entry.erb +6 -0
  114. data/lib/templates/doing-dayone.erb +5 -0
  115. metadata +136 -51
  116. data/doc/class_list.html +0 -51
@@ -28,26 +28,30 @@ module Doing
28
28
  plugins
29
29
  end
30
30
 
31
- # Public: Setup the plugin search path
31
+ # Setup the plugin search path
32
+ #
33
+ # @param add_dir [String] optional additional
34
+ # path to include
35
+ #
36
+ # @return [Array] Returns an Array of plugin search paths
32
37
  #
33
- # Returns an Array of plugin search paths
34
38
  def plugins_path(add_dir = nil)
35
39
  paths = Array(File.join(File.dirname(__FILE__), 'plugins'))
36
40
  paths << File.join(add_dir) if add_dir
37
41
  paths.map { |d| File.expand_path(d) }
38
42
  end
39
43
 
40
- ##
44
+
41
45
  # Register a plugin
42
46
  #
43
- # param: +[String|Array]+ title The name of the plugin (can be an array of names)
44
- #
45
- # param: +type+ The plugin type (:import, :export)
47
+ # @param title [String|Array] The name of the
48
+ # plugin (can be an array of names)
49
+ # @param type [Symbol] The plugin type
50
+ # (:import, :export)
51
+ # @param klass [Class] The class responding to
52
+ # :render or :import
46
53
  #
47
- # param: +klass+ The class responding to :render or :import
48
- #
49
- #
50
- # returns: Success boolean
54
+ # @return [Boolean] Success boolean
51
55
  #
52
56
  def register(title, type, klass)
53
57
  type = validate_plugin(title, type, klass)
@@ -90,6 +94,15 @@ module Doing
90
94
  type
91
95
  end
92
96
 
97
+ ##
98
+ ## Converts a partial symbol to a valid plugin type,
99
+ ## e.g. :imp => :import
100
+ ##
101
+ ## @param type [Symbol] the symbol to test
102
+ ## @param default [Symbol] fallback value
103
+ ##
104
+ ## @return [Symbol] :import or :export
105
+ ##
93
106
  def valid_type(type, default: nil)
94
107
  type ||= default
95
108
 
@@ -154,10 +167,13 @@ module Doing
154
167
  end
155
168
 
156
169
  ##
157
- ## Return a regular expression of all
158
- ## plugin triggers for type
170
+ ## Return a regular expression of all plugin triggers
171
+ ## for type
172
+ ##
173
+ ## @param type [Symbol] The type :import or
174
+ ## :export
159
175
  ##
160
- ## @param type The type :import or :export
176
+ ## @return [Regexp] regular expression
161
177
  ##
162
178
  def plugin_regex(type: :export)
163
179
  type = valid_type(type)
@@ -168,24 +184,43 @@ module Doing
168
184
  Regexp.new("^(?:#{pattern.join('|')})$", true)
169
185
  end
170
186
 
187
+ ##
188
+ ## Return array of available template names
189
+ ##
190
+ ## @param type [Symbol] Plugin type (:import,
191
+ ## :export)
192
+ ##
193
+ ## @return [Array<String>] template names
194
+ ##
171
195
  def plugin_templates(type: :export)
172
196
  type = valid_type(type)
173
197
  templates = []
174
198
  plugs = plugins[type].clone
175
199
  plugs.delete_if { |_t, o| o[:templates].nil? }.each do |_, options|
176
200
  options[:templates].each do |t|
177
- templates << t[:name]
201
+ out = t[:name]
202
+ out += " (#{t[:format]})" if t.key?(:format)
203
+ templates << out
178
204
  end
179
205
  end
180
206
 
181
- templates
207
+ templates.sort.uniq
182
208
  end
183
209
 
210
+ ##
211
+ ## Return a regular expression of all template
212
+ ## triggers for type
213
+ ##
214
+ ## @param type [Symbol] The type :import or
215
+ ## :export
216
+ ##
217
+ ## @return [Regexp] regular expression
218
+ ##
184
219
  def template_regex(type: :export)
185
220
  type = valid_type(type)
186
221
  pattern = []
187
222
  plugs = plugins[type].clone
188
- plugs.delete_if { |_t, o| o[:templates].nil? }.each do |_, options|
223
+ plugs.delete_if { |_, o| o[:templates].nil? }.each do |_, options|
189
224
  options[:templates].each do |t|
190
225
  pattern << t[:trigger].normalize_trigger
191
226
  end
@@ -193,16 +228,44 @@ module Doing
193
228
  Regexp.new("^(?:#{pattern.join('|')})$", true)
194
229
  end
195
230
 
196
- def template_for_trigger(trigger, type: :export)
197
- type = valid_type(type)
198
- plugs = plugins[type].clone
199
- plugs.delete_if { |_t, o| o[:templates].nil? }.each do |_, options|
231
+ ##
232
+ ## Find and return the appropriate template for a
233
+ ## trigger string. Outputs a string that can be
234
+ ## written out to the terminal for redirection
235
+ ##
236
+ ## @param trigger [String] The trigger to test
237
+ ## @param type [Symbol] the plugin type
238
+ ## (:import, :export)
239
+ ##
240
+ ## @return [String] string content of template for trigger
241
+ ##
242
+ def template_for_trigger(trigger, type: :export, save_to: nil)
243
+ plugins[valid_type(type)].clone.delete_if { |_t, o| o[:templates].nil? }.each do |_, options|
200
244
  options[:templates].each do |t|
201
- return options[:class].template(trigger) if trigger =~ /^(?:#{t[:trigger].normalize_trigger})$/
245
+ next unless trigger =~ /^(?:#{t[:trigger].normalize_trigger})$/
246
+
247
+ tpl = options[:class].template(trigger)
248
+ return tpl unless save_to
249
+
250
+ raise PluginException.new('No default filename defined', :export, t[:name]) unless t.key?(:filename)
251
+
252
+ return save_template(tpl, save_to, t[:filename])
202
253
  end
203
254
  end
204
255
  raise Errors::InvalidArgument, "No template type matched \"#{trigger}\""
205
256
  end
257
+
258
+ def save_template(tpl, dir, filename)
259
+ dir = File.expand_path(dir)
260
+ FileUtils.mkdir_p(dir) unless File.exist?(dir)
261
+ raise DoingRuntimeError, "Path #{dir} exists but is not a directory" unless File.directory?(dir)
262
+
263
+ file = File.join(dir, filename)
264
+ File.open(file, 'w') do |f|
265
+ f.puts(tpl)
266
+ Doing.logger.warn('File update:', "Template written to #{file}")
267
+ end
268
+ end
206
269
  end
207
270
  end
208
271
  end
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ # title: Day One Export
6
+ # description: Export entries to Day One plist for auto import
7
+ # author: Brett Terpstra
8
+ # url: https://brettterpstra.com
9
+ module Doing
10
+ class DayOneRenderer
11
+ attr_accessor :items, :page_title, :totals
12
+
13
+ def initialize(page_title, items, totals)
14
+ @page_title = page_title
15
+ @items = items
16
+ @totals = totals
17
+ end
18
+
19
+ def get_binding
20
+ binding()
21
+ end
22
+ end
23
+
24
+ class DayoneExport
25
+ include Doing::Util
26
+
27
+ def self.settings
28
+ {
29
+ trigger: 'day(?:one)?(?:-(?:days?|entries))?',
30
+ templates: [
31
+ { name: 'dayone', trigger: 'day(?:one)?$', format: 'erb', filename: 'dayone.erb' },
32
+ { name: 'dayone_entry', trigger: 'day(?:one)-entr(?:y|ies)?$', format: 'erb', filename: 'dayone-entry.erb' }
33
+ ]
34
+ }
35
+ end
36
+
37
+ def self.template(trigger)
38
+ case trigger
39
+ when /day(?:one)-entr(?:y|ies)?$/
40
+ IO.read(File.join(File.dirname(__FILE__), '../../../templates/doing-dayone-entry.erb'))
41
+ else
42
+ IO.read(File.join(File.dirname(__FILE__), '../../../templates/doing-dayone.erb'))
43
+ end
44
+ end
45
+
46
+ def self.render(wwid, items, variables: {})
47
+
48
+ return if items.nil?
49
+
50
+ opt = variables[:options]
51
+ trigger = opt[:output]
52
+ digest = case trigger
53
+ when /-days?$/
54
+ :day
55
+ when /-entries$/
56
+ :entries
57
+ else
58
+ :digest
59
+ end
60
+
61
+ all_items = []
62
+ days = {}
63
+ flagged = false
64
+ tags = []
65
+
66
+ items.each do |i|
67
+ day_flagged = false
68
+ date_key = i.date.strftime('%Y-%m-%d')
69
+
70
+ if String.method_defined? :force_encoding
71
+ title = i.title.force_encoding('utf-8').link_urls(format: :markdown)
72
+ note = i.note.map { |line| line.force_encoding('utf-8').strip.link_urls(format: :markdown) } if i.note
73
+ else
74
+ title = i.title.link_urls(format: :markdown)
75
+ note = i.note.map { |line| line.strip.link_urls(format: :markdown) } if i.note
76
+ end
77
+
78
+ title = "#{title} @project(#{i.section})" unless variables[:is_single]
79
+
80
+ tags.concat(i.tag_array).sort!.uniq!
81
+ flagged = day_flagged = true if i.tags?(wwid.config['marker_tag'])
82
+
83
+ interval = wwid.get_interval(i, record: true) if i.title =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
84
+ interval ||= false
85
+ human_time = false
86
+ if interval
87
+ d, h, m = wwid.format_time(wwid.get_interval(i, formatted: false))
88
+ human_times = []
89
+ human_times << format('%<d>d day%<p>s', d: d, p: d == 1 ? '' : 's') if d > 0
90
+ human_times << format('%<h>d hour%<p>s', h: h, p: h == 1 ? '' : 's') if h > 0
91
+ human_times << format('%<m>d minute%<p>s', m: m, p: m == 1 ? '' : 's') if m > 0
92
+ human_time = human_times.join(', ')
93
+ end
94
+
95
+ done = i.tags?('done') ? ' ' : ' '
96
+
97
+ item = {
98
+ date_object: i.date,
99
+ date: i.date.strftime('%a %-I:%M%p'),
100
+ shortdate: i.date.relative_date,
101
+ done: done,
102
+ note: note,
103
+ section: i.section,
104
+ time: interval,
105
+ human_time: human_time,
106
+ title: title.strip,
107
+ starred: day_flagged,
108
+ tags: i.tag_array
109
+ }
110
+ all_items << item
111
+
112
+
113
+ if days.key?(date_key)
114
+ days[date_key][:starred] = true if day_flagged
115
+ days[date_key][:tags] = days[date_key][:tags].concat(i.tag_array).sort.uniq
116
+ days[date_key][:entries].push(item)
117
+ else
118
+ days[date_key] ||= { tags: [], entries: [], starred: false }
119
+ days[date_key][:starred] = true if day_flagged
120
+ days[date_key][:tags] = days[date_key][:tags].concat(i.tag_array).sort.uniq
121
+ days[date_key][:entries].push(item)
122
+ end
123
+ end
124
+
125
+
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
+ else
129
+ self.template('dayone')
130
+ end
131
+
132
+ totals = opt[:totals] ? wwid.tag_times(format: :markdown, sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order]) : ''
133
+
134
+ case digest
135
+ when :day
136
+ days.each do |k, hsh|
137
+ title = "#{k}: #{variables[:page_title]}"
138
+ to_dayone(template: template,
139
+ title: title,
140
+ items: hsh[:entries],
141
+ totals: '',
142
+ date: Time.parse(k),
143
+ tags: tags,
144
+ starred: hsh[:starred])
145
+ end
146
+ 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
+ else
150
+ self.template('dayone-entry')
151
+ end
152
+ all_items.each do |item|
153
+ to_dayone(template: entry_template,
154
+ title: '',
155
+ items: [item],
156
+ totals: '',
157
+ date: item[:date_object],
158
+ tags: item[:tags],
159
+ starred: item[:starred])
160
+ end
161
+ else
162
+ to_dayone(template: template,
163
+ title: variables[:page_title],
164
+ items: all_items,
165
+ totals: totals,
166
+ date: Time.now,
167
+ tags: tags,
168
+ starred: flagged)
169
+ end
170
+
171
+ @out = ''
172
+ end
173
+
174
+ def self.to_dayone(template: self.template(nil), title: 'doing', items: [], totals: '', date: Time.now, tags: [], starred: false)
175
+ mdx = DayOneRenderer.new(title, items, totals)
176
+
177
+ engine = ERB.new(template)
178
+ content = engine.result(mdx.get_binding)
179
+
180
+ uuid = SecureRandom.uuid
181
+ # uuid = `uuidgen`.strip
182
+
183
+ plist = {
184
+ 'Creation Date' => date,
185
+ 'Creator' => { 'Software Agent' => 'Doing/2.0.0' },
186
+ 'Entry Text' => content,
187
+ 'Starred' => starred,
188
+ 'Tags' => tags.sort.uniq.delete_if { |t| t =~ /(done|cancell?ed|from)/ },
189
+ 'UUID' => uuid
190
+ }
191
+
192
+ container = File.expand_path('~/Library/Group Containers/')
193
+ dayone_dir = Dir.glob('*.dayoneapp2', base: container).first
194
+ import_dir = File.join(container, dayone_dir, 'Data', 'Auto Import', 'Default Journal.dayone', 'entries')
195
+ FileUtils.mkdir_p(import_dir) unless File.exist?(import_dir)
196
+ entry_file = File.join(import_dir, "#{uuid}.doentry")
197
+ Doing.logger.debug('Day One Export:', "Exporting to #{entry_file}")
198
+ File.open(entry_file, 'w') do |f|
199
+ f.puts plist.to_plist
200
+ end
201
+
202
+ Doing.logger.count(:exported, level: :info, count: items.count, message: '%count %items exported to Day One import folder')
203
+ end
204
+
205
+ Doing::Plugins.register 'dayone', :export, self
206
+ Doing::Plugins.register 'dayone-days', :export, self
207
+ Doing::Plugins.register 'dayone-entries', :export, self
208
+ end
209
+ end
@@ -12,8 +12,8 @@ module Doing
12
12
  {
13
13
  trigger: 'html?|web(?:page)?',
14
14
  templates: [
15
- { name: 'haml', trigger: 'h[ta]ml?|web' },
16
- { name: 'css', trigger: 'css|styl(?:e|us)' }
15
+ { name: 'html', trigger: 'h[ta]ml?|web', format: 'haml', filename: 'html_export.haml' },
16
+ { name: 'html_style', trigger: 'css|styl(?:e|us)', format: 'css', filename: 'html_export.css' }
17
17
  ]
18
18
  }
19
19
  end
@@ -134,6 +134,7 @@ module Doing
134
134
  end
135
135
  end
136
136
 
137
+ Doing::Plugins.register 'json', :export, self
137
138
  Doing::Plugins.register 'timeline', :export, self
138
139
  end
139
140
  end
@@ -25,7 +25,7 @@ module Doing
25
25
  def self.settings
26
26
  {
27
27
  trigger: 'markdown|mk?d|gfm',
28
- templates: [{ name: 'markdown', trigger: 'mk?d|markdown' }]
28
+ templates: [{ name: 'markdown', trigger: 'mk?d|markdown', format: 'erb', filename: 'doing-markdown.erb' }]
29
29
  }
30
30
  end
31
31
 
@@ -23,40 +23,36 @@ 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'])
27
- reset = Doing::Color.default
26
+ flag = Doing::Color.send(wwid.config['marker_color'])
27
+ reset = Doing::Color.reset + Doing::Color.default
28
28
  else
29
- # flag = ''
29
+ flag = ''
30
30
  reset = ''
31
31
  end
32
32
 
33
+ placeholders = {}
34
+
33
35
  if (!item.note.empty?) && wwid.config['include_notes']
34
36
  note = item.note.map(&:strip).delete_if(&:empty?)
35
37
  note.map! { |line| "#{line.sub(/^\t*/, '')} " }
36
38
 
37
39
  if opt[:wrap_width]&.positive?
38
40
  width = opt[:wrap_width]
39
- note.map! { |line| line.chomp.gsub(/(.{1,#{width}})(\s+|\Z)/, "\\1\n") }
40
- note = note.join("\n").split(/\n/).delete_if(&:empty?)
41
+ note.map! do |line|
42
+ line.simple_wrap(width)
43
+ # line.chomp.gsub(/(.{1,#{width}})(\s+|\Z)/, "\\1\n")
44
+ end
45
+ note = note.delete_if(&:empty?)
41
46
  end
42
47
  else
43
48
  note = []
44
49
  end
45
50
 
46
- output = opt[:template].dup
47
-
48
- output.gsub!(/%[a-z]+/) do |m|
49
- if Doing::Color.respond_to?(m.sub(/^%/, ''))
50
- Doing::Color.send(m.sub(/^%/, ''))
51
- else
52
- m
53
- end
54
- end
55
-
56
- output.sub!(/%(\d+)?date/) do
57
- pad = Regexp.last_match(1).to_i
58
- format("%#{pad}s", item.date.strftime(opt[:format]))
59
- end
51
+ # output.sub!(/%(\d+)?date/) do
52
+ # pad = Regexp.last_match(1).to_i
53
+ # format("%#{pad}s", item.date.strftime(opt[:format]))
54
+ # end
55
+ placeholders['date'] = item.date.strftime(opt[:format])
60
56
 
61
57
  interval = wwid.get_interval(item, record: true, formatted: false) if opt[:times]
62
58
  if interval
@@ -71,7 +67,8 @@ module Doing
71
67
  end
72
68
 
73
69
  interval ||= ''
74
- output.sub!(/%interval/, interval)
70
+ # output.sub!(/%interval/, interval)
71
+ placeholders['interval'] = interval
75
72
 
76
73
  duration = item.duration if opt[:duration]
77
74
  if duration
@@ -85,78 +82,89 @@ module Doing
85
82
  end
86
83
  end
87
84
  duration ||= ''
88
- output.sub!(/%duration/, duration)
89
-
90
- output.sub!(/%(\d+)?shortdate/) do
91
- pad = Regexp.last_match(1) || 13
92
- format("%#{pad}s", item.date.relative_date)
93
- end
94
-
95
- output.sub!(/%section/, item.section) if item.section
96
-
97
- title_rx = /(?mi)%(?<width>-?\d+)?(?:(?<ichar>[ _t])(?<icount>\d+))?(?<prefix>.[ _t]?)?title(?<after>.*?)$/
98
- title_color = Doing::Color.reset + output.match(/(?mi)^(.*?)(%.*?title)/)[1].last_color
99
-
100
- title_offset = Doing::Color.uncolor(output).match(title_rx).begin(0)
101
-
102
- output.sub!(title_rx) do
103
- m = Regexp.last_match
104
-
105
- after = m['after']
106
- pad = m['width'].to_i
107
- indent = ''
108
- if m['ichar']
109
- char = m['ichar'] =~ /t/ ? "\t" : ' '
110
- indent = char * m['icount'].to_i
111
- end
112
- prefix = m['prefix']
113
- if opt[:wrap_width]&.positive? || pad.positive?
114
- width = pad.positive? ? pad : opt[:wrap_width]
115
- item.title.wrap(width, pad: pad, indent: indent, offset: title_offset, prefix: prefix, color: title_color, after: after, reset: reset)
116
- # flag + item.title.gsub(/(.{#{opt[:wrap_width]}})(?=\s+|\Z)/, "\\1\n ").sub(/\s*$/, '') + reset
117
- else
118
- format("%s%#{pad}s%s", prefix, item.title.sub(/\s*$/, ''), after)
119
- end
120
- end
121
-
122
- # output.sub!(/(?i-m)^([\s\S]*?)(%(?:[io]d|(?:\^[\s\S])?(?:(?:[ _t]|[^a-z0-9])?\d+)?(?:[\s\S][ _t]?)?)?note)([\s\S]*?)$/, '\1\3\2')
123
- if opt[:tags_color]
124
- output.highlight_tags!(opt[:tags_color])
125
- end
126
-
127
- if note.empty?
128
- output.gsub!(/%(chomp|[io]d|(\^.)?(([ _t]|[^a-z0-9])?\d+)?(.[ _t]?)?)?note/, '')
129
- else
130
- output.sub!(/%note/, "\n#{note.map { |l| "\t#{l.strip} " }.join("\n")}")
131
- output.sub!(/%idnote/, "\n#{note.map { |l| "\t\t#{l.strip} " }.join("\n")}")
132
- output.sub!(/%odnote/, "\n#{note.map { |l| "#{l.strip} " }.join("\n")}")
133
- output.sub!(/(?mi)%(?:\^(?<mchar>.))?(?:(?<ichar>[ _t]|[^a-z0-9])?(?<icount>\d+))?(?<prefix>.[ _t]?)?note/) do
134
- m = Regexp.last_match
135
- mark = m['mchar'] || ''
136
- indent = if m['ichar']
137
- char = m['ichar'] =~ /t/ ? "\t" : ' '
138
- char * m['icount'].to_i
139
- else
140
- ''
141
- end
142
- prefix = m['prefix'] || ''
143
- "\n#{note.map { |l| "#{mark}#{indent}#{prefix}#{l.strip} " }.join("\n")}"
144
- end
145
-
146
- output.sub!(/%chompnote/) do
147
- note.map { |l| l.gsub(/\n+/, ' ').gsub(/(^\s*|\s*$)/, '').gsub(/\s+/, ' ') }.join(' ')
148
- end
149
- end
150
-
151
- output.gsub!(/%hr(_under)?/) do
85
+ # output.sub!(/%duration/, duration)
86
+ placeholders['duration'] = duration
87
+
88
+ # output.sub!(/%(\d+)?shortdate/) do
89
+ # pad = Regexp.last_match(1) || 13
90
+ # format("%#{pad}s", item.date.relative_date)
91
+ # end
92
+ placeholders['shortdate'] = format("%13s", item.date.relative_date)
93
+ # output.sub!(/%section/, item.section) if item.section
94
+ placeholders['section'] = item.section || ''
95
+ placeholders['title'] = item.title
96
+
97
+ # title_rx = /(?mi)%(?<width>-?\d+)?(?:(?<ichar>[ _t])(?<icount>\d+))?(?<prefix>.[ _t]?)?title(?<after>.*?)$/
98
+ # title_color = Doing::Color.reset + output.match(/(?mi)^(.*?)(%.*?title)/)[1].last_color
99
+
100
+ # title_offset = Doing::Color.uncolor(output).match(title_rx).begin(0)
101
+
102
+ # output.sub!(title_rx) do
103
+ # m = Regexp.last_match
104
+
105
+ # after = m['after']
106
+ # pad = m['width'].to_i
107
+ # indent = ''
108
+ # if m['ichar']
109
+ # char = m['ichar'] =~ /t/ ? "\t" : ' '
110
+ # indent = char * m['icount'].to_i
111
+ # end
112
+ # prefix = m['prefix']
113
+ # if opt[:wrap_width]&.positive? || pad.positive?
114
+ # width = pad.positive? ? pad : opt[:wrap_width]
115
+ # item.title.wrap(width, pad: pad, indent: indent, offset: title_offset, prefix: prefix, color: title_color, after: after, reset: reset)
116
+ # # flag + item.title.gsub(/(.{#{opt[:wrap_width]}})(?=\s+|\Z)/, "\\1\n ").sub(/\s*$/, '') + reset
117
+ # else
118
+ # format("%s%#{pad}s%s", prefix, item.title.sub(/\s*$/, ''), after)
119
+ # end
120
+ # end
121
+
122
+
123
+
124
+ placeholders['note'] = note
125
+ placeholders['idnote'] = note.empty? ? '' : "\n#{note.map { |l| "\t\t#{l.strip} " }.join("\n")}"
126
+ placeholders['odnote'] = note.empty? ? '' : "\n#{note.map { |l| "#{l.strip} " }.join("\n")}"
127
+ placeholders['chompnote'] = note.empty? ? '' : note.map { |l| l.gsub(/\n+/, ' ').gsub(/(^\s*|\s*$)/, '').gsub(/\s+/, ' ') }.join(' ')
128
+
129
+ # if note.empty?
130
+ # output.gsub!(/%(chomp|[io]d|(\^.)?(([ _t]|[^a-z0-9])?\d+)?(.[ _t]?)?)?note/, '')
131
+ # else
132
+ # output.sub!(/%note/, "\n#{note.map { |l| "\t#{l.strip} " }.join("\n")}")
133
+ # output.sub!(/%idnote/, "\n#{note.map { |l| "\t\t#{l.strip} " }.join("\n")}")
134
+ # output.sub!(/%odnote/, "\n#{note.map { |l| "#{l.strip} " }.join("\n")}")
135
+ # output.sub!(/(?mi)%(?:\^(?<mchar>.))?(?:(?<ichar>[ _t]|[^a-z0-9])?(?<icount>\d+))?(?<prefix>.[ _t]?)?note/) do
136
+ # m = Regexp.last_match
137
+ # mark = m['mchar'] || ''
138
+ # indent = if m['ichar']
139
+ # char = m['ichar'] =~ /t/ ? "\t" : ' '
140
+ # char * m['icount'].to_i
141
+ # else
142
+ # ''
143
+ # end
144
+ # prefix = m['prefix'] || ''
145
+ # "\n#{note.map { |l| "#{mark}#{indent}#{prefix}#{l.strip} " }.join("\n")}"
146
+ # end
147
+
148
+ # output.sub!(/%chompnote/) do
149
+ # note.map { |l| l.gsub(/\n+/, ' ').gsub(/(^\s*|\s*$)/, '').gsub(/\s+/, ' ') }.join(' ')
150
+ # end
151
+ # end
152
+
153
+ template = opt[:template].dup
154
+ template.sub!(/(?i-m)^([\s\S]*?)(%(?:[io]d|(?:\^[\s\S])?(?:(?:[ _t]|[^a-z0-9])?\d+)?(?:[\s\S][ _t]?)?)?note)([\s\S]*?)$/, '\1\3\2')
155
+ output = Doing::TemplateString.new(template, placeholders: placeholders, wrap_width: opt[:wrap_width], color: flag, tags_color: opt[:tags_color], reset: reset).colored
156
+
157
+ output.gsub!(/(?<!\\)%hr(_under)?/) do
152
158
  o = ''
153
159
  `tput cols`.to_i.times do
154
160
  o += Regexp.last_match(1).nil? ? '-' : '_'
155
161
  end
156
162
  o
157
163
  end
158
- output.gsub!(/%n/, "\n")
159
- output.gsub!(/%t/, "\t")
164
+ output.gsub!(/(?<!\\)%n/, "\n")
165
+ output.gsub!(/(?<!\\)%t/, "\t")
166
+
167
+ output.gsub!(/\\%/, '%')
160
168
 
161
169
  out += "#{output}\n"
162
170
  end