doing 1.0.54 → 1.0.55
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/README.md +25 -6
- data/bin/doing +699 -557
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +175 -54
- metadata +2 -2
data/lib/doing/version.rb
CHANGED
data/lib/doing/wwid.rb
CHANGED
@@ -40,6 +40,17 @@ class Hash
|
|
40
40
|
result
|
41
41
|
end
|
42
42
|
end
|
43
|
+
|
44
|
+
def matches_search?(search)
|
45
|
+
item = self
|
46
|
+
text = item['note'] ? item['title'] + item['note'].join(' ') : item['title']
|
47
|
+
pattern = if search.strip =~ %r{^/.*?/$}
|
48
|
+
search.sub(%r{/(.*?)/}, '\1')
|
49
|
+
else
|
50
|
+
search.split('').join('.{0,3}')
|
51
|
+
end
|
52
|
+
text =~ /#{pattern}/i ? true : false
|
53
|
+
end
|
43
54
|
end
|
44
55
|
|
45
56
|
##
|
@@ -170,6 +181,7 @@ class WWID
|
|
170
181
|
@config['autotag']['synonyms'] ||= {}
|
171
182
|
@config['doing_file'] ||= '~/what_was_i_doing.md'
|
172
183
|
@config['current_section'] ||= 'Currently'
|
184
|
+
@config['config_editor_app'] ||= nil
|
173
185
|
@config['editor_app'] ||= nil
|
174
186
|
|
175
187
|
@config['html_template'] ||= {}
|
@@ -372,7 +384,7 @@ class WWID
|
|
372
384
|
tmpfile.unlink
|
373
385
|
end
|
374
386
|
|
375
|
-
input
|
387
|
+
input.split(/\n/).delete_if {|line| line =~ /^#/ }.join("\n").strip
|
376
388
|
end
|
377
389
|
|
378
390
|
#
|
@@ -385,9 +397,20 @@ class WWID
|
|
385
397
|
def format_input(input)
|
386
398
|
raise 'No content in entry' if input.nil? || input.strip.empty?
|
387
399
|
|
388
|
-
input_lines = input.split(/[\n\r]+/)
|
389
|
-
title = input_lines[0]
|
400
|
+
input_lines = input.split(/[\n\r]+/).delete_if {|line| line =~ /^#/ || line =~ /^\s*$/ }
|
401
|
+
title = input_lines[0]&.strip
|
402
|
+
raise 'No content in first line' if title.nil? || title.strip.empty?
|
403
|
+
|
390
404
|
note = input_lines.length > 1 ? input_lines[1..-1] : []
|
405
|
+
# If title line ends in a parenthetical, use that as the note
|
406
|
+
if note.empty? && title =~ /\(.*?\)$/
|
407
|
+
title.sub!(/\((.*?)\)$/) do
|
408
|
+
m = Regexp.last_match
|
409
|
+
note.push(m[1])
|
410
|
+
''
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
391
414
|
note.map!(&:strip)
|
392
415
|
note.delete_if { |line| line =~ /^\s*$/ || line =~ /^#/ }
|
393
416
|
|
@@ -698,7 +721,6 @@ class WWID
|
|
698
721
|
## @param opt (Hash) Additional Options
|
699
722
|
##
|
700
723
|
def last_entry(opt = {})
|
701
|
-
opt[:tag] ||= []
|
702
724
|
opt[:tag_bool] ||= 'AND'
|
703
725
|
opt[:section] ||= @current_section
|
704
726
|
|
@@ -726,7 +748,11 @@ class WWID
|
|
726
748
|
all_items.concat(@content[section]['items'].dup) if @content.key?(section)
|
727
749
|
end
|
728
750
|
|
729
|
-
|
751
|
+
if opt[:tag] && opt[:tag].length.positive?
|
752
|
+
all_items.select! { |item| item.has_tags?(opt[:tag], opt[:tag_bool]) }
|
753
|
+
elsif opt[:search]&.length
|
754
|
+
all_items.select! { |item| item.matches_search?(opt[:search]) }
|
755
|
+
end
|
730
756
|
|
731
757
|
all_items.max_by { |item| item['date'] }
|
732
758
|
end
|
@@ -781,9 +807,10 @@ class WWID
|
|
781
807
|
items.map! do |item|
|
782
808
|
break if index == count
|
783
809
|
|
784
|
-
tag_match =
|
810
|
+
tag_match = opt[:tag].nil? || opt[:tag].empty? ? true : item.has_tags?(opt[:tag], opt[:tag_bool])
|
811
|
+
search_match = opt[:search].nil? || opt[:search].empty? ? true : item.matches_search?(opt[:search])
|
785
812
|
|
786
|
-
if tag_match
|
813
|
+
if tag_match && search_match
|
787
814
|
if opt[:autotag]
|
788
815
|
new_title = autotag(item['title']) if @auto_tag
|
789
816
|
if new_title == item['title']
|
@@ -797,17 +824,23 @@ class WWID
|
|
797
824
|
done_date = next_start - 1
|
798
825
|
next_start = item['date']
|
799
826
|
elsif opt[:back]
|
800
|
-
|
827
|
+
if opt[:back].is_a? Integer
|
828
|
+
done_date = item['date'] + opt[:back]
|
829
|
+
else
|
830
|
+
done_date = item['date'] + (opt[:back] - item['date'])
|
831
|
+
end
|
801
832
|
else
|
802
833
|
done_date = Time.now
|
803
834
|
end
|
804
835
|
|
805
836
|
title = item['title']
|
806
837
|
opt[:tags].each do |tag|
|
807
|
-
tag.strip
|
808
|
-
if opt[:remove]
|
809
|
-
title
|
810
|
-
|
838
|
+
tag = tag.strip
|
839
|
+
if opt[:remove]
|
840
|
+
if title =~ /@#{tag}\b/
|
841
|
+
title.gsub!(/(^| )@#{tag}(\([^)]*\))?/, '')
|
842
|
+
@results.push(%(Removed @#{tag}: "#{title}" in #{section}))
|
843
|
+
end
|
811
844
|
elsif title !~ /@#{tag}/
|
812
845
|
title.chomp!
|
813
846
|
title += if opt[:date]
|
@@ -853,6 +886,75 @@ class WWID
|
|
853
886
|
write(@doing_file)
|
854
887
|
end
|
855
888
|
|
889
|
+
##
|
890
|
+
## @brief Edit the last entry
|
891
|
+
##
|
892
|
+
## @param section (String) The section, default "All"
|
893
|
+
##
|
894
|
+
def edit_last(section: 'All', options: {})
|
895
|
+
section = guess_section(section)
|
896
|
+
|
897
|
+
if section =~ /^all$/i
|
898
|
+
items = []
|
899
|
+
@content.each do |_k, v|
|
900
|
+
items.concat(v['items'])
|
901
|
+
end
|
902
|
+
# section = combined['items'].dup.sort_by { |item| item['date'] }.reverse[0]['section']
|
903
|
+
else
|
904
|
+
items = @content[section]['items']
|
905
|
+
end
|
906
|
+
|
907
|
+
items = items.sort_by { |item| item['date'] }.reverse
|
908
|
+
|
909
|
+
idx = nil
|
910
|
+
|
911
|
+
if options[:tag] && !options[:tag].empty?
|
912
|
+
items.each_with_index do |item, i|
|
913
|
+
if item.has_tags?(options[:tag], options[:tag_bool])
|
914
|
+
idx = i
|
915
|
+
break
|
916
|
+
end
|
917
|
+
end
|
918
|
+
elsif options[:search]
|
919
|
+
items.each_with_index do |item, i|
|
920
|
+
if item.matches_search?(options[:search])
|
921
|
+
idx = i
|
922
|
+
break
|
923
|
+
end
|
924
|
+
end
|
925
|
+
else
|
926
|
+
idx = 0
|
927
|
+
end
|
928
|
+
|
929
|
+
if idx.nil?
|
930
|
+
@results.push('No entries found')
|
931
|
+
return
|
932
|
+
end
|
933
|
+
|
934
|
+
section = items[idx]['section']
|
935
|
+
|
936
|
+
section_items = @content[section]['items']
|
937
|
+
s_idx = section_items.index(items[idx])
|
938
|
+
|
939
|
+
current_item = section_items[s_idx]['title']
|
940
|
+
old_note = section_items[s_idx]['note'] ? section_items[s_idx]['note'].map(&:strip).join("\n") : nil
|
941
|
+
current_item += "\n#{old_note}" unless old_note.nil?
|
942
|
+
new_item = fork_editor(current_item)
|
943
|
+
title, note = format_input(new_item)
|
944
|
+
|
945
|
+
if title.nil? || title.empty?
|
946
|
+
@results.push('No content provided')
|
947
|
+
elsif title == section_items[s_idx]['title'] && note == old_note
|
948
|
+
@results.push('No change in content')
|
949
|
+
else
|
950
|
+
section_items[s_idx]['title'] = title
|
951
|
+
section_items[s_idx]['note'] = note
|
952
|
+
@results.push("Entry edited: #{section_items[s_idx]['title']}")
|
953
|
+
@content[section]['items'] = section_items
|
954
|
+
write(@doing_file)
|
955
|
+
end
|
956
|
+
end
|
957
|
+
|
856
958
|
##
|
857
959
|
## @brief Add a note to the last entry in a section
|
858
960
|
##
|
@@ -948,7 +1050,7 @@ class WWID
|
|
948
1050
|
|
949
1051
|
if opt[:new_item]
|
950
1052
|
title, note = format_input(opt[:new_item])
|
951
|
-
note.push(opt[:note].
|
1053
|
+
note.push(opt[:note].map(&:chomp)) if opt[:note]
|
952
1054
|
title += " @#{tag}"
|
953
1055
|
add_item(title.cap_first, opt[:section], { note: note.join(' ').rstrip, back: opt[:back] })
|
954
1056
|
end
|
@@ -1106,10 +1208,7 @@ class WWID
|
|
1106
1208
|
end
|
1107
1209
|
end
|
1108
1210
|
|
1109
|
-
|
1110
|
-
warn 'Invalid section object'
|
1111
|
-
return
|
1112
|
-
end
|
1211
|
+
raise 'Invalid section object' unless opt[:section].instance_of? Hash
|
1113
1212
|
|
1114
1213
|
items = opt[:section]['items'].sort_by { |item| item['date'] }
|
1115
1214
|
|
@@ -1127,19 +1226,23 @@ class WWID
|
|
1127
1226
|
|
1128
1227
|
if opt[:tag_filter] && !opt[:tag_filter]['tags'].empty?
|
1129
1228
|
items.delete_if do |item|
|
1130
|
-
|
1131
|
-
|
1229
|
+
case opt[:tag_filter]['bool']
|
1230
|
+
when /(AND|ALL)/
|
1231
|
+
del = false
|
1132
1232
|
opt[:tag_filter]['tags'].each do |tag|
|
1133
|
-
|
1233
|
+
unless item['title'] =~ /@#{tag}/
|
1234
|
+
del = true
|
1235
|
+
break
|
1236
|
+
end
|
1134
1237
|
end
|
1135
|
-
|
1136
|
-
|
1238
|
+
del
|
1239
|
+
when /NONE/
|
1137
1240
|
del = false
|
1138
1241
|
opt[:tag_filter]['tags'].each do |tag|
|
1139
1242
|
del = true if item['title'] =~ /@#{tag}/
|
1140
1243
|
end
|
1141
1244
|
del
|
1142
|
-
|
1245
|
+
when /(OR|ANY)/
|
1143
1246
|
del = true
|
1144
1247
|
opt[:tag_filter]['tags'].each do |tag|
|
1145
1248
|
del = false if item['title'] =~ /@#{tag}/
|
@@ -1150,15 +1253,7 @@ class WWID
|
|
1150
1253
|
end
|
1151
1254
|
|
1152
1255
|
if opt[:search]
|
1153
|
-
items.keep_if
|
1154
|
-
text = item['note'] ? item['title'] + item['note'].join(' ') : item['title']
|
1155
|
-
pattern = if opt[:search].strip =~ %r{^/.*?/$}
|
1156
|
-
opt[:search].sub(%r{/(.*?)/}, '\1')
|
1157
|
-
else
|
1158
|
-
opt[:search].split('').join('.{0,3}')
|
1159
|
-
end
|
1160
|
-
text =~ /#{pattern}/i
|
1161
|
-
end
|
1256
|
+
items.keep_if {|item| item.matches_search?(opt[:search]) }
|
1162
1257
|
end
|
1163
1258
|
|
1164
1259
|
if opt[:only_timed]
|
@@ -1187,9 +1282,10 @@ class WWID
|
|
1187
1282
|
|
1188
1283
|
out = ''
|
1189
1284
|
|
1190
|
-
raise 'Unknown output format' if opt[:output] &&
|
1285
|
+
raise 'Unknown output format' if opt[:output] && (opt[:output] !~ /^(template|html|csv|json|timeline)$/i)
|
1191
1286
|
|
1192
|
-
|
1287
|
+
case opt[:output]
|
1288
|
+
when /^csv$/i
|
1193
1289
|
output = [CSV.generate_line(%w[date title note timer section])]
|
1194
1290
|
items.each do |i|
|
1195
1291
|
note = ''
|
@@ -1202,7 +1298,7 @@ class WWID
|
|
1202
1298
|
output.push(CSV.generate_line([i['date'], i['title'], note, interval, i['section']]))
|
1203
1299
|
end
|
1204
1300
|
out = output.join('')
|
1205
|
-
|
1301
|
+
when /^(json|timeline)/i
|
1206
1302
|
items_out = []
|
1207
1303
|
max = items[-1]['date'].strftime('%F')
|
1208
1304
|
min = items[0]['date'].strftime('%F')
|
@@ -1298,7 +1394,7 @@ class WWID
|
|
1298
1394
|
EOTEMPLATE
|
1299
1395
|
return template
|
1300
1396
|
end
|
1301
|
-
|
1397
|
+
when /^html$/i
|
1302
1398
|
page_title = section
|
1303
1399
|
items_out = []
|
1304
1400
|
items.each do |i|
|
@@ -1412,7 +1508,7 @@ class WWID
|
|
1412
1508
|
else
|
1413
1509
|
colors['default']
|
1414
1510
|
end
|
1415
|
-
output.gsub!(
|
1511
|
+
output.gsub!(/(\s|m)(@[^ (]+)/, "\\1#{colors[opt[:tags_color]]}\\2#{last_color}")
|
1416
1512
|
end
|
1417
1513
|
output.sub!(/%note/, note)
|
1418
1514
|
output.sub!(/%odnote/, note.gsub(/^\t*/, ''))
|
@@ -1427,7 +1523,7 @@ class WWID
|
|
1427
1523
|
output.gsub!(/%n/, "\n")
|
1428
1524
|
output.gsub!(/%t/, "\t")
|
1429
1525
|
|
1430
|
-
out += output
|
1526
|
+
out += "#{output}\n"
|
1431
1527
|
end
|
1432
1528
|
out += tag_times('text', opt[:sort_tags]) if opt[:totals]
|
1433
1529
|
end
|
@@ -1488,7 +1584,8 @@ class WWID
|
|
1488
1584
|
|
1489
1585
|
if tags && !tags.empty?
|
1490
1586
|
items.delete_if do |item|
|
1491
|
-
|
1587
|
+
case bool
|
1588
|
+
when /(AND|ALL)/i
|
1492
1589
|
score = 0
|
1493
1590
|
tags.each do |tag|
|
1494
1591
|
score += 1 if item['title'] =~ /@#{tag}/i
|
@@ -1496,14 +1593,14 @@ class WWID
|
|
1496
1593
|
res = score < tags.length
|
1497
1594
|
moved_items.push(item) if res
|
1498
1595
|
res
|
1499
|
-
|
1596
|
+
when /NONE/i
|
1500
1597
|
del = false
|
1501
1598
|
tags.each do |tag|
|
1502
1599
|
del = true if item['title'] =~ /@#{tag}/i
|
1503
1600
|
end
|
1504
1601
|
moved_items.push(item) if del
|
1505
1602
|
del
|
1506
|
-
|
1603
|
+
when /(OR|ANY)/i
|
1507
1604
|
del = true
|
1508
1605
|
tags.each do |tag|
|
1509
1606
|
del = false if item['title'] =~ /@#{tag}/i
|
@@ -1513,7 +1610,7 @@ class WWID
|
|
1513
1610
|
end
|
1514
1611
|
end
|
1515
1612
|
moved_items.each do |item|
|
1516
|
-
if label &&
|
1613
|
+
if label && section != 'Currently'
|
1517
1614
|
item['title'] =
|
1518
1615
|
item['title'].sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{section})")
|
1519
1616
|
end
|
@@ -1522,23 +1619,27 @@ class WWID
|
|
1522
1619
|
@content[destination]['items'] += items
|
1523
1620
|
@results.push("Archived #{items.length} items from #{section} to #{destination}")
|
1524
1621
|
else
|
1622
|
+
count = items.length if items.length < count
|
1525
1623
|
|
1526
|
-
|
1527
|
-
|
1528
|
-
@content[section]['items'] = if count == 0
|
1624
|
+
@content[section]['items'] = if count.zero?
|
1529
1625
|
[]
|
1530
1626
|
else
|
1531
1627
|
items[0..count - 1]
|
1532
1628
|
end
|
1533
1629
|
|
1534
|
-
items.
|
1535
|
-
if label &&
|
1630
|
+
items.map! do |item|
|
1631
|
+
if label && section != 'Currently'
|
1536
1632
|
item['title'] =
|
1537
1633
|
item['title'].sub(/(?:@from\(.*?\))?(.*)$/, "\\1 @from(#{section})")
|
1538
1634
|
end
|
1635
|
+
item
|
1636
|
+
end
|
1637
|
+
if items.count > count
|
1638
|
+
@content[destination]['items'].concat(items[count..-1])
|
1639
|
+
else
|
1640
|
+
@content[destination]['items'].concat(items)
|
1539
1641
|
end
|
1540
1642
|
|
1541
|
-
@content[destination]['items'] += items[count..-1]
|
1542
1643
|
@results.push("Archived #{items.length - count} items from #{section} to #{destination}")
|
1543
1644
|
end
|
1544
1645
|
end
|
@@ -1659,8 +1760,11 @@ class WWID
|
|
1659
1760
|
cfg = @config['templates']['recent']
|
1660
1761
|
section ||= @current_section
|
1661
1762
|
section = guess_section(section)
|
1763
|
+
|
1662
1764
|
list_section({ section: section, wrap_width: cfg['wrap_width'], count: count,
|
1663
|
-
format: cfg['date_format'], template: cfg['template'],
|
1765
|
+
format: cfg['date_format'], template: cfg['template'],
|
1766
|
+
order: 'asc', times: times, totals: opt[:totals],
|
1767
|
+
sort_tags: opt[:sort_tags], tags_color: opt[:tags_color] })
|
1664
1768
|
end
|
1665
1769
|
|
1666
1770
|
##
|
@@ -1669,12 +1773,29 @@ class WWID
|
|
1669
1773
|
## @param times (Bool) Show times
|
1670
1774
|
## @param section (String) Section to pull from, default Currently
|
1671
1775
|
##
|
1672
|
-
def last(times
|
1673
|
-
section
|
1674
|
-
section = guess_section(section)
|
1776
|
+
def last(times: true, section: nil, options: {})
|
1777
|
+
section = section.nil? || section =~ /all/i ? 'All' : guess_section(section)
|
1675
1778
|
cfg = @config['templates']['last']
|
1676
|
-
|
1677
|
-
|
1779
|
+
|
1780
|
+
opts = {
|
1781
|
+
section: section,
|
1782
|
+
wrap_width: cfg['wrap_width'],
|
1783
|
+
count: 1,
|
1784
|
+
format: cfg['date_format'],
|
1785
|
+
template: cfg['template'],
|
1786
|
+
times: times
|
1787
|
+
}
|
1788
|
+
|
1789
|
+
if options[:tag]
|
1790
|
+
opts[:tag_filter] = {
|
1791
|
+
'tags' => options[:tag],
|
1792
|
+
'bool' => options[:tag_bool]
|
1793
|
+
}
|
1794
|
+
end
|
1795
|
+
|
1796
|
+
opts[:search] = options[:search] if options[:search]
|
1797
|
+
|
1798
|
+
list_section(opts)
|
1678
1799
|
end
|
1679
1800
|
|
1680
1801
|
##
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: doing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.55
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brett Terpstra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-07-
|
11
|
+
date: 2021-07-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|