doing 2.1.113 → 2.1.117

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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +1 -1
  5. data/Rakefile +1 -1
  6. data/docs/doc/Array.html +1 -1
  7. data/docs/doc/BooleanTermParser/Clause.html +1 -1
  8. data/docs/doc/BooleanTermParser/Operator.html +1 -1
  9. data/docs/doc/BooleanTermParser/Query.html +1 -1
  10. data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
  11. data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
  12. data/docs/doc/BooleanTermParser.html +1 -1
  13. data/docs/doc/Doing/ArrayCleanup.html +1 -1
  14. data/docs/doc/Doing/ArrayNestedHash.html +1 -1
  15. data/docs/doc/Doing/ArrayTags.html +1 -1
  16. data/docs/doc/Doing/ByDayExport.html +1 -1
  17. data/docs/doc/Doing/CSVExport.html +1 -1
  18. data/docs/doc/Doing/CalendarImport.html +1 -1
  19. data/docs/doc/Doing/Change.html +1 -1
  20. data/docs/doc/Doing/Changes.html +1 -1
  21. data/docs/doc/Doing/ChronifyArray.html +1 -1
  22. data/docs/doc/Doing/ChronifyNumeric.html +1 -1
  23. data/docs/doc/Doing/ChronifyString.html +1 -1
  24. data/docs/doc/Doing/Color.html +1 -1
  25. data/docs/doc/Doing/Completion/BashCompletions.html +1 -1
  26. data/docs/doc/Doing/Completion/FigCompletions.html +1 -1
  27. data/docs/doc/Doing/Completion/FishCompletions.html +1 -1
  28. data/docs/doc/Doing/Completion/StringUtils.html +1 -1
  29. data/docs/doc/Doing/Completion/ZshCompletions.html +1 -1
  30. data/docs/doc/Doing/Completion.html +1 -1
  31. data/docs/doc/Doing/Configuration.html +1 -1
  32. data/docs/doc/Doing/DayOneRenderer.html +1 -1
  33. data/docs/doc/Doing/DayoneExport.html +1 -1
  34. data/docs/doc/Doing/DoingExport.html +1 -1
  35. data/docs/doc/Doing/DoingImport.html +1 -1
  36. data/docs/doc/Doing/Entry.html +1 -1
  37. data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
  38. data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
  39. data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
  40. data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
  41. data/docs/doc/Doing/Errors/HistoryLimitError.html +1 -1
  42. data/docs/doc/Doing/Errors/InvalidPlugin.html +1 -1
  43. data/docs/doc/Doing/Errors/MissingBackupFile.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/HTMLExport.html +1 -1
  50. data/docs/doc/Doing/Hooks.html +1 -1
  51. data/docs/doc/Doing/Item.html +1 -1
  52. data/docs/doc/Doing/ItemDates.html +1 -1
  53. data/docs/doc/Doing/ItemQuery.html +1 -1
  54. data/docs/doc/Doing/ItemState.html +1 -1
  55. data/docs/doc/Doing/ItemTags.html +1 -1
  56. data/docs/doc/Doing/Items.html +1 -1
  57. data/docs/doc/Doing/JSONExport.html +1 -1
  58. data/docs/doc/Doing/JSONImport.html +1 -1
  59. data/docs/doc/Doing/Logger.html +1 -1
  60. data/docs/doc/Doing/MarkdownExport.html +1 -1
  61. data/docs/doc/Doing/Note.html +1 -1
  62. data/docs/doc/Doing/Pager.html +1 -1
  63. data/docs/doc/Doing/Plugins.html +1 -1
  64. data/docs/doc/Doing/Prompt.html +1 -1
  65. data/docs/doc/Doing/PromptChoose.html +1 -1
  66. data/docs/doc/Doing/PromptFZF.html +1 -1
  67. data/docs/doc/Doing/PromptInput.html +1 -1
  68. data/docs/doc/Doing/PromptSTD.html +1 -1
  69. data/docs/doc/Doing/PromptYN.html +1 -1
  70. data/docs/doc/Doing/Section.html +1 -1
  71. data/docs/doc/Doing/StringHighlight.html +1 -1
  72. data/docs/doc/Doing/StringNormalize.html +1 -1
  73. data/docs/doc/Doing/StringQuery.html +1 -1
  74. data/docs/doc/Doing/StringTags.html +1 -1
  75. data/docs/doc/Doing/StringTransform.html +1 -1
  76. data/docs/doc/Doing/StringTruncate.html +1 -1
  77. data/docs/doc/Doing/StringURL.html +1 -1
  78. data/docs/doc/Doing/SymbolNormalize.html +1 -1
  79. data/docs/doc/Doing/TaskPaperExport.html +1 -1
  80. data/docs/doc/Doing/TemplateExport.html +1 -1
  81. data/docs/doc/Doing/TemplateString.html +1 -1
  82. data/docs/doc/Doing/TimingImport.html +1 -1
  83. data/docs/doc/Doing/Types.html +1 -1
  84. data/docs/doc/Doing/Util/Backup.html +1 -1
  85. data/docs/doc/Doing/Util.html +1 -1
  86. data/docs/doc/Doing/Version.html +1 -1
  87. data/docs/doc/Doing/WWID.html +18 -2
  88. data/docs/doc/Doing.html +2 -2
  89. data/docs/doc/FalseClass.html +1 -1
  90. data/docs/doc/GLI/Commands/Help.html +1 -1
  91. data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
  92. data/docs/doc/GLI/Commands.html +1 -1
  93. data/docs/doc/GLI.html +1 -1
  94. data/docs/doc/Hash.html +1 -1
  95. data/docs/doc/Numeric.html +1 -1
  96. data/docs/doc/Object.html +1 -1
  97. data/docs/doc/PhraseParser/Operator.html +1 -1
  98. data/docs/doc/PhraseParser/PhraseClause.html +1 -1
  99. data/docs/doc/PhraseParser/Query.html +1 -1
  100. data/docs/doc/PhraseParser/QueryParser.html +1 -1
  101. data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
  102. data/docs/doc/PhraseParser/TermClause.html +1 -1
  103. data/docs/doc/PhraseParser.html +1 -1
  104. data/docs/doc/Status.html +1 -1
  105. data/docs/doc/String.html +1 -1
  106. data/docs/doc/Symbol.html +1 -1
  107. data/docs/doc/Time.html +1 -1
  108. data/docs/doc/TrueClass.html +1 -1
  109. data/docs/doc/_index.html +1 -1
  110. data/docs/doc/file.README.html +2 -2
  111. data/docs/doc/index.html +2 -2
  112. data/docs/doc/top-level-namespace.html +1 -1
  113. data/doing.rdoc +1 -1
  114. data/lib/doing/plugins/export/template_export.rb +1 -1
  115. data/lib/doing/string/transform.rb +1 -1
  116. data/lib/doing/template_string.rb +149 -7
  117. data/lib/doing/version.rb +1 -1
  118. data/lib/doing/wwid/display.rb +45 -2
  119. data/man/doing.1.ronn +2 -0
  120. metadata +1 -1
