doing 1.0.93 → 2.0.6.pre
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.
- checksums.yaml +4 -4
- data/AUTHORS +19 -0
- data/CHANGELOG.md +616 -0
- data/COMMANDS.md +1181 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +110 -0
- data/LICENSE +23 -0
- data/README.md +15 -699
- data/Rakefile +79 -0
- data/_config.yml +1 -0
- data/bin/doing +1055 -494
- data/doing.gemspec +34 -0
- data/doing.rdoc +1839 -0
- data/example_plugin.rb +209 -0
- data/generate_completions.sh +5 -0
- data/img/doing-colors.jpg +0 -0
- data/img/doing-printf-wrap-800.jpg +0 -0
- data/img/doing-show-note-formatting-800.jpg +0 -0
- data/lib/completion/_doing.zsh +203 -0
- data/lib/completion/doing.bash +449 -0
- data/lib/completion/doing.fish +329 -0
- data/lib/doing/array.rb +8 -0
- data/lib/doing/cli_status.rb +70 -0
- data/lib/doing/colors.rb +136 -0
- data/lib/doing/configuration.rb +312 -0
- data/lib/doing/errors.rb +109 -0
- data/lib/doing/hash.rb +31 -0
- data/lib/doing/hooks.rb +59 -0
- data/lib/doing/item.rb +155 -0
- data/lib/doing/log_adapter.rb +344 -0
- data/lib/doing/markdown_document_listener.rb +174 -0
- data/lib/doing/note.rb +59 -0
- data/lib/doing/pager.rb +95 -0
- data/lib/doing/plugin_manager.rb +208 -0
- data/lib/doing/plugins/export/csv_export.rb +48 -0
- data/lib/doing/plugins/export/html_export.rb +83 -0
- data/lib/doing/plugins/export/json_export.rb +140 -0
- data/lib/doing/plugins/export/markdown_export.rb +85 -0
- data/lib/doing/plugins/export/taskpaper_export.rb +34 -0
- data/lib/doing/plugins/export/template_export.rb +141 -0
- data/lib/doing/plugins/import/cal_to_json.scpt +0 -0
- data/lib/doing/plugins/import/calendar_import.rb +76 -0
- data/lib/doing/plugins/import/doing_import.rb +144 -0
- data/lib/doing/plugins/import/timing_import.rb +78 -0
- data/lib/doing/string.rb +348 -0
- data/lib/doing/symbol.rb +16 -0
- data/lib/doing/time.rb +18 -0
- data/lib/doing/util.rb +186 -0
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +1868 -2349
- data/lib/doing/wwidfile.rb +117 -0
- data/lib/doing.rb +43 -3
- data/lib/examples/commands/autotag.rb +63 -0
- data/lib/examples/commands/wiki.rb +81 -0
- data/lib/examples/plugins/hooks.rb +22 -0
- data/lib/examples/plugins/say_export.rb +202 -0
- data/lib/examples/plugins/templates/wiki.css +169 -0
- data/lib/examples/plugins/templates/wiki.haml +27 -0
- data/lib/examples/plugins/templates/wiki_index.haml +18 -0
- data/lib/examples/plugins/wiki_export.rb +87 -0
- data/lib/templates/doing-markdown.erb +5 -0
- data/man/doing.1 +964 -0
- data/man/doing.1.html +711 -0
- data/man/doing.1.ronn +600 -0
- data/package-lock.json +3 -0
- data/rdoc_to_mmd.rb +42 -0
- data/rdocfixer.rb +13 -0
- data/scripts/generate_bash_completions.rb +211 -0
- data/scripts/generate_fish_completions.rb +204 -0
- data/scripts/generate_zsh_completions.rb +168 -0
- metadata +82 -7
- data/lib/doing/helpers.rb +0 -191
- data/lib/doing/markdown_export.rb +0 -16
@@ -0,0 +1,208 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doing
|
4
|
+
# Plugin handling
|
5
|
+
module Plugins
|
6
|
+
class << self
|
7
|
+
def user_home
|
8
|
+
@user_home ||= Util.user_home
|
9
|
+
end
|
10
|
+
|
11
|
+
def plugins
|
12
|
+
@plugins ||= {
|
13
|
+
import: {},
|
14
|
+
export: {}
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Load plugins from plugins folder
|
20
|
+
#
|
21
|
+
def load_plugins(add_dir = nil)
|
22
|
+
plugins_path(add_dir).each do |plugin_search_path|
|
23
|
+
Dir.glob(File.join(plugin_search_path, '**', '*.rb')).sort.each do |plugin|
|
24
|
+
require plugin
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
plugins
|
29
|
+
end
|
30
|
+
|
31
|
+
# Public: Setup the plugin search path
|
32
|
+
#
|
33
|
+
# Returns an Array of plugin search paths
|
34
|
+
def plugins_path(add_dir = nil)
|
35
|
+
paths = Array(File.join(File.dirname(__FILE__), 'plugins'))
|
36
|
+
paths << File.join(add_dir) if add_dir
|
37
|
+
paths.map { |d| File.expand_path(d) }
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Register a plugin
|
42
|
+
#
|
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)
|
46
|
+
#
|
47
|
+
# param: +klass+ The class responding to :render or :import
|
48
|
+
#
|
49
|
+
#
|
50
|
+
# returns: Success boolean
|
51
|
+
#
|
52
|
+
def register(title, type, klass)
|
53
|
+
type = validate_plugin(title, type, klass)
|
54
|
+
return unless type
|
55
|
+
|
56
|
+
if title.is_a?(Array)
|
57
|
+
title.each { |t| register(t, type, klass) }
|
58
|
+
return
|
59
|
+
end
|
60
|
+
|
61
|
+
settings = if klass.respond_to? :settings
|
62
|
+
klass.settings
|
63
|
+
else
|
64
|
+
{ trigger: title.normalize_trigger, config: {} }
|
65
|
+
end
|
66
|
+
|
67
|
+
plugins[type] ||= {}
|
68
|
+
plugins[type][title] = {
|
69
|
+
trigger: settings[:trigger].normalize_trigger || title.normalize_trigger,
|
70
|
+
class: klass,
|
71
|
+
templates: settings[:templates] || nil,
|
72
|
+
config: settings[:config] || {}
|
73
|
+
}
|
74
|
+
|
75
|
+
return unless ENV['DOING_PLUGIN_DEBUG']
|
76
|
+
|
77
|
+
Doing.logger.debug('Plugin Manager:', "Registered #{type} plugin \"#{title}\"")
|
78
|
+
end
|
79
|
+
|
80
|
+
def validate_plugin(title, type, klass)
|
81
|
+
type = valid_type(type)
|
82
|
+
if type == :import && !klass.respond_to?(:import)
|
83
|
+
raise Errors::PluginUncallable.new('Import plugins must respond to :import', type: type, plugin: title)
|
84
|
+
end
|
85
|
+
|
86
|
+
if type == :export && !klass.respond_to?(:render)
|
87
|
+
raise Errors::PluginUncallable.new('Export plugins must respond to :render', type: type, plugin: title)
|
88
|
+
end
|
89
|
+
|
90
|
+
type
|
91
|
+
end
|
92
|
+
|
93
|
+
def valid_type(type, default: nil)
|
94
|
+
type ||= default
|
95
|
+
|
96
|
+
t = type.to_s
|
97
|
+
type = case t
|
98
|
+
when /^i(m(p(o(r(t)?)?)?)?)?$/
|
99
|
+
:import
|
100
|
+
when /^e(x(p(o(r(t)?)?)?)?)?$/
|
101
|
+
:export
|
102
|
+
else
|
103
|
+
raise Errors::InvalidPluginType, 'Invalid plugin type'
|
104
|
+
end
|
105
|
+
|
106
|
+
type.to_sym
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
## @brief List available plugins to stdout
|
111
|
+
##
|
112
|
+
## @param options { type, separator }
|
113
|
+
##
|
114
|
+
def list_plugins(options = {})
|
115
|
+
separator = options[:column] ? "\n" : "\t"
|
116
|
+
type = options[:type].nil? || options[:type] =~ /all/i ? 'all' : valid_type(options[:type])
|
117
|
+
|
118
|
+
case type
|
119
|
+
when :import
|
120
|
+
puts plugin_names(type: :import, separator: separator)
|
121
|
+
when :export
|
122
|
+
puts plugin_names(type: :export, separator: separator)
|
123
|
+
else
|
124
|
+
print 'Import plugins: '
|
125
|
+
puts plugin_names(type: :import, separator: ', ')
|
126
|
+
print 'Export plugins: '
|
127
|
+
puts plugin_names(type: :export, separator: ', ')
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
##
|
132
|
+
## @brief Return array of available plugin names
|
133
|
+
##
|
134
|
+
## @param type Plugin type (:import, :export)
|
135
|
+
##
|
136
|
+
## @returns [Array<String>] plugin names
|
137
|
+
##
|
138
|
+
def available_plugins(type: :export)
|
139
|
+
type = valid_type(type)
|
140
|
+
plugins[type].keys.sort
|
141
|
+
end
|
142
|
+
|
143
|
+
##
|
144
|
+
## @brief Return string version of plugin names
|
145
|
+
##
|
146
|
+
## @param type Plugin type (:import, :export)
|
147
|
+
## @param separator The separator to join names with
|
148
|
+
##
|
149
|
+
## @return [String] Plugin names
|
150
|
+
##
|
151
|
+
def plugin_names(type: :export, separator: '|')
|
152
|
+
type = valid_type(type)
|
153
|
+
available_plugins(type: type).join(separator)
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
## @brief Return a regular expression of all
|
158
|
+
## plugin triggers for type
|
159
|
+
##
|
160
|
+
## @param type The type :import or :export
|
161
|
+
##
|
162
|
+
def plugin_regex(type: :export)
|
163
|
+
type = valid_type(type)
|
164
|
+
pattern = []
|
165
|
+
plugins[type].each do |_, options|
|
166
|
+
pattern << options[:trigger].normalize_trigger
|
167
|
+
end
|
168
|
+
Regexp.new("^(?:#{pattern.join('|')})$", true)
|
169
|
+
end
|
170
|
+
|
171
|
+
def plugin_templates(type: :export)
|
172
|
+
type = valid_type(type)
|
173
|
+
templates = []
|
174
|
+
plugs = plugins[type].clone
|
175
|
+
plugs.delete_if { |_t, o| o[:templates].nil? }.each do |_, options|
|
176
|
+
options[:templates].each do |t|
|
177
|
+
templates << t[:name]
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
templates
|
182
|
+
end
|
183
|
+
|
184
|
+
def template_regex(type: :export)
|
185
|
+
type = valid_type(type)
|
186
|
+
pattern = []
|
187
|
+
plugs = plugins[type].clone
|
188
|
+
plugs.delete_if { |_t, o| o[:templates].nil? }.each do |_, options|
|
189
|
+
options[:templates].each do |t|
|
190
|
+
pattern << t[:trigger].normalize_trigger
|
191
|
+
end
|
192
|
+
end
|
193
|
+
Regexp.new("^(?:#{pattern.join('|')})$", true)
|
194
|
+
end
|
195
|
+
|
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|
|
200
|
+
options[:templates].each do |t|
|
201
|
+
return options[:class].template(trigger) if trigger =~ /^(?:#{t[:trigger].normalize_trigger})$/
|
202
|
+
end
|
203
|
+
end
|
204
|
+
raise Errors::InvalidArgument, "No template type matched \"#{trigger}\""
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# title: CSV Export
|
4
|
+
# description: Export CSV formatted data with header row
|
5
|
+
# author: Brett Terpstra
|
6
|
+
# url: https://brettterpstra.com
|
7
|
+
module Doing
|
8
|
+
##
|
9
|
+
## @brief CSV Export
|
10
|
+
##
|
11
|
+
class CSVExport
|
12
|
+
include Doing::Util
|
13
|
+
|
14
|
+
def self.settings
|
15
|
+
{
|
16
|
+
trigger: 'csv'
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.render(wwid, items, variables: {})
|
21
|
+
return if items.nil?
|
22
|
+
|
23
|
+
opt = variables[:options]
|
24
|
+
|
25
|
+
output = [CSV.generate_line(%w[start end title note timer section])]
|
26
|
+
items.each do |i|
|
27
|
+
note = format_note(i.note)
|
28
|
+
end_date = i.end_date
|
29
|
+
interval = end_date && opt[:times] ? wwid.get_interval(i, formatted: false) : 0
|
30
|
+
output.push(CSV.generate_line([i.date, end_date, i.title, note, interval, i.section]))
|
31
|
+
end
|
32
|
+
Doing.logger.debug('CSV Export:', "#{items.count} items output to CSV")
|
33
|
+
output.join('')
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.format_note(note)
|
37
|
+
out = ''
|
38
|
+
if note
|
39
|
+
arr = note.map(&:strip).delete_if { |e| e =~ /^\s*$/ }
|
40
|
+
out = arr.join("\n") unless arr.empty?
|
41
|
+
end
|
42
|
+
|
43
|
+
out
|
44
|
+
end
|
45
|
+
|
46
|
+
Doing::Plugins.register 'csv', :export, self
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# title: HTML Export
|
4
|
+
# description: Export styled HTML view of data
|
5
|
+
# author: Brett Terpstra
|
6
|
+
# url: https://brettterpstra.com
|
7
|
+
module Doing
|
8
|
+
class HTMLExport
|
9
|
+
include Doing::Util
|
10
|
+
|
11
|
+
def self.settings
|
12
|
+
{
|
13
|
+
trigger: 'html?|web(?:page)?',
|
14
|
+
templates: [
|
15
|
+
{ name: 'haml', trigger: 'h[ta]ml?|web' },
|
16
|
+
{ name: 'css', trigger: 'css|styl(?:e|us)' }
|
17
|
+
]
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.template(trigger)
|
22
|
+
if trigger =~ /^(css|sty)/
|
23
|
+
IO.read(File.join(File.dirname(__FILE__), '../../../templates/doing.css'))
|
24
|
+
else
|
25
|
+
IO.read(File.join(File.dirname(__FILE__), '../../../templates/doing.haml'))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.render(wwid, items, variables: {})
|
30
|
+
return if items.nil?
|
31
|
+
|
32
|
+
opt = variables[:options]
|
33
|
+
|
34
|
+
items_out = []
|
35
|
+
items.each do |i|
|
36
|
+
# if i.has_key?('note')
|
37
|
+
# note = '<span class="note">' + i.note.map{|n| n.strip }.join('<br>') + '</span>'
|
38
|
+
# else
|
39
|
+
# note = ''
|
40
|
+
# end
|
41
|
+
if String.method_defined? :force_encoding
|
42
|
+
title = i.title.force_encoding('utf-8').link_urls
|
43
|
+
note = i.note.map { |line| line.force_encoding('utf-8').strip.link_urls } if i.note
|
44
|
+
else
|
45
|
+
title = i.title.link_urls
|
46
|
+
note = i.note.map { |line| line.strip.link_urls } if i.note
|
47
|
+
end
|
48
|
+
|
49
|
+
interval = wwid.get_interval(i) if i.title =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
50
|
+
interval ||= false
|
51
|
+
|
52
|
+
items_out << {
|
53
|
+
date: i.date.strftime('%a %-I:%M%p'),
|
54
|
+
title: title.gsub(/(@[^ (]+(\(.*?\))?)/im, '<span class="tag">\1</span>').strip, #+ " #{note}"
|
55
|
+
note: note,
|
56
|
+
time: interval,
|
57
|
+
section: i.section
|
58
|
+
}
|
59
|
+
end
|
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']))
|
63
|
+
else
|
64
|
+
self.template('html')
|
65
|
+
end
|
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']))
|
69
|
+
else
|
70
|
+
self.template('css')
|
71
|
+
end
|
72
|
+
|
73
|
+
totals = opt[:totals] ? wwid.tag_times(format: :html, sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order]) : ''
|
74
|
+
engine = Haml::Engine.new(template)
|
75
|
+
Doing.logger.debug('HTML Export:', "#{items_out.count} items output to HTML")
|
76
|
+
@out = engine.render(Object.new,
|
77
|
+
{ :@items => items_out, :@page_title => variables[:page_title], :@style => style, :@totals => totals })
|
78
|
+
end
|
79
|
+
|
80
|
+
Doing::Plugins.register 'html', :export, self
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# title: JSON Export
|
4
|
+
# description: Export JSON-formatted data, including entry durations and tag totals
|
5
|
+
# author: Brett Terpstra
|
6
|
+
# url: https://brettterpstra.com
|
7
|
+
module Doing
|
8
|
+
class JSONExport
|
9
|
+
include Doing::Util
|
10
|
+
|
11
|
+
def self.settings
|
12
|
+
{
|
13
|
+
trigger: 'json|time(?:line)?'
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.render(wwid, items, variables: {})
|
18
|
+
return if items.nil?
|
19
|
+
|
20
|
+
opt = variables[:options]
|
21
|
+
opt[:output] = case opt[:output]
|
22
|
+
when /^t/
|
23
|
+
'timeline'
|
24
|
+
else
|
25
|
+
'json'
|
26
|
+
end
|
27
|
+
items_out = []
|
28
|
+
last_date = items[-1].date + (60 * 60 * 24)
|
29
|
+
max = last_date.strftime('%F')
|
30
|
+
min = items[0].date.strftime('%F')
|
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
|
39
|
+
|
40
|
+
end_date = i.end_date || ''
|
41
|
+
interval = wwid.get_interval(i, formatted: false) || 0
|
42
|
+
note ||= ''
|
43
|
+
|
44
|
+
tags = []
|
45
|
+
attributes = {}
|
46
|
+
skip_tags = %w[meanwhile done cancelled flagged]
|
47
|
+
i.title.scan(/@([^(\s]+)(?:\((.*?)\))?/).each do |tag|
|
48
|
+
tags.push(tag[0]) unless skip_tags.include?(tag[0])
|
49
|
+
attributes[tag[0]] = tag[1] if tag[1]
|
50
|
+
end
|
51
|
+
|
52
|
+
if opt[:output] == 'json'
|
53
|
+
|
54
|
+
i = {
|
55
|
+
date: i.date,
|
56
|
+
end_date: end_date,
|
57
|
+
title: title.strip, #+ " #{note}"
|
58
|
+
note: note.instance_of?(Array) ? note.to_s : note,
|
59
|
+
time: '%02d:%02d:%02d' % wwid.format_time(interval),
|
60
|
+
tags: tags
|
61
|
+
}
|
62
|
+
|
63
|
+
attributes.each { |attr, val| i[attr.to_sym] = val }
|
64
|
+
|
65
|
+
items_out << i
|
66
|
+
|
67
|
+
elsif opt[:output] == 'timeline'
|
68
|
+
new_item = {
|
69
|
+
'id' => index + 1,
|
70
|
+
'content' => title.strip, #+ " #{note}"
|
71
|
+
'title' => title.strip + " (#{'%02d:%02d:%02d' % wwid.format_time(interval)})",
|
72
|
+
'start' => i.date.strftime('%F %T'),
|
73
|
+
'type' => 'box',
|
74
|
+
'style' => 'color:#4c566b;background-color:#d8dee9;'
|
75
|
+
}
|
76
|
+
|
77
|
+
|
78
|
+
if interval && interval.to_i > 0
|
79
|
+
new_item['end'] = end_date.strftime('%F %T')
|
80
|
+
if interval.to_i > 3600
|
81
|
+
new_item['type'] = 'range'
|
82
|
+
new_item['style'] = 'color:white;background-color:#a2bf8a;'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
new_item['style'] = 'color:white;background-color:#f7921e;' if i.tags?(wwid.config['marker_tag'])
|
86
|
+
items_out.push(new_item)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
if opt[:output] == 'json'
|
90
|
+
Doing.logger.debug('JSON Export:', "#{items_out.count} items output to JSON")
|
91
|
+
JSON.pretty_generate({
|
92
|
+
'section' => variables[:page_title],
|
93
|
+
'items' => items_out,
|
94
|
+
'timers' => wwid.tag_times(format: :json, sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order])
|
95
|
+
})
|
96
|
+
elsif opt[:output] == 'timeline'
|
97
|
+
template = <<~EOTEMPLATE
|
98
|
+
<!doctype html>
|
99
|
+
<html>
|
100
|
+
<head>
|
101
|
+
<link href="https://unpkg.com/vis-timeline@7.4.9/dist/vis-timeline-graph2d.min.css" rel="stylesheet" type="text/css" />
|
102
|
+
<script src="https://unpkg.com/vis-timeline@7.4.9/dist/vis-timeline-graph2d.min.js"></script>
|
103
|
+
</head>
|
104
|
+
<body>
|
105
|
+
<div id="mytimeline"></div>
|
106
|
+
#{' '}
|
107
|
+
<script type="text/javascript">
|
108
|
+
// DOM element where the Timeline will be attached
|
109
|
+
var container = document.getElementById('mytimeline');
|
110
|
+
#{' '}
|
111
|
+
// Create a DataSet with data (enables two way data binding)
|
112
|
+
var data = new vis.DataSet(#{items_out.to_json});
|
113
|
+
#{' '}
|
114
|
+
// Configuration for the Timeline
|
115
|
+
var options = {
|
116
|
+
width: '100%',
|
117
|
+
height: '800px',
|
118
|
+
margin: {
|
119
|
+
item: 20
|
120
|
+
},
|
121
|
+
stack: true,
|
122
|
+
min: '#{min}',
|
123
|
+
max: '#{max}'
|
124
|
+
};
|
125
|
+
#{' '}
|
126
|
+
// Create a Timeline
|
127
|
+
var timeline = new vis.Timeline(container, data, options);
|
128
|
+
</script>
|
129
|
+
</body>
|
130
|
+
</html>
|
131
|
+
EOTEMPLATE
|
132
|
+
Doing.logger.debug('Timeline Export:', "#{items_out.count} items output to Timeline")
|
133
|
+
template
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
Doing::Plugins.register 'timeline', :export, self
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# title: Markdown Export
|
4
|
+
# description: Export GFM-style task list
|
5
|
+
# author: Brett Terpstra
|
6
|
+
# url: https://brettterpstra.com
|
7
|
+
module Doing
|
8
|
+
class MarkdownRenderer
|
9
|
+
attr_accessor :items, :page_title, :totals
|
10
|
+
|
11
|
+
def initialize(page_title, items, totals)
|
12
|
+
@page_title = page_title
|
13
|
+
@items = items
|
14
|
+
@totals = totals
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_binding
|
18
|
+
binding()
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class MarkdownExport
|
23
|
+
include Doing::Util
|
24
|
+
|
25
|
+
def self.settings
|
26
|
+
{
|
27
|
+
trigger: 'markdown|mk?d|gfm',
|
28
|
+
templates: [{ name: 'markdown', trigger: 'mk?d|markdown' }]
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.template(_trigger)
|
33
|
+
IO.read(File.join(File.dirname(__FILE__), '../../../templates/doing-markdown.erb'))
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.render(wwid, items, variables: {})
|
37
|
+
return if items.nil?
|
38
|
+
|
39
|
+
opt = variables[:options]
|
40
|
+
|
41
|
+
all_items = []
|
42
|
+
items.each do |i|
|
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
|
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
|
49
|
+
end
|
50
|
+
|
51
|
+
title = "#{title} @project(#{i.section})" unless variables[:is_single]
|
52
|
+
|
53
|
+
interval = wwid.get_interval(i, record: true) if i.title =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/ && opt[:times]
|
54
|
+
interval ||= false
|
55
|
+
|
56
|
+
done = i.title =~ /(?<= |^)@done/ ? 'x' : ' '
|
57
|
+
|
58
|
+
all_items << {
|
59
|
+
date: i.date.strftime('%a %-I:%M%p'),
|
60
|
+
shortdate: i.date.relative_date,
|
61
|
+
done: done,
|
62
|
+
note: note,
|
63
|
+
section: i.section,
|
64
|
+
time: interval,
|
65
|
+
title: title.strip
|
66
|
+
}
|
67
|
+
end
|
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']))
|
71
|
+
else
|
72
|
+
self.template(nil)
|
73
|
+
end
|
74
|
+
|
75
|
+
totals = opt[:totals] ? wwid.tag_times(format: :markdown, sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order]) : ''
|
76
|
+
|
77
|
+
mdx = MarkdownRenderer.new(variables[:page_title], all_items, totals)
|
78
|
+
Doing.logger.debug('Markdown Export:', "#{all_items.count} items output to Markdown")
|
79
|
+
engine = ERB.new(template)
|
80
|
+
@out = engine.result(mdx.get_binding)
|
81
|
+
end
|
82
|
+
|
83
|
+
Doing::Plugins.register 'markdown', :export, self
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# title: TaskPaper Export
|
4
|
+
# description: Export TaskPaper-friendly data
|
5
|
+
# author: Brett Terpstra
|
6
|
+
# url: https://brettterpstra.com
|
7
|
+
module Doing
|
8
|
+
class TaskPaperExport
|
9
|
+
include Doing::Util
|
10
|
+
|
11
|
+
def self.settings
|
12
|
+
{
|
13
|
+
trigger: 'task(?:paper)?|tp'
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.render(wwid, items, variables: {})
|
18
|
+
return if items.nil?
|
19
|
+
|
20
|
+
options = variables[:options]
|
21
|
+
|
22
|
+
options[:highlight] = false
|
23
|
+
options[:wrap_width] = 0
|
24
|
+
options[:tags_color] = false
|
25
|
+
options[:output] = 'template'
|
26
|
+
options[:template] = '- %title @date(%date)%note'
|
27
|
+
|
28
|
+
Doing.logger.debug('TaskPaper Export:', "#{items.count} items output to TaskPaper format")
|
29
|
+
@out = wwid.list_section(options)
|
30
|
+
end
|
31
|
+
|
32
|
+
Doing::Plugins.register 'taskpaper', :export, self
|
33
|
+
end
|
34
|
+
end
|