doing 2.1.21 → 2.1.25

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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.yardoc/checksums +19 -16
  3. data/.yardoc/object_types +0 -0
  4. data/.yardoc/objects/root.dat +0 -0
  5. data/CHANGELOG.md +51 -13
  6. data/Gemfile.lock +1 -1
  7. data/README.md +1 -1
  8. data/Rakefile +6 -3
  9. data/bin/doing +254 -103
  10. data/docs/doc/Array.html +74 -34
  11. data/docs/doc/BooleanTermParser/Clause.html +5 -5
  12. data/docs/doc/BooleanTermParser/Operator.html +4 -4
  13. data/docs/doc/BooleanTermParser/Query.html +8 -8
  14. data/docs/doc/BooleanTermParser/QueryParser.html +2 -2
  15. data/docs/doc/BooleanTermParser/QueryTransformer.html +2 -2
  16. data/docs/doc/BooleanTermParser.html +1 -1
  17. data/docs/doc/Doing/Color.html +4 -4
  18. data/docs/doc/Doing/Completion.html +2 -2
  19. data/docs/doc/Doing/Configuration.html +16 -18
  20. data/docs/doc/Doing/Errors/DoingNoTraceError.html +2 -2
  21. data/docs/doc/Doing/Errors/DoingRuntimeError.html +2 -2
  22. data/docs/doc/Doing/Errors/DoingStandardError.html +2 -2
  23. data/docs/doc/Doing/Errors/EmptyInput.html +2 -2
  24. data/docs/doc/Doing/Errors/NoResults.html +2 -2
  25. data/docs/doc/Doing/Errors/PluginException.html +3 -3
  26. data/docs/doc/Doing/Errors/UserCancelled.html +2 -2
  27. data/docs/doc/Doing/Errors/WrongCommand.html +2 -2
  28. data/docs/doc/Doing/Errors.html +1 -1
  29. data/docs/doc/Doing/Hooks.html +6 -6
  30. data/docs/doc/Doing/Item.html +50 -16
  31. data/docs/doc/Doing/Items.html +10 -10
  32. data/docs/doc/Doing/LogAdapter.html +24 -24
  33. data/docs/doc/Doing/Note.html +7 -7
  34. data/docs/doc/Doing/Pager.html +4 -4
  35. data/docs/doc/Doing/Plugins.html +7 -7
  36. data/docs/doc/Doing/Prompt.html +16 -16
  37. data/docs/doc/Doing/Section.html +6 -6
  38. data/docs/doc/Doing/TemplateString.html +8 -8
  39. data/docs/doc/Doing/Types.html +206 -0
  40. data/docs/doc/Doing/Util/Backup.html +10 -10
  41. data/docs/doc/Doing/Util.html +16 -19
  42. data/docs/doc/Doing/WWID.html +65 -53
  43. data/docs/doc/Doing.html +4 -4
  44. data/docs/doc/GLI/Commands/Help.html +185 -0
  45. data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +17 -17
  46. data/docs/doc/GLI/Commands.html +5 -3
  47. data/docs/doc/GLI.html +4 -2
  48. data/docs/doc/Hash.html +119 -21
  49. data/docs/doc/Numeric.html +5 -5
  50. data/docs/doc/PhraseParser/Operator.html +4 -4
  51. data/docs/doc/PhraseParser/PhraseClause.html +5 -5
  52. data/docs/doc/PhraseParser/Query.html +10 -10
  53. data/docs/doc/PhraseParser/QueryParser.html +2 -2
  54. data/docs/doc/PhraseParser/QueryTransformer.html +2 -2
  55. data/docs/doc/PhraseParser/TermClause.html +5 -5
  56. data/docs/doc/PhraseParser.html +1 -1
  57. data/docs/doc/Status.html +7 -7
  58. data/docs/doc/String.html +206 -51
  59. data/docs/doc/Symbol.html +8 -8
  60. data/docs/doc/Time.html +6 -6
  61. data/docs/doc/_index.html +51 -14
  62. data/docs/doc/class_list.html +1 -1
  63. data/docs/doc/file.README.html +2 -2
  64. data/docs/doc/index.html +2 -2
  65. data/docs/doc/method_list.html +348 -252
  66. data/docs/doc/top-level-namespace.html +2 -93
  67. data/docs/index.md +1 -1
  68. data/doing.rdoc +177 -20
  69. data/example_plugin.rb +2 -2
  70. data/lib/completion/_doing.zsh +24 -24
  71. data/lib/completion/doing.bash +31 -20
  72. data/lib/completion/doing.fish +32 -10
  73. data/lib/doing/array.rb +5 -4
  74. data/lib/doing/array_chronify.rb +4 -3
  75. data/lib/doing/changelog/change.rb +115 -0
  76. data/lib/doing/changelog/changes.rb +73 -0
  77. data/lib/doing/changelog/entry.rb +21 -0
  78. data/lib/doing/changelog/version.rb +97 -0
  79. data/lib/doing/changelog.rb +6 -0
  80. data/lib/doing/completion/fish_completion.rb +2 -1
  81. data/lib/doing/configuration.rb +20 -14
  82. data/lib/doing/good.rb +64 -0
  83. data/lib/doing/hash.rb +28 -5
  84. data/lib/doing/help_monkey_patch.rb +31 -0
  85. data/lib/doing/hooks.rb +8 -4
  86. data/lib/doing/item.rb +24 -35
  87. data/lib/doing/log_adapter.rb +1 -1
  88. data/lib/doing/pager.rb +1 -1
  89. data/lib/doing/plugins/export/template_export.rb +9 -3
  90. data/lib/doing/plugins/import/calendar_import.rb +1 -1
  91. data/lib/doing/plugins/import/doing_import.rb +1 -1
  92. data/lib/doing/plugins/import/timing_import.rb +1 -1
  93. data/lib/doing/prompt.rb +4 -2
  94. data/lib/doing/string.rb +30 -12
  95. data/lib/doing/string_chronify.rb +1 -1
  96. data/lib/doing/template_string.rb +9 -2
  97. data/lib/doing/types.rb +23 -16
  98. data/lib/doing/util.rb +12 -11
  99. data/lib/doing/version.rb +1 -1
  100. data/lib/doing/wwid.rb +65 -46
  101. data/lib/doing.rb +3 -0
  102. data/lib/helpers/threaded_tests.rb +106 -99
  103. data/lib/helpers/threaded_tests_string.rb +50 -0
  104. metadata +12 -2