@@ -46,7 +46,7 @@ module Doing
46
46
  ##
47
47
  def wrap(len, pad: 0, indent: ' ', offset: 0, prefix: '', color: '', after: '', reset: '', pad_first: false)
48
48
  color.empty? ? '' : after.last_color
49
- note_rx = /(?mi)(?<!\\)%(?<width>-?\d+)?(?:\^(?<mchar>.))?(?:(?<ichar>[ _t]|[^a-z0-9])(?<icount>\d+))?(?<prefix>.[ _t]?)?note/
49
+ note_rx = /(?mi)(?<!\\)%(?<width>\*|-?\d+)?(?:\^(?<mchar>.))?(?:(?<ichar>[ _t]|[^a-z0-9])(?<icount>\d+))?(?<prefix>.[ _t]?)?note/
50
50
  note = ''
51
51
  after = after.dup if after.frozen?
52
52
  after.sub!(note_rx) do
@@ -15,6 +15,7 @@ module Doing
15
15
  @original = string
16
16
  super(Doing::Color.coloring? ? Color.reset + string : string)
17
17
 
18
+ @stretch_widths = stretch_widths(placeholders)
18
19
  placeholders.each { |k, v| fill(k, v, wrap_width: wrap_width, color: color, tags_color: tags_color) }
