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,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# title: Template Export
|
4
|
+
# description: Default export option using configured template placeholders
|
5
|
+
# author: Brett Terpstra
|
6
|
+
# url: https://brettterpstra.com
|
7
|
+
module Doing
|
8
|
+
class TemplateExport
|
9
|
+
include Doing::Color
|
10
|
+
include Doing::Util
|
11
|
+
|
12
|
+
def self.settings
|
13
|
+
{
|
14
|
+
trigger: 'template|doing'
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.render(wwid, items, variables: {})
|
19
|
+
return if items.nil?
|
20
|
+
|
21
|
+
opt = variables[:options]
|
22
|
+
|
23
|
+
out = ''
|
24
|
+
items.each do |item|
|
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
|
28
|
+
else
|
29
|
+
flag = ''
|
30
|
+
reset = ''
|
31
|
+
end
|
32
|
+
|
33
|
+
if (!item.note.empty?) && wwid.config['include_notes']
|
34
|
+
note = item.note.map(&:strip).delete_if(&:empty?)
|
35
|
+
note.map! { |line| "#{line.sub(/^\t*/, '')} " }
|
36
|
+
|
37
|
+
if opt[:wrap_width]&.positive?
|
38
|
+
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
|
+
end
|
42
|
+
else
|
43
|
+
note = []
|
44
|
+
end
|
45
|
+
output = opt[:template].dup
|
46
|
+
|
47
|
+
output.gsub!(/%[a-z]+/) do |m|
|
48
|
+
if Doing::Color.respond_to?(m.sub(/^%/, ''))
|
49
|
+
Doing::Color.send(m.sub(/^%/, ''))
|
50
|
+
else
|
51
|
+
m
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
output.sub!(/%(\d+)?date/) do
|
56
|
+
pad = Regexp.last_match(1).to_i
|
57
|
+
format("%#{pad}s", item.date.strftime(opt[:format]))
|
58
|
+
end
|
59
|
+
|
60
|
+
interval = wwid.get_interval(item, record: true) if opt[:times]
|
61
|
+
|
62
|
+
interval ||= ''
|
63
|
+
output.sub!(/%interval/, interval)
|
64
|
+
|
65
|
+
output.sub!(/%(\d+)?shortdate/) do
|
66
|
+
pad = Regexp.last_match(1) || 13
|
67
|
+
format("%#{pad}s", item.date.relative_date)
|
68
|
+
end
|
69
|
+
|
70
|
+
output.sub!(/%section/, item.section) if item.section
|
71
|
+
|
72
|
+
title_offset = Doing::Color.uncolor(output).match(/%(-?\d+)?([ _t]\d+)?title/).begin(0)
|
73
|
+
output.sub!(/%(-?\d+)?(([ _t])(\d+))?title(.*?)$/) do
|
74
|
+
m = Regexp.last_match
|
75
|
+
pad = m[1].to_i
|
76
|
+
indent = ''
|
77
|
+
if m[2]
|
78
|
+
char = m[3] =~ /t/ ? "\t" : " "
|
79
|
+
indent = char * m[4].to_i
|
80
|
+
end
|
81
|
+
after = m[5]
|
82
|
+
if opt[:wrap_width]&.positive? || pad.positive?
|
83
|
+
width = pad.positive? ? pad : opt[:wrap_width]
|
84
|
+
item.title.wrap(width, pad: pad, indent: indent, offset: title_offset, prefix: flag, after: after, reset: reset)
|
85
|
+
# flag + item.title.gsub(/(.{#{opt[:wrap_width]}})(?=\s+|\Z)/, "\\1\n ").sub(/\s*$/, '') + reset
|
86
|
+
else
|
87
|
+
format("%s%#{pad}s%s%s", flag, item.title.sub(/\s*$/, ''), reset, after)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# output.sub!(/(?i-m)^([\s\S]*?)(%(?:[io]d|(?:\^[\s\S])?(?:(?:[ _t]|[^a-z0-9])?\d+)?(?:[\s\S][ _t]?)?)?note)([\s\S]*?)$/, '\1\3\2')
|
92
|
+
if opt[:tags_color]
|
93
|
+
output.highlight_tags!(opt[:tags_color])
|
94
|
+
end
|
95
|
+
|
96
|
+
if note.empty?
|
97
|
+
output.gsub!(/%([io]d|(\^.)?(([ _t]|[^a-z0-9])?\d+)?(.[ _t]?)?)?note/, '')
|
98
|
+
else
|
99
|
+
output.sub!(/%note/, "\n#{note.map { |l| "\t#{l.strip} " }.join("\n")}")
|
100
|
+
output.sub!(/%idnote/, "\n#{note.map { |l| "\t\t#{l.strip} " }.join("\n")}")
|
101
|
+
output.sub!(/%odnote/, "\n#{note.map { |l| "#{l.strip} " }.join("\n")}")
|
102
|
+
output.sub!(/(?mi)%(?:\^(?<mchar>.))?(?:(?<ichar>[ _t]|[^a-z0-9])?(?<icount>\d+))?(?<prefix>.[ _t]?)?note/) do
|
103
|
+
m = Regexp.last_match
|
104
|
+
mark = m['mchar'] || ''
|
105
|
+
indent = if m['ichar']
|
106
|
+
char = m['ichar'] =~ /t/ ? "\t" : ' '
|
107
|
+
char * m['icount'].to_i
|
108
|
+
else
|
109
|
+
''
|
110
|
+
end
|
111
|
+
prefix = m['prefix'] || ''
|
112
|
+
"\n#{note.map { |l| "#{mark}#{indent}#{prefix}#{l.strip} " }.join("\n")}"
|
113
|
+
end
|
114
|
+
output.sub!(/%chompnote/) do |_m|
|
115
|
+
chomp_note = note.map do |l|
|
116
|
+
l.gsub(/\n+/, ' ').gsub(/(^\s*|\s*$)/, '').gsub(/\s+/, ' ')
|
117
|
+
end
|
118
|
+
chomp_note.join(' ')
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
output.gsub!(/%hr(_under)?/) do |_m|
|
123
|
+
o = ''
|
124
|
+
`tput cols`.to_i.times do
|
125
|
+
o += Regexp.last_match(1).nil? ? '-' : '_'
|
126
|
+
end
|
127
|
+
o
|
128
|
+
end
|
129
|
+
output.gsub!(/%n/, "\n")
|
130
|
+
output.gsub!(/%t/, "\t")
|
131
|
+
|
132
|
+
out += "#{output}\n"
|
133
|
+
end
|
134
|
+
# Doing.logger.debug('Template Export:', "#{items.count} items output to template #{opt[:template]}")
|
135
|
+
out += wwid.tag_times(format: wwid.config['timer_format'].to_sym, sort_by_name: opt[:sort_tags], sort_order: opt[:tag_order]) if opt[:totals]
|
136
|
+
out
|
137
|
+
end
|
138
|
+
|
139
|
+
Doing::Plugins.register ['template', 'doing'], :export, self
|
140
|
+
end
|
141
|
+
end
|
Binary file
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# title: Calendar.app Import
|
4
|
+
# description: Import entries from a Calendar.app calendar
|
5
|
+
# author: Brett Terpstra
|
6
|
+
# url: https://brettterpstra.com
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
module Doing
|
10
|
+
##
|
11
|
+
## @brief Plugin for importing from Calendar.app on macOS
|
12
|
+
##
|
13
|
+
class CalendarImport
|
14
|
+
include Doing::Util
|
15
|
+
|
16
|
+
def self.settings
|
17
|
+
{
|
18
|
+
trigger: 'i?cal(?:endar)?'
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.import(wwid, _path, options: {})
|
23
|
+
limit_start = options[:start].to_i
|
24
|
+
limit_end = options[:end].to_i
|
25
|
+
|
26
|
+
section = options[:section] || wwid.config['current_section']
|
27
|
+
options[:no_overlap] ||= false
|
28
|
+
options[:autotag] ||= wwid.auto_tag
|
29
|
+
|
30
|
+
wwid.add_section(section) unless wwid.content.key?(section)
|
31
|
+
|
32
|
+
tags = options[:tag] ? options[:tag].split(/[ ,]+/).map { |t| t.sub(/^@?/, '') } : []
|
33
|
+
prefix = options[:prefix] || '[Calendar.app]'
|
34
|
+
|
35
|
+
script = File.join(File.dirname(__FILE__), 'cal_to_json.scpt')
|
36
|
+
res = `/usr/bin/osascript "#{script}" #{limit_start} #{limit_end}`.strip
|
37
|
+
data = JSON.parse(res)
|
38
|
+
|
39
|
+
new_items = []
|
40
|
+
data.each do |entry|
|
41
|
+
# Only process entries with a start and end date
|
42
|
+
next unless entry.key?('start') && entry.key?('end')
|
43
|
+
|
44
|
+
# Round down seconds and convert UTC to local time
|
45
|
+
start_time = Time.parse(entry['start']).getlocal
|
46
|
+
end_time = Time.parse(entry['end']).getlocal
|
47
|
+
next unless start_time && end_time
|
48
|
+
|
49
|
+
title = "#{prefix} "
|
50
|
+
title += entry['name']
|
51
|
+
tags.each do |tag|
|
52
|
+
if title =~ /\b#{tag}\b/i
|
53
|
+
title.sub!(/\b#{tag}\b/i, "@#{tag}")
|
54
|
+
else
|
55
|
+
title += " @#{tag}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
title = wwid.autotag(title) if options[:autotag]
|
59
|
+
title += " @done(#{end_time.strftime('%Y-%m-%d %H:%M')})"
|
60
|
+
title.gsub!(/ +/, ' ')
|
61
|
+
title.strip!
|
62
|
+
new_entry = { 'title' => title, 'date' => start_time, 'section' => section }
|
63
|
+
new_entry.note = entry['notes'].split(/\n/).map(&:chomp) if entry.key?('notes')
|
64
|
+
new_items.push(new_entry)
|
65
|
+
end
|
66
|
+
total = new_items.count
|
67
|
+
new_items = wwid.dedup(new_items, options[:no_overlap])
|
68
|
+
dups = total - new_items.count
|
69
|
+
Doing.logger.info(%(Skipped #{dups} items with overlapping times)) if dups.positive?
|
70
|
+
wwid.content[section][:items].concat(new_items)
|
71
|
+
Doing.logger.info(%(Imported #{new_items.count} items to #{section}))
|
72
|
+
end
|
73
|
+
|
74
|
+
Doing::Plugins.register 'calendar', :import, self
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# title: Doing Format Import
|
4
|
+
# description: Import entries from a Doing-formatted file
|
5
|
+
# author: Brett Terpstra
|
6
|
+
# url: https://brettterpstra.com
|
7
|
+
module Doing
|
8
|
+
class DoingImport
|
9
|
+
include Doing::Util
|
10
|
+
|
11
|
+
def self.settings
|
12
|
+
{
|
13
|
+
trigger: 'doing'
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
## @brief Imports a Doing file
|
19
|
+
##
|
20
|
+
## @param wwid WWID object
|
21
|
+
## @param path (String) Path to Doing file
|
22
|
+
## @param options (Hash) Additional Options
|
23
|
+
##
|
24
|
+
## @return Nothing
|
25
|
+
##
|
26
|
+
def self.import(wwid, path, options: {})
|
27
|
+
exit_now! 'Path to Doing file required' if path.nil?
|
28
|
+
|
29
|
+
exit_now! 'File not found' unless File.exist?(File.expand_path(path))
|
30
|
+
|
31
|
+
options[:no_overlap] ||= false
|
32
|
+
options[:autotag] ||= wwid.auto_tag
|
33
|
+
|
34
|
+
tags = options[:tag] ? options[:tag].split(/[ ,]+/).map { |t| t.sub(/^@?/, '') } : []
|
35
|
+
prefix = options[:prefix] || ''
|
36
|
+
|
37
|
+
@old_items = []
|
38
|
+
|
39
|
+
wwid.content.each { |_, v| @old_items.concat(v[:items]) }
|
40
|
+
|
41
|
+
new_items = read_doing_file(path)
|
42
|
+
|
43
|
+
if options[:date_filter]
|
44
|
+
new_items = wwid.filter_items(new_items, opt: { count: 0, date_filter: options[:date_filter] })
|
45
|
+
end
|
46
|
+
|
47
|
+
if options[:before] || options[:after]
|
48
|
+
new_items = wwid.filter_items(new_items, opt: { count: 0, before: options[:before], after: options[:after] })
|
49
|
+
end
|
50
|
+
|
51
|
+
imported = []
|
52
|
+
|
53
|
+
new_items.each do |item|
|
54
|
+
next if duplicate?(item)
|
55
|
+
|
56
|
+
title = "#{prefix} #{item.title}"
|
57
|
+
tags.each do |tag|
|
58
|
+
if title =~ /\b#{tag}\b/i
|
59
|
+
title.sub!(/\b#{tag}\b/i, "@#{tag}")
|
60
|
+
else
|
61
|
+
title += " @#{tag}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
title = wwid.autotag(title) if options[:autotag]
|
65
|
+
title.gsub!(/ +/, ' ')
|
66
|
+
title.strip!
|
67
|
+
section = options[:section] || item.section
|
68
|
+
section ||= wwid.config['current_section']
|
69
|
+
|
70
|
+
new_item = Item.new(item.date, title, section)
|
71
|
+
new_item.note = item.note
|
72
|
+
|
73
|
+
imported.push(new_item)
|
74
|
+
end
|
75
|
+
|
76
|
+
dups = new_items.count - imported.count
|
77
|
+
Doing.logger.info('Skipped:', %(#{dups} duplicate items)) if dups.positive?
|
78
|
+
|
79
|
+
imported = wwid.dedup(imported, !options[:overlap])
|
80
|
+
overlaps = new_items.count - imported.count - dups
|
81
|
+
Doing.logger.debug('Skipped:', "#{overlaps} items with overlapping times") if overlaps.positive?
|
82
|
+
|
83
|
+
imported.each do |item|
|
84
|
+
wwid.add_section(item.section) unless wwid.content.key?(item.section)
|
85
|
+
wwid.content[item.section][:items].push(item)
|
86
|
+
end
|
87
|
+
|
88
|
+
Doing.logger.info('Imported:', "#{imported.count} items")
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.duplicate?(item)
|
92
|
+
@old_items.each do |oi|
|
93
|
+
return true if item.equal?(oi)
|
94
|
+
end
|
95
|
+
|
96
|
+
false
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.read_doing_file(path)
|
100
|
+
doing_file = File.expand_path(path)
|
101
|
+
|
102
|
+
return nil unless File.exist?(doing_file) && File.file?(doing_file) && File.stat(doing_file).size.positive?
|
103
|
+
|
104
|
+
input = IO.read(doing_file)
|
105
|
+
input = input.force_encoding('utf-8') if input.respond_to? :force_encoding
|
106
|
+
|
107
|
+
lines = input.split(/[\n\r]/)
|
108
|
+
current = 0
|
109
|
+
|
110
|
+
items = []
|
111
|
+
section = ''
|
112
|
+
|
113
|
+
lines.each do |line|
|
114
|
+
next if line =~ /^\s*$/
|
115
|
+
|
116
|
+
case line
|
117
|
+
when /^(\S[\S ]+):\s*(@\S+\s*)*$/
|
118
|
+
section = Regexp.last_match(1)
|
119
|
+
current = 0
|
120
|
+
when /^\s*- (\d{4}-\d\d-\d\d \d\d:\d\d) \| (.*)/
|
121
|
+
date = Regexp.last_match(1).strip
|
122
|
+
title = Regexp.last_match(2).strip
|
123
|
+
item = Item.new(date, title, section)
|
124
|
+
items.push(item)
|
125
|
+
current += 1
|
126
|
+
when /^\S/
|
127
|
+
next
|
128
|
+
else
|
129
|
+
next if current.zero?
|
130
|
+
|
131
|
+
prev_item = items[current - 1]
|
132
|
+
prev_item.note = Note.new unless prev_item.note
|
133
|
+
|
134
|
+
prev_item.note.add(line)
|
135
|
+
# end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
items
|
140
|
+
end
|
141
|
+
|
142
|
+
Doing::Plugins.register 'doing', :import, self
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# title: Timing.app Import
|
4
|
+
# description: Import entries from a Timing.app report (JSON)
|
5
|
+
# author: Brett Terpstra
|
6
|
+
# url: https://brettterpstra.com
|
7
|
+
module Doing
|
8
|
+
class TimingImport
|
9
|
+
include Doing::Util
|
10
|
+
|
11
|
+
def self.settings
|
12
|
+
{
|
13
|
+
trigger: 'tim(?:ing)?'
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
## @brief Imports a Timing report
|
19
|
+
##
|
20
|
+
## @param path (String) Path to JSON report file
|
21
|
+
## @param options (Hash) Additional Options
|
22
|
+
##
|
23
|
+
def self.import(wwid, path, options: {})
|
24
|
+
exit_now! 'Path to JSON report required' if path.nil?
|
25
|
+
section = options[:section] || wwid.config['current_section']
|
26
|
+
options[:no_overlap] ||= false
|
27
|
+
options[:autotag] ||= wwid.auto_tag
|
28
|
+
wwid.add_section(section) unless wwid.content.key?(section)
|
29
|
+
|
30
|
+
add_tags = options[:tag] ? options[:tag].split(/[ ,]+/).map { |t| t.sub(/^@?/, '') } : []
|
31
|
+
prefix = options[:prefix] || '[Timing.app]'
|
32
|
+
exit_now! 'File not found' unless File.exist?(File.expand_path(path))
|
33
|
+
|
34
|
+
data = JSON.parse(IO.read(File.expand_path(path)))
|
35
|
+
new_items = []
|
36
|
+
data.each do |entry|
|
37
|
+
# Only process task entries
|
38
|
+
next if entry.key?('activityType') && entry['activityType'] != 'Task'
|
39
|
+
# Only process entries with a start and end date
|
40
|
+
next unless entry.key?('startDate') && entry.key?('endDate')
|
41
|
+
|
42
|
+
# Round down seconds and convert UTC to local time
|
43
|
+
start_time = Time.parse(entry['startDate'].sub(/:\d\dZ$/, ':00Z')).getlocal
|
44
|
+
end_time = Time.parse(entry['endDate'].sub(/:\d\dZ$/, ':00Z')).getlocal
|
45
|
+
next unless start_time && end_time
|
46
|
+
|
47
|
+
tags = entry['project'].split(/ ▸ /).map { |proj| proj.gsub(/[^a-z0-9]+/i, '').downcase }
|
48
|
+
tags.concat(add_tags)
|
49
|
+
title = "#{prefix} "
|
50
|
+
title += entry.key?('activityTitle') && entry['activityTitle'] != '(Untitled Task)' ? entry['activityTitle'] : 'Working on'
|
51
|
+
tags.each do |tag|
|
52
|
+
if title =~ /\b#{tag}\b/i
|
53
|
+
title.sub!(/\b#{tag}\b/i, "@#{tag}")
|
54
|
+
else
|
55
|
+
title += " @#{tag}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
title = wwid.autotag(title) if options[:autotag]
|
59
|
+
title += " @done(#{end_time.strftime('%Y-%m-%d %H:%M')})"
|
60
|
+
title.gsub!(/ +/, ' ')
|
61
|
+
title.strip!
|
62
|
+
new_item = Item.new(start_time, title, section)
|
63
|
+
new_item.note.add(entry['notes']) if entry.key?('notes')
|
64
|
+
new_items.push(new_item)
|
65
|
+
end
|
66
|
+
total = new_items.count
|
67
|
+
skipped = data.count - total
|
68
|
+
Doing.logger.debug('Skipped:' , %(#{skipped} items, invalid type or no time interval)) if skipped.positive?
|
69
|
+
new_items = wwid.dedup(new_items, options[:no_overlap])
|
70
|
+
dups = total - new_items.count
|
71
|
+
Doing.logger.debug('Skipped:' , %(#{dups} items with overlapping times)) if dups.positive?
|
72
|
+
wwid.content[section][:items].concat(new_items)
|
73
|
+
Doing.logger.info('Imported:', %(#{new_items.count} items to #{section}))
|
74
|
+
end
|
75
|
+
|
76
|
+
Doing::Plugins.register 'timing', :import, self
|
77
|
+
end
|
78
|
+
end
|