doing 2.1.30 → 2.1.34
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/.irbrc +1 -0
- data/CHANGELOG.md +4972 -0
- data/Dockerfile-2.6 +3 -1
- data/Dockerfile-2.7 +4 -2
- data/Dockerfile-3.0 +3 -1
- data/Gemfile.lock +1 -67
- data/README.md +1 -1
- data/bash_profile +13 -0
- data/bin/commands/again.rb +1 -1
- data/bin/commands/archive.rb +3 -3
- data/bin/commands/cancel.rb +1 -1
- data/bin/commands/commands.rb +8 -8
- data/bin/commands/completion.rb +61 -19
- data/bin/commands/config.rb +22 -19
- data/bin/commands/done.rb +2 -2
- data/bin/commands/flag.rb +1 -1
- data/bin/commands/grep.rb +6 -33
- data/bin/commands/last.rb +1 -1
- data/bin/commands/meanwhile.rb +2 -2
- data/bin/commands/now.rb +2 -2
- data/bin/commands/on.rb +6 -16
- data/bin/commands/open.rb +1 -1
- data/bin/commands/recent.rb +5 -17
- data/bin/commands/rotate.rb +17 -0
- data/bin/commands/sections.rb +82 -7
- data/bin/commands/show.rb +8 -28
- data/bin/commands/since.rb +5 -16
- data/bin/commands/tag_dir.rb +27 -3
- data/bin/commands/today.rb +3 -28
- data/bin/commands/view.rb +3 -3
- data/bin/commands/yesterday.rb +3 -36
- data/bin/doing +29 -139
- data/docs/doc/Array.html +1 -1
- 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 +1 -1
- data/docs/doc/Doing/Completion.html +324 -4
- data/docs/doc/Doing/Configuration.html +3 -3
- 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 +144 -3
- data/docs/doc/Doing/Items.html +209 -1
- data/docs/doc/Doing/LogAdapter.html +1 -1
- data/docs/doc/Doing/Logger.html +1807 -0
- data/docs/doc/Doing/Note.html +109 -3
- data/docs/doc/Doing/Pager.html +1 -1
- data/docs/doc/Doing/Plugins.html +1 -1
- data/docs/doc/Doing/Prompt.html +1 -1
- data/docs/doc/Doing/Section.html +1 -1
- data/docs/doc/Doing/TemplateString.html +1 -1
- data/docs/doc/Doing/Types.html +3 -3
- data/docs/doc/Doing/Util/Backup.html +1 -1
- data/docs/doc/Doing/Util.html +1 -1
- data/docs/doc/Doing/WWID.html +8 -58
- data/docs/doc/Doing.html +4 -4
- data/docs/doc/FalseClass.html +1 -1
- data/docs/doc/GLI/Commands/Help.html +1 -1
- 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/Object.html +1 -1
- 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 +1 -1
- data/docs/doc/Symbol.html +1 -1
- data/docs/doc/Time.html +1 -1
- data/docs/doc/TrueClass.html +1 -1
- data/docs/doc/_index.html +12 -10
- data/docs/doc/class_list.html +1 -1
- data/docs/doc/file.README.html +2 -2
- data/docs/doc/index.html +2 -2
- data/docs/doc/method_list.html +424 -304
- data/docs/doc/top-level-namespace.html +105 -1
- data/docs/index.md +1 -1
- data/doing.gemspec +24 -24
- data/doing.rdoc +259 -26
- data/example_plugin.rb +7 -5
- data/inputrc +57 -0
- data/lib/completion/_doing.zsh +48 -52
- data/lib/completion/doing.bash +14 -25
- data/lib/completion/doing.fish +41 -15
- data/lib/doing/add_options.rb +152 -0
- data/lib/doing/array/array.rb +16 -0
- data/lib/doing/changelog/changes.rb +1 -1
- data/lib/doing/chronify/string.rb +1 -1
- data/lib/doing/completion/bash_completion.rb +12 -51
- data/lib/doing/completion/fish_completion.rb +17 -53
- data/lib/doing/completion/zsh_completion.rb +21 -59
- data/lib/doing/completion.rb +203 -17
- data/lib/doing/configuration.rb +7 -1
- data/lib/doing/item.rb +30 -5
- data/lib/doing/items.rb +53 -5
- data/lib/doing/{log_adapter.rb → logger.rb} +8 -2
- data/lib/doing/note.rb +24 -8
- data/lib/doing/plugins/export/dayone_export.rb +8 -6
- data/lib/doing/plugins/export/html_export.rb +4 -4
- data/lib/doing/plugins/export/json_export.rb +19 -20
- data/lib/doing/plugins/export/markdown_export.rb +2 -2
- data/lib/doing/plugins/export/template_export.rb +4 -4
- data/lib/doing/plugins/import/calendar_import.rb +2 -2
- data/lib/doing/plugins/import/doing_import.rb +2 -2
- data/lib/doing/plugins/import/timing_import.rb +2 -2
- data/lib/doing/string/highlight.rb +3 -4
- data/lib/doing/string/string.rb +8 -0
- data/lib/doing/string/tags.rb +1 -1
- data/lib/doing/types.rb +2 -2
- data/lib/doing/util.rb +1 -1
- data/lib/doing/util_backup.rb +12 -12
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +119 -120
- data/lib/doing.rb +61 -3
- data/lib/examples/commands/wiki.rb +27 -19
- data/lib/examples/plugins/capture_thing_import.rb +1 -1
- data/lib/helpers/threaded_tests.rb +2 -0
- data/scripts/setting_replace.rb +11 -0
- metadata +109 -124
- data/.yardoc/checksums +0 -29
- data/.yardoc/complete +0 -0
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/.yardoc/proxy_types +0 -0
- data/bin/commands/add_section.rb +0 -15
data/lib/doing/wwid.rb
CHANGED
|
@@ -14,7 +14,7 @@ module Doing
|
|
|
14
14
|
class WWID
|
|
15
15
|
attr_reader :additional_configs, :current_section, :doing_file, :content
|
|
16
16
|
|
|
17
|
-
attr_accessor :config, :config_file, :
|
|
17
|
+
attr_accessor :config, :config_file, :default_option
|
|
18
18
|
|
|
19
19
|
include Color
|
|
20
20
|
# include Util
|
|
@@ -26,7 +26,12 @@ module Doing
|
|
|
26
26
|
@timers = {}
|
|
27
27
|
@recorded_items = []
|
|
28
28
|
@content = Items.new
|
|
29
|
-
|
|
29
|
+
Doing.auto_tag = true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# For backwards compatibility where @wwid.config was accessed instead of Doing.config.settings
|
|
33
|
+
def config
|
|
34
|
+
Doing.config.settings
|
|
30
35
|
end
|
|
31
36
|
|
|
32
37
|
##
|
|
@@ -48,7 +53,7 @@ module Doing
|
|
|
48
53
|
## @param path [String] Override path to a doing file, optional
|
|
49
54
|
##
|
|
50
55
|
def init_doing_file(path = nil)
|
|
51
|
-
@doing_file = File.expand_path(
|
|
56
|
+
@doing_file = File.expand_path(Doing.setting('doing_file'))
|
|
52
57
|
|
|
53
58
|
if path.nil?
|
|
54
59
|
create(@doing_file) unless File.exist?(@doing_file)
|
|
@@ -77,7 +82,7 @@ module Doing
|
|
|
77
82
|
lines.each do |line|
|
|
78
83
|
next if line =~ /^\s*$/
|
|
79
84
|
|
|
80
|
-
if line =~ /^(\S[\S ]+)
|
|
85
|
+
if line =~ /^(\S[\S ]+):\s*(@\S+\s*)*$/
|
|
81
86
|
section = Regexp.last_match(1)
|
|
82
87
|
@content.add_section(Section.new(section, original: line), log: false)
|
|
83
88
|
elsif line =~ /^\s*- (\d{4}-\d\d-\d\d \d\d:\d\d) \| (.*)/
|
|
@@ -117,7 +122,7 @@ module Doing
|
|
|
117
122
|
FileUtils.mkdir_p(File.dirname(filename)) unless File.directory?(File.dirname(filename))
|
|
118
123
|
|
|
119
124
|
File.open(filename, 'w+') do |f|
|
|
120
|
-
f.puts "#{
|
|
125
|
+
f.puts "#{Doing.setting('current_section')}:"
|
|
121
126
|
end
|
|
122
127
|
end
|
|
123
128
|
|
|
@@ -189,7 +194,7 @@ module Doing
|
|
|
189
194
|
|
|
190
195
|
raise EmptyInput, 'No content' if title.sub(/^.*?\| */, '').strip.empty?
|
|
191
196
|
|
|
192
|
-
title.expand_date_tags(
|
|
197
|
+
title.expand_date_tags(Doing.setting('date_tags'))
|
|
193
198
|
|
|
194
199
|
if title =~ date_rx
|
|
195
200
|
m = title.match(date_rx)
|
|
@@ -236,7 +241,7 @@ module Doing
|
|
|
236
241
|
##
|
|
237
242
|
def guess_section(frag, guessed: false, suggest: false)
|
|
238
243
|
return 'All' if frag =~ /^all$/i
|
|
239
|
-
frag ||=
|
|
244
|
+
frag ||= Doing.setting('current_section')
|
|
240
245
|
|
|
241
246
|
return frag.cap_first if @content.section?(frag)
|
|
242
247
|
|
|
@@ -342,7 +347,7 @@ module Doing
|
|
|
342
347
|
##
|
|
343
348
|
def add_item(title, section = nil, opt)
|
|
344
349
|
opt ||= {}
|
|
345
|
-
section ||=
|
|
350
|
+
section ||= Doing.setting('current_section')
|
|
346
351
|
@content.add_section(section, log: false)
|
|
347
352
|
opt[:back] ||= opt[:date] ? opt[:date] : Time.now
|
|
348
353
|
opt[:date] ||= Time.now
|
|
@@ -354,9 +359,9 @@ module Doing
|
|
|
354
359
|
title = [title.strip.cap_first]
|
|
355
360
|
title = title.join(' ')
|
|
356
361
|
|
|
357
|
-
if
|
|
362
|
+
if Doing.auto_tag
|
|
358
363
|
title = autotag(title)
|
|
359
|
-
title.add_tags!(
|
|
364
|
+
title.add_tags!(Doing.setting('default_tags')) if Doing.setting('default_tags').good?
|
|
360
365
|
end
|
|
361
366
|
|
|
362
367
|
title.compress!
|
|
@@ -499,7 +504,7 @@ module Doing
|
|
|
499
504
|
# Remove @done tag
|
|
500
505
|
title = item.title.sub(/\s*@done(\(.*?\))?/, '').chomp
|
|
501
506
|
section = opt[:in].nil? ? item.section : guess_section(opt[:in])
|
|
502
|
-
|
|
507
|
+
Doing.auto_tag = false
|
|
503
508
|
|
|
504
509
|
note = opt[:note] || Note.new
|
|
505
510
|
|
|
@@ -553,7 +558,7 @@ module Doing
|
|
|
553
558
|
def last_entry(opt)
|
|
554
559
|
opt ||= {}
|
|
555
560
|
opt[:tag_bool] ||= :and
|
|
556
|
-
opt[:section] ||=
|
|
561
|
+
opt[:section] ||= Doing.setting('current_section')
|
|
557
562
|
|
|
558
563
|
items = filter_items(Items.new, opt: opt)
|
|
559
564
|
|
|
@@ -667,12 +672,14 @@ module Doing
|
|
|
667
672
|
items = section =~ /^all$/i ? @content.clone : @content.in_section(section)
|
|
668
673
|
end
|
|
669
674
|
|
|
670
|
-
opt[:time_filter]
|
|
671
|
-
|
|
672
|
-
if opt[:from]
|
|
673
|
-
opt[:
|
|
674
|
-
|
|
675
|
-
|
|
675
|
+
if !opt[:time_filter]
|
|
676
|
+
opt[:time_filter] = [nil, nil]
|
|
677
|
+
if opt[:from] && !opt[:date_filter]
|
|
678
|
+
if opt[:from][0].is_a?(String) && opt[:from][0] =~ time_rx
|
|
679
|
+
opt[:time_filter] = opt[:from]
|
|
680
|
+
elsif opt[:from][0].is_a?(Time)
|
|
681
|
+
opt[:date_filter] = opt[:from]
|
|
682
|
+
end
|
|
676
683
|
end
|
|
677
684
|
end
|
|
678
685
|
|
|
@@ -1076,7 +1083,7 @@ module Doing
|
|
|
1076
1083
|
end
|
|
1077
1084
|
|
|
1078
1085
|
if opt[:flag]
|
|
1079
|
-
tag =
|
|
1086
|
+
tag = Doing.setting('marker_tag', 'flagged')
|
|
1080
1087
|
items.map! do |i|
|
|
1081
1088
|
old_item = i.clone
|
|
1082
1089
|
i.tag(tag, date: false, remove: opt[:remove], single: single)
|
|
@@ -1117,7 +1124,7 @@ module Doing
|
|
|
1117
1124
|
items.map! do |i|
|
|
1118
1125
|
old_item = i.clone
|
|
1119
1126
|
i.tag(tag, date: false, remove: opt[:remove], single: single)
|
|
1120
|
-
i.expand_date_tags(
|
|
1127
|
+
i.expand_date_tags(Doing.setting('date_tags'))
|
|
1121
1128
|
Hooks.trigger :post_entry_updated, self, i, old_item
|
|
1122
1129
|
end
|
|
1123
1130
|
end
|
|
@@ -1176,7 +1183,7 @@ module Doing
|
|
|
1176
1183
|
end
|
|
1177
1184
|
|
|
1178
1185
|
def verify_duration(date, finish_date, title: nil)
|
|
1179
|
-
max_elapsed =
|
|
1186
|
+
max_elapsed = Doing.setting('interaction.confirm_longer_than', 0)
|
|
1180
1187
|
max_elapsed = max_elapsed.chronify_qty if max_elapsed.is_a?(String)
|
|
1181
1188
|
date = date.chronify(guess: :end, context: :today) if finish_date.is_a?(String)
|
|
1182
1189
|
|
|
@@ -1255,7 +1262,7 @@ module Doing
|
|
|
1255
1262
|
removed = []
|
|
1256
1263
|
|
|
1257
1264
|
if opt[:autotag]
|
|
1258
|
-
new_title = autotag(item.title) if
|
|
1265
|
+
new_title = autotag(item.title) if Doing.auto_tag
|
|
1259
1266
|
if new_title == item.title
|
|
1260
1267
|
logger.count(:skipped, level: :debug, message: '%count unchaged %items')
|
|
1261
1268
|
# logger.debug('Autotag:', 'No changes')
|
|
@@ -1289,7 +1296,7 @@ module Doing
|
|
|
1289
1296
|
tag = tag.strip
|
|
1290
1297
|
|
|
1291
1298
|
if tag =~ /^done$/ && opt[:date] && item.should_time?
|
|
1292
|
-
max_elapsed =
|
|
1299
|
+
max_elapsed = Doing.setting('interaction.confirm_longer_than', 0)
|
|
1293
1300
|
max_elapsed = max_elapsed.chronify_qty if max_elapsed.is_a?(String)
|
|
1294
1301
|
elapsed = done_date - item.date
|
|
1295
1302
|
|
|
@@ -1344,7 +1351,7 @@ module Doing
|
|
|
1344
1351
|
logger.warn('Skipped:', 'Archiving is skipped when operating on all entries')
|
|
1345
1352
|
end
|
|
1346
1353
|
|
|
1347
|
-
item.expand_date_tags(
|
|
1354
|
+
item.expand_date_tags(Doing.setting('date_tags'))
|
|
1348
1355
|
Hooks.trigger :post_entry_updated, self, item, old_item
|
|
1349
1356
|
end
|
|
1350
1357
|
|
|
@@ -1424,7 +1431,7 @@ module Doing
|
|
|
1424
1431
|
def stop_start(target_tag, opt)
|
|
1425
1432
|
opt ||= {}
|
|
1426
1433
|
tag = target_tag.dup
|
|
1427
|
-
opt[:section] ||=
|
|
1434
|
+
opt[:section] ||= Doing.setting('current_section')
|
|
1428
1435
|
opt[:archive] ||= false
|
|
1429
1436
|
opt[:back] ||= Time.now
|
|
1430
1437
|
opt[:new_item] ||= false
|
|
@@ -1485,7 +1492,7 @@ module Doing
|
|
|
1485
1492
|
$stdout.puts output
|
|
1486
1493
|
else
|
|
1487
1494
|
Util.write_to_file(file, output, backup: backup)
|
|
1488
|
-
run_after if
|
|
1495
|
+
run_after if Doing.setting('run_after')
|
|
1489
1496
|
end
|
|
1490
1497
|
end
|
|
1491
1498
|
|
|
@@ -1593,7 +1600,7 @@ module Doing
|
|
|
1593
1600
|
## @return [Array] View names
|
|
1594
1601
|
##
|
|
1595
1602
|
def views
|
|
1596
|
-
|
|
1603
|
+
Doing.setting('views') ? Doing.setting('views').keys : []
|
|
1597
1604
|
end
|
|
1598
1605
|
|
|
1599
1606
|
##
|
|
@@ -1612,7 +1619,7 @@ module Doing
|
|
|
1612
1619
|
## @param title [String] The title of the view to retrieve
|
|
1613
1620
|
##
|
|
1614
1621
|
def get_view(title)
|
|
1615
|
-
return
|
|
1622
|
+
return Doing.setting(['views', title], nil)
|
|
1616
1623
|
|
|
1617
1624
|
false
|
|
1618
1625
|
end
|
|
@@ -1626,21 +1633,21 @@ module Doing
|
|
|
1626
1633
|
logger.benchmark(:list_section, :start)
|
|
1627
1634
|
opt[:config_template] ||= 'default'
|
|
1628
1635
|
|
|
1629
|
-
tpl_cfg =
|
|
1636
|
+
tpl_cfg = Doing.setting(['templates', opt[:config_template]])
|
|
1630
1637
|
|
|
1631
1638
|
cfg = if opt[:view_template]
|
|
1632
|
-
|
|
1639
|
+
Doing.setting(['views', opt[:view_template]]).deep_merge(tpl_cfg, { extend_existing_arrays: true, sort_merged_arrays: true })
|
|
1633
1640
|
else
|
|
1634
1641
|
tpl_cfg
|
|
1635
1642
|
end
|
|
1636
1643
|
|
|
1637
1644
|
cfg.deep_merge({
|
|
1638
|
-
'wrap_width' =>
|
|
1639
|
-
'date_format' =>
|
|
1640
|
-
'order' =>
|
|
1641
|
-
'tags_color' =>
|
|
1642
|
-
'duration' =>
|
|
1643
|
-
'interval_format' =>
|
|
1645
|
+
'wrap_width' => Doing.setting('wrap_width') || 0,
|
|
1646
|
+
'date_format' => Doing.setting('default_date_format'),
|
|
1647
|
+
'order' => Doing.setting('order') || :asc,
|
|
1648
|
+
'tags_color' => Doing.setting('tags_color'),
|
|
1649
|
+
'duration' => Doing.setting('duration'),
|
|
1650
|
+
'interval_format' => Doing.setting('interval_format')
|
|
1644
1651
|
}, { extend_existing_arrays: true, sort_merged_arrays: true })
|
|
1645
1652
|
|
|
1646
1653
|
opt[:duration] ||= cfg['duration'] || false
|
|
@@ -1653,6 +1660,7 @@ module Doing
|
|
|
1653
1660
|
opt[:tag_order] ||= :asc
|
|
1654
1661
|
opt[:tags_color] = cfg['tags_color'] || false if opt[:tags_color].nil?
|
|
1655
1662
|
opt[:template] ||= cfg['template']
|
|
1663
|
+
opt[:sort_tags] ||= opt[:tag_sort]
|
|
1656
1664
|
|
|
1657
1665
|
# opt[:highlight] ||= true
|
|
1658
1666
|
title = ''
|
|
@@ -1697,7 +1705,7 @@ module Doing
|
|
|
1697
1705
|
end
|
|
1698
1706
|
|
|
1699
1707
|
opt[:output] ||= 'template'
|
|
1700
|
-
opt[:wrap_width] ||=
|
|
1708
|
+
opt[:wrap_width] ||= Doing.setting('templates.default.wrap_width', 0)
|
|
1701
1709
|
|
|
1702
1710
|
logger.benchmark(:list_section, :finish)
|
|
1703
1711
|
output(items, title, is_single, opt)
|
|
@@ -1710,7 +1718,7 @@ module Doing
|
|
|
1710
1718
|
## @param section [String] The source section
|
|
1711
1719
|
## @param options [Hash] Options
|
|
1712
1720
|
##
|
|
1713
|
-
def archive(section =
|
|
1721
|
+
def archive(section = Doing.setting('current_section'), options)
|
|
1714
1722
|
options ||= {}
|
|
1715
1723
|
count = options[:keep] || 0
|
|
1716
1724
|
destination = options[:destination] || 'Archive'
|
|
@@ -1746,13 +1754,13 @@ module Doing
|
|
|
1746
1754
|
opt[:totals] ||= false
|
|
1747
1755
|
opt[:sort_tags] ||= false
|
|
1748
1756
|
|
|
1749
|
-
cfg =
|
|
1750
|
-
'wrap_width' =>
|
|
1751
|
-
'date_format' =>
|
|
1752
|
-
'order' =>
|
|
1753
|
-
'tags_color' =>
|
|
1754
|
-
'duration' =>
|
|
1755
|
-
'interval_format' =>
|
|
1757
|
+
cfg = Doing.setting('templates').deep_merge(Doing.setting('templates.default'), { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
|
|
1758
|
+
'wrap_width' => Doing.setting('wrap_width') || 0,
|
|
1759
|
+
'date_format' => Doing.setting('default_date_format'),
|
|
1760
|
+
'order' => Doing.setting('order') || :asc,
|
|
1761
|
+
'tags_color' => Doing.setting('tags_color'),
|
|
1762
|
+
'duration' => Doing.setting('duration'),
|
|
1763
|
+
'interval_format' => Doing.setting('interval_format')
|
|
1756
1764
|
}, { extend_existing_arrays: true, sort_merged_arrays: true })
|
|
1757
1765
|
|
|
1758
1766
|
template = opt[:template] || cfg['template']
|
|
@@ -1768,6 +1776,7 @@ module Doing
|
|
|
1768
1776
|
from: opt[:from],
|
|
1769
1777
|
format: cfg['date_format'],
|
|
1770
1778
|
interval_format: opt[:interval_format],
|
|
1779
|
+
only_timed: opt[:only_timed],
|
|
1771
1780
|
order: cfg['order'] || :asc,
|
|
1772
1781
|
output: output,
|
|
1773
1782
|
section: opt[:section],
|
|
@@ -1800,19 +1809,19 @@ module Doing
|
|
|
1800
1809
|
# :date_filter expects an array with start and end date
|
|
1801
1810
|
dates = dates.split_date_range if dates.instance_of?(String)
|
|
1802
1811
|
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1812
|
+
opt[:section] = section
|
|
1813
|
+
opt[:count] = 0
|
|
1814
|
+
opt[:order] = :asc
|
|
1815
|
+
opt[:date_filter] = dates
|
|
1816
|
+
opt[:times] = times
|
|
1817
|
+
opt[:output] = output
|
|
1818
|
+
|
|
1819
|
+
time_rx = /^(\d{1,2}+(:\d{1,2}+)?( *(am|pm))?|midnight|noon)$/
|
|
1820
|
+
if opt[:from] && opt[:from][0].is_a?(String) && opt[:from][0] =~ time_rx
|
|
1821
|
+
opt[:time_filter] = opt[:from]
|
|
1822
|
+
end
|
|
1823
|
+
|
|
1824
|
+
list_section(opt)
|
|
1816
1825
|
end
|
|
1817
1826
|
|
|
1818
1827
|
##
|
|
@@ -1827,30 +1836,20 @@ module Doing
|
|
|
1827
1836
|
opt ||= {}
|
|
1828
1837
|
opt[:totals] ||= false
|
|
1829
1838
|
opt[:sort_tags] ||= false
|
|
1839
|
+
opt[:config_template] ||= 'today'
|
|
1840
|
+
opt[:yesterday] = true
|
|
1841
|
+
|
|
1830
1842
|
section = guess_section(section)
|
|
1831
1843
|
y = (Time.now - (60 * 60 * 24)).strftime('%Y-%m-%d')
|
|
1832
1844
|
opt[:after] = "#{y} #{opt[:after]}" if opt[:after]
|
|
1833
1845
|
opt[:before] = "#{y} #{opt[:before]}" if opt[:before]
|
|
1834
1846
|
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
duration: opt[:duration],
|
|
1840
|
-
from: opt[:from],
|
|
1841
|
-
order: opt[:order],
|
|
1842
|
-
output: output,
|
|
1843
|
-
section: section,
|
|
1844
|
-
sort_tags: opt[:sort_tags],
|
|
1845
|
-
tag_order: opt[:tag_order],
|
|
1846
|
-
times: times,
|
|
1847
|
-
totals: opt[:totals],
|
|
1848
|
-
yesterday: true,
|
|
1849
|
-
config_template: opt[:config_template] || 'today',
|
|
1850
|
-
template: opt[:template]
|
|
1851
|
-
}
|
|
1847
|
+
opt[:output] = output
|
|
1848
|
+
opt[:section] = section
|
|
1849
|
+
opt[:times] = times
|
|
1850
|
+
opt[:count] = 0
|
|
1852
1851
|
|
|
1853
|
-
list_section(
|
|
1852
|
+
list_section(opt)
|
|
1854
1853
|
end
|
|
1855
1854
|
|
|
1856
1855
|
##
|
|
@@ -1866,18 +1865,18 @@ module Doing
|
|
|
1866
1865
|
opt[:totals] ||= false
|
|
1867
1866
|
opt[:sort_tags] ||= false
|
|
1868
1867
|
|
|
1869
|
-
cfg =
|
|
1870
|
-
'wrap_width' =>
|
|
1871
|
-
'date_format' =>
|
|
1872
|
-
'order' =>
|
|
1873
|
-
'tags_color' =>
|
|
1874
|
-
'duration' =>
|
|
1875
|
-
'interval_format' =>
|
|
1868
|
+
cfg = Doing.setting('templates.recent').deep_merge(Doing.setting('templates.default'), { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
|
|
1869
|
+
'wrap_width' => Doing.setting('wrap_width') || 0,
|
|
1870
|
+
'date_format' => Doing.setting('default_date_format'),
|
|
1871
|
+
'order' => Doing.setting('order') || :asc,
|
|
1872
|
+
'tags_color' => Doing.setting('tags_color'),
|
|
1873
|
+
'duration' => Doing.setting('duration'),
|
|
1874
|
+
'interval_format' => Doing.setting('interval_format')
|
|
1876
1875
|
}, { extend_existing_arrays: true, sort_merged_arrays: true })
|
|
1877
1876
|
opt[:duration] ||= cfg['duration'] || false
|
|
1878
1877
|
opt[:interval_format] ||= cfg['interval_format'] || 'text'
|
|
1879
1878
|
|
|
1880
|
-
section ||=
|
|
1879
|
+
section ||= Doing.setting('current_section')
|
|
1881
1880
|
section = guess_section(section)
|
|
1882
1881
|
|
|
1883
1882
|
opt[:section] = section
|
|
@@ -1899,13 +1898,13 @@ module Doing
|
|
|
1899
1898
|
##
|
|
1900
1899
|
def last(times: true, section: nil, options: {})
|
|
1901
1900
|
section = section.nil? || section =~ /all/i ? 'All' : guess_section(section)
|
|
1902
|
-
cfg =
|
|
1903
|
-
'wrap_width' =>
|
|
1904
|
-
'date_format' =>
|
|
1905
|
-
'order' =>
|
|
1906
|
-
'tags_color' =>
|
|
1907
|
-
'duration' =>
|
|
1908
|
-
'interval_format' =>
|
|
1901
|
+
cfg = Doing.setting(['templates', options[:config_template]]).deep_merge(Doing.setting('templates.default'), { extend_existing_arrays: true, sort_merged_arrays: true }).deep_merge({
|
|
1902
|
+
'wrap_width' => Doing.setting('wrap_width', 0),
|
|
1903
|
+
'date_format' => Doing.setting('default_date_format'),
|
|
1904
|
+
'order' => Doing.setting('order', :asc),
|
|
1905
|
+
'tags_color' => Doing.setting('tags_color'),
|
|
1906
|
+
'duration' => Doing.setting('duration'),
|
|
1907
|
+
'interval_format' => Doing.setting('interval_format')
|
|
1909
1908
|
}, { extend_existing_arrays: true, sort_merged_arrays: true })
|
|
1910
1909
|
options[:duration] ||= cfg['duration'] || false
|
|
1911
1910
|
options[:interval_format] ||= cfg['interval_format'] || 'text'
|
|
@@ -1947,7 +1946,7 @@ module Doing
|
|
|
1947
1946
|
##
|
|
1948
1947
|
def autotag(string)
|
|
1949
1948
|
return unless string
|
|
1950
|
-
return string unless
|
|
1949
|
+
return string unless Doing.auto_tag
|
|
1951
1950
|
|
|
1952
1951
|
original = string.dup
|
|
1953
1952
|
text = string.dup
|
|
@@ -1960,7 +1959,7 @@ module Doing
|
|
|
1960
1959
|
replaced: []
|
|
1961
1960
|
}
|
|
1962
1961
|
|
|
1963
|
-
|
|
1962
|
+
Doing.setting('autotag.whitelist').each do |tag|
|
|
1964
1963
|
next if text =~ /@#{tag}\b/i
|
|
1965
1964
|
|
|
1966
1965
|
text.sub!(/(?<= |\A)(#{tag.strip})(?= |\Z)/i) do |m|
|
|
@@ -1970,7 +1969,7 @@ module Doing
|
|
|
1970
1969
|
end
|
|
1971
1970
|
end
|
|
1972
1971
|
|
|
1973
|
-
|
|
1972
|
+
Doing.setting('autotag.synonyms').each do |tag, v|
|
|
1974
1973
|
v.each do |word|
|
|
1975
1974
|
word = word.wildcard_to_rx
|
|
1976
1975
|
next unless text =~ /\b#{word}\b/i
|
|
@@ -1982,8 +1981,8 @@ module Doing
|
|
|
1982
1981
|
end
|
|
1983
1982
|
end
|
|
1984
1983
|
|
|
1985
|
-
if
|
|
1986
|
-
|
|
1984
|
+
if Doing.setting('autotag.transform')
|
|
1985
|
+
Doing.setting('autotag.transform').each do |tag|
|
|
1987
1986
|
next unless tag =~ /\S+:\S+/
|
|
1988
1987
|
|
|
1989
1988
|
if tag =~ /::/
|
|
@@ -2055,11 +2054,11 @@ module Doing
|
|
|
2055
2054
|
def tag_times(format: :text, sort_by: :time, sort_order: :asc)
|
|
2056
2055
|
return '' if @timers.empty?
|
|
2057
2056
|
|
|
2058
|
-
max = @timers.keys.sort_by
|
|
2057
|
+
max = @timers.keys.sort_by(&:length).reverse[0].length + 1
|
|
2059
2058
|
|
|
2060
2059
|
total = @timers.delete('All')
|
|
2061
2060
|
|
|
2062
|
-
tags_data = @timers.delete_if { |_k, v| v
|
|
2061
|
+
tags_data = @timers.delete_if { |_k, v| v.zero? }
|
|
2063
2062
|
sorted_tags_data = if sort_by.normalize_tag_sort == :name
|
|
2064
2063
|
tags_data.sort_by { |k, _v| k }
|
|
2065
2064
|
else
|
|
@@ -2070,7 +2069,7 @@ module Doing
|
|
|
2070
2069
|
case format
|
|
2071
2070
|
when :html
|
|
2072
2071
|
|
|
2073
|
-
output = <<
|
|
2072
|
+
output = <<EOHEAD
|
|
2074
2073
|
<table>
|
|
2075
2074
|
<caption id="tagtotals">Tag Totals</caption>
|
|
2076
2075
|
<colgroup>
|
|
@@ -2084,13 +2083,13 @@ module Doing
|
|
|
2084
2083
|
</tr>
|
|
2085
2084
|
</thead>
|
|
2086
2085
|
<tbody>
|
|
2087
|
-
|
|
2086
|
+
EOHEAD
|
|
2088
2087
|
sorted_tags_data.reverse.each do |k, v|
|
|
2089
|
-
if v
|
|
2088
|
+
if v.positive?
|
|
2090
2089
|
output += "<tr><td style='text-align:left;'>#{k}</td><td style='text-align:left;'>#{v.time_string(format: :clock)}</td></tr>\n"
|
|
2091
2090
|
end
|
|
2092
2091
|
end
|
|
2093
|
-
tail = <<
|
|
2092
|
+
tail = <<EOTAIL
|
|
2094
2093
|
<tr>
|
|
2095
2094
|
<td style="text-align:left;" colspan="2"></td>
|
|
2096
2095
|
</tr>
|
|
@@ -2102,21 +2101,21 @@ EOS
|
|
|
2102
2101
|
</tr>
|
|
2103
2102
|
</tfoot>
|
|
2104
2103
|
</table>
|
|
2105
|
-
|
|
2104
|
+
EOTAIL
|
|
2106
2105
|
output + tail
|
|
2107
2106
|
when :markdown
|
|
2108
|
-
pad = sorted_tags_data.map {|k,
|
|
2107
|
+
pad = sorted_tags_data.map { |k, _| k }.group_by(&:size).max.last[0].length
|
|
2109
2108
|
pad = 7 if pad < 7
|
|
2110
|
-
output = <<~
|
|
2111
|
-
| #{' ' * (pad - 7)
|
|
2109
|
+
output = <<~EOHEADER
|
|
2110
|
+
| #{' ' * (pad - 7)}project | time |
|
|
2112
2111
|
| #{'-' * (pad - 1)}: | :------- |
|
|
2113
|
-
|
|
2112
|
+
EOHEADER
|
|
2114
2113
|
sorted_tags_data.reverse.each do |k, v|
|
|
2115
|
-
if v
|
|
2114
|
+
if v.positive?
|
|
2116
2115
|
output += "| #{' ' * (pad - k.length)}#{k} | #{v.time_string(format: :clock)} |\n"
|
|
2117
2116
|
end
|
|
2118
2117
|
end
|
|
2119
|
-
tail =
|
|
2118
|
+
tail = '[Tag Totals]'
|
|
2120
2119
|
output + tail
|
|
2121
2120
|
when :json
|
|
2122
2121
|
output = []
|
|
@@ -2203,38 +2202,37 @@ EOS
|
|
|
2203
2202
|
end
|
|
2204
2203
|
|
|
2205
2204
|
##
|
|
2206
|
-
## Load configuration files and updated the @
|
|
2205
|
+
## Load configuration files and updated the @settings
|
|
2207
2206
|
## attribute with a Doing::Configuration object
|
|
2208
2207
|
##
|
|
2209
2208
|
## @param filename [String] (optional) path to
|
|
2210
2209
|
## alternative config file
|
|
2211
2210
|
##
|
|
2212
2211
|
def configure(filename = nil)
|
|
2212
|
+
logger.benchmark(:configure, :start)
|
|
2213
|
+
|
|
2213
2214
|
if filename
|
|
2214
2215
|
Doing.config_with(filename, { ignore_local: true })
|
|
2215
2216
|
elsif ENV['DOING_CONFIG']
|
|
2216
2217
|
Doing.config_with(ENV['DOING_CONFIG'], { ignore_local: true })
|
|
2217
2218
|
end
|
|
2218
2219
|
|
|
2219
|
-
logger.benchmark(:configure, :start)
|
|
2220
|
-
config = Doing.config
|
|
2221
2220
|
logger.benchmark(:configure, :finish)
|
|
2222
2221
|
|
|
2223
|
-
|
|
2224
|
-
@config = config.settings
|
|
2222
|
+
Doing.set('backup_dir', ENV['DOING_BACKUP_DIR']) if ENV['DOING_BACKUP_DIR']
|
|
2225
2223
|
end
|
|
2226
2224
|
|
|
2227
2225
|
def get_diff(filename = nil)
|
|
2228
|
-
configure if
|
|
2226
|
+
configure if Doing.settings.nil?
|
|
2229
2227
|
|
|
2230
|
-
filename ||=
|
|
2228
|
+
filename ||= Doing.setting('doing_file')
|
|
2231
2229
|
init_doing_file(filename)
|
|
2232
2230
|
current_content = @content.clone
|
|
2233
2231
|
backup_file = Util::Backup.last_backup(filename, count: 1)
|
|
2234
2232
|
raise DoingRuntimeError, 'No undo history to diff' if backup_file.nil?
|
|
2235
2233
|
|
|
2236
2234
|
backup = WWID.new
|
|
2237
|
-
backup.config =
|
|
2235
|
+
backup.config = Doing.settings
|
|
2238
2236
|
backup.init_doing_file(backup_file)
|
|
2239
2237
|
current_content.diff(backup.content)
|
|
2240
2238
|
end
|
|
@@ -2251,6 +2249,7 @@ EOS
|
|
|
2251
2249
|
output = @other_content_top ? "#{@other_content_top.join("\n")}\n" : ''
|
|
2252
2250
|
was_color = Color.coloring?
|
|
2253
2251
|
Color.coloring = false
|
|
2252
|
+
@content.dedup!(match_section: true)
|
|
2254
2253
|
output += @content.to_s
|
|
2255
2254
|
output += @other_content_bottom.join("\n") unless @other_content_bottom.nil?
|
|
2256
2255
|
# Just strip all ANSI colors from the content before writing to doing file
|
|
@@ -2378,12 +2377,12 @@ EOS
|
|
|
2378
2377
|
end
|
|
2379
2378
|
|
|
2380
2379
|
def run_after
|
|
2381
|
-
return unless
|
|
2380
|
+
return unless Doing.setting('run_after')
|
|
2382
2381
|
|
|
2383
|
-
_, stderr, status = Open3.capture3(
|
|
2382
|
+
_, stderr, status = Open3.capture3(Doing.setting('run_after'))
|
|
2384
2383
|
return unless status.exitstatus.positive?
|
|
2385
2384
|
|
|
2386
|
-
logger.log_now(:error, 'Script error:', "Error running #{
|
|
2385
|
+
logger.log_now(:error, 'Script error:', "Error running #{Doing.setting('run_after')}")
|
|
2387
2386
|
logger.log_now(:error, 'STDERR output:', stderr)
|
|
2388
2387
|
end
|
|
2389
2388
|
end
|
data/lib/doing.rb
CHANGED
|
@@ -42,7 +42,7 @@ require_relative 'doing/items'
|
|
|
42
42
|
require_relative 'doing/note'
|
|
43
43
|
require_relative 'doing/item'
|
|
44
44
|
require_relative 'doing/wwid'
|
|
45
|
-
require_relative 'doing/
|
|
45
|
+
require_relative 'doing/logger'
|
|
46
46
|
require_relative 'doing/prompt'
|
|
47
47
|
require_relative 'doing/errors'
|
|
48
48
|
require_relative 'doing/hooks'
|
|
@@ -57,19 +57,77 @@ require_relative 'doing/chronify/chronify'
|
|
|
57
57
|
# Main doing module
|
|
58
58
|
module Doing
|
|
59
59
|
class << self
|
|
60
|
+
attr_accessor :auto_tag
|
|
60
61
|
#
|
|
61
62
|
# Fetch the logger
|
|
62
63
|
#
|
|
63
|
-
# @return the
|
|
64
|
+
# @return the Logger instance.
|
|
64
65
|
#
|
|
65
66
|
def logger
|
|
66
|
-
@logger ||=
|
|
67
|
+
@logger ||= Logger.new((ENV['DOING_LOG_LEVEL'] || :info).to_sym)
|
|
67
68
|
end
|
|
68
69
|
|
|
70
|
+
##
|
|
71
|
+
## Holds a Configuration object with methods and a @settings hash
|
|
72
|
+
##
|
|
73
|
+
## @return [Configuration] Configuration object
|
|
74
|
+
##
|
|
69
75
|
def config
|
|
70
76
|
@config ||= Configuration.new
|
|
71
77
|
end
|
|
72
78
|
|
|
79
|
+
##
|
|
80
|
+
## Shortcut for Doing.config.settings
|
|
81
|
+
##
|
|
82
|
+
## @return [Hash] Settings hash
|
|
83
|
+
##
|
|
84
|
+
def settings
|
|
85
|
+
config.settings
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
##
|
|
89
|
+
## Fetch a config setting using a dot-separated keypath
|
|
90
|
+
## or array of keys
|
|
91
|
+
##
|
|
92
|
+
## @param keypath [String|Array] Either a
|
|
93
|
+
## dot-separated key path
|
|
94
|
+
## (search.case) or array of keys
|
|
95
|
+
## (['search', 'case'])
|
|
96
|
+
## @param default A default value to return if the
|
|
97
|
+
## provided path returns nil result
|
|
98
|
+
##
|
|
99
|
+
def setting(keypath, default = nil)
|
|
100
|
+
cfg = config.settings
|
|
101
|
+
case keypath
|
|
102
|
+
when Array
|
|
103
|
+
cfg.dig(*keypath) || default
|
|
104
|
+
when String
|
|
105
|
+
unless keypath =~ /^[.*]?$/
|
|
106
|
+
real_path = config.resolve_key_path(keypath, create: false)
|
|
107
|
+
return default unless real_path&.count&.positive?
|
|
108
|
+
|
|
109
|
+
cfg = cfg.dig(*real_path)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
cfg.nil? ? default : cfg
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def set(keypath, value)
|
|
117
|
+
real_path = config.resolve_key_path(keypath, create: false)
|
|
118
|
+
return nil unless real_path&.count&.positive?
|
|
119
|
+
|
|
120
|
+
config.settings.deep_set(real_path, value)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
##
|
|
124
|
+
## Update configuration from specified file
|
|
125
|
+
##
|
|
126
|
+
## @param file [String] Path to new config file
|
|
127
|
+
## @param options [Hash] options
|
|
128
|
+
##
|
|
129
|
+
## @option options :ignore_local Ignore local configuration files
|
|
130
|
+
##
|
|
73
131
|
def config_with(file, options = {})
|
|
74
132
|
@config = Configuration.new(file, options: options)
|
|
75
133
|
end
|