doing 1.0.65 → 1.0.69

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a774718572df78395293b6294bcfa4bac94cfe2cde0ffec43e8bbea60b874534
4
- data.tar.gz: fba4b65439084eae5b291bd65f5a1d14d49e5b5c4c8b70abd5e8f73fa39b3545
3
+ metadata.gz: bf5235acbab516c74ea7ff28c610dcbc49b9d9f108056c1b3c60ac0100bd9ec5
4
+ data.tar.gz: ca6abc5bd66a950d78ab16aabc781660b0ea5ee3f6dc50bb8a9fd0b986cce21e
5
5
  SHA512:
6
- metadata.gz: 01bf52dbc6e7077e6ff2a67ab22504dda4be927f31da8be7e5383136b5cac500cd7920203b7383ebe302af5ddce375e0ee7389273a275edfde27971de8ae87bb
7
- data.tar.gz: 79e7b8dcf00223118a0e3f5bb648ed33e920dc915909f62d1c21f2a19332f3d35336d3e5ca85a31632416b9fb9364d58db2fb6a4f90e7a200bc7cb706f76e14b
6
+ metadata.gz: b9f62baa0a4348212c54c1f9f3aa5bca041d81bc5061221310a85747b05c0b9b7af9036d309fcffd78e192a7a415f462ceeb96f8c276cef320249651f4890d35
7
+ data.tar.gz: cc61feaf78e9a3d6865bbb3d5fecdff00659b9b00a7430971012bfca0856e45ee87efd864dde603a7a6e6abf6a571f01f0e8d60afbbb7ebe060364f65c908b6b
data/README.md CHANGED
@@ -29,7 +29,7 @@ _Side note:_ I actually use the library behind this utility as part of another s
29
29
 
30
30
  ## Installation
31
31
 
32
- The current version of `doing` is <!--VER-->1.0.64<!--END VER-->.
32
+ The current version of `doing` is <!--VER-->1.0.68<!--END VER-->.
33
33
 
34
34
  $ [sudo] gem install doing
35
35
 
@@ -464,6 +464,7 @@ You can also include a `--no-date` switch to add `@done` without a finish date,
464
464
 
465
465
  By default `doing finish` works on a single entry, the last entry or the most recent entry matching a `--tag` or `--search` query. Specifying `doing finish 10` would finish any unfinished entries within the last 10 entries. In the case of `--tag` or `--search` queries, the count serves as the maximum number of matches doing will act on, sorted in reverse date order (most recent first). A count of 0 will disable the limit entirely, acting on all matching entries.
466
466
 
467
+ Both `finish` and `cancel` accept `--unfinished` as an argument. This causes them to act on the last entry not already marked @done, no matter how far back it's dated or how many @done entries come after it. You can use `doing finish --unfinished X -s SECTION` to finish the last X unfinished entries in SECTION.
467
468
 
468
469
  ##### Tagging and Autotagging
469
470
 
data/bin/doing CHANGED
@@ -52,7 +52,7 @@ flag %i[f doing_file]
52
52
 
53
53
  desc 'Add an entry'
54
54
  arg_name 'ENTRY'
55
- command [:now, :next] do |c|
55
+ command %i[now next] do |c|
56
56
  c.desc 'Section'
57
57
  c.arg_name 'NAME'
58
58
  c.flag %i[s section], default_value: wwid.current_section
@@ -105,7 +105,8 @@ command [:now, :next] do |c|
105
105
  wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:f] })
106
106
  wwid.write(wwid.doing_file)
107
107
  elsif $stdin.stat.size.positive?
108
- title, note = wwid.format_input($stdin.read)
108
+ input = $stdin.read
109
+ title, note = wwid.format_input(input)
109
110
  note.push(options[:n]) if options[:n]
110
111
  wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:f] })
111
112
  wwid.write(wwid.doing_file)
@@ -239,7 +240,7 @@ long_desc %(
239
240
 
240
241
  Example `doing template HAML > ~/styles/my_doing.haml`
241
242
  )
242
- arg_name 'TYPE', must_match: /^(html|haml|css)/i
243
+ arg_name 'TYPE', must_match: /^(?:html|haml|css)/i
243
244
  command :template do |c|
244
245
  c.action do |_global_options, options, args|
245
246
  raise 'No type specified, use `doing template [HAML|CSS]`' if args.empty?
@@ -443,7 +444,10 @@ command :cancel do |c|
443
444
 
444
445
  c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
445
446
  c.arg_name 'BOOLEAN'
446
- c.flag [:bool], must_match: /^(and|or|not)$/i, default_value: 'AND'
447
+ c.flag [:bool], must_match: /(?:and|all|any|or|not|none)/i, default_value: 'AND'
448
+
449
+ c.desc 'Cancel last entry (or entries) not already marked @done'
450
+ c.switch %i[u unfinished], negatable: false, default_value: false
447
451
 
448
452
  c.action do |_global_options, options, args|
449
453
  section = wwid.guess_section(options[:s]) || options[:s].cap_first
@@ -452,7 +456,16 @@ command :cancel do |c|
452
456
  tags = []
453
457
  else
454
458
  tags = options[:tag].split(/ *, */).map { |t| t.strip.sub(/^@/, '') }
455
- options[:bool] = options[:bool] =~ /^(and|or|not)$/i ? options[:bool].upcase : 'AND'
459
+ options[:bool] = case options[:bool]
460
+ when /(and|all)/i
461
+ 'AND'
462
+ when /(any|or)/i
463
+ 'OR'
464
+ when /(not|none)/i
465
+ 'NOT'
466
+ else
467
+ 'AND'
468
+ end
456
469
  end
457
470
 
458
471
  raise 'Only one argument allowed' if args.length > 1
@@ -468,7 +481,8 @@ command :cancel do |c|
468
481
  sequential: false,
469
482
  tag: tags,
470
483
  tag_bool: options[:bool],
471
- tags: ['done']
484
+ tags: ['done'],
485
+ unfinished: options[:unfinished]
472
486
  }
473
487
  wwid.tag_last(opts)
474
488
  end
@@ -500,7 +514,10 @@ command :finish do |c|
500
514
 
501
515
  c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
502
516
  c.arg_name 'BOOLEAN'
503
- c.flag [:bool], must_match: /^(and|or|not)$/i, default_value: 'AND'
517
+ c.flag [:bool], must_match: /(?:and|all|any|or|not|none)/i, default_value: 'AND'
518
+
519
+ c.desc 'Finish last entry (or entries) not already marked @done'
520
+ c.switch %i[u unfinished], negatable: false, default_value: false
504
521
 
505
522
  c.desc %(Auto-generate finish dates from next entry's start time.
506
523
  Automatically generate completion dates 1 minute before next start date.
@@ -537,7 +554,16 @@ command :finish do |c|
537
554
  tags = []
538
555
  else
539
556
  tags = options[:tag].split(/ *, */).map { |t| t.strip.sub(/^@/, '') }
540
- options[:bool] = options[:bool] =~ /^(and|or|not)$/i ? options[:bool].upcase : 'AND'
557
+ options[:bool] = case options[:bool]
558
+ when /(and|all)/i
559
+ 'AND'
560
+ when /(any|or)/i
561
+ 'OR'
562
+ when /(not|none)/i
563
+ 'NOT'
564
+ else
565
+ 'AND'
566
+ end
541
567
  end
542
568
 
543
569
  raise 'Only one argument allowed' if args.length > 1
@@ -555,7 +581,8 @@ command :finish do |c|
555
581
  sequential: options[:auto],
556
582
  tag: tags,
557
583
  tag_bool: options[:bool],
558
- tags: ['done']
584
+ tags: ['done'],
585
+ unfinished: options[:unfinished]
559
586
  }
560
587
  wwid.tag_last(opts)
561
588
  end
@@ -582,7 +609,7 @@ command [:again, :resume] do |c|
582
609
 
583
610
  c.desc 'Boolean used to combine multiple tags'
584
611
  c.arg_name 'BOOLEAN'
585
- c.flag [:bool], must_match: /^(and|or|not)$/i, default_value: 'AND'
612
+ c.flag [:bool], must_match: /(?:and|all|any|or|not|none)/i, default_value: 'AND'
586
613
 
587
614
  c.desc 'Note'
588
615
  c.arg_name 'TEXT'
@@ -590,6 +617,16 @@ command [:again, :resume] do |c|
590
617
 
591
618
  c.action do |_global_options, options, _args|
592
619
  tags = options[:tag].nil? ? [] : options[:tag].split(/ *, */).map { |t| t.sub(/^@/, '').strip }
620
+ options[:bool] = case options[:bool]
621
+ when /(and|all)/i
622
+ 'AND'
623
+ when /(any|or)/i
624
+ 'OR'
625
+ when /(not|none)/i
626
+ 'NOT'
627
+ else
628
+ 'AND'
629
+ end
593
630
  opts = {
594
631
  in: options[:in],
595
632
  note: options[:n],
@@ -619,6 +656,9 @@ command :tag do |c|
619
656
  c.desc 'Remove given tag(s)'
620
657
  c.switch %i[r remove], negatable: false, default_value: false
621
658
 
659
+ c.desc 'Tag last entry (or entries) not marked @done'
660
+ c.switch %i[u unfinished], negatable: false, default_value: false
661
+
622
662
  c.desc 'Autotag entries based on autotag configuration in ~/.doingrc'
623
663
  c.switch %i[a autotag], negatable: false, default_value: false
624
664
 
@@ -662,7 +702,8 @@ command :tag do |c|
662
702
  date: options[:date],
663
703
  remove: options[:r],
664
704
  section: section,
665
- tags: tags
705
+ tags: tags,
706
+ unfinished: options[:unfinished]
666
707
  }
667
708
  wwid.tag_last(opts)
668
709
  end
@@ -677,9 +718,17 @@ command [:mark, :flag] do |c|
677
718
  c.desc 'Remove mark'
678
719
  c.switch %i[r remove], negatable: false, default_value: false
679
720
 
721
+ c.desc 'Mark last entry not marked @done'
722
+ c.switch %i[u unfinished], negatable: false, default_value: false
723
+
680
724
  c.action do |_global_options, options, _args|
681
725
  mark = wwid.config['marker_tag'] || 'flagged'
682
- wwid.tag_last({ tags: [mark], section: options[:s], remove: options[:r] })
726
+ wwid.tag_last({
727
+ remove: options[:r],
728
+ section: options[:s],
729
+ tags: [mark],
730
+ unfinished: options[:unfinished]
731
+ })
683
732
  end
684
733
  end
685
734
 
@@ -694,9 +743,9 @@ command :show do |c|
694
743
  c.arg_name 'TAG'
695
744
  c.flag [:tag]
696
745
 
697
- c.desc 'Tag boolean (AND,OR,NONE)'
746
+ c.desc 'Tag boolean (AND,OR,NOT)'
698
747
  c.arg_name 'BOOLEAN'
699
- c.flag %i[b bool], must_match: /^(and|or|not)$/i, default_value: 'OR'
748
+ c.flag %i[b bool], must_match: /(?:and|all|any|or|not|none)/i, default_value: 'OR'
700
749
 
701
750
  c.desc 'Max count to show'
702
751
  c.arg_name 'MAX'
@@ -708,7 +757,7 @@ command :show do |c|
708
757
 
709
758
  c.desc 'Sort order (asc/desc)'
710
759
  c.arg_name 'ORDER'
711
- c.flag %i[s sort], must_match: /^(a|d)/i, default_value: 'ASC'
760
+ c.flag %i[s sort], must_match: /^[ad].*/i, default_value: 'ASC'
712
761
 
713
762
  c.desc %(
714
763
  Date range to show, or a single day to filter date on.
@@ -728,14 +777,14 @@ command :show do |c|
728
777
  default = 'time'
729
778
  default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
730
779
  c.arg_name 'KEY'
731
- c.flag [:tag_sort], must_match: /^(name|time)/i, default_value: default
780
+ c.flag [:tag_sort], must_match: /^(?:name|time)/i, default_value: default
732
781
 
733
782
  c.desc 'Only show items with recorded time intervals'
734
783
  c.switch [:only_timed], default_value: false, negatable: false
735
784
 
736
785
  c.desc 'Output to export format (csv|html|json|template|timeline)'
737
786
  c.arg_name 'FORMAT'
738
- c.flag %i[o output], must_match: /^(template|html|csv|json|timeline)$/i
787
+ c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
739
788
  c.action do |_global_options, options, args|
740
789
  tag_filter = false
741
790
  tags = []
@@ -757,13 +806,9 @@ command :show do |c|
757
806
  end
758
807
  if args.length.positive?
759
808
  args.each do |arg|
760
- if arg =~ /,/
761
- arg.split(/,/).each do |tag|
762
- tags.push(tag.strip.sub(/^@/, ''))
763
- end
764
- else
765
- tags.push(arg.strip.sub(/^@/, ''))
766
- end
809
+ arg.split(/,/).each do |tag|
810
+ tags.push(tag.strip.sub(/^@/, ''))
811
+ end
767
812
  end
768
813
  end
769
814
  else
@@ -771,11 +816,21 @@ command :show do |c|
771
816
  end
772
817
 
773
818
  tags.concat(options[:tag].split(/ *, */).map { |t| t.sub(/^@/, '').strip }) if options[:tag]
819
+ options[:bool] = case options[:bool]
820
+ when /(and|all)/i
821
+ 'AND'
822
+ when /(any|or)/i
823
+ 'OR'
824
+ when /(not|none)/i
825
+ 'NOT'
826
+ else
827
+ 'AND'
828
+ end
774
829
 
775
830
  unless tags.empty?
776
831
  tag_filter = {
777
832
  'tags' => tags,
778
- 'bool' => options[:b]
833
+ 'bool' => options[:bool]
779
834
  }
780
835
  end
781
836
 
@@ -832,7 +887,7 @@ command [:grep, :search] do |c|
832
887
 
833
888
  c.desc 'Output to export format (csv|html|json|template|timeline)'
834
889
  c.arg_name 'FORMAT'
835
- c.flag %i[o output], must_match: /^(template|html|csv|json|timeline)$/i
890
+ c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
836
891
 
837
892
  c.desc 'Show time intervals on @done tasks'
838
893
  c.switch %i[t times], default_value: true
@@ -844,7 +899,7 @@ command [:grep, :search] do |c|
844
899
  default = 'time'
845
900
  default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
846
901
  c.arg_name 'KEY'
847
- c.flag [:tag_sort], must_match: /^(name|time)$/i, default_value: default
902
+ c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
848
903
 
849
904
  c.desc 'Only show items with recorded time intervals'
850
905
  c.switch [:only_timed], default_value: false, negatable: false
@@ -891,7 +946,7 @@ command :recent do |c|
891
946
  default = 'time'
892
947
  default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
893
948
  c.arg_name 'KEY'
894
- c.flag [:tag_sort], must_match: /^(name|time)$/i, default_value: default
949
+ c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
895
950
 
896
951
  c.action do |global_options, options, args|
897
952
  section = wwid.guess_section(options[:s]) || options[:s].cap_first
@@ -931,11 +986,11 @@ command :today do |c|
931
986
  default = 'time'
932
987
  default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
933
988
  c.arg_name 'KEY'
934
- c.flag [:tag_sort], must_match: /^(name|time)$/i, default_value: default
989
+ c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
935
990
 
936
991
  c.desc 'Output to export format (csv|html|json|template|timeline)'
937
992
  c.arg_name 'FORMAT'
938
- c.flag %i[o output], must_match: /^(template|html|csv|json|timeline)$/i
993
+ c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
939
994
 
940
995
  c.action do |_global_options, options, _args|
941
996
  options[:t] = true if options[:totals]
@@ -966,11 +1021,11 @@ command :on do |c|
966
1021
  default = 'time'
967
1022
  default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
968
1023
  c.arg_name 'KEY'
969
- c.flag [:tag_sort], must_match: /^(name|time)$/i, default_value: default
1024
+ c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
970
1025
 
971
1026
  c.desc 'Output to export format (csv|html|json|template|timeline)'
972
1027
  c.arg_name 'FORMAT'
973
- c.flag %i[o output], must_match: /^(template|html|csv|json|timeline)$/i
1028
+ c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
974
1029
 
975
1030
  c.action do |_global_options, options, args|
976
1031
  exit_now! 'Missing date argument' if args.empty?
@@ -1008,7 +1063,7 @@ command :yesterday do |c|
1008
1063
 
1009
1064
  c.desc 'Output to export format (csv|html|json|template|timeline)'
1010
1065
  c.arg_name 'FORMAT'
1011
- c.flag %i[o output], must_match: /^(template|html|csv|json|timeline)$/i
1066
+ c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
1012
1067
 
1013
1068
  c.desc 'Show time intervals on @done tasks'
1014
1069
  c.switch %i[t times], default_value: true
@@ -1020,7 +1075,7 @@ command :yesterday do |c|
1020
1075
  default = 'time'
1021
1076
  default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
1022
1077
  c.arg_name 'KEY'
1023
- c.flag [:tag_sort], must_match: /^(name|time)$/i, default_value: default
1078
+ c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
1024
1079
 
1025
1080
  c.action do |_global_options, options, _args|
1026
1081
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
@@ -1044,7 +1099,7 @@ command :last do |c|
1044
1099
 
1045
1100
  c.desc 'Tag boolean'
1046
1101
  c.arg_name 'BOOLEAN'
1047
- c.flag [:bool], must_match: /(and|or|not)/i, default_value: 'AND'
1102
+ c.flag [:bool], must_match: /(?:and|all|any|or|not|none)/i, default_value: 'AND'
1048
1103
 
1049
1104
  c.desc 'Search filter, surround with slashes for regex (/query/)'
1050
1105
  c.arg_name 'QUERY'
@@ -1057,7 +1112,15 @@ command :last do |c|
1057
1112
  tags = []
1058
1113
  else
1059
1114
  tags = options[:tag].split(/ *, */).map { |t| t.strip.sub(/^@/, '') }
1060
- options[:bool] = options[:bool] =~ /^(and|or|not)$/i ? options[:bool].upcase : 'AND'
1115
+ options[:bool] = case options[:bool]
1116
+ when /(any|or)/i
1117
+ :or
1118
+ when /(not|none)/i
1119
+ :not
1120
+ else
1121
+ :and
1122
+ end
1123
+
1061
1124
  end
1062
1125
 
1063
1126
  if options[:e]
@@ -1130,7 +1193,7 @@ command :view do |c|
1130
1193
 
1131
1194
  c.desc 'Output to export format (csv|html|json|template|timeline)'
1132
1195
  c.arg_name 'FORMAT'
1133
- c.flag %i[o output], must_match: /^(template|html|csv|json|timeline)$/i
1196
+ c.flag %i[o output], must_match: /^(?:template|html|csv|json|timeline)$/i
1134
1197
 
1135
1198
  c.desc 'Show time intervals on @done tasks'
1136
1199
  c.switch %i[t times], default_value: true
@@ -1145,7 +1208,7 @@ command :view do |c|
1145
1208
  default = 'time'
1146
1209
  default = wwid.config['tag_sort'] if wwid.config.key?('tag_sort')
1147
1210
  c.arg_name 'KEY'
1148
- c.flag [:tag_sort], must_match: /^(name|time)$/i, default_value: default
1211
+ c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
1149
1212
 
1150
1213
  c.desc 'Only show items with recorded time intervals'
1151
1214
  c.switch [:only_timed], default_value: false, negatable: true
@@ -1178,7 +1241,7 @@ command :view do |c|
1178
1241
  else
1179
1242
  view['tags'].gsub(/[, ]+/, ' ').split(' ').map(&:strip)
1180
1243
  end
1181
- tag_filter['bool'] = view.key?('tags_bool') && !view['tags_bool'].nil? ? view['tags_bool'].upcase : 'OR'
1244
+ tag_filter['bool'] = view.key?('tags_bool') && !view['tags_bool'].nil? ? view['tags_bool'].normalize_bool : :or
1182
1245
  end
1183
1246
 
1184
1247
  # If the -o/--output flag was specified, override any default in the view template
@@ -1257,7 +1320,7 @@ command :archive do |c|
1257
1320
 
1258
1321
  c.desc 'Tag boolean (AND|OR|NOT)'
1259
1322
  c.arg_name 'BOOLEAN'
1260
- c.flag [:bool], must_match: /(and|or|not)/i, default_value: 'AND'
1323
+ c.flag [:bool], must_match: /(?:and|all|any|or|not|none)/i, default_value: 'AND'
1261
1324
 
1262
1325
  c.desc 'Search filter'
1263
1326
  c.arg_name 'QUERY'
@@ -1281,6 +1344,16 @@ command :archive do |c|
1281
1344
 
1282
1345
  tags.concat(options[:tag].split(/ *, */).map { |t| t.sub(/^@/, '').strip }) if options[:tag]
1283
1346
 
1347
+ options[:bool] = case options[:bool]
1348
+ when /(and|all)/i
1349
+ 'AND'
1350
+ when /(any|or)/i
1351
+ 'OR'
1352
+ when /(not|none)/i
1353
+ 'NOT'
1354
+ else
1355
+ 'AND'
1356
+ end
1284
1357
  opts = {
1285
1358
  bool: options[:bool],
1286
1359
  destination: options[:to],
data/lib/doing.rb CHANGED
@@ -8,4 +8,5 @@ require 'tempfile'
8
8
  require 'chronic'
9
9
  require 'haml'
10
10
  require 'json'
11
+ require 'doing/helpers.rb'
11
12
  require 'doing/wwid.rb'
@@ -0,0 +1,121 @@
1
+ ##
2
+ ## @brief Hash helpers
3
+ ##
4
+ class ::Hash
5
+ def has_tags?(tags, bool = :and)
6
+ tags = tags.split(/ *, */) if tags.is_a? String
7
+ bool = bool.normalize_bool if bool.is_a? String
8
+ item = self
9
+ tags.map! {|t| t.strip.sub(/^@/, '')}
10
+ case bool
11
+ when :and
12
+ result = true
13
+ tags.each do |tag|
14
+ unless item['title'] =~ /@#{tag}/
15
+ result = false
16
+ break
17
+ end
18
+ end
19
+ result
20
+ when :not
21
+ result = true
22
+ tags.each do |tag|
23
+ if item['title'] =~ /@#{tag}/
24
+ result = false
25
+ break
26
+ end
27
+ end
28
+ result
29
+ else
30
+ result = false
31
+ tags.each do |tag|
32
+ if item['title'] =~ /@#{tag}/
33
+ result = true
34
+ break
35
+ end
36
+ end
37
+ result
38
+ end
39
+ end
40
+
41
+ def matches_search?(search)
42
+ item = self
43
+ text = item['note'] ? item['title'] + item['note'].join(' ') : item['title']
44
+ pattern = if search.strip =~ %r{^/.*?/$}
45
+ search.sub(%r{/(.*?)/}, '\1')
46
+ else
47
+ search.split('').join('.{0,3}')
48
+ end
49
+ text =~ /#{pattern}/i ? true : false
50
+ end
51
+ end
52
+
53
+ ##
54
+ ## @brief String helpers
55
+ ##
56
+ class ::String
57
+ def cap_first
58
+ sub(/^\w/) do |m|
59
+ m.upcase
60
+ end
61
+ end
62
+
63
+
64
+ ##
65
+ ## @brief Convert a boolean string to a symbol
66
+ ##
67
+ ## @return Symbol :and, :or, or :not
68
+ ##
69
+ def normalize_bool!
70
+ replace normalize_bool
71
+ end
72
+
73
+ def normalize_bool(default = :and)
74
+ case self
75
+ when /(and|all)/i
76
+ :and
77
+ when /(any|or)/i
78
+ :or
79
+ when /(not|none)/i
80
+ :not
81
+ else
82
+ default.is_a?(Symbol) ? default : default.normalize_bool
83
+ end
84
+ end
85
+
86
+
87
+ ##
88
+ ## @brief Turn raw urls into HTML links
89
+ ##
90
+ ## @param opt (Hash) Additional Options
91
+ ##
92
+ def link_urls(opt = {})
93
+ opt[:format] ||= :html
94
+ if opt[:format] == :html
95
+ gsub(%r{(?mi)((http|https)://)?([\w\-_]+(\.[\w\-_]+)+)([\w\-.,@?^=%&amp;:/~+#]*[\w\-@^=%&amp;/~+#])?}) do |_match|
96
+ m = Regexp.last_match
97
+ proto = m[1].nil? ? 'http://' : ''
98
+ %(<a href="#{proto}#{m[0]}" title="Link to #{m[0]}">[#{m[3]}]</a>)
99
+ end.gsub(/<(\w+:.*?)>/) do |match|
100
+ m = Regexp.last_match
101
+ if m[1] =~ /<a href/
102
+ match
103
+ else
104
+ %(<a href="#{m[1]}" title="Link to #{m[1]}">[link]</a>)
105
+ end
106
+ end
107
+ else
108
+ self
109
+ end
110
+ end
111
+ end
112
+
113
+ class ::Symbol
114
+ def normalize_bool!
115
+ replace normalize_bool
116
+ end
117
+
118
+ def normalize_bool
119
+ to_s.normalize_bool
120
+ end
121
+ end
data/lib/doing/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Doing
2
- VERSION = '1.0.65'
2
+ VERSION = '1.0.69'
3
3
  end
data/lib/doing/wwid.rb CHANGED
@@ -4,92 +4,6 @@ require 'deep_merge'
4
4
  require 'open3'
5
5
  require 'pp'
6
6
 
7
- ##
8
- ## @brief Hash helpers
9
- ##
10
- class ::Hash
11
- def has_tags?(tags, bool = 'AND')
12
- tags = tags.split(/ *, */) if tags.is_a? String
13
- item = self
14
- case bool
15
- when 'AND'
16
- result = true
17
- tags.each do |tag|
18
- unless item['title'] =~ /@#{tag}/
19
- result = false
20
- break
21
- end
22
- end
23
- result
24
- when 'NOT'
25
- result = true
26
- tags.each do |tag|
27
- if item['title'] =~ /@#{tag}/
28
- result = false
29
- break
30
- end
31
- end
32
- result
33
- else
34
- result = false
35
- tags.each do |tag|
36
- if item['title'] =~ /@#{tag}/
37
- result = true
38
- break
39
- end
40
- end
41
- result
42
- end
43
- end
44
-
45
- def matches_search?(search)
46
- item = self
47
- text = item['note'] ? item['title'] + item['note'].join(' ') : item['title']
48
- pattern = if search.strip =~ %r{^/.*?/$}
49
- search.sub(%r{/(.*?)/}, '\1')
50
- else
51
- search.split('').join('.{0,3}')
52
- end
53
- text =~ /#{pattern}/i ? true : false
54
- end
55
- end
56
-
57
- ##
58
- ## @brief String helpers
59
- ##
60
- class String
61
- def cap_first
62
- sub(/^\w/) do |m|
63
- m.upcase
64
- end
65
- end
66
-
67
- ##
68
- ## @brief Turn raw urls into HTML links
69
- ##
70
- ## @param opt (Hash) Additional Options
71
- ##
72
- def link_urls(opt = {})
73
- opt[:format] ||= :html
74
- if opt[:format] == :html
75
- gsub(%r{(?mi)((http|https)://)?([\w\-_]+(\.[\w\-_]+)+)([\w\-.,@?^=%&amp;:/~+#]*[\w\-@^=%&amp;/~+#])?}) do |_match|
76
- m = Regexp.last_match
77
- proto = m[1].nil? ? 'http://' : ''
78
- %(<a href="#{proto}#{m[0]}" title="Link to #{m[0]}">[#{m[3]}]</a>)
79
- end.gsub(/<(\w+:.*?)>/) do |match|
80
- m = Regexp.last_match
81
- if m[1] =~ /<a href/
82
- match
83
- else
84
- %(<a href="#{m[1]}" title="Link to #{m[1]}">[link]</a>)
85
- end
86
- end
87
- else
88
- self
89
- end
90
- end
91
- end
92
-
93
7
  ##
94
8
  ## @brief Main "What Was I Doing" methods
95
9
  ##
@@ -791,7 +705,7 @@ class WWID
791
705
  opt[:section] ||= 'all'
792
706
  opt[:note] ||= []
793
707
  opt[:tag] ||= []
794
- opt[:tag_bool] ||= 'AND'
708
+ opt[:tag_bool] ||= :and
795
709
 
796
710
  last = last_entry(opt)
797
711
  if last.nil?
@@ -817,7 +731,7 @@ class WWID
817
731
  ## @param opt (Hash) Additional Options
818
732
  ##
819
733
  def last_entry(opt = {})
820
- opt[:tag_bool] ||= 'AND'
734
+ opt[:tag_bool] ||= :and
821
735
  opt[:section] ||= @current_section
822
736
 
823
737
  sec_arr = []
@@ -867,6 +781,7 @@ class WWID
867
781
  opt[:autotag] ||= false
868
782
  opt[:back] ||= false
869
783
  opt[:took] ||= nil
784
+ opt[:unfinished] ||= false
870
785
 
871
786
  sec_arr = []
872
787
 
@@ -902,10 +817,11 @@ class WWID
902
817
  items.map! do |item|
903
818
  break if idx == count
904
819
 
820
+ finished = opt[:unfinished] && item.has_tags?('done', :and)
905
821
  tag_match = opt[:tag].nil? || opt[:tag].empty? ? true : item.has_tags?(opt[:tag], opt[:tag_bool])
906
822
  search_match = opt[:search].nil? || opt[:search].empty? ? true : item.matches_search?(opt[:search])
907
823
 
908
- if tag_match && search_match
824
+ if tag_match && search_match && !finished
909
825
  if opt[:autotag]
910
826
  new_title = autotag(item['title']) if @auto_tag
911
827
  if new_title == item['title']
@@ -1308,7 +1224,7 @@ class WWID
1308
1224
  @content.each do |_k, v|
1309
1225
  combined['items'] += v['items']
1310
1226
  end
1311
- section = if opt[:tag_filter] && opt[:tag_filter]['bool'] != 'NONE'
1227
+ section = if opt[:tag_filter] && opt[:tag_filter]['bool'].normalize_bool != :not
1312
1228
  opt[:tag_filter]['tags'].map do |tag|
1313
1229
  "@#{tag}"
1314
1230
  end.join(' + ')
@@ -1339,31 +1255,7 @@ class WWID
1339
1255
  end
1340
1256
 
1341
1257
  if opt[:tag_filter] && !opt[:tag_filter]['tags'].empty?
1342
- items.delete_if do |item|
1343
- case opt[:tag_filter]['bool']
1344
- when /(AND|ALL)/
1345
- del = false
1346
- opt[:tag_filter]['tags'].each do |tag|
1347
- unless item['title'] =~ /@#{tag}/
1348
- del = true
1349
- break
1350
- end
1351
- end
1352
- del
1353
- when /NONE/
1354
- del = false
1355
- opt[:tag_filter]['tags'].each do |tag|
1356
- del = true if item['title'] =~ /@#{tag}/
1357
- end
1358
- del
1359
- when /(OR|ANY)/
1360
- del = true
1361
- opt[:tag_filter]['tags'].each do |tag|
1362
- del = false if item['title'] =~ /@#{tag}/
1363
- end
1364
- del
1365
- end
1366
- end
1258
+ items.select! { |item| item.has_tags?(opt[:tag_filter]['tags'], opt[:tag_filter]['bool']) }
1367
1259
  end
1368
1260
 
1369
1261
  if opt[:search]
@@ -1655,7 +1547,7 @@ class WWID
1655
1547
  count = options[:keep] || 0
1656
1548
  destination = options[:destination] || 'Archive'
1657
1549
  tags = options[:tags] || []
1658
- bool = options[:bool] || 'AND'
1550
+ bool = options[:bool] || :and
1659
1551
 
1660
1552
  section = choose_section if section.nil? || section =~ /choose/i
1661
1553
  archive_all = section =~ /^all$/i # && !(tags.nil? || tags.empty?)
@@ -1683,7 +1575,7 @@ class WWID
1683
1575
  def do_archive(sect, destination, opt = {})
1684
1576
  count = opt[:count] || 0
1685
1577
  tags = opt[:tags] || []
1686
- bool = opt[:bool] || 'AND'
1578
+ bool = opt[:bool] || :and
1687
1579
  label = opt[:label] || true
1688
1580
 
1689
1581
  if sect =~ /^all$/i
@@ -1696,7 +1588,7 @@ class WWID
1696
1588
  counter = 0
1697
1589
 
1698
1590
  all_sections.each do |section|
1699
- items = @content[section]['items']
1591
+ items = @content[section]['items'].dup
1700
1592
 
1701
1593
  moved_items = []
1702
1594
  if !tags.empty? || opt[:search]
@@ -1720,13 +1612,7 @@ class WWID
1720
1612
  @content[destination]['items'].concat(moved_items)
1721
1613
  @results.push("Archived #{moved_items.length} items from #{section} to #{destination}")
1722
1614
  else
1723
- count = items.length if count == 0 || items.length < count
1724
-
1725
- @content[section]['items'] = if count.zero?
1726
- []
1727
- else
1728
- items[0..count - 1]
1729
- end
1615
+ count = items.length if items.length < count
1730
1616
 
1731
1617
  items.map! do |item|
1732
1618
  if label && section != 'Currently'
@@ -1735,12 +1621,19 @@ class WWID
1735
1621
  end
1736
1622
  item
1737
1623
  end
1624
+
1738
1625
  if items.count > count
1739
1626
  @content[destination]['items'].concat(items[count..-1])
1740
1627
  else
1741
1628
  @content[destination]['items'].concat(items)
1742
1629
  end
1743
1630
 
1631
+ @content[section]['items'] = if count.zero?
1632
+ []
1633
+ else
1634
+ items[0..count - 1]
1635
+ end
1636
+
1744
1637
  @results.push("Archived #{items.length - count} items from #{section} to #{destination}")
1745
1638
  end
1746
1639
  end
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.65
4
+ version: 1.0.69
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-24 00:00:00.000000000 Z
11
+ date: 2021-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -165,6 +165,7 @@ files:
165
165
  - README.md
166
166
  - bin/doing
167
167
  - lib/doing.rb
168
+ - lib/doing/helpers.rb
168
169
  - lib/doing/version.rb
169
170
  - lib/doing/wwid.rb
170
171
  - lib/templates/doing.css