doing 2.1.14 → 2.1.18
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.irbrc +1 -0
- data/.yardoc/checksums +14 -12
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/CHANGELOG.md +66 -0
- data/Gemfile.lock +3 -2
- data/README.md +56 -19
- data/bin/doing +134 -47
- data/docs/doc/Array.html +117 -3
- data/docs/doc/BooleanTermParser/Clause.html +1 -1
- data/docs/doc/BooleanTermParser/Operator.html +1 -1
- data/docs/doc/BooleanTermParser/Query.html +1 -1
- data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
- data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
- data/docs/doc/BooleanTermParser.html +1 -1
- data/docs/doc/Doing/Color.html +6 -2
- data/docs/doc/Doing/Completion.html +1 -1
- data/docs/doc/Doing/Configuration.html +8 -4
- data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
- data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
- data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
- data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
- data/docs/doc/Doing/Errors/NoResults.html +1 -1
- data/docs/doc/Doing/Errors/PluginException.html +1 -1
- data/docs/doc/Doing/Errors/UserCancelled.html +1 -1
- data/docs/doc/Doing/Errors/WrongCommand.html +1 -1
- data/docs/doc/Doing/Errors.html +1 -1
- data/docs/doc/Doing/Hooks.html +1 -1
- data/docs/doc/Doing/Item.html +224 -2
- data/docs/doc/Doing/Items.html +2 -2
- data/docs/doc/Doing/LogAdapter.html +1 -1
- data/docs/doc/Doing/Note.html +2 -2
- data/docs/doc/Doing/Pager.html +1 -1
- data/docs/doc/Doing/Plugins.html +1 -1
- data/docs/doc/Doing/Prompt.html +69 -1
- data/docs/doc/Doing/Section.html +1 -1
- data/docs/doc/Doing/TemplateString.html +2 -2
- data/docs/doc/Doing/Util/Backup.html +1 -1
- data/docs/doc/Doing/Util.html +1 -1
- data/docs/doc/Doing/WWID.html +71 -67
- data/docs/doc/Doing.html +3 -3
- data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
- data/docs/doc/GLI/Commands.html +1 -1
- data/docs/doc/GLI.html +1 -1
- data/docs/doc/Hash.html +1 -1
- data/docs/doc/Numeric.html +279 -0
- data/docs/doc/PhraseParser/Operator.html +1 -1
- data/docs/doc/PhraseParser/PhraseClause.html +1 -1
- data/docs/doc/PhraseParser/Query.html +1 -1
- data/docs/doc/PhraseParser/QueryParser.html +1 -1
- data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
- data/docs/doc/PhraseParser/TermClause.html +1 -1
- data/docs/doc/PhraseParser.html +1 -1
- data/docs/doc/Status.html +1 -1
- data/docs/doc/String.html +997 -118
- data/docs/doc/Symbol.html +1 -1
- data/docs/doc/Time.html +1 -1
- data/docs/doc/_index.html +14 -9
- data/docs/doc/class_list.html +1 -1
- data/docs/doc/file.README.html +41 -15
- data/docs/doc/index.html +41 -15
- data/docs/doc/method_list.html +448 -312
- data/docs/doc/top-level-namespace.html +2 -2
- data/docs/index.md +56 -19
- data/doing.gemspec +1 -0
- data/doing.rdoc +36 -6
- data/example_plugin.rb +2 -4
- data/lib/completion/_doing.zsh +8 -8
- data/lib/completion/doing.bash +12 -12
- data/lib/completion/doing.fish +8 -3
- data/lib/doing/array_chronify.rb +57 -0
- data/lib/doing/colors.rb +4 -0
- data/lib/doing/configuration.rb +6 -2
- data/lib/doing/item.rb +83 -0
- data/lib/doing/log_adapter.rb +3 -3
- data/lib/doing/numeric_chronify.rb +40 -0
- data/lib/doing/plugins/export/dayone_export.rb +1 -1
- data/lib/doing/plugins/export/json_export.rb +2 -2
- data/lib/doing/plugins/export/template_export.rb +49 -90
- data/lib/doing/prompt.rb +52 -0
- data/lib/doing/string.rb +137 -33
- data/lib/doing/string_chronify.rb +112 -14
- data/lib/doing/template_string.rb +1 -1
- data/lib/doing/time.rb +4 -4
- data/lib/doing/util_backup.rb +1 -1
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +107 -101
- data/lib/doing.rb +35 -31
- data/lib/examples/plugins/say_export.rb +1 -4
- metadata +26 -2
data/lib/doing/string.rb
CHANGED
@@ -101,6 +101,46 @@ module Doing
|
|
101
101
|
gsub(/(\s|m)(@[^ ("']+)/, "\\1#{tag_color}\\2#{last_color}")
|
102
102
|
end
|
103
103
|
|
104
|
+
def to_phrase_query(query)
|
105
|
+
parser = PhraseParser::QueryParser.new
|
106
|
+
transformer = PhraseParser::QueryTransformer.new
|
107
|
+
parse_tree = parser.parse(query)
|
108
|
+
transformer.apply(parse_tree).to_elasticsearch
|
109
|
+
end
|
110
|
+
|
111
|
+
def ignore_case(search, case_type)
|
112
|
+
(case_type == :smart && search !~ /[A-Z]/) || case_type == :ignore
|
113
|
+
end
|
114
|
+
|
115
|
+
def highlight_search!(search, distance: nil, negate: false, case_type: nil)
|
116
|
+
replace highlight_search(search, distance: distance, negate: negate, case_type: case_type)
|
117
|
+
end
|
118
|
+
|
119
|
+
def highlight_search(search, distance: nil, negate: false, case_type: nil)
|
120
|
+
out = dup
|
121
|
+
prefs = Doing.config.settings['search'] || {}
|
122
|
+
matching = prefs.fetch('matching', 'pattern').normalize_matching
|
123
|
+
distance ||= prefs.fetch('distance', 3).to_i
|
124
|
+
case_type ||= prefs.fetch('case', 'smart').normalize_case
|
125
|
+
|
126
|
+
if search.is_rx? || matching == :fuzzy
|
127
|
+
rx = search.to_rx(distance: distance, case_type: case_type)
|
128
|
+
out.gsub!(rx) { |m| m.bgyellow.black }
|
129
|
+
else
|
130
|
+
query = to_phrase_query(search.strip)
|
131
|
+
|
132
|
+
if query[:must].nil? && query[:must_not].nil?
|
133
|
+
query[:must] = query[:should]
|
134
|
+
query[:should] = []
|
135
|
+
end
|
136
|
+
query[:must].concat(query[:should]).each do |s|
|
137
|
+
rx = Regexp.new(s.wildcard_to_rx, ignore_case(s, case_type))
|
138
|
+
out.gsub!(rx) { |m| m.bgyellow.black }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
out
|
142
|
+
end
|
143
|
+
|
104
144
|
##
|
105
145
|
## Test if line should be ignored
|
106
146
|
##
|
@@ -259,7 +299,13 @@ module Doing
|
|
259
299
|
end
|
260
300
|
end
|
261
301
|
|
262
|
-
|
302
|
+
##
|
303
|
+
## Pluralize a string based on quantity
|
304
|
+
##
|
305
|
+
## @param number [Integer] the quantity of the
|
306
|
+
## object the string represents
|
307
|
+
##
|
308
|
+
def to_p(number)
|
263
309
|
number == 1 ? self : "#{self}s"
|
264
310
|
end
|
265
311
|
|
@@ -268,10 +314,6 @@ module Doing
|
|
268
314
|
##
|
269
315
|
## @return [Symbol] :oldest or :newest
|
270
316
|
##
|
271
|
-
def normalize_age!(default = :newest)
|
272
|
-
replace normalize_age(default)
|
273
|
-
end
|
274
|
-
|
275
317
|
def normalize_age(default = :newest)
|
276
318
|
case self
|
277
319
|
when /^o/i
|
@@ -283,6 +325,11 @@ module Doing
|
|
283
325
|
end
|
284
326
|
end
|
285
327
|
|
328
|
+
## @see #normalize_age
|
329
|
+
def normalize_age!(default = :newest)
|
330
|
+
replace normalize_age(default)
|
331
|
+
end
|
332
|
+
|
286
333
|
##
|
287
334
|
## Convert a sort order string to a qualified type
|
288
335
|
##
|
@@ -308,10 +355,6 @@ module Doing
|
|
308
355
|
##
|
309
356
|
## @return Symbol :smart, :sensitive, :ignore
|
310
357
|
##
|
311
|
-
def normalize_case!
|
312
|
-
replace normalize_case
|
313
|
-
end
|
314
|
-
|
315
358
|
def normalize_case(default = :smart)
|
316
359
|
case self
|
317
360
|
when /^(c|sens)/i
|
@@ -325,15 +368,16 @@ module Doing
|
|
325
368
|
end
|
326
369
|
end
|
327
370
|
|
371
|
+
## @see #normalize_case
|
372
|
+
def normalize_case!
|
373
|
+
replace normalize_case
|
374
|
+
end
|
375
|
+
|
328
376
|
##
|
329
377
|
## Convert a boolean string to a symbol
|
330
378
|
##
|
331
379
|
## @return Symbol :and, :or, or :not
|
332
380
|
##
|
333
|
-
def normalize_bool!(default = :and)
|
334
|
-
replace normalize_bool(default)
|
335
|
-
end
|
336
|
-
|
337
381
|
def normalize_bool(default = :and)
|
338
382
|
case self
|
339
383
|
when /(and|all)/i
|
@@ -349,15 +393,19 @@ module Doing
|
|
349
393
|
end
|
350
394
|
end
|
351
395
|
|
396
|
+
## @see #normalize_bool
|
397
|
+
def normalize_bool!(default = :and)
|
398
|
+
replace normalize_bool(default)
|
399
|
+
end
|
400
|
+
|
352
401
|
##
|
353
402
|
## Convert a matching configuration string to a symbol
|
354
403
|
##
|
404
|
+
## @param default [Symbol] the default matching
|
405
|
+
## type to return if the string
|
406
|
+
## doesn't match a known symbol
|
355
407
|
## @return Symbol :fuzzy, :pattern, :exact
|
356
408
|
##
|
357
|
-
def normalize_matching!(default = :pattern)
|
358
|
-
replace normalize_bool(default)
|
359
|
-
end
|
360
|
-
|
361
409
|
def normalize_matching(default = :pattern)
|
362
410
|
case self
|
363
411
|
when /^f/i
|
@@ -371,30 +419,64 @@ module Doing
|
|
371
419
|
end
|
372
420
|
end
|
373
421
|
|
374
|
-
|
375
|
-
|
422
|
+
## @see #normalize_matching
|
423
|
+
def normalize_matching!(default = :pattern)
|
424
|
+
replace normalize_bool(default)
|
376
425
|
end
|
377
426
|
|
427
|
+
##
|
428
|
+
## Adds ?: to any parentheticals in a regular expression
|
429
|
+
## to avoid match groups
|
430
|
+
##
|
431
|
+
## @return [String] modified regular expression
|
432
|
+
##
|
378
433
|
def normalize_trigger
|
379
434
|
gsub(/\((?!\?:)/, '(?:').downcase
|
380
435
|
end
|
381
436
|
|
437
|
+
## @see #normalize_trigger
|
438
|
+
def normalize_trigger!
|
439
|
+
replace normalize_trigger
|
440
|
+
end
|
441
|
+
|
442
|
+
##
|
443
|
+
## Convert ? and * wildcards to regular expressions.
|
444
|
+
## Uses \S (non-whitespace) instead of . (any character)
|
445
|
+
##
|
446
|
+
## @return [String] Regular expression string
|
447
|
+
##
|
382
448
|
def wildcard_to_rx
|
383
449
|
gsub(/\?/, '\S').gsub(/\*/, '\S*?')
|
384
450
|
end
|
385
451
|
|
452
|
+
##
|
453
|
+
## Add @ prefix to string if needed, maintains +/- prefix
|
454
|
+
##
|
455
|
+
## @return [String] @string
|
456
|
+
##
|
386
457
|
def add_at
|
387
458
|
strip.sub(/^([+-]*)@/, '\1')
|
388
459
|
end
|
389
460
|
|
461
|
+
##
|
462
|
+
## Convert a list of tags to an array. Tags can be with
|
463
|
+
## or without @ symbols, separated by any character, and
|
464
|
+
## can include parenthetical values (with spaces)
|
465
|
+
##
|
466
|
+
## @return [Array] array of tags including @ symbols
|
467
|
+
##
|
390
468
|
def to_tags
|
391
|
-
gsub(/ *, */, ' ').
|
392
|
-
end
|
393
|
-
|
394
|
-
def add_tags!(tags, remove: false)
|
395
|
-
replace add_tags(tags, remove: remove)
|
469
|
+
gsub(/ *, */, ' ').scan(/(@?(?:\S+(?:\(.+\)))|@?(?:\S+))/).map(&:first).sort.uniq.map(&:add_at)
|
396
470
|
end
|
397
471
|
|
472
|
+
##
|
473
|
+
## @brief Adds tags to a string
|
474
|
+
##
|
475
|
+
## @param tags [String or Array] List of tags to add. @ symbol optional
|
476
|
+
## @param remove [Boolean] remove tags instead of adding
|
477
|
+
##
|
478
|
+
## @return [String] the tagged string
|
479
|
+
##
|
398
480
|
def add_tags(tags, remove: false)
|
399
481
|
title = self.dup
|
400
482
|
tags = tags.to_tags
|
@@ -402,6 +484,11 @@ module Doing
|
|
402
484
|
title
|
403
485
|
end
|
404
486
|
|
487
|
+
## @see #add_tags
|
488
|
+
def add_tags!(tags, remove: false)
|
489
|
+
replace add_tags(tags, remove: remove)
|
490
|
+
end
|
491
|
+
|
405
492
|
##
|
406
493
|
## Add, rename, or remove a tag in place
|
407
494
|
##
|
@@ -484,10 +571,6 @@ module Doing
|
|
484
571
|
##
|
485
572
|
## @return Deduplicated string
|
486
573
|
##
|
487
|
-
def dedup_tags!
|
488
|
-
replace dedup_tags
|
489
|
-
end
|
490
|
-
|
491
574
|
def dedup_tags
|
492
575
|
title = dup
|
493
576
|
tags = title.scan(/(?<=\A| )(@(\S+?)(\([^)]+\))?)(?= |\Z)/).uniq
|
@@ -505,6 +588,11 @@ module Doing
|
|
505
588
|
title
|
506
589
|
end
|
507
590
|
|
591
|
+
## @see #dedup_tags
|
592
|
+
def dedup_tags!
|
593
|
+
replace dedup_tags
|
594
|
+
end
|
595
|
+
|
508
596
|
# Returns the last escape sequence from a string.
|
509
597
|
#
|
510
598
|
# Actually returns all escape codes, with the assumption
|
@@ -525,11 +613,9 @@ module Doing
|
|
525
613
|
##
|
526
614
|
## @param opt [Hash] Additional Options
|
527
615
|
##
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
end
|
532
|
-
|
616
|
+
## @option opt [Symbol] :format can be :markdown or
|
617
|
+
## :html (default)
|
618
|
+
##
|
533
619
|
def link_urls(**opt)
|
534
620
|
fmt = opt.fetch(:format, :html)
|
535
621
|
return self unless fmt
|
@@ -541,6 +627,12 @@ module Doing
|
|
541
627
|
str.replace_qualified_urls(format: fmt).clean_unlinked_urls
|
542
628
|
end
|
543
629
|
|
630
|
+
## @see #link_urls
|
631
|
+
def link_urls!(**opt)
|
632
|
+
fmt = opt.fetch(:format, :html)
|
633
|
+
replace link_urls(format: fmt)
|
634
|
+
end
|
635
|
+
|
544
636
|
# Remove <self-linked> formatting
|
545
637
|
def remove_self_links
|
546
638
|
gsub(/<(.*?)>/) do |match|
|
@@ -590,6 +682,18 @@ module Doing
|
|
590
682
|
end
|
591
683
|
end
|
592
684
|
|
685
|
+
##
|
686
|
+
## Convert a string value to an appropriate type. If
|
687
|
+
## kind is not specified, '[one, two]' becomes an Array,
|
688
|
+
## '1' becomes Integer, '1.5' becomes Float, 'true' or
|
689
|
+
## 'yes' becomes TrueClass, 'false' or 'no' becomes
|
690
|
+
## FalseClass.
|
691
|
+
##
|
692
|
+
## @param kind [String] specify string, array,
|
693
|
+
## integer, float, symbol, or boolean
|
694
|
+
## (falls back to string if value is
|
695
|
+
## not recognized)
|
696
|
+
## @return Converted object type
|
593
697
|
def set_type(kind = nil)
|
594
698
|
if kind
|
595
699
|
case kind.to_s
|
@@ -64,22 +64,120 @@ module Doing
|
|
64
64
|
when /^(\d+):(\d\d)$/
|
65
65
|
minutes += Regexp.last_match(1).to_i * 60
|
66
66
|
minutes += Regexp.last_match(2).to_i
|
67
|
-
when /^(\d+(?:\.\d+)?)([hmd])
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
67
|
+
when /^(\d+(?:\.\d+)?)([hmd])?/
|
68
|
+
scan(/(\d+(?:\.\d+)?)([hmd])?/).each do |m|
|
69
|
+
amt = m[0]
|
70
|
+
type = m[1].nil? ? 'm' : m[1]
|
71
|
+
|
72
|
+
minutes += case type.downcase
|
73
|
+
when 'm'
|
74
|
+
amt.to_i
|
75
|
+
when 'h'
|
76
|
+
(amt.to_f * 60).round
|
77
|
+
when 'd'
|
78
|
+
(amt.to_f * 60 * 24).round
|
79
|
+
else
|
80
|
+
0
|
81
|
+
end
|
82
|
+
end
|
81
83
|
end
|
82
84
|
minutes * 60
|
83
85
|
end
|
86
|
+
|
87
|
+
##
|
88
|
+
## Convert DD:HH:MM to seconds
|
89
|
+
##
|
90
|
+
## @return [Integer] rounded number of seconds
|
91
|
+
##
|
92
|
+
def to_seconds
|
93
|
+
mtch = match(/(\d+):(\d+):(\d+)/)
|
94
|
+
|
95
|
+
raise Errors::DoingRuntimeError, "Invalid time string: #{self}" unless mtch
|
96
|
+
|
97
|
+
h = mtch[1]
|
98
|
+
m = mtch[2]
|
99
|
+
s = mtch[3]
|
100
|
+
(h.to_i * 60 * 60) + (m.to_i * 60) + s.to_i
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
## Convert DD:HH:MM to a natural language string
|
105
|
+
##
|
106
|
+
## @param format [Symbol] The format to output (:dhm, :hm, :m, :clock, :natural)
|
107
|
+
##
|
108
|
+
def time_string(format: :dhm)
|
109
|
+
to_seconds.time_string(format: format)
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
## Convert (chronify) natural language dates
|
114
|
+
## within configured date tags (tags whose value is
|
115
|
+
## expected to be a date). Modifies string in place.
|
116
|
+
##
|
117
|
+
## @param additional_tags [Array] An array of
|
118
|
+
## additional tags to
|
119
|
+
## consider date_tags
|
120
|
+
##
|
121
|
+
def expand_date_tags(additional_tags = nil)
|
122
|
+
iso_rx = /\d{4}-\d\d-\d\d \d\d:\d\d/
|
123
|
+
|
124
|
+
watch_tags = [
|
125
|
+
'start(?:ed)?',
|
126
|
+
'beg[ia]n',
|
127
|
+
'done',
|
128
|
+
'finished',
|
129
|
+
'completed?',
|
130
|
+
'waiting',
|
131
|
+
'defer(?:red)?'
|
132
|
+
]
|
133
|
+
|
134
|
+
if additional_tags
|
135
|
+
date_tags = additional_tags
|
136
|
+
date_tags = date_tags.split(/ *, */) if date_tags.is_a?(String)
|
137
|
+
date_tags.map! do |tag|
|
138
|
+
tag.sub(/^@/, '').gsub(/\((?!\?:)(.*?)\)/, '(?:\1)').strip
|
139
|
+
end
|
140
|
+
watch_tags.concat(date_tags).uniq!
|
141
|
+
end
|
142
|
+
|
143
|
+
done_rx = /(?<=^| )@(?<tag>#{watch_tags.join('|')})\((?<date>.*?)\)/i
|
144
|
+
|
145
|
+
gsub!(done_rx) do
|
146
|
+
m = Regexp.last_match
|
147
|
+
t = m['tag']
|
148
|
+
d = m['date']
|
149
|
+
future = t =~ /^(done|complete)/ ? false : true
|
150
|
+
parsed_date = d =~ iso_rx ? Time.parse(d) : d.chronify(guess: :begin, future: future)
|
151
|
+
parsed_date.nil? ? m[0] : "@#{t}(#{parsed_date.strftime('%F %R')})"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
##
|
156
|
+
## Splits a range string and returns an array of
|
157
|
+
## DateTime objects as [start, end]. If only one date is
|
158
|
+
## given, end time is nil.
|
159
|
+
##
|
160
|
+
## @return [Array<DateTime>] Start and end dates as
|
161
|
+
## array
|
162
|
+
## @example Process a natural language date range
|
163
|
+
## "mon 3pm to mon 5pm".split_date_range
|
164
|
+
##
|
165
|
+
def split_date_range
|
166
|
+
date_string = dup
|
167
|
+
case date_string
|
168
|
+
when / (to|through|thru|(un)?til|-+) /
|
169
|
+
dates = date_string.split(/ (?:to|through|thru|(?:un)?til|-+) /)
|
170
|
+
start = dates[0].chronify(guess: :begin)
|
171
|
+
finish = dates[-1].chronify(guess: :end)
|
172
|
+
else
|
173
|
+
start = date_string.chronify(guess: :begin)
|
174
|
+
finish = nil
|
175
|
+
end
|
176
|
+
|
177
|
+
raise InvalidTimeExpression, 'Unrecognized date string' unless start
|
178
|
+
|
179
|
+
Doing.logger.debug('Parser:', "date range interpreted as #{start.strftime('%F %R')} -- #{finish ? finish.strftime('%F %R') : 'now'}")
|
180
|
+
[start, finish]
|
181
|
+
end
|
84
182
|
end
|
85
183
|
end
|
@@ -176,7 +176,7 @@ module Doing
|
|
176
176
|
' '
|
177
177
|
else
|
178
178
|
line = l.gsub(/%/, '\%').strip.wrap(width, pad: pad, indent: indent, offset: 0, prefix: prefix, color: last_color, after: after, reset: reset, pad_first: true)
|
179
|
-
line.highlight_tags!(tags_color, last_color: last_color) unless tags_color.nil? || tags_color.empty?
|
179
|
+
line.highlight_tags!(tags_color, last_color: last_color) unless !tags_color || tags_color.nil? || tags_color.empty?
|
180
180
|
"#{line} "
|
181
181
|
end
|
182
182
|
end.join("\n")
|
data/lib/doing/time.rb
CHANGED
@@ -25,10 +25,10 @@ module Doing
|
|
25
25
|
h = h % 24
|
26
26
|
|
27
27
|
output = []
|
28
|
-
output.push("#{d} #{'day'.
|
29
|
-
output.push("#{h} #{'hour'.
|
30
|
-
output.push("#{m} #{'minute'.
|
31
|
-
output.push("#{s} #{'second'.
|
28
|
+
output.push("#{d} #{'day'.to_p(d)}") if d.positive?
|
29
|
+
output.push("#{h} #{'hour'.to_p(h)}") if h.positive?
|
30
|
+
output.push("#{m} #{'minute'.to_p(m)}") if m.positive?
|
31
|
+
output.push("#{s} #{'second'.to_p(s)}") if s.positive?
|
32
32
|
output.join(', ')
|
33
33
|
end
|
34
34
|
|
data/lib/doing/util_backup.rb
CHANGED
@@ -79,7 +79,7 @@ module Doing
|
|
79
79
|
def redo_backup(filename = nil, count: 1)
|
80
80
|
filename ||= Doing.config.settings['doing_file']
|
81
81
|
# redo_file = File.join(backup_dir, "undone___#{File.basename(filename)}")
|
82
|
-
undones = Dir.glob("undone*#{File.basename(filename)}", base: backup_dir).sort
|
82
|
+
undones = Dir.glob("undone*#{File.basename(filename)}", base: backup_dir).sort.reverse
|
83
83
|
total = undones.count
|
84
84
|
count = total if count > total
|
85
85
|
|
data/lib/doing/version.rb
CHANGED