doing 1.0.54 → 1.0.55
Sign up to get free protection for your applications and to get access to all the features.
- 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
|