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
data/lib/doing/string.rb
ADDED
@@ -0,0 +1,348 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doing
|
4
|
+
##
|
5
|
+
## @brief String helpers
|
6
|
+
##
|
7
|
+
class ::String
|
8
|
+
include Doing::Color
|
9
|
+
def to_rx(distance)
|
10
|
+
gsub(/(.)/, "\\1.{0,#{distance}}")
|
11
|
+
end
|
12
|
+
|
13
|
+
def truthy?
|
14
|
+
if self =~ /^(0|f(alse)?|n(o)?)$/i
|
15
|
+
false
|
16
|
+
else
|
17
|
+
true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def highlight_tags!(color = 'yellow')
|
22
|
+
replace highlight_tags(color)
|
23
|
+
end
|
24
|
+
|
25
|
+
def highlight_tags(color = 'yellow')
|
26
|
+
escapes = scan(/(\e\[[\d;]+m)[^\e]+@/)
|
27
|
+
tag_color = Doing::Color.send(color)
|
28
|
+
last_color = if !escapes.empty?
|
29
|
+
escapes[-1][0]
|
30
|
+
else
|
31
|
+
Doing::Color.default
|
32
|
+
end
|
33
|
+
gsub(/(\s|m)(@[^ ("']+)/, "\\1#{tag_color}\\2#{last_color}")
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
## @brief Test if line should be ignored
|
38
|
+
##
|
39
|
+
## @return [Boolean] line is empty or comment
|
40
|
+
##
|
41
|
+
def ignore?
|
42
|
+
line = self
|
43
|
+
line =~ /^#/ || line =~ /^\s*$/
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
## @brief Truncate to nearest word
|
48
|
+
##
|
49
|
+
## @param len The length
|
50
|
+
##
|
51
|
+
def truncate(len, ellipsis: '...')
|
52
|
+
return self if length <= len
|
53
|
+
|
54
|
+
total = 0
|
55
|
+
res = []
|
56
|
+
|
57
|
+
split(/ /).each do |word|
|
58
|
+
break if total + 1 + word.length > len
|
59
|
+
|
60
|
+
total += 1 + word.length
|
61
|
+
res.push(word)
|
62
|
+
end
|
63
|
+
res.join(' ') + ellipsis
|
64
|
+
end
|
65
|
+
|
66
|
+
def truncate!(len, ellipsis: '...')
|
67
|
+
replace truncate(len, ellipsis: ellipsis)
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
## @brief Truncate string in the middle
|
72
|
+
##
|
73
|
+
## @param len The length
|
74
|
+
## @param ellipsis The ellipsis
|
75
|
+
##
|
76
|
+
def truncmiddle(len, ellipsis: '...')
|
77
|
+
return self if length <= len
|
78
|
+
len -= (ellipsis.length / 2).to_i
|
79
|
+
total = length
|
80
|
+
half = total / 2
|
81
|
+
cut = (total - len) / 2
|
82
|
+
sub(/(.{#{half - cut}}).*?(.{#{half - cut}})$/, "\\1#{ellipsis}\\2")
|
83
|
+
end
|
84
|
+
|
85
|
+
def truncmiddle!(len, ellipsis: '...')
|
86
|
+
replace truncmiddle(len, ellipsis: ellipsis)
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
## @brief Remove color escape codes
|
91
|
+
##
|
92
|
+
## @return clean string
|
93
|
+
##
|
94
|
+
def uncolor
|
95
|
+
gsub(/\e\[[\d;]+m/,'')
|
96
|
+
end
|
97
|
+
|
98
|
+
def uncolor!
|
99
|
+
replace uncolor
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
## @brief Wrap string at word breaks, respecting tags
|
104
|
+
##
|
105
|
+
## @param len [Integer] The length
|
106
|
+
## @param offset [Integer] (Optional) The width to pad each subsequent line
|
107
|
+
## @param prefix [String] (Optional) A prefix to add to each line
|
108
|
+
##
|
109
|
+
def wrap(len, pad: 0, indent: ' ', offset: 0, prefix: '', after: '', reset: '')
|
110
|
+
note_rx = /(?i-m)(%(?:[io]d|(?:\^[\s\S])?(?:(?:[ _t]|[^a-z0-9])?\d+)?(?:[\s\S][ _t]?)?)?note)/
|
111
|
+
str = gsub(/@\w+\(.*?\)/) { |tag| tag.gsub(/\s/, '%%%%') }
|
112
|
+
words = str.split(/ /).map { |word| word.gsub(/%%%%/, ' ') }
|
113
|
+
out = []
|
114
|
+
line = []
|
115
|
+
words.each do |word|
|
116
|
+
if line.join(' ').length + word.length + 1 > len
|
117
|
+
out.push(line.join(' '))
|
118
|
+
line.clear
|
119
|
+
end
|
120
|
+
|
121
|
+
line << word
|
122
|
+
end
|
123
|
+
out.push(line.join(' '))
|
124
|
+
note = ''
|
125
|
+
after.sub!(note_rx) do
|
126
|
+
note = Regexp.last_match(0)
|
127
|
+
''
|
128
|
+
end
|
129
|
+
|
130
|
+
out[0] = format("%-#{pad}s%s", out[0], after)
|
131
|
+
left_pad = ' ' * (offset)
|
132
|
+
left_pad += indent
|
133
|
+
out.map { |l| "#{left_pad}#{prefix}#{l}" }.join("\n").strip + " #{note}".chomp
|
134
|
+
end
|
135
|
+
|
136
|
+
##
|
137
|
+
## @brief Capitalize on the first character on string
|
138
|
+
##
|
139
|
+
## @return Capitalized string
|
140
|
+
##
|
141
|
+
def cap_first
|
142
|
+
sub(/^\w/) do |m|
|
143
|
+
m.upcase
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
##
|
148
|
+
## @brief Convert a sort order string to a qualified type
|
149
|
+
##
|
150
|
+
## @return (String) 'asc' or 'desc'
|
151
|
+
##
|
152
|
+
def normalize_order!
|
153
|
+
replace normalize_order
|
154
|
+
end
|
155
|
+
|
156
|
+
def normalize_order(default = 'asc')
|
157
|
+
case self
|
158
|
+
when /^a/i
|
159
|
+
'asc'
|
160
|
+
when /^d/i
|
161
|
+
'desc'
|
162
|
+
else
|
163
|
+
default
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
##
|
168
|
+
## @brief Convert a boolean string to a symbol
|
169
|
+
##
|
170
|
+
## @return Symbol :and, :or, or :not
|
171
|
+
##
|
172
|
+
def normalize_bool!
|
173
|
+
replace normalize_bool
|
174
|
+
end
|
175
|
+
|
176
|
+
def normalize_bool(default = :and)
|
177
|
+
case self
|
178
|
+
when /(and|all)/i
|
179
|
+
:and
|
180
|
+
when /(any|or)/i
|
181
|
+
:or
|
182
|
+
when /(not|none)/i
|
183
|
+
:not
|
184
|
+
else
|
185
|
+
default.is_a?(Symbol) ? default : default.normalize_bool
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def normalize_trigger!
|
190
|
+
replace normalize_trigger
|
191
|
+
end
|
192
|
+
|
193
|
+
def normalize_trigger
|
194
|
+
gsub(/\((?!\?:)/, '(?:').downcase
|
195
|
+
end
|
196
|
+
|
197
|
+
def to_tags
|
198
|
+
gsub(/ *, */, ' ').gsub(/ +/, ' ').split(/ /).sort.uniq.map { |t| t.strip.sub(/^@/, '') }
|
199
|
+
end
|
200
|
+
|
201
|
+
def add_tags!(tags, remove: false)
|
202
|
+
replace add_tags(tags, remove: remove)
|
203
|
+
end
|
204
|
+
|
205
|
+
def add_tags(tags, remove: false)
|
206
|
+
title = self.dup
|
207
|
+
tags = tags.to_tags if tags.is_a?(String)
|
208
|
+
tags.each { |tag| title.tag!(tag, remove: remove) }
|
209
|
+
title
|
210
|
+
end
|
211
|
+
|
212
|
+
def tag!(tag, value: nil, remove: false, rename_to: nil, regex: false, single: false)
|
213
|
+
replace tag(tag, value: value, remove: remove, rename_to: rename_to, regex: regex, single: single)
|
214
|
+
end
|
215
|
+
|
216
|
+
def tag(tag, value: nil, remove: false, rename_to: nil, regex: false, single: false)
|
217
|
+
log_level = single ? :info : :debug
|
218
|
+
title = dup
|
219
|
+
title.chomp!
|
220
|
+
tag = tag.sub(/^@?/, '')
|
221
|
+
case_sensitive = tag !~ /[A-Z]/
|
222
|
+
|
223
|
+
rx_tag = if regex
|
224
|
+
tag.gsub(/\./, '\S')
|
225
|
+
else
|
226
|
+
tag.gsub(/\?/, '.').gsub(/\*/, '\S*?')
|
227
|
+
end
|
228
|
+
|
229
|
+
if remove || rename_to
|
230
|
+
return title unless title =~ /#{rx_tag}(?=[ (]|$)/
|
231
|
+
|
232
|
+
rx = Regexp.new("(^| )@#{rx_tag}(\\([^)]*\\))?(?= |$)", case_sensitive)
|
233
|
+
if title =~ rx
|
234
|
+
title.gsub!(rx) do
|
235
|
+
m = Regexp.last_match
|
236
|
+
rename_to ? "#{m[1]}@#{rename_to}#{m[2]}" : m[1]
|
237
|
+
end
|
238
|
+
|
239
|
+
title.dedup_tags!
|
240
|
+
title.chomp!
|
241
|
+
|
242
|
+
if rename_to
|
243
|
+
f = "@#{tag}".cyan
|
244
|
+
t = "@#{rename_to}".cyan
|
245
|
+
Doing.logger.write(log_level, 'Tag:', %(renamed #{f} to #{t} in "#{title}"))
|
246
|
+
else
|
247
|
+
f = "@#{tag}".cyan
|
248
|
+
Doing.logger.write(log_level, 'Tag:', %(removed #{f} from "#{title}"))
|
249
|
+
end
|
250
|
+
else
|
251
|
+
Doing.logger.debug('Skipped:', "not tagged #{"@#{tag}".cyan}")
|
252
|
+
end
|
253
|
+
elsif title =~ /@#{tag}(?=[ (]|$)/
|
254
|
+
Doing.logger.debug('Skipped:', "already tagged #{"@#{tag}".cyan}")
|
255
|
+
return title
|
256
|
+
else
|
257
|
+
add = tag
|
258
|
+
add += "(#{value})" unless value.nil?
|
259
|
+
title.chomp!
|
260
|
+
title += " @#{add}"
|
261
|
+
|
262
|
+
title.dedup_tags!
|
263
|
+
title.chomp!
|
264
|
+
Doing.logger.write(log_level, 'Tag:', %(added #{('@' + tag).cyan} to "#{title}"))
|
265
|
+
end
|
266
|
+
|
267
|
+
title.gsub(/ +/, ' ')
|
268
|
+
end
|
269
|
+
|
270
|
+
##
|
271
|
+
## @brief Remove duplicate tags, leaving only first occurrence
|
272
|
+
##
|
273
|
+
## @return Deduplicated string
|
274
|
+
##
|
275
|
+
def dedup_tags!
|
276
|
+
replace dedup_tags
|
277
|
+
end
|
278
|
+
|
279
|
+
def dedup_tags
|
280
|
+
title = dup
|
281
|
+
tags = title.scan(/(?<=^| )(@(\S+?)(\([^)]+\))?)(?= |$)/).uniq
|
282
|
+
tags.each do |tag|
|
283
|
+
found = false
|
284
|
+
title.gsub!(/( |^)#{tag[1]}(\([^)]+\))?(?= |$)/) do |m|
|
285
|
+
if found
|
286
|
+
''
|
287
|
+
else
|
288
|
+
found = true
|
289
|
+
m
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
title
|
294
|
+
end
|
295
|
+
|
296
|
+
##
|
297
|
+
## @brief Turn raw urls into HTML links
|
298
|
+
##
|
299
|
+
## @param opt (Hash) Additional Options
|
300
|
+
##
|
301
|
+
def link_urls!(opt = {})
|
302
|
+
replace link_urls(opt)
|
303
|
+
end
|
304
|
+
|
305
|
+
def link_urls(opt = {})
|
306
|
+
opt[:format] ||= :html
|
307
|
+
str = self.dup
|
308
|
+
|
309
|
+
if :format == :markdown
|
310
|
+
# Remove <self-linked> formatting
|
311
|
+
str.gsub!(/<(.*?)>/) do |match|
|
312
|
+
m = Regexp.last_match
|
313
|
+
if m[1] =~ /^https?:/
|
314
|
+
m[1]
|
315
|
+
else
|
316
|
+
match
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# Replace qualified urls
|
322
|
+
str.gsub!(%r{(?mi)(?<!["'\[(\\])((http|https)://)([\w\-_]+(\.[\w\-_]+)+)([\w\-.,@?^=%&:/~+#]*[\w\-@^=%&/~+#])?}) do |_match|
|
323
|
+
m = Regexp.last_match
|
324
|
+
proto = m[1].nil? ? 'http://' : ''
|
325
|
+
case opt[:format]
|
326
|
+
when :html
|
327
|
+
%(<a href="#{proto}#{m[0]}" title="Link to #{m[0].sub(/^https?:\/\//, '')}">[#{m[3]}]</a>)
|
328
|
+
when :markdown
|
329
|
+
"[#{m[0]}](#{proto}#{m[0]})"
|
330
|
+
else
|
331
|
+
m[0]
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# Clean up unlinked <urls>
|
336
|
+
str.gsub!(/<(\w+:.*?)>/) do |match|
|
337
|
+
m = Regexp.last_match
|
338
|
+
if m[1] =~ /<a href/
|
339
|
+
match
|
340
|
+
else
|
341
|
+
%(<a href="#{m[1]}" title="Link to #{m[1]}">[link]</a>)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
str
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
data/lib/doing/symbol.rb
ADDED
data/lib/doing/time.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Doing
|
2
|
+
##
|
3
|
+
## @brief Date helpers
|
4
|
+
##
|
5
|
+
class ::Time
|
6
|
+
def relative_date
|
7
|
+
if self > Date.today.to_time
|
8
|
+
strftime('%_I:%M%P')
|
9
|
+
elsif self > (Date.today - 6).to_time
|
10
|
+
strftime('%a %_I:%M%P')
|
11
|
+
elsif self.year == Date.today.year
|
12
|
+
strftime('%m/%d %_I:%M%P')
|
13
|
+
else
|
14
|
+
strftime('%m/%d/%Y %_I:%M%P')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/doing/util.rb
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doing
|
4
|
+
# Utilities
|
5
|
+
module Util
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def user_home
|
9
|
+
if Dir.respond_to?('home')
|
10
|
+
Dir.home
|
11
|
+
else
|
12
|
+
File.expand_path('~')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
## @brief Test if command line tool is available
|
18
|
+
##
|
19
|
+
## @param cli (String) The name or path of the cli
|
20
|
+
##
|
21
|
+
def exec_available(cli)
|
22
|
+
return false if cli.nil?
|
23
|
+
|
24
|
+
if File.exist?(File.expand_path(cli))
|
25
|
+
File.executable?(File.expand_path(cli))
|
26
|
+
else
|
27
|
+
system "which #{cli}", out: File::NULL, err: File::NULL
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def merge_default_proc(target, overwrite)
|
32
|
+
return unless target.is_a?(Hash) && overwrite.is_a?(Hash) && target.default_proc.nil?
|
33
|
+
|
34
|
+
target.default_proc = overwrite.default_proc
|
35
|
+
end
|
36
|
+
|
37
|
+
def duplicate_frozen_values(target)
|
38
|
+
target.each do |key, val|
|
39
|
+
target[key] = val.dup if val.frozen? && duplicable?(val)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Non-destructive version of deep_merge_hashes! See that method.
|
44
|
+
#
|
45
|
+
# Returns the merged hashes.
|
46
|
+
def deep_merge_hashes(master_hash, other_hash)
|
47
|
+
deep_merge_hashes!(master_hash.dup, other_hash)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Merges a master hash with another hash, recursively.
|
51
|
+
#
|
52
|
+
# master_hash - the "parent" hash whose values will be overridden
|
53
|
+
# other_hash - the other hash whose values will be persisted after the merge
|
54
|
+
#
|
55
|
+
# This code was lovingly stolen from some random gem:
|
56
|
+
# http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html
|
57
|
+
#
|
58
|
+
# Thanks to whoever made it.
|
59
|
+
def deep_merge_hashes!(target, overwrite)
|
60
|
+
merge_values(target, overwrite)
|
61
|
+
merge_default_proc(target, overwrite)
|
62
|
+
duplicate_frozen_values(target)
|
63
|
+
|
64
|
+
target
|
65
|
+
end
|
66
|
+
|
67
|
+
def duplicable?(obj)
|
68
|
+
case obj
|
69
|
+
when nil, false, true, Symbol, Numeric
|
70
|
+
false
|
71
|
+
else
|
72
|
+
true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def mergable?(value)
|
77
|
+
value.is_a?(Hash)
|
78
|
+
end
|
79
|
+
|
80
|
+
def merge_values(target, overwrite)
|
81
|
+
target.merge!(overwrite) do |_key, old_val, new_val|
|
82
|
+
if new_val.nil?
|
83
|
+
old_val
|
84
|
+
elsif mergable?(old_val) && mergable?(new_val)
|
85
|
+
deep_merge_hashes(old_val, new_val)
|
86
|
+
else
|
87
|
+
new_val
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
## @brief Write content to a file
|
94
|
+
##
|
95
|
+
## @param file (String) The path to the file to (over)write
|
96
|
+
## @param content (String) The content to write to the file
|
97
|
+
## @param backup (Boolean) create a ~ backup
|
98
|
+
##
|
99
|
+
def write_to_file(file, content, backup: true)
|
100
|
+
unless file
|
101
|
+
puts content
|
102
|
+
return
|
103
|
+
end
|
104
|
+
|
105
|
+
file = File.expand_path(file)
|
106
|
+
|
107
|
+
if File.exist?(file) && backup
|
108
|
+
# Create a backup copy for the undo command
|
109
|
+
FileUtils.cp(file, "#{file}~")
|
110
|
+
end
|
111
|
+
|
112
|
+
File.open(file, 'w+') do |f|
|
113
|
+
f.puts content
|
114
|
+
end
|
115
|
+
|
116
|
+
Hooks.trigger :post_write, file
|
117
|
+
end
|
118
|
+
|
119
|
+
def safe_load_file(filename)
|
120
|
+
SafeYAML.load_file(filename) || {}
|
121
|
+
end
|
122
|
+
|
123
|
+
def default_editor
|
124
|
+
@default_editor = find_default_editor
|
125
|
+
end
|
126
|
+
|
127
|
+
def editor_with_args
|
128
|
+
args_for_editor(default_editor)
|
129
|
+
end
|
130
|
+
|
131
|
+
def args_for_editor(editor)
|
132
|
+
return editor if editor =~ /-\S/
|
133
|
+
|
134
|
+
args = case editor
|
135
|
+
when /^(subl|code|mate)$/
|
136
|
+
['-w']
|
137
|
+
when /^(vim|mvim)$/
|
138
|
+
['-f']
|
139
|
+
else
|
140
|
+
[]
|
141
|
+
end
|
142
|
+
"#{editor} #{args.join(' ')}"
|
143
|
+
end
|
144
|
+
|
145
|
+
def find_default_editor(editor_for = 'default')
|
146
|
+
if ENV['DOING_EDITOR_TEST']
|
147
|
+
return ENV['EDITOR']
|
148
|
+
end
|
149
|
+
|
150
|
+
editor_config = Doing.config.settings['editors']
|
151
|
+
|
152
|
+
if editor_config.is_a?(String)
|
153
|
+
Doing.logger.warn('Deprecated:', "Please update your configuration, 'editors' should be a mapping. Delete the key and run `doing config --update`.")
|
154
|
+
return editor_config
|
155
|
+
end
|
156
|
+
|
157
|
+
if editor_config[editor_for]
|
158
|
+
editor = editor_config[editor_for]
|
159
|
+
Doing.logger.debug('Editor:', "Using #{editor} from config 'editors->#{editor_for}'")
|
160
|
+
return editor unless editor.nil? || editor.empty?
|
161
|
+
end
|
162
|
+
|
163
|
+
if editor_for != 'editor' && editor_config['default']
|
164
|
+
editor = editor_config['default']
|
165
|
+
Doing.logger.debug('Editor:', "Using #{editor} from config: 'editors->default'")
|
166
|
+
return editor unless editor.nil? || editor.empty?
|
167
|
+
end
|
168
|
+
|
169
|
+
editor ||= ENV['DOING_EDITOR'] || ENV['GIT_EDITOR'] || ENV['EDITOR']
|
170
|
+
|
171
|
+
unless editor.nil? || editor.empty?
|
172
|
+
Doing.logger.debug('Editor:', "Found editor in environment variables: #{editor}")
|
173
|
+
return editor
|
174
|
+
end
|
175
|
+
|
176
|
+
Doing.logger.debug('ENV:', 'No EDITOR environment variable, testing available editors')
|
177
|
+
editors = %w[vim vi code subl mate mvim nano emacs]
|
178
|
+
editors.each do |ed|
|
179
|
+
return ed if exec_available(ed)
|
180
|
+
Doing.logger.debug('ENV:', "#{ed} not available")
|
181
|
+
end
|
182
|
+
|
183
|
+
nil
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
data/lib/doing/version.rb
CHANGED