doing 2.1.21 → 2.1.25

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