doing 2.1.1pre → 2.1.5pre
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/.yardoc/checksums +19 -15
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/CHANGELOG.md +58 -8
- data/Gemfile.lock +25 -1
- data/README.md +1 -1
- data/Rakefile +2 -0
- data/bin/doing +447 -149
- data/doc/Array.html +63 -1
- data/doc/BooleanTermParser/Clause.html +293 -0
- data/doc/BooleanTermParser/Operator.html +172 -0
- data/doc/BooleanTermParser/Query.html +417 -0
- data/doc/BooleanTermParser/QueryParser.html +135 -0
- data/doc/BooleanTermParser/QueryTransformer.html +124 -0
- data/doc/BooleanTermParser.html +115 -0
- data/doc/Doing/CLIFormat.html +131 -0
- data/doc/Doing/Color.html +2 -2
- data/doc/Doing/Completion.html +1 -1
- data/doc/Doing/Configuration.html +168 -73
- data/doc/Doing/Errors/DoingNoTraceError.html +1 -1
- data/doc/Doing/Errors/DoingRuntimeError.html +1 -1
- data/doc/Doing/Errors/DoingStandardError.html +1 -1
- data/doc/Doing/Errors/EmptyInput.html +1 -1
- data/doc/Doing/Errors/NoResults.html +1 -1
- data/doc/Doing/Errors/PluginException.html +1 -1
- data/doc/Doing/Errors/UserCancelled.html +1 -1
- data/doc/Doing/Errors/WrongCommand.html +1 -1
- data/doc/Doing/Errors.html +1 -1
- data/doc/Doing/Hooks.html +1 -1
- data/doc/Doing/Item.html +177 -86
- data/doc/Doing/Items.html +36 -2
- data/doc/Doing/LogAdapter.html +70 -1
- data/doc/Doing/Note.html +5 -134
- data/doc/Doing/Pager.html +1 -1
- data/doc/Doing/Plugins.html +380 -40
- data/doc/Doing/Prompt.html +70 -18
- data/doc/Doing/Section.html +1 -1
- data/doc/Doing/TemplateString.html +713 -0
- data/doc/Doing/Util/Backup.html +686 -0
- data/doc/Doing/Util.html +16 -4
- data/doc/Doing/WWID.html +133 -73
- data/doc/Doing.html +4 -4
- data/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
- data/doc/GLI/Commands.html +1 -1
- data/doc/GLI.html +1 -1
- data/doc/Hash.html +1 -1
- data/doc/PhraseParser/Operator.html +172 -0
- data/doc/PhraseParser/PhraseClause.html +303 -0
- data/doc/PhraseParser/Query.html +495 -0
- data/doc/PhraseParser/QueryParser.html +136 -0
- data/doc/PhraseParser/QueryTransformer.html +124 -0
- data/doc/PhraseParser/TermClause.html +293 -0
- data/doc/PhraseParser.html +115 -0
- data/doc/Status.html +1 -1
- data/doc/String.html +319 -13
- data/doc/Symbol.html +35 -1
- data/doc/Time.html +70 -2
- data/doc/_index.html +132 -4
- data/doc/class_list.html +1 -1
- data/doc/file.README.html +2 -2
- data/doc/index.html +2 -2
- data/doc/method_list.html +648 -160
- data/doc/top-level-namespace.html +2 -2
- data/doing.gemspec +3 -0
- data/doing.rdoc +263 -82
- data/lib/completion/doing.bash +18 -18
- data/lib/doing/array.rb +9 -0
- data/lib/doing/boolean_term_parser.rb +86 -0
- data/lib/doing/configuration.rb +63 -24
- data/lib/doing/item.rb +112 -10
- data/lib/doing/items.rb +6 -0
- data/lib/doing/log_adapter.rb +28 -0
- data/lib/doing/note.rb +31 -30
- data/lib/doing/phrase_parser.rb +124 -0
- data/lib/doing/plugin_manager.rb +57 -13
- data/lib/doing/plugins/export/dayone_export.rb +209 -0
- data/lib/doing/plugins/export/template_export.rb +113 -81
- data/lib/doing/prompt.rb +26 -13
- data/lib/doing/string.rb +114 -29
- data/lib/doing/string_chronify.rb +5 -1
- data/lib/doing/symbol.rb +4 -0
- data/lib/doing/template_string.rb +197 -0
- data/lib/doing/time.rb +32 -0
- data/lib/doing/util.rb +6 -7
- data/lib/doing/util_backup.rb +287 -0
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +152 -55
- data/lib/doing.rb +9 -0
- data/lib/templates/doing-dayone-entry.erb +6 -0
- data/lib/templates/doing-dayone.erb +5 -0
- metadata +85 -2
data/lib/doing/note.rb
CHANGED
|
@@ -22,7 +22,7 @@ module Doing
|
|
|
22
22
|
## Add note contents, optionally replacing existing note
|
|
23
23
|
##
|
|
24
24
|
## @param note [Array] The note to add, can be
|
|
25
|
-
##
|
|
25
|
+
## String, Array, or Note
|
|
26
26
|
## @param replace [Boolean] replace existing
|
|
27
27
|
## content
|
|
28
28
|
##
|
|
@@ -36,32 +36,7 @@ module Doing
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
##
|
|
39
|
-
##
|
|
40
|
-
##
|
|
41
|
-
## @param lines [Array] Array of strings
|
|
42
|
-
##
|
|
43
|
-
def append(lines)
|
|
44
|
-
concat(lines)
|
|
45
|
-
replace compress
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
##
|
|
49
|
-
## Append a string to the note content
|
|
50
|
-
##
|
|
51
|
-
## @param input [String] The input string,
|
|
52
|
-
## newlines will be split
|
|
53
|
-
##
|
|
54
|
-
def append_string(input)
|
|
55
|
-
concat(input.split(/\n/).map(&:strip))
|
|
56
|
-
replace compress
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def compress!
|
|
60
|
-
replace compress
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
##
|
|
64
|
-
## Remove blank lines and comment lines (#)
|
|
39
|
+
## Remove blank lines and comments (#)
|
|
65
40
|
##
|
|
66
41
|
## @return [Array] compressed array
|
|
67
42
|
##
|
|
@@ -69,8 +44,8 @@ module Doing
|
|
|
69
44
|
delete_if { |l| l =~ /^\s*$/ || l =~ /^#/ }
|
|
70
45
|
end
|
|
71
46
|
|
|
72
|
-
def
|
|
73
|
-
replace
|
|
47
|
+
def compress!
|
|
48
|
+
replace compress
|
|
74
49
|
end
|
|
75
50
|
|
|
76
51
|
##
|
|
@@ -83,6 +58,10 @@ module Doing
|
|
|
83
58
|
map(&:strip)
|
|
84
59
|
end
|
|
85
60
|
|
|
61
|
+
def strip_lines!
|
|
62
|
+
replace strip_lines
|
|
63
|
+
end
|
|
64
|
+
|
|
86
65
|
##
|
|
87
66
|
## Note as multi-line string
|
|
88
67
|
def to_s
|
|
@@ -101,11 +80,33 @@ module Doing
|
|
|
101
80
|
## @param other [Note] The other Note
|
|
102
81
|
##
|
|
103
82
|
## @return [Boolean] true if equal
|
|
104
|
-
##
|
|
105
83
|
def equal?(other)
|
|
106
84
|
return false unless other.is_a?(Note)
|
|
107
85
|
|
|
108
86
|
to_s == other.to_s
|
|
109
87
|
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
##
|
|
92
|
+
## Append an array of strings to note
|
|
93
|
+
##
|
|
94
|
+
## @param lines [Array] Array of strings
|
|
95
|
+
##
|
|
96
|
+
def append(lines)
|
|
97
|
+
concat(lines)
|
|
98
|
+
replace compress
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
##
|
|
102
|
+
## Append a string to the note content
|
|
103
|
+
##
|
|
104
|
+
## @param input [String] The input string,
|
|
105
|
+
## newlines will be split
|
|
106
|
+
##
|
|
107
|
+
def append_string(input)
|
|
108
|
+
concat(input.split(/\n/).map(&:strip))
|
|
109
|
+
replace compress
|
|
110
|
+
end
|
|
110
111
|
end
|
|
111
112
|
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'parslet'
|
|
4
|
+
|
|
5
|
+
module PhraseParser
|
|
6
|
+
# This parser adds quoted phrases (using matched double quotes) in addition to
|
|
7
|
+
# terms. This is done creating multiple types of clauses instead of just one.
|
|
8
|
+
# A phrase clause generates an Elasticsearch match_phrase query.
|
|
9
|
+
class QueryParser < Parslet::Parser
|
|
10
|
+
rule(:term) { match('[^\s"]').repeat(1).as(:term) }
|
|
11
|
+
rule(:quote) { str('"') }
|
|
12
|
+
rule(:operator) { (str('+') | str('-')).as(:operator) }
|
|
13
|
+
rule(:phrase) do
|
|
14
|
+
(quote >> (term >> space.maybe).repeat >> quote).as(:phrase)
|
|
15
|
+
end
|
|
16
|
+
rule(:clause) { (operator.maybe >> (phrase | term)).as(:clause) }
|
|
17
|
+
rule(:space) { match('\s').repeat(1) }
|
|
18
|
+
rule(:query) { (clause >> space.maybe).repeat.as(:query) }
|
|
19
|
+
root(:query)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class QueryTransformer < Parslet::Transform
|
|
23
|
+
rule(:clause => subtree(:clause)) do
|
|
24
|
+
if clause[:term]
|
|
25
|
+
TermClause.new(clause[:operator]&.to_s, clause[:term].to_s)
|
|
26
|
+
elsif clause[:phrase]
|
|
27
|
+
phrase = clause[:phrase].map { |p| p[:term].to_s }.join(' ')
|
|
28
|
+
PhraseClause.new(clause[:operator]&.to_s, phrase)
|
|
29
|
+
else
|
|
30
|
+
raise "Unexpected clause type: '#{clause}'"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
rule(query: sequence(:clauses)) { Query.new(clauses) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class Operator
|
|
37
|
+
def self.symbol(str)
|
|
38
|
+
case str
|
|
39
|
+
when '+'
|
|
40
|
+
:must
|
|
41
|
+
when '-'
|
|
42
|
+
:must_not
|
|
43
|
+
when nil
|
|
44
|
+
:should
|
|
45
|
+
else
|
|
46
|
+
raise "Unknown operator: #{str}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class TermClause
|
|
52
|
+
attr_accessor :operator, :term
|
|
53
|
+
|
|
54
|
+
def initialize(operator, term)
|
|
55
|
+
self.operator = Operator.symbol(operator)
|
|
56
|
+
self.term = term
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Phrase
|
|
61
|
+
class PhraseClause
|
|
62
|
+
attr_accessor :operator, :phrase
|
|
63
|
+
|
|
64
|
+
def initialize(operator, phrase)
|
|
65
|
+
self.operator = Operator.symbol(operator)
|
|
66
|
+
self.phrase = phrase
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
## Query object
|
|
71
|
+
class Query
|
|
72
|
+
attr_accessor :should_clauses, :must_not_clauses, :must_clauses
|
|
73
|
+
|
|
74
|
+
def initialize(clauses)
|
|
75
|
+
grouped = clauses.chunk(&:operator).to_h
|
|
76
|
+
self.should_clauses = grouped.fetch(:should, [])
|
|
77
|
+
self.must_not_clauses = grouped.fetch(:must_not, [])
|
|
78
|
+
self.must_clauses = grouped.fetch(:must, [])
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def to_elasticsearch
|
|
82
|
+
query = {}
|
|
83
|
+
|
|
84
|
+
if should_clauses.any?
|
|
85
|
+
query[:should] = should_clauses.map do |clause|
|
|
86
|
+
clause_to_query(clause)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
if must_clauses.any?
|
|
91
|
+
query[:must] = must_clauses.map do |clause|
|
|
92
|
+
clause_to_query(clause)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
if must_not_clauses.any?
|
|
97
|
+
query[:must_not] = must_not_clauses.map do |clause|
|
|
98
|
+
clause_to_query(clause)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
query
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def clause_to_query(clause)
|
|
106
|
+
case clause
|
|
107
|
+
when TermClause
|
|
108
|
+
match(clause.term)
|
|
109
|
+
when PhraseClause
|
|
110
|
+
match_phrase(clause.phrase)
|
|
111
|
+
else
|
|
112
|
+
raise "Unknown clause type: #{clause}"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def match(term)
|
|
117
|
+
term
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def match_phrase(phrase)
|
|
121
|
+
phrase
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
data/lib/doing/plugin_manager.rb
CHANGED
|
@@ -28,26 +28,30 @@ module Doing
|
|
|
28
28
|
plugins
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
#
|
|
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
|
|
44
|
-
#
|
|
45
|
-
# param
|
|
46
|
-
#
|
|
47
|
-
# param
|
|
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
|
|
48
53
|
#
|
|
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
|
-
##
|
|
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
|
-
## @
|
|
176
|
+
## @return [Regexp] regular expression
|
|
161
177
|
##
|
|
162
178
|
def plugin_regex(type: :export)
|
|
163
179
|
type = valid_type(type)
|
|
@@ -168,6 +184,14 @@ 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 = []
|
|
@@ -181,6 +205,15 @@ module Doing
|
|
|
181
205
|
templates
|
|
182
206
|
end
|
|
183
207
|
|
|
208
|
+
##
|
|
209
|
+
## Return a regular expression of all template
|
|
210
|
+
## triggers for type
|
|
211
|
+
##
|
|
212
|
+
## @param type [Symbol] The type :import or
|
|
213
|
+
## :export
|
|
214
|
+
##
|
|
215
|
+
## @return [Regexp] regular expression
|
|
216
|
+
##
|
|
184
217
|
def template_regex(type: :export)
|
|
185
218
|
type = valid_type(type)
|
|
186
219
|
pattern = []
|
|
@@ -193,6 +226,17 @@ module Doing
|
|
|
193
226
|
Regexp.new("^(?:#{pattern.join('|')})$", true)
|
|
194
227
|
end
|
|
195
228
|
|
|
229
|
+
##
|
|
230
|
+
## Find and return the appropriate template for a
|
|
231
|
+
## trigger string. Outputs a string that can be
|
|
232
|
+
## written out to the terminal for redirection
|
|
233
|
+
##
|
|
234
|
+
## @param trigger [String] The trigger to test
|
|
235
|
+
## @param type [Symbol] the plugin type
|
|
236
|
+
## (:import, :export)
|
|
237
|
+
##
|
|
238
|
+
## @return [String] string content of template for trigger
|
|
239
|
+
##
|
|
196
240
|
def template_for_trigger(trigger, type: :export)
|
|
197
241
|
type = valid_type(type)
|
|
198
242
|
plugs = plugins[type].clone
|
|
@@ -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)?$' },
|
|
32
|
+
{ name: 'dayone_entry', trigger: 'day(?:one)-entr(?:y|ies)?$'}
|
|
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
|