doing 1.0.90 → 2.0.2.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/AUTHORS +19 -0
- data/CHANGELOG.md +590 -0
- data/COMMANDS.md +1181 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +110 -0
- data/LICENSE +23 -0
- data/README.md +14 -697
- data/Rakefile +79 -0
- data/_config.yml +1 -0
- data/bin/doing +1037 -481
- data/doing.fish +278 -0
- data/doing.gemspec +34 -0
- data/doing.rdoc +1759 -0
- data/example_plugin.rb +209 -0
- data/generate_completions.sh +4 -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 +151 -0
- data/lib/completion/doing.bash +416 -0
- data/lib/completion/doing.fish +278 -0
- data/lib/doing/array.rb +8 -0
- data/lib/doing/cli_status.rb +66 -0
- data/lib/doing/colors.rb +136 -0
- data/lib/doing/configuration.rb +310 -0
- data/lib/doing/errors.rb +102 -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 +342 -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 +346 -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 +1838 -2266
- data/lib/doing/wwidfile.rb +117 -0
- data/lib/doing.rb +43 -2
- data/lib/examples/commands/wiki.rb +80 -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 +210 -0
- data/scripts/generate_fish_completions.rb +201 -0
- data/scripts/generate_zsh_completions.rb +164 -0
- metadata +82 -6
- data/lib/doing/helpers.rb +0 -121
@@ -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.fmt_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.fmt_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
|