doing 1.0.93 → 2.0.6.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 +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