data/bin/doing CHANGED
@@ -3,8 +3,8 @@
3
3
 
4
4
  $LOAD_PATH.unshift File.join(__dir__, '..', 'lib')
5
5
  require 'gli'
6
+ require 'doing/help_monkey_patch'
6
7
  require 'doing'
7
- require 'doing/types'
8
8
  require 'tempfile'
9
9
  require 'pp'
10
10
 
@@ -22,17 +22,12 @@ end
22
22
 
23
23
  include GLI::App
24
24
  include Doing::Errors
25
+
25
26
  version Doing::VERSION
26
27
  hide_commands_without_desc true
27
28
  autocomplete_commands true
28
29
 
29
- InvalidExportType = Class.new(RuntimeError)
30
- MissingConfigFile = Class.new(RuntimeError)
31
- TagArray = Class.new(Array)
32
- DateBeginString = Class.new(DateTime)
33
- DateEndString = Class.new(DateTime)
34
- DateRangeString = Class.new(Array)
35
- DateIntervalString = Class.new(DateTime)
30
+ include Doing::Types
36
31
 
37
32
  colors = Doing::Color
38
33
  wwid = Doing::WWID.new
@@ -72,6 +67,13 @@ if settings.dig('plugins', 'command_path')
72
67
  commands_from File.expand_path(settings.dig('plugins', 'command_path'))
73
68
  end
74
69
 
70
+ accept TemplateName do |value|
71
+ res = settings['templates'].keys.select { |k| k =~ value.to_rx(distance: 2) }
72
+ raise InvalidArgument, "Unknown template: #{value}" unless res.good?
73
+
74
+ res.group_by(&:length).min.last[0]
75
+ end
76
+
75
77
  accept DateBeginString do |value|
76
78
  if value =~ REGEX_TIME
77
79
  res = value
@@ -102,6 +104,13 @@ accept DateRangeString do |value|
102
104
  [start, finish]
103
105
  end
104
106
 
107
+ accept DateRangeOptionalString do |value|
108
+ start, finish = value.split_date_range
109
+ raise InvalidTimeExpression, 'Invalid range' unless start
110
+
111
+ [start, finish]
112
+ end
113
+
105
114
  accept DateIntervalString do |value|
106
115
  res = value.chronify_qty
107
116
  raise InvalidTimeExpression, 'Invalid time quantity' unless res
@@ -134,7 +143,7 @@ desc 'Use a pager when output is longer than screen'
134
143
  switch %i[p pager], default_value: settings['paginate']
135
144
 
136
145
  desc 'Answer yes/no menus with default option'
137
- switch [:default], default_value: false
146
+ switch [:default], default_value: false, negatable: false
138
147
 
139
148
  desc 'Answer all yes/no menus with yes'
140
149
  switch [:yes], negatable: false
@@ -251,7 +260,7 @@ command %i[again resume] do |c|
251
260
 
252
261
  options[:note] = note
253
262
 
254
- opts = options.dup
263
+ opts = options.clone
255
264
 
256
265
  opts[:tag] = tags
257
266
  opts[:tag_bool] = options[:bool].normalize_bool
@@ -370,7 +379,7 @@ desc 'Add a completed item with @done(date). No argument finishes last entry'
370
379
  long_desc 'Use this command to add an entry after you\'ve already finished it. It will be immediately marked as @done.
371
380
  You can modify the start and end times of the entry using the --back, --took, and --at flags, making it an easy
372
381
  way to add entries in post and maintain accurate (albeit manual) time tracking.'
373
- arg_name 'ENTRY'
382
+ arg_name 'ENTRY', optional: true
374
383
  command %i[done did] do |c|
375
384
  c.example 'doing done', desc: 'Tag the last entry @done'
376
385
  c.example 'doing done I already finished this', desc: 'Add a new entry and immediately mark it @done'
@@ -502,7 +511,7 @@ command %i[done did] do |c|
502
511
  raise NoResults, 'No results'
503
512
  end
504
513
 
505
- old_entry = last_entry.dup
514
+ old_entry = last_entry.clone
506
515
  last_entry.note.add(note)
507
516
  input = ["#{last_entry.date.strftime('%F %R | ')}#{last_entry.title}", last_entry.note.strip_lines.join("\n")].join("\n")
508
517
  else
@@ -511,13 +520,13 @@ command %i[done did] do |c|
511
520
  end
512
521
 
513
522
  input = wwid.fork_editor(input).strip
514
- raise EmptyInput, 'No content' unless input && !input.empty?
523
+ raise EmptyInput, 'No content' unless input.good?
515
524
 
516
525
  d, title, note = wwid.format_input(input)
517
526
 
518
527
  if options[:ask]
519
528
  ask_note = Doing::Prompt.read_lines(prompt: 'Add a note')
520
- note.add(ask_note) unless ask_note.empty?
529
+ note.add(ask_note) if ask_note.good?
521
530
  end
522
531
 
523
532
  date = d.nil? ? date : d
@@ -533,15 +542,16 @@ command %i[done did] do |c|
533
542
  if (is_new)
534
543
  Doing::Hooks.trigger :pre_entry_add, wwid, new_entry
535
544
  wwid.content.push(new_entry)
536
- Doing::Hooks.trigger :post_entry_added, wwid, new_entry.dup
545
+ Doing::Hooks.trigger :post_entry_added, wwid, new_entry
537
546
  else
547
+ old = old_entry.clone
538
548
  wwid.content.update_item(old_entry, new_entry)
539
- Doing::Hooks.trigger :post_entry_updated, wwid, new_entry unless options[:archive]
549
+ Doing::Hooks.trigger :post_entry_updated, wwid, new_entry, old unless options[:archive]
540
550
  end
541
551
 
542
552
  if options[:archive]
543
553
  wwid.move_item(new_entry, 'Archive', label: true)
544
- Doing::Hooks.trigger :post_entry_updated, wwid, new_entry
554
+ Doing::Hooks.trigger :post_entry_updated, wwid, new_entry, old_entry
545
555
  end
546
556
 
547
557
  wwid.write(wwid.doing_file)
@@ -580,7 +590,7 @@ command %i[done did] do |c|
580
590
 
581
591
  Doing::Hooks.trigger :pre_entry_add, wwid, new_entry
582
592
  wwid.content.push(new_entry)
583
- Doing::Hooks.trigger :post_entry_added, wwid, new_entry.dup
593
+ Doing::Hooks.trigger :post_entry_added, wwid, new_entry
584
594
  wwid.write(wwid.doing_file)
585
595
  Doing.logger.info('New entry:', %(added "#{new_entry.date.relative_date}: #{new_entry.title}" to #{section}))
586
596
  elsif $stdin.stat.size.positive?
@@ -604,7 +614,7 @@ command %i[done did] do |c|
604
614
 
605
615
  Doing::Hooks.trigger :pre_entry_add, wwid, new_entry
606
616
  wwid.content.push(new_entry)
607
- Doing::Hooks.trigger :post_entry_added, wwid, new_entry.dup
617
+ Doing::Hooks.trigger :post_entry_added, wwid, new_entry
608
618
 
609
619
  wwid.write(wwid.doing_file)
610
620
  Doing.logger.info('New entry:', %(added "#{new_entry.date.relative_date}: #{new_entry.title}" to #{section}))
@@ -617,7 +627,7 @@ end
617
627
  # @@finish
618
628
  desc 'Mark last X entries as @done'
619
629
  long_desc 'Marks the last X entries with a @done tag and current date. Does not alter already completed entries.'
620
- arg_name 'COUNT'
630
+ arg_name 'COUNT', optional: true
621
631
  command :finish do |c|
622
632
  c.example 'doing finish', desc: 'Mark the last entry @done'
623
633
  c.example 'doing finish --auto --section Later 10', desc: 'Add @done to any unfinished entries in the last 10 in Later, setting the finish time based on the start time of the task after it'
@@ -897,7 +907,7 @@ long_desc 'The @meanwhile tag allows you to have long-running entries that encom
897
907
  This command makes it easy to start and stop these overarching entries. Just run `doing meanwhile Starting work on this
898
908
  big project` to start a @meanwhile entry, add other entries as you work on the project, then use `doing meanwhile` by
899
909
  itself to mark the entry as @done.'
900
- arg_name 'ENTRY'
910
+ arg_name 'ENTRY', optional: true
901
911
  command :meanwhile do |c|
902
912
  c.example 'doing meanwhile "Long task that will have others after it before it\'s done"', desc: 'Add a new long-running entry, completing any current @meanwhile entry'
903
913
  c.example 'doing meanwhile', desc: 'Finish any open @meanwhile entry'
@@ -948,7 +958,7 @@ command :meanwhile do |c|
948
958
  input += date.strftime('%F %R | ')
949
959
  input += args.join(' ') unless args.empty?
950
960
  input += "\n#{options[:note]}" if options[:note]
951
- input += "\n#{ask_note}" unless ask_note.empty?
961
+ input += "\n#{ask_note}" unless ask_note.good?
952
962
 
953
963
  input = wwid.fork_editor(input).strip
954
964
  elsif !args.empty?
@@ -957,7 +967,7 @@ command :meanwhile do |c|
957
967
  input = $stdin.read.strip
958
968
  end
959
969
 
960
- if input && !input.empty?
970
+ if input.good?
961
971
  d, input, note = wwid.format_input(input)
962
972
  unless d.nil?
963
973
  Doing.logger.debug('Parser:', 'Date detected in input, overriding command line values')
@@ -970,7 +980,7 @@ command :meanwhile do |c|
970
980
 
971
981
  unless options[:editor]
972
982
  note.add(options[:note]) if options[:note]
973
- note.add(ask_note) unless ask_note.empty?
983
+ note.add(ask_note) if ask_note.good?
974
984
  end
975
985
 
976
986
  wwid.stop_start('meanwhile', { new_item: input, back: date, section: section, archive: options[:archive], note: note })
@@ -987,7 +997,7 @@ long_desc %(
987
997
 
988
998
  Use -e to load the last entry in a text editor where you can append a note.
989
999
  )
990
- arg_name 'NOTE_TEXT'
1000
+ arg_name 'NOTE_TEXT', optional: true
991
1001
  command :note do |c|
992
1002
  c.example 'doing note', desc: 'Open the last entry in $EDITOR to append a note'
993
1003
  c.example 'doing note "Just a quick annotation"', desc: 'Add a quick note to the last entry'
@@ -1055,8 +1065,8 @@ command :note do |c|
1055
1065
  options[:search] = search
1056
1066
  end
1057
1067
 
1058
-
1059
1068
  last_entry = wwid.last_entry(options)
1069
+ old_entry = last_entry.clone
1060
1070
 
1061
1071
  unless last_entry
1062
1072
  Doing.logger.warn('Not found:', 'No entry matching parameters was found.')
@@ -1065,44 +1075,44 @@ command :note do |c|
1065
1075
 
1066
1076
  last_note = last_entry.note || Doing::Note.new
1067
1077
  new_note = Doing::Note.new
1068
- ask_note = options[:ask] ? Doing::Prompt.read_lines(prompt: 'Add a note') : ''
1069
1078
 
1070
- if options[:editor] || (args.empty? && $stdin.stat.size.zero? && !options[:remove] && !options[:ask])
1071
- raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
1079
+ if $stdin.stat.size.positive?
1080
+ new_note.add($stdin.read.strip)
1081
+ end
1072
1082
 
1073
- input = !args.empty? ? args.join(' ') : ''
1083
+ unless args.empty?
1084
+ new_note.add(args.join(' '))
1085
+ end
1086
+
1087
+ if options[:editor]
1088
+ raise MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
1074
1089
 
1075
1090
  if options[:remove]
1076
- prev_input = Doing::Note.new
1091
+ input = Doing::Note.new
1077
1092
  else
1078
- prev_input = last_entry.note || Doing::Note.new
1093
+ input = last_entry.note || Doing::Note.new
1079
1094
  end
1080
1095
 
1096
+ input.add(new_note)
1081
1097
 
1082
- input = prev_input.add(input)
1083
- input.add(ask_note) unless ask_note.empty?
1084
-
1085
- input = wwid.fork_editor(prev_input.strip_lines.join("\n"), message: nil).strip
1086
- note = input
1098
+ new_note = Doing::Note.new(wwid.fork_editor(input.strip_lines.join("\n"), message: nil).strip)
1087
1099
  options[:remove] = true
1088
- new_note.add(note)
1089
- elsif !args.empty?
1090
- new_note.add(args.join(' '))
1091
- elsif $stdin.stat.size.positive?
1092
- new_note.add($stdin.read.strip)
1093
- else
1094
- raise EmptyInput, 'You must provide content when adding a note' unless options[:remove] || !ask_note.empty?
1100
+ end
1095
1101
 
1102
+ if (new_note.empty? && !options[:remove]) || options[:ask]
1103
+ $stderr.puts last_note if last_note.good?
1104
+ $stderr.puts new_note if new_note.good?
1105
+ new_note.add(Doing::Prompt.read_lines(prompt: 'Add a note'))
1096
1106
  end
1097
1107
 
1098
- new_note.add(ask_note) unless ask_note.empty?
1108
+ raise EmptyInput, 'You must provide content when adding a note' unless options[:remove] || new_note.good?
1099
1109
 
1100
1110
  if last_note.equal?(new_note)
1101
1111
  Doing.logger.debug('Skipped:', 'No note change')
1102
1112
  else
1103
1113
  last_note.add(new_note, replace: options[:remove])
1104
1114
  Doing.logger.info('Entry updated:', last_entry.title)
1105
- Doing::Hooks.trigger :post_entry_updated, wwid, last_entry
1115
+ Doing::Hooks.trigger :post_entry_updated, wwid, last_entry, old_entry
1106
1116
  end
1107
1117
  # new_entry = Doing::Item.new(last_entry.date, last_entry.title, last_entry.section, new_note)
1108
1118
  wwid.write(wwid.doing_file)
@@ -1138,6 +1148,13 @@ command %i[now next] do |c|
1138
1148
  c.arg_name 'DATE_STRING'
1139
1149
  c.flag %i[b back started], type: DateBeginString
1140
1150
 
1151
+ c.desc %(
1152
+ Set a start and optionally end time as a date range ("from 1pm to 2:30pm").
1153
+ If an end time is provided, a dated @done tag will be added
1154
+ )
1155
+ c.arg_name 'TIME_RANGE'
1156
+ c.flag [:from], type: DateRangeString
1157
+
1141
1158
  c.desc 'Timed entry, marks last entry in section as @done'
1142
1159
  c.switch %i[f finish_last], negatable: false, default_value: false
1143
1160
 
@@ -1153,13 +1170,17 @@ command %i[now next] do |c|
1153
1170
  # # c.flag [:a, :app]
1154
1171
 
1155
1172
  c.action do |_global_options, options, args|
1173
+ raise InvalidArgument, "--back and --from cannot be used together" if options[:back] && options[:from]
1174
+
1156
1175
  if options[:back]
1157
1176
  date = options[:back]
1158
-
1159
- raise InvalidTimeExpression.new('unable to parse date string', topic: 'Parser:') if date.nil?
1177
+ elsif options[:from]
1178
+ date, finish_date = options[:from]
1179
+ options[:done] = finish_date
1160
1180
  else
1161
1181
  date = Time.now
1162
1182
  end
1183
+ raise InvalidTimeExpression.new('unable to parse date string', topic: 'Parser:') if date.nil?
1163
1184
 
1164
1185
  if options[:section]
1165
1186
  section = wwid.guess_section(options[:section]) || options[:section].cap_first
@@ -1174,16 +1195,17 @@ command %i[now next] do |c|
1174
1195
 
1175
1196
  input = date.strftime('%F %R | ')
1176
1197
  input += args.join(' ') unless args.empty?
1198
+ input += " @done(#{options[:done].strftime('%F %R')})" if options[:done]
1177
1199
  input += "\n#{options[:note]}" if options[:note]
1178
- input += "\n#{ask_note}" unless ask_note.empty?
1200
+ input += "\n#{ask_note}" if ask_note.good?
1179
1201
  input = wwid.fork_editor(input).strip
1180
1202
 
1181
1203
  d, title, note = wwid.format_input(input)
1182
- raise EmptyInput, 'No content' if title.strip.empty?
1204
+ raise EmptyInput, 'No content' unless title.good?
1183
1205
 
1184
1206
  if ask_note.empty? && options[:ask]
1185
1207
  ask_note = Doing::Prompt.read_lines(prompt: 'Add a note')
1186
- note.add(ask_note) unless ask_note.empty?
1208
+ note.add(ask_note) if ask_note.good?
1187
1209
  end
1188
1210
 
1189
1211
  date = d.nil? ? date : d
@@ -1193,8 +1215,15 @@ command %i[now next] do |c|
1193
1215
  d, title, note = wwid.format_input(args.join(' '))
1194
1216
  date = d.nil? ? date : d
1195
1217
  note.add(options[:note]) if options[:note]
1196
- note.add(ask_note) unless ask_note.empty?
1197
- wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
1218
+ note.add(ask_note) if ask_note.good?
1219
+ entry = wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
1220
+ if options[:done] && entry.should_finish?
1221
+ if entry.should_time?
1222
+ entry.tag('done', value: options[:done])
1223
+ else
1224
+ entry.tag('done')
1225
+ end
1226
+ end
1198
1227
  wwid.write(wwid.doing_file)
1199
1228
  elsif $stdin.stat.size.positive?
1200
1229
  input = $stdin.read.strip
@@ -1206,15 +1235,22 @@ command %i[now next] do |c|
1206
1235
  note.add(options[:note]) if options[:note]
1207
1236
  if ask_note.empty? && options[:ask]
1208
1237
  ask_note = Doing::Prompt.read_lines(prompt: 'Add a note')
1209
- note.add(ask_note) unless ask_note.empty?
1238
+ note.add(ask_note) if ask_note.good?
1239
+ end
1240
+ entry = wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
1241
+ if options[:done] && entry.should_finish?
1242
+ if entry.should_time?
1243
+ entry.tag('done', value: options[:done])
1244
+ else
1245
+ entry.tag('done')
1246
+ end
1210
1247
  end
1211
- wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
1212
1248
  wwid.write(wwid.doing_file)
1213
1249
  else
1214
1250
  tags = wwid.all_tags(wwid.content)
1215
1251
  $stderr.puts Doing::Color.boldgreen("Add a new entry. Tab will autocomplete known tags. Ctrl-c to cancel.")
1216
1252
  title = Doing::Prompt.read_line(prompt: 'Entry content', completions: tags)
1217
- raise EmptyInput, 'You must provide content when creating a new entry' if title.strip.empty?
1253
+ raise EmptyInput, 'You must provide content when creating a new entry' unless title.good?
1218
1254
 
1219
1255
  note = Doing::Note.new
1220
1256
  note.add(options[:note]) if options[:note]
@@ -1222,7 +1258,14 @@ command %i[now next] do |c|
1222
1258
  ask_note = res ? Doing::Prompt.read_lines(prompt: 'Enter note') : []
1223
1259
  note.add(ask_note)
1224
1260
 
1225
- wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
1261
+ entry = wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:finish_last] })
1262
+ if options[:done] && entry.should_finish?
1263
+ if entry.should_time?
1264
+ entry.tag('done', value: options[:done])
1265
+ else
1266
+ entry.tag('done')
1267
+ end
1268
+ end
1226
1269
  wwid.write(wwid.doing_file)
1227
1270
  end
1228
1271
  end
@@ -1233,7 +1276,7 @@ desc 'Reset the start time of an entry'
1233
1276
  long_desc 'Update the start time of the last entry or the last entry matching a tag/search filter.
1234
1277
  If no argument is provided, the start time will be reset to the current time.
1235
1278
  If a date string is provided as an argument, the start time will be set to the parsed result.'
1236
- arg_name 'DATE_STRING'
1279
+ arg_name 'DATE_STRING', optional: true
1237
1280
  command %i[reset begin] do |c|
1238
1281
  c.example 'doing reset', desc: 'Reset the start time of the last entry to the current time'
1239
1282
  c.example 'doing reset --tag project1', desc: 'Reset the start time of the most recent entry tagged @project1 to the current time'
@@ -1247,6 +1290,9 @@ command %i[reset begin] do |c|
1247
1290
  c.desc 'Resume entry (remove @done)'
1248
1291
  c.switch %i[r resume], default_value: true
1249
1292
 
1293
+ c.desc 'Change start date but do not remove @done (shortcut for --no-resume)'
1294
+ c.switch [:n]
1295
+
1250
1296
  c.desc 'Reset last entry matching tag. Wildcards allowed (*, ?)'
1251
1297
  c.arg_name 'TAG'
1252
1298
  c.flag [:tag]
@@ -1322,8 +1368,10 @@ command %i[reset begin] do |c|
1322
1368
  return
1323
1369
  end
1324
1370
 
1371
+ old_item = last_entry.clone
1372
+
1325
1373
  wwid.reset_item(last_entry, date: reset_date, resume: options[:resume])
1326
- Doing::Hooks.trigger :post_entry_updated, wwid, last_entry
1374
+ Doing::Hooks.trigger :post_entry_updated, wwid, last_entry, old_item
1327
1375
  # new_entry = Doing::Item.new(last_entry.date, last_entry.title, last_entry.section, new_note)
1328
1376
 
1329
1377
  wwid.write(wwid.doing_file)
@@ -1687,6 +1735,14 @@ command %i[grep search] do |c|
1687
1735
  c.arg_name 'FORMAT'
1688
1736
  c.flag %i[o output]
1689
1737
 
1738
+ c.desc "Output using a template from configuration"
1739
+ c.arg_name 'TEMPLATE_KEY'
1740
+ c.flag [:config_template], type: TemplateName, default_value: 'default'
1741
+
1742
+ c.desc 'Override output format with a template string containing %placeholders'
1743
+ c.arg_name 'TEMPLATE_STRING'
1744
+ c.flag [:template]
1745
+
1690
1746
  c.desc 'Show time intervals on @done tasks'
1691
1747
  c.switch %i[t times], default_value: true, negatable: true
1692
1748
 
@@ -1741,7 +1797,7 @@ command %i[grep search] do |c|
1741
1797
  options[:fuzzy] = false
1742
1798
  raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
1743
1799
 
1744
- template = settings['templates']['default'].deep_merge(settings)
1800
+ template = settings['templates'][options[:config_template]].deep_merge(settings)
1745
1801
  tags_color = template.key?('tags_color') ? template['tags_color'] : nil
1746
1802
 
1747
1803
  section = wwid.guess_section(options[:section]) if options[:section]
@@ -1798,6 +1854,14 @@ command :last do |c|
1798
1854
  c.arg_name 'QUERY'
1799
1855
  c.flag [:search]
1800
1856
 
1857
+ c.desc "Output using a template from configuration"
1858
+ c.arg_name 'TEMPLATE_KEY'
1859
+ c.flag [:config_template], type: TemplateName, default_value: 'last'
1860
+
1861
+ c.desc 'Override output format with a template string containing %placeholders'
1862
+ c.arg_name 'TEMPLATE_STRING'
1863
+ c.flag [:template]
1864
+
1801
1865
  c.desc "Highlight search matches in output. Only affects command line output"
1802
1866
  c.switch %i[h hilite], default_value: settings.dig('search', 'highlight')
1803
1867
 
@@ -1851,6 +1915,8 @@ command :last do |c|
1851
1915
  else
1852
1916
  last = wwid.last(times: true, section: options[:section],
1853
1917
  options: {
1918
+ config_template: options[:config_template],
1919
+ template: options[:template],
1854
1920
  duration: options[:duration],
1855
1921
  search: options[:search],
1856
1922
  fuzzy: options[:fuzzy],
@@ -1885,6 +1951,14 @@ command :recent do |c|
1885
1951
  c.desc 'Show time intervals on @done tasks'
1886
1952
  c.switch %i[t times], default_value: true, negatable: true
1887
1953
 
1954
+ c.desc "Output using a template from configuration"
1955
+ c.arg_name 'TEMPLATE_KEY'
1956
+ c.flag [:config_template], type: TemplateName, default_value: 'recent'
1957
+
1958
+ c.desc 'Override output format with a template string containing %placeholders'
1959
+ c.arg_name 'TEMPLATE_STRING'
1960
+ c.flag [:template]
1961
+
1888
1962
  c.desc 'Show elapsed time on entries without @done tag'
1889
1963
  c.switch [:duration]
1890
1964
 
@@ -1928,7 +2002,9 @@ command :recent do |c|
1928
2002
  times: options[:times],
1929
2003
  totals: options[:totals],
1930
2004
  interactive: options[:interactive],
1931
- duration: options[:duration]
2005
+ duration: options[:duration],
2006
+ config_template: options[:config_template],
2007
+ template: options[:template]
1932
2008
  }
1933
2009
 
1934
2010
  Doing::Pager::page wwid.recent(count, section.cap_first, opts)
@@ -2043,6 +2119,14 @@ command :show do |c|
2043
2119
  c.desc 'Only show items with recorded time intervals'
2044
2120
  c.switch [:only_timed], default_value: false, negatable: false
2045
2121
 
2122
+ c.desc "Output using a template from configuration"
2123
+ c.arg_name 'TEMPLATE_KEY'
2124
+ c.flag [:config_template], type: TemplateName, default_value: 'default'
2125
+
2126
+ c.desc 'Override output format with a template string containing %placeholders'
2127
+ c.arg_name 'TEMPLATE_STRING'
2128
+ c.flag [:template]
2129
+
2046
2130
  c.desc 'Select section or tag to display from a menu'
2047
2131
  c.switch %i[m menu], negatable: false, default_value: false
2048
2132
 
@@ -2099,7 +2183,7 @@ command :show do |c|
2099
2183
 
2100
2184
  options[:times] = true if options[:totals]
2101
2185
 
2102
- template = settings['templates']['default'].deep_merge({
2186
+ template = settings['templates'][options[:config_template]].deep_merge({
2103
2187
  'wrap_width' => settings['wrap_width'] || 0,
2104
2188
  'date_format' => settings['default_date_format'],
2105
2189
  'order' => settings['order'] || 'asc',
@@ -2116,7 +2200,7 @@ command :show do |c|
2116
2200
 
2117
2201
  options[:section] = section
2118
2202
 
2119
- unless tags.empty?
2203
+ if tags.good?
2120
2204
  tag_filter = {
2121
2205
  'tags' => tags,
2122
2206
  'bool' => options[:bool].normalize_bool
@@ -2135,7 +2219,7 @@ command :show do |c|
2135
2219
  # options[:bool] = :and unless tags.empty?
2136
2220
 
2137
2221
  tags = tag.split(/ +/).map { |t| t.strip.sub(/^@?/, '') } if tag =~ /^@/
2138
- unless tags.empty?
2222
+ if tags.good?
2139
2223
  tag_filter = {
2140
2224
  'tags' => tags,
2141
2225
  'bool' => options[:bool].normalize_bool
@@ -2146,7 +2230,7 @@ command :show do |c|
2146
2230
 
2147
2231
  options[:age] ||= :newest
2148
2232
 
2149
- opt = options.dup
2233
+ opt = options.clone
2150
2234
  opt[:age] = options[:age].normalize_age(:newest) if options[:age]
2151
2235
  opt[:sort_tags] = options[:tag_sort] =~ /^n/i
2152
2236
  opt[:count] = options[:count].to_i
@@ -2163,6 +2247,7 @@ end
2163
2247
 
2164
2248
  # @@tags
2165
2249
  desc 'List all tags in the current Doing file'
2250
+ arg_name 'MAX_COUNT', optional: true, type: Integer
2166
2251
  command :tags do |c|
2167
2252
  c.desc 'Section'
2168
2253
  c.arg_name 'SECTION_NAME'
@@ -2171,6 +2256,9 @@ command :tags do |c|
2171
2256
  c.desc 'Show count of occurrences'
2172
2257
  c.switch %i[c counts]
2173
2258
 
2259
+ c.desc 'Output in a single line with @ symbols. Ignored if --counts is specified.'
2260
+ c.switch %i[l line]
2261
+
2174
2262
  c.desc 'Sort by name or count'
2175
2263
  c.arg_name 'SORT_ORDER'
2176
2264
  c.flag %i[sort], default_value: 'name', must_match: /^(?:n(?:ame)?|c(?:ount)?)$/
@@ -2214,6 +2302,7 @@ command :tags do |c|
2214
2302
 
2215
2303
  c.action do |_global, options, args|
2216
2304
  section = wwid.guess_section(options[:section]) || options[:section].cap_first
2305
+ options[:count] = args.count.positive? ? args[0].to_i : 0
2217
2306
 
2218
2307
  items = wwid.filter_items([], opt: options)
2219
2308
 
@@ -2241,7 +2330,11 @@ command :tags do |c|
2241
2330
  if options[:counts]
2242
2331
  tags.each { |t, c| puts "#{t} (#{c})" }
2243
2332
  else
2244
- tags.each { |t, c| puts "#{t}" }
2333
+ if options[:line]
2334
+ puts tags.map { |t, c| t }.to_tags.join(' ')
2335
+ else
2336
+ tags.each { |t, c| puts "#{t}" }
2337
+ end
2245
2338
  end
2246
2339
  end
2247
2340
  end
@@ -2279,6 +2372,14 @@ command :today do |c|
2279
2372
  c.arg_name 'FORMAT'
2280
2373
  c.flag %i[o output]
2281
2374
 
2375
+ c.desc "Output using a template from configuration"
2376
+ c.arg_name 'TEMPLATE_KEY'
2377
+ c.flag [:config_template], type: TemplateName, default_value: 'today'
2378
+
2379
+ c.desc 'Override output format with a template string containing %placeholders'
2380
+ c.arg_name 'TEMPLATE_STRING'
2381
+ c.flag [:template]
2382
+
2282
2383
  c.desc 'View entries before specified time (e.g. 8am, 12:30pm, 15:00)'
2283
2384
  c.arg_name 'TIME_STRING'
2284
2385
  c.flag [:before]
@@ -2298,7 +2399,7 @@ command :today do |c|
2298
2399
 
2299
2400
  options[:times] = true if options[:totals]
2300
2401
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
2301
- filter_options = %i[after before duration from section sort_tags totals].each_with_object({}) { |k, hsh| hsh[k] = options[k] }
2402
+ filter_options = %i[after before duration from section sort_tags totals template config_template].each_with_object({}) { |k, hsh| hsh[k] = options[k] }
2302
2403
 
2303
2404
  Doing::Pager.page wwid.today(options[:times], options[:output], filter_options).chomp
2304
2405
  end
@@ -2338,6 +2439,14 @@ command :on do |c|
2338
2439
  c.arg_name 'FORMAT'
2339
2440
  c.flag %i[o output]
2340
2441
 
2442
+ c.desc "Output using a template from configuration"
2443
+ c.arg_name 'TEMPLATE_KEY'
2444
+ c.flag [:config_template], type: TemplateName, default_value: 'default'
2445
+
2446
+ c.desc 'Override output format with a template string containing %placeholders'
2447
+ c.arg_name 'TEMPLATE_STRING'
2448
+ c.flag [:template]
2449
+
2341
2450
  c.action do |_global_options, options, args|
2342
2451
  raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
2343
2452
 
@@ -2359,7 +2468,7 @@ command :on do |c|
2359
2468
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
2360
2469
 
2361
2470
  Doing::Pager.page wwid.list_date([start, finish], options[:section], options[:times], options[:output],
2362
- { duration: options[:duration], totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
2471
+ { template: options[:template], config_template: options[:config_template], duration: options[:duration], totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
2363
2472
  end
2364
2473
  end
2365
2474
 
@@ -2395,6 +2504,14 @@ command :since do |c|
2395
2504
  c.arg_name 'FORMAT'
2396
2505
  c.flag %i[o output]
2397
2506
 
2507
+ c.desc "Output using a template from configuration"
2508
+ c.arg_name 'TEMPLATE_KEY'
2509
+ c.flag [:config_template], type: TemplateName, default_value: 'default'
2510
+
2511
+ c.desc 'Override output format with a template string containing %placeholders'
2512
+ c.arg_name 'TEMPLATE_STRING'
2513
+ c.flag [:template]
2514
+
2398
2515
  c.action do |_global_options, options, args|
2399
2516
  raise DoingRuntimeError, %(Invalid output type "#{options[:output]}") if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
2400
2517
 
@@ -2416,7 +2533,7 @@ command :since do |c|
2416
2533
  options[:sort_tags] = options[:tag_sort] =~ /^n/i
2417
2534
 
2418
2535
  Doing::Pager.page wwid.list_date([start, finish], options[:section], options[:times], options[:output],
2419
- { duration: options[:duration], totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
2536
+ { template: options[:template], config_template: options[:config_template], duration: options[:duration], totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
2420
2537
  end
2421
2538
  end
2422
2539
 
@@ -2551,17 +2668,17 @@ command :view do |c|
2551
2668
  view = wwid.get_view(title)
2552
2669
 
2553
2670
  if view
2554
- page_title = view.key?('title') ? view['title'] : title.cap_first
2671
+ page_title = view['title'] || title.cap_first
2555
2672
  only_timed = if (view.key?('only_timed') && view['only_timed']) || options[:only_timed]
2556
2673
  true
2557
2674
  else
2558
2675
  false
2559
2676
  end
2560
2677
 
2561
- template = view.key?('template') ? view['template'] : nil
2562
- date_format = view.key?('date_format') ? view['date_format'] : nil
2678
+ template = view['template'] || nil
2679
+ date_format = view['date_format'] || nil
2563
2680
 
2564
- tags_color = view.key?('tags_color') ? view['tags_color'] : nil
2681
+ tags_color = view['tags_color'] || nil
2565
2682
  tag_filter = false
2566
2683
  if options[:tag]
2567
2684
  tag_filter = { 'tags' => [], 'bool' => 'OR' }
@@ -2572,7 +2689,7 @@ command :view do |c|
2572
2689
  else
2573
2690
  options[:tag].gsub(/[, ]+/, ' ').split(' ').map(&:strip)
2574
2691
  end
2575
- elsif view.key?('tags') && !(view['tags'].nil? || view['tags'].empty?)
2692
+ elsif view.key?('tags') && view['tags'].good?
2576
2693
  tag_filter = { 'tags' => [], 'bool' => 'OR' }
2577
2694
  bool = view.key?('tags_bool') && !view['tags_bool'].nil? ? view['tags_bool'].normalize_bool : :pattern
2578
2695
  tag_filter['bool'] = bool
@@ -2591,19 +2708,19 @@ command :view do |c|
2591
2708
  section = if options[:section]
2592
2709
  section
2593
2710
  else
2594
- view.key?('section') ? view['section'] : settings['current_section']
2711
+ view['section'] || settings['current_section']
2595
2712
  end
2596
- order = view.key?('order') ? view['order'].normalize_order : 'asc'
2713
+ order = view['order']&.normalize_order || 'asc'
2597
2714
 
2598
2715
  totals = if options[:totals]
2599
2716
  true
2600
2717
  else
2601
- view.key?('totals') ? view['totals'] : false
2718
+ view['totals'] || false
2602
2719
  end
2603
2720
  tag_order = if options[:tag_order]
2604
2721
  options[:tag_order].normalize_order
2605
2722
  else
2606
- view.key?('tag_order') ? view['tag_order'].normalize_order : 'asc'
2723
+ view['tag_order']&.normalize_order || 'asc'
2607
2724
  end
2608
2725
 
2609
2726
  options[:times] = true if totals
@@ -2630,7 +2747,7 @@ command :view do |c|
2630
2747
 
2631
2748
  options[:age] ||= :newest
2632
2749
 
2633
- opts = options.dup
2750
+ opts = options.clone
2634
2751
  opts[:age] = options[:age].normalize_age(:newest)
2635
2752
  opts[:count] = count
2636
2753
  opts[:format] = date_format
@@ -2676,6 +2793,14 @@ command :yesterday do |c|
2676
2793
  c.arg_name 'FORMAT'
2677
2794
  c.flag %i[o output]
2678
2795
 
2796
+ c.desc "Output using a template from configuration"
2797
+ c.arg_name 'TEMPLATE_KEY'
2798
+ c.flag [:config_template], type: TemplateName, default_value: 'today'
2799
+
2800
+ c.desc 'Override output format with a template string containing %placeholders'
2801
+ c.arg_name 'TEMPLATE_STRING'
2802
+ c.flag [:template]
2803
+
2679
2804
  c.desc 'Show time intervals on @done tasks'
2680
2805
  c.switch %i[t times], default_value: true, negatable: true
2681
2806
 
@@ -2717,16 +2842,10 @@ command :yesterday do |c|
2717
2842
  end.join(' to ').split_date_range
2718
2843
  end
2719
2844
 
2720
- opt = {
2721
- after: options[:after],
2722
- before: options[:before],
2723
- duration: options[:duration],
2724
- from: options[:from],
2725
- sort_tags: options[:sort_tags],
2726
- tag_order: options[:tag_order].normalize_order,
2727
- totals: options[:totals],
2728
- order: settings.dig('templates', 'today', 'order')
2729
- }
2845
+ opt = options.clone
2846
+ opt[:tag_order] = options[:tag_order].normalize_order
2847
+ opt[:order] = settings.dig('templates', options[:config_template], 'order')
2848
+
2730
2849
  Doing::Pager.page wwid.yesterday(options[:section], options[:times], options[:output], opt).chomp
2731
2850
  end
2732
2851
  end
@@ -3015,7 +3134,7 @@ command :open do |c|
3015
3134
  end
3016
3135
 
3017
3136
  c.action do |_global_options, options, _args|
3018
- params = options.dup
3137
+ params = options.clone
3019
3138
  params.delete_if do |k, v|
3020
3139
  k.instance_of?(String) || v.nil? || v == false
3021
3140
  end
@@ -3167,7 +3286,7 @@ command %i[archive move] do |c|
3167
3286
  search.sub!(/^'?/, "'") if options[:exact]
3168
3287
  end
3169
3288
 
3170
- opts = options.dup
3289
+ opts = options.clone
3171
3290
  opts[:search] = search
3172
3291
  opts[:bool] = options[:bool].normalize_bool
3173
3292
  opts[:destination] = options[:to]
@@ -3587,17 +3706,44 @@ end
3587
3706
 
3588
3707
  # @@changelog @@changes
3589
3708
  desc 'List recent changes in Doing'
3590
- long_desc 'Display a formatted list of changes in recent versions, latest at the top'
3591
- command %i[changelog changes] do |c|
3709
+ long_desc %(Display a formatted list of changes in recent versions.
3710
+
3711
+ Without flags, displays only the most recent version.
3712
+ Use --lookup or --all for history.)
3713
+ command %i[changes changelog] do |c|
3714
+ c.desc 'Display all versions'
3715
+ c.switch %i[a all], default_value: false, negatable: false
3716
+
3717
+ c.desc %(Look up a specific version. Specify versions as "MAJ.MIN.PATCH", MIN
3718
+ and PATCH are optional. Use > or < to see all changes since or prior
3719
+ to a version.)
3720
+ c.arg_name 'VERSION'
3721
+ c.flag %i[l lookup], must_match: /^(?:(?:(?:[<>=]|p(?:rior)|b(?:efore)|o(?:lder)|s(?:ince)|a(?:fter)|n(?:ewer))? *[\d.*?]+ *)+|(?:[\d.]+ *-+ *[\d.]+))$/
3722
+
3723
+ c.desc %(Show changelogs matching search terms (uses pattern-based searching).
3724
+ Add slashes to search with regular expressions, e.g. `--search "/output.*flag/"`)
3725
+ c.flag %i[s search]
3726
+
3727
+ c.example 'doing changes', desc: 'View changes in the current version'
3728
+ c.example 'doing changes --all', desc: 'See the entire changelog'
3729
+ c.example 'doing changes --lookup 2.0.21', desc: 'See changes from version 2.0.21'
3730
+ c.example 'doing changes --lookup "> 2.1"', desc: 'See all changes since 2.1.0'
3731
+ c.example 'doing changes --search "tags +bool"', desc: 'See all changes containing "tags" and "bool"'
3732
+ c.example 'doing changes -l "> 2.1" -s "pattern"', desc: 'Lookup and search can be combined'
3733
+
3734
+
3592
3735
  c.action do |_global_options, options, args|
3593
- changelog = File.expand_path(File.join(File.dirname(__FILE__), '..', 'CHANGELOG.md'))
3594
- if File.exist?(changelog)
3595
- parsed = TTY::Markdown.parse(IO.read(changelog), width: 80, symbols: {override: {bullet: "•"}})
3596
- Doing::Pager.paginate = true
3597
- Doing::Pager.page parsed
3598
- else
3599
- raise "Error locating changelog"
3600
- end
3736
+ cl = Doing::Changes.new(lookup: options[:lookup], search: options[:search])
3737
+
3738
+ content = if options[:all] || options[:search] || options[:lookup]
3739
+ cl.to_s
3740
+ else
3741
+ cl.latest
3742
+ end
3743
+
3744
+ parsed = TTY::Markdown.parse(content, width: 80, symbols: {override: {bullet: "•"}})
3745
+ Doing::Pager.paginate = true
3746
+ Doing::Pager.page parsed
3601
3747
  end
3602
3748
  end
3603
3749
 
@@ -3654,7 +3800,7 @@ pre do |global, _command, _options, _args|
3654
3800
  Doing::Pager.paginate = global[:pager]
3655
3801
 
3656
3802
  $stdout.puts "doing v#{Doing::VERSION}" if global[:version]
3657
- unless STDOUT.isatty
3803
+ unless $stdout.isatty
3658
3804
  Doing::Color.coloring = global[:pager] ? global[:color] : false
3659
3805
  else
3660
3806
  Doing::Color.coloring = global[:color]
@@ -3704,7 +3850,12 @@ around do |global, command, options, arguments, code|
3704
3850
  Doing::Prompt.force_answer = false
3705
3851
  Doing.config.force_answer = false
3706
3852
  else
3707
- Doing::Prompt.default_answer = global[:default]
3853
+ Doing::Prompt.default_answer = if $stdout.isatty
3854
+ global[:default]
3855
+ else
3856
+ true
3857
+ end
3858
+
3708
3859
  Doing.config.force_answer = global[:default] ? true : false
3709
3860
  end
3710
3861