19
20
  end
20
21
 
@@ -97,15 +98,15 @@ module Doing
97
98
 
98
99
  def fill(placeholder, value, wrap_width: 0, color: '', tags_color: '', reset: '')
99
100
  reparse
100
- rx = /(?mi)(?<!\\)%(?<width>-?\d+)?(?:\^(?<mchar>.))?(?:(?<ichar>[ _t]|[^a-z0-9])(?<icount>\d+))?(?<prefix>.[ _t]?)?#{placeholder.sub(
101
- /^%/, ''
102
- )}(?<after>.*?)$/
101
+ placeholder_name = placeholder.sub(/^%/, '')
102
+ rx = /(?mi)(?<!\\)%(?<width>\*|-?\d+)?(?:\^(?<mchar>.))?(?:(?<ichar>[ _t]|[^a-z0-9])(?<icount>\d+))?(?<prefix>.[ _t]?)?#{placeholder_name}(?<after>.*?)$/
103
103
  ph = raw.match(rx)
104
104
 
105
105
  return unless ph
106
106
 
107
107
  placeholder_offset = ph.begin(0)
108
- last_colors = parsed_colors[:colors].select { |v| v[:index] <= placeholder_offset + 4 }
108
+ color_offset = parsed_colors[:string].match(rx)&.begin(0) || placeholder_offset
109
+ last_colors = parsed_colors[:colors].select { |v| v[:index] <= color_offset + 4 }
109
110
 
110
111
  last_color = last_colors.map { |v| v[:color] }.pop(3).join('')
111
112
 
@@ -117,9 +118,9 @@ module Doing
117
118
  if !value.good?
118
119
  after
119
120
  else
120
- pad = m['width'].to_i
121
+ pad = m['width'] == '*' ? next_stretch_width(placeholder_name) : m['width'].to_i
121
122
  mark = m['mchar'] || ''
122
- if placeholder == 'shortdate' && m['width'].nil?
123
+ if placeholder_name == 'shortdate' && m['width'].nil?
123
124
  fmt_string = Doing.setting('shortdate_format.older', '%m/%d/%y %_I:%M%P', exact: true)
124
125
  pad = Date.today.strftime(fmt_string).length
125
126
  end
@@ -142,7 +143,6 @@ module Doing
142
143
 
143
144
  if wrap_width.positive? || pad.positive?
144
145
  width = pad.positive? ? pad : wrap_width
145
-
146
146
  out = value.gsub(/%/, '\%').strip.wrap(width,
147
147
  pad: pad,
148
148
  indent: indent,
@@ -184,5 +184,147 @@ module Doing
184
184
  end
185
185
  @parsed_colors = parse_colors
186
186
  end
187
+
188
+ private
189
+
190
+ def stretch_widths(placeholders)
191
+ keys = placeholders.keys.map { |k| Regexp.escape(k.sub(/^%/, '')) }.sort_by(&:length).reverse
192
+ return {} if keys.empty?
193
+
194
+ token_rx = /(?<!\\)%(?<width>\*|-?\d+)?(?:\^(?<mchar>.))?(?:(?<ichar>[ _t]|[^a-z0-9])(?<icount>\d+))?(?<prefix>.[ _t]?)?(?<name>#{keys.join('|')})/
195
+ queues = Hash.new { |h, k| h[k] = [] }
196
+ terminal_width = detected_terminal_width
197
+ raw.each_line do |line|
198
+ tokens = line.to_enum(:scan, token_rx).map { Regexp.last_match.dup }
199
+ next if tokens.empty?
200
+
201
+ literal_width = visible_literal_width(line.gsub(token_rx, ''))
202
+ reserved_width = literal_width
203
+ stretch_tokens = []
204
+
205
+ tokens.each do |token|
206
+ width_token = token['width']
207
+ name = token['name']
208
+ value = placeholders[name] || placeholders["%#{name}"]
209
+ natural = natural_placeholder_width(name, value)
210
+ if width_token == '*'
211
+ if block_placeholder?(name)
212
+ queues[name] << block_placeholder_width(terminal_width, token)
213
+ else
214
+ stretch_tokens << token
215
+ end
216
+ else
217
+ reserved_width += reserved_placeholder_width(width_token, natural)
218
+ end
219
+ end
220
+
221
+ next if stretch_tokens.empty?
222
+
223
+ remaining = terminal_width - reserved_width
224
+ widths = split_stretch_widths(remaining, stretch_tokens.length)
225
+ stretch_tokens.each_with_index do |token, idx|
226
+ queues[token['name']] << widths[idx]
227
+ end
228
+ end
229
+
230
+ queues
231
+ end
232
+
233
+ def split_stretch_widths(remaining, count)
234
+ return [] if count.zero?
235
+ return Array.new(count, 1) if remaining < count
236
+
237
+ base = remaining / count
238
+ extra = remaining % count
239
+ Array.new(count) { |idx| base + (idx < extra ? 1 : 0) }
240
+ end
241
+
242
+ def natural_placeholder_width(name, value)
243
+ # note placeholders are rendered on their own wrapped lines and should not
244
+ # reserve horizontal width on the title line
245
+ return 0 if block_placeholder?(name)
246
+ return 0 unless value.good?
247
+
248
+ normalized = if name =~ /^tags/ && value.respond_to?(:map)
249
+ value.map(&:to_s).join(' ')
250
+ elsif value.respond_to?(:join)
251
+ value.join("\n")
252
+ else
253
+ value.to_s
254
+ end
255
+
256
+ normalized.split("\n").map { |line| visible_literal_width(line) }.max || 0
257
+ end
258
+
259
+ def reserved_placeholder_width(width_token, natural_width)
260
+ return natural_width if width_token.nil? || width_token.empty?
261
+
262
+ minimum = width_token.to_i.abs
263
+ [natural_width, minimum].max
264
+ end
265
+
266
+ def block_placeholder?(name)
267
+ %w[note idnote odnote].include?(name)
268
+ end
269
+
270
+ def block_placeholder_width(terminal_width, token)
271
+ padding = block_placeholder_padding(token)
272
+ [terminal_width - padding, 1].max
273
+ end
274
+
275
+ def block_placeholder_padding(token)
276
+ indent = if token['ichar']
277
+ char = token['ichar'] =~ /t/ ? "\t" : ' '
278
+ char * token['icount'].to_i
279
+ else
280
+ "\t"
281
+ end
282
+
283
+ visible_literal_width("#{indent}#{token['prefix']}")
284
+ end
285
+
286
+ def next_stretch_width(name)
287
+ widths = @stretch_widths[name]
288
+ return 1 if widths.nil? || widths.empty?
289
+
290
+ [widths.shift.to_i, 1].max
291
+ end
292
+
293
+ def visible_literal_width(string)
294
+ visible = strip_template_colors(string).gsub(/\\(%)/, '\1')
295
+ visible.uncolor.length
296
+ end
297
+
298
+ def detected_terminal_width
299
+ if $stdout.tty?
300
+ begin
301
+ require 'io/console'
302
+ console = IO.console
303
+ width = console.winsize[1].to_i if console
304
+ return width if width&.positive?
305
+ rescue StandardError
306
+ # Fall through to tty-screen detection.
307
+ end
308
+ end
309
+
310
+ tty_width = TTY::Screen.columns.to_i
311
+ return tty_width if tty_width.positive?
312
+
313
+ env_width = ENV['COLUMNS'].to_i
314
+ return env_width if env_width.positive?
315
+
316
+ 80
317
+ end
318
+
319
+ def strip_template_colors(string)
320
+ working = string.dup
321
+ string.scan(/(?<!\\)(%((?:[fb]g?)?#[a-fA-F0-9]{6}|[a-z]+))/).each do |color|
322
+ valid_color = color[1].validate_color
323
+ next unless valid_color
324
+
325
+ working.sub!(/(?<!\\)%#{valid_color}/, '')
326
+ end
327
+ working
328
+ end
187
329
  end
188
330
  end
data/lib/doing/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Doing
4
- VERSION = '2.1.113'
4
+ VERSION = '2.1.117'
5
5
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Doing
4
4
  class WWID
5
+ TEMPLATE_WIDTH_PLACEHOLDERS = %w[id tags date interval duration shortdate section title note idnote odnote chompnote].freeze
5
6
  ##
6
7
  ## Display contents of a section based on options
7
8
  ##
@@ -43,7 +44,7 @@ module Doing
43
44
  opt[:order] ||= cfg['order'] || :asc
44
45
  opt[:tag_order] ||= :asc
45
46
  opt[:tags_color] = cfg['tags_color'] || false if opt[:tags_color].nil?
46
- opt[:template] ||= cfg['template']
47
+ opt[:template] ||= resolved_template(cfg)
47
48
  opt[:sort_tags] ||= opt[:tag_sort]
48
49
  end
49
50
 
@@ -263,7 +264,7 @@ module Doing
263
264
  opt[:wrap_width] = cfg['wrap_width']
264
265
  opt[:count] = count
265
266
  opt[:format] = cfg['date_format']
266
- opt[:template] = opt[:template] || cfg['template']
267
+ opt[:template] = opt[:template] || resolved_template(cfg)
267
268
  opt[:order] = :asc
268
269
  end
269
270
 
@@ -372,6 +373,48 @@ module Doing
372
373
 
373
374
  private
374
375
 
376
+ def resolved_template(cfg)
377
+ template = cfg['template']
378
+ return template unless Doing.setting('template_version', 1).to_i >= 2
379
+
380
+ placeholders = cfg['placeholders']
381
+ return template unless placeholders.is_a?(Hash)
382
+
383
+ apply_placeholder_widths(template, placeholders)
384
+ end
385
+
386
+ def apply_placeholder_widths(template, placeholders)
387
+ keys = TEMPLATE_WIDTH_PLACEHOLDERS.map { |k| Regexp.escape(k) }.sort_by(&:length).reverse
388
+ token_rx = /(?<!\\)%(?<width>\*|-?\d+)?(?:\^(?<mchar>.))?(?:(?<ichar>[ _t]|[^a-z0-9])(?<icount>\d+))?(?<prefix>.[ _t]?)?(?<name>#{keys.join('|')})/i
389
+
390
+ template.gsub(token_rx) do
391
+ m = Regexp.last_match
392
+ name = m['name']
393
+
394
+ width = normalized_placeholder_width(placeholders[name] || placeholders[name.to_sym])
395
+ mchar = m['mchar'] ? "^#{m['mchar']}" : ''
396
+ indent = m['ichar'] ? "#{m['ichar']}#{m['icount']}" : ''
397
+ prefix = m['prefix'] || ''
398
+
399
+ "%#{width}#{mchar}#{indent}#{prefix}#{name}"
400
+ end
401
+ end
402
+
403
+ def normalized_placeholder_width(config)
404
+ width = config['width'] || config[:width] if config.is_a?(Hash)
405
+
406
+ return '' if width.nil?
407
+
408
+ case width.to_s
409
+ when /^stretch$/i
410
+ '*'
411
+ when /^auto$/i, /^$/
412
+ ''
413
+ else
414
+ width.to_i.to_s
415
+ end
416
+ end
417
+
375
418
  ##
376
419
  ## Generate output using available export plugins
377
420
  ##
data/man/doing.1.ronn CHANGED
@@ -199,6 +199,8 @@ The config also contains templates for various command outputs. Include placehol
199
199
 
200
200
  Date formats are based on Ruby [`strftime`](http://www.ruby-doc.org/stdlib-2.1.1/libdoc/date/rdoc/Date.html#method-i-strftime) formatting.
201
201
 
202
+ `%title` and `%note` placeholders accept optional width formatting. Numeric width (for example `%60title`) wraps to that width. Negative width applies a minimum padded width. You can also use `*` as a stretch width marker (for example `%*title`), which expands that placeholder to fill remaining space on the template line based on terminal columns. Stretch width now accounts for the rendered minimum width of trailing fixed placeholders, so `%*title` expands up to where right-side content begins and that trailing content can sit at the right edge. Placeholders without any width marker keep natural rendered width and do not expand. When multiple `%*...` placeholders are on one line, remaining width is split evenly from left to right.
203
+
202
204
  My normal template for the `recent` command looks like this:
203
205
 
204
206
  recent:
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doing
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.113
4
+ version: 2.1.117
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra