doing 2.1.39 → 2.1.42

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 (229) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/CHANGELOG.md +67 -0
  4. data/Gemfile.lock +1 -1
  5. data/README.md +1 -1
  6. data/Rakefile +4 -4
  7. data/bin/commands/again.rb +1 -3
  8. data/bin/commands/changes.rb +50 -34
  9. data/bin/commands/commands.rb +77 -52
  10. data/bin/commands/commands_accepting.rb +57 -53
  11. data/bin/commands/config.rb +45 -36
  12. data/bin/commands/done.rb +1 -18
  13. data/bin/commands/finish.rb +90 -59
  14. data/bin/commands/flag.rb +5 -1
  15. data/bin/commands/grep.rb +3 -14
  16. data/bin/commands/last.rb +2 -8
  17. data/bin/commands/meanwhile.rb +13 -6
  18. data/bin/commands/now.rb +151 -107
  19. data/bin/commands/on.rb +8 -18
  20. data/bin/commands/recent.rb +2 -8
  21. data/bin/commands/reset.rb +24 -1
  22. data/bin/commands/select.rb +1 -1
  23. data/bin/commands/show.rb +6 -17
  24. data/bin/commands/since.rb +1 -12
  25. data/bin/commands/tag_dir.rb +49 -15
  26. data/bin/commands/today.rb +2 -13
  27. data/bin/commands/undo.rb +4 -6
  28. data/bin/commands/view.rb +1 -1
  29. data/bin/commands/yesterday.rb +2 -13
  30. data/bin/doing +15 -8
  31. data/{Dockerfile → docker/Dockerfile} +3 -1
  32. data/{Dockerfile-2.6 → docker/Dockerfile-2.6} +2 -2
  33. data/{Dockerfile-2.7 → docker/Dockerfile-2.7} +2 -2
  34. data/{Dockerfile-3.0 → docker/Dockerfile-3.0} +2 -2
  35. data/{bash_profile → docker/bash_profile} +0 -0
  36. data/{inputrc → docker/inputrc} +0 -0
  37. data/docs/doc/Array.html +85 -2
  38. data/docs/doc/BooleanTermParser/Clause.html +1 -1
  39. data/docs/doc/BooleanTermParser/Operator.html +1 -1
  40. data/docs/doc/BooleanTermParser/Query.html +1 -1
  41. data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
  42. data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
  43. data/docs/doc/BooleanTermParser.html +1 -1
  44. data/docs/doc/Doing/ArrayNestedHash.html +198 -0
  45. data/docs/doc/Doing/ArrayTags.html +424 -0
  46. data/docs/doc/Doing/CSVExport.html +266 -0
  47. data/docs/doc/Doing/CalendarImport.html +232 -0
  48. data/docs/doc/Doing/Change.html +617 -0
  49. data/docs/doc/Doing/Changes.html +468 -0
  50. data/docs/doc/Doing/ChronifyArray.html +347 -0
  51. data/docs/doc/Doing/ChronifyNumeric.html +271 -0
  52. data/docs/doc/Doing/ChronifyString.html +682 -0
  53. data/docs/doc/Doing/Color.html +167 -21
  54. data/docs/doc/Doing/Completion/BashCompletions.html +445 -0
  55. data/docs/doc/Doing/Completion/FishCompletions.html +445 -0
  56. data/docs/doc/Doing/Completion/StringUtils.html +229 -0
  57. data/docs/doc/Doing/Completion/ZshCompletions.html +445 -0
  58. data/docs/doc/Doing/Completion.html +17 -3
  59. data/docs/doc/Doing/Configuration.html +3 -2
  60. data/docs/doc/Doing/DayOneRenderer.html +383 -0
  61. data/docs/doc/Doing/DayoneExport.html +290 -0
  62. data/docs/doc/Doing/DoingImport.html +391 -0
  63. data/docs/doc/Doing/Entry.html +381 -0
  64. data/docs/doc/Doing/Errors/DoingNoTraceError.html +7 -3
  65. data/docs/doc/Doing/Errors/DoingRuntimeError.html +7 -3
  66. data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
  67. data/docs/doc/Doing/Errors/EmptyInput.html +10 -2
  68. data/docs/doc/Doing/Errors/HistoryLimitError.html +194 -0
  69. data/docs/doc/Doing/Errors/InvalidPlugin.html +194 -0
  70. data/docs/doc/Doing/Errors/MissingBackupFile.html +194 -0
  71. data/docs/doc/Doing/Errors/NoResults.html +10 -2
  72. data/docs/doc/Doing/Errors/PluginException.html +1 -1
  73. data/docs/doc/Doing/Errors/UserCancelled.html +10 -2
  74. data/docs/doc/Doing/Errors/WrongCommand.html +10 -2
  75. data/docs/doc/Doing/Errors.html +9 -9
  76. data/docs/doc/Doing/HTMLExport.html +256 -0
  77. data/docs/doc/Doing/Hooks.html +1 -1
  78. data/docs/doc/Doing/Item.html +179 -1660
  79. data/docs/doc/Doing/ItemDates.html +564 -0
  80. data/docs/doc/Doing/ItemQuery.html +614 -0
  81. data/docs/doc/Doing/ItemState.html +387 -0
  82. data/docs/doc/Doing/ItemTags.html +498 -0
  83. data/docs/doc/Doing/Items.html +581 -15
  84. data/docs/doc/Doing/JSONExport.html +222 -0
  85. data/docs/doc/Doing/Logger.html +1 -1
  86. data/docs/doc/Doing/MarkdownExport.html +266 -0
  87. data/docs/doc/Doing/MarkdownRenderer.html +383 -0
  88. data/docs/doc/Doing/Note.html +18 -4
  89. data/docs/doc/Doing/Pager.html +1 -1
  90. data/docs/doc/Doing/Plugins.html +181 -76
  91. data/docs/doc/Doing/Prompt.html +32 -683
  92. data/docs/doc/Doing/PromptChoose.html +484 -0
  93. data/docs/doc/Doing/PromptFZF.html +391 -0
  94. data/docs/doc/Doing/PromptInput.html +572 -0
  95. data/docs/doc/Doing/PromptSTD.html +293 -0
  96. data/docs/doc/Doing/PromptYN.html +237 -0
  97. data/docs/doc/Doing/Section.html +58 -2
  98. data/docs/doc/Doing/StringHighlight.html +533 -0
  99. data/docs/doc/Doing/StringNormalize.html +929 -0
  100. data/docs/doc/Doing/StringQuery.html +725 -0
  101. data/docs/doc/Doing/StringTags.html +884 -0
  102. data/docs/doc/Doing/StringTransform.html +599 -0
  103. data/docs/doc/Doing/StringTruncate.html +448 -0
  104. data/docs/doc/Doing/StringURL.html +409 -0
  105. data/docs/doc/Doing/SymbolNormalize.html +341 -0
  106. data/docs/doc/Doing/TaskPaperExport.html +222 -0
  107. data/docs/doc/Doing/TemplateExport.html +249 -0
  108. data/docs/doc/Doing/TemplateString.html +102 -3
  109. data/docs/doc/Doing/TimingImport.html +285 -0
  110. data/docs/doc/Doing/Types.html +1 -1
  111. data/docs/doc/Doing/Util/Backup.html +11 -163
  112. data/docs/doc/Doing/Util.html +67 -10
  113. data/docs/doc/Doing/Version.html +523 -0
  114. data/docs/doc/Doing/WWID/WWIDUtil.html +510 -0
  115. data/docs/doc/Doing/WWID.html +476 -139
  116. data/docs/doc/Doing/WWIDDisplay.html +865 -0
  117. data/docs/doc/Doing/WWIDEditor.html +466 -0
  118. data/docs/doc/Doing/WWIDFileTools.html +359 -0
  119. data/docs/doc/Doing/WWIDFilter.html +466 -0
  120. data/docs/doc/Doing/WWIDGuess.html +299 -0
  121. data/docs/doc/Doing/WWIDInteractive.html +752 -0
  122. data/docs/doc/Doing/WWIDModify.html +1078 -0
  123. data/docs/doc/Doing/WWIDTags.html +302 -0
  124. data/docs/doc/Doing/WWIDTimers.html +359 -0
  125. data/docs/doc/Doing/WWIDUtil.html +510 -0
  126. data/docs/doc/Doing.html +9 -6
  127. data/docs/doc/FalseClass.html +1 -1
  128. data/docs/doc/GLI/Commands/Help.html +1 -1
  129. data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
  130. data/docs/doc/GLI/Commands.html +1 -1
  131. data/docs/doc/GLI.html +1 -1
  132. data/docs/doc/Hash.html +1 -1
  133. data/docs/doc/Numeric.html +23 -78
  134. data/docs/doc/Object.html +1 -1
  135. data/docs/doc/PhraseParser/Operator.html +1 -1
  136. data/docs/doc/PhraseParser/PhraseClause.html +1 -1
  137. data/docs/doc/PhraseParser/Query.html +1 -1
  138. data/docs/doc/PhraseParser/QueryParser.html +1 -1
  139. data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
  140. data/docs/doc/PhraseParser/TermClause.html +1 -1
  141. data/docs/doc/PhraseParser.html +1 -1
  142. data/docs/doc/Status.html +1 -1
  143. data/docs/doc/String.html +58 -633
  144. data/docs/doc/Symbol.html +9 -224
  145. data/docs/doc/Time.html +119 -13
  146. data/docs/doc/TrueClass.html +1 -1
  147. data/docs/doc/_index.html +348 -4
  148. data/docs/doc/class_list.html +1 -1
  149. data/docs/doc/file.README.html +2 -2
  150. data/docs/doc/index.html +2 -2
  151. data/docs/doc/method_list.html +1904 -592
  152. data/docs/doc/top-level-namespace.html +12 -4
  153. data/docs/index.md +1 -1
  154. data/doing.rdoc +67 -15
  155. data/lib/completion/_doing.zsh +6 -6
  156. data/lib/completion/doing.bash +10 -10
  157. data/lib/completion/doing.fish +10 -3
  158. data/lib/doing/add_options.rb +39 -1
  159. data/lib/doing/array/array.rb +18 -12
  160. data/lib/doing/array/cleanup.rb +31 -0
  161. data/lib/doing/array/nested_hash.rb +1 -1
  162. data/lib/doing/array/tags.rb +6 -5
  163. data/lib/doing/changelog/changelog.rb +6 -0
  164. data/lib/doing/chronify/array.rb +65 -25
  165. data/lib/doing/chronify/chronify.rb +12 -0
  166. data/lib/doing/chronify/numeric.rb +3 -2
  167. data/lib/doing/chronify/string.rb +1 -1
  168. data/lib/doing/colors.rb +77 -30
  169. data/lib/doing/completion/completion_string.rb +25 -0
  170. data/lib/doing/completion.rb +4 -5
  171. data/lib/doing/configuration.rb +7 -3
  172. data/lib/doing/errors.rb +51 -35
  173. data/lib/doing/good.rb +8 -0
  174. data/lib/doing/hooks.rb +3 -3
  175. data/lib/doing/item/dates.rb +112 -0
  176. data/lib/doing/item/item.rb +128 -0
  177. data/lib/doing/{item.rb → item/query.rb} +2 -353
  178. data/lib/doing/item/state.rb +59 -0
  179. data/lib/doing/item/tags.rb +87 -0
  180. data/lib/doing/items/filter.rb +67 -0
  181. data/lib/doing/items/items.rb +57 -0
  182. data/lib/doing/items/modify.rb +36 -0
  183. data/lib/doing/items/sections.rb +83 -0
  184. data/lib/doing/items/util.rb +74 -0
  185. data/lib/doing/normalize.rb +10 -2
  186. data/lib/doing/note.rb +1 -1
  187. data/lib/doing/pager.rb +9 -3
  188. data/lib/doing/plugin_manager.rb +33 -8
  189. data/lib/doing/plugins/export/markdown_export.rb +4 -2
  190. data/lib/doing/plugins/export/template_export.rb +4 -4
  191. data/lib/doing/plugins/import/cal_to_json.scpt +0 -0
  192. data/lib/doing/plugins/import/doing_import.rb +1 -1
  193. data/lib/doing/prompt/choose.rb +118 -0
  194. data/lib/doing/prompt/fzf.rb +84 -0
  195. data/lib/doing/prompt/input.rb +129 -0
  196. data/lib/doing/prompt/prompt.rb +41 -0
  197. data/lib/doing/prompt/std.rb +32 -0
  198. data/lib/doing/prompt/yn.rb +64 -0
  199. data/lib/doing/section.rb +4 -0
  200. data/lib/doing/string/highlight.rb +1 -1
  201. data/lib/doing/string/query.rb +1 -1
  202. data/lib/doing/string/string.rb +18 -7
  203. data/lib/doing/string/tags.rb +14 -3
  204. data/lib/doing/string/transform.rb +7 -1
  205. data/lib/doing/string/truncate.rb +1 -1
  206. data/lib/doing/string/url.rb +1 -1
  207. data/lib/doing/time.rb +19 -1
  208. data/lib/doing/util.rb +12 -6
  209. data/lib/doing/util_backup.rb +62 -57
  210. data/lib/doing/version.rb +1 -1
  211. data/lib/doing/wwid/display.rb +396 -0
  212. data/lib/doing/wwid/editor.rb +214 -0
  213. data/lib/doing/wwid/filetools.rb +183 -0
  214. data/lib/doing/wwid/filter.rb +226 -0
  215. data/lib/doing/wwid/guess.rb +85 -0
  216. data/lib/doing/wwid/interactive.rb +377 -0
  217. data/lib/doing/wwid/modify.rb +617 -0
  218. data/lib/doing/wwid/tags.rb +51 -0
  219. data/lib/doing/wwid/timers.rb +342 -0
  220. data/lib/doing/wwid/wwid.rb +121 -0
  221. data/lib/doing/wwid/wwidutil.rb +101 -0
  222. data/lib/doing.rb +7 -7
  223. data/lib/helpers/threaded_tests.rb +1 -0
  224. metadata +94 -14
  225. data/lib/doing/changelog.rb +0 -6
  226. data/lib/doing/completion/string.rb +0 -17
  227. data/lib/doing/items.rb +0 -196
  228. data/lib/doing/prompt.rb +0 -330
  229. data/lib/doing/wwid.rb +0 -2398
data/lib/doing/prompt.rb DELETED
@@ -1,330 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Doing
4
- # Terminal Prompt methods
5
- module Prompt
6
- class << self
7
- attr_writer :force_answer, :default_answer
8
-
9
- include Color
10
-
11
- ##
12
- ## Clear the terminal screen
13
- ##
14
- def clear_screen(msg = nil)
15
- puts "\e[H\e[2J" if STDOUT.tty?
16
- puts msg if msg.good?
17
- end
18
-
19
- def force_answer
20
- @force_answer ||= nil
21
- end
22
-
23
- def default_answer
24
- @default_answer ||= false
25
- end
26
-
27
- def enter_text(prompt, default_response: '')
28
- $stdin.reopen('/dev/tty')
29
- return default_response if @default_answer
30
-
31
- print "#{yellow(prompt).sub(/:?$/, ':')} #{reset}"
32
- $stdin.gets.strip
33
- end
34
-
35
- def read_line(prompt: 'Enter text', completions: [], default_response: '')
36
- $stdin.reopen('/dev/tty')
37
- return default_response if @default_answer
38
-
39
- unless completions.empty?
40
- completions.sort!
41
- comp = proc { |s| completions.grep(/^#{Regexp.escape(s)}/) }
42
- Readline.completion_append_character = ' '
43
- Readline.completion_proc = comp
44
- end
45
-
46
- begin
47
- Readline.readline("#{yellow(prompt).sub(/:?$/, ':')} #{reset}", true).strip
48
- rescue Interrupt
49
- raise UserCancelled
50
- end
51
- end
52
-
53
- def read_lines(prompt: 'Enter text', completions: [], default_response: '')
54
- $stdin.reopen('/dev/tty')
55
- return default_response if @default_answer
56
-
57
- completions.sort!
58
- comp = proc { |s| completions.grep(/^#{Regexp.escape(s)}/) }
59
- Readline.completion_append_character = ' '
60
- Readline.completion_proc = comp
61
- puts format(['%<promptcolor>s%<prompt>s %<textcolor>sEnter a blank line',
62
- '(%<keycolor>sreturn twice%<textcolor>s)',
63
- 'to end editing and save,',
64
- '%<keycolor>sCTRL-C%<textcolor>s to cancel%<reset>s'].join(' '),
65
- { promptcolor: boldgreen, prompt: prompt.sub(/:?$/, ':'),
66
- textcolor: yellow, keycolor: boldwhite, reset: reset })
67
-
68
- res = []
69
-
70
- begin
71
- while (line = Readline.readline('> ', true))
72
- break if line.strip.empty?
73
-
74
- res << line.chomp
75
- end
76
- rescue Interrupt
77
- raise UserCancelled
78
- end
79
-
80
- res.join("\n").strip
81
- end
82
-
83
- def request_lines(prompt: 'Enter text', default_response: '')
84
- $stdin.reopen('/dev/tty')
85
- return default_response if @default_answer
86
-
87
- ask_note = []
88
- reader = TTY::Reader.new(interrupt: -> { raise Errors::UserCancelled }, track_history: false)
89
- puts "#{boldgreen(prompt.sub(/:?$/, ':'))} #{yellow('Hit return for a new line, ')}#{boldwhite('enter a blank line (')}#{boldyellow('return twice')}#{boldwhite(') to end editing')}"
90
- loop do
91
- res = reader.read_line(green('> '))
92
- break if res.strip.empty?
93
-
94
- ask_note.push(res)
95
- end
96
- ask_note.join("\n").strip
97
- end
98
-
99
- ##
100
- ## Ask a yes or no question in the terminal
101
- ##
102
- ## @param question [String] The question
103
- ## to ask
104
- ## @param default_response (Bool) default
105
- ## response if no input
106
- ##
107
- ## @return (Bool) yes or no
108
- ##
109
- def yn(question, default_response: false)
110
- return @force_answer == :yes ? true : false unless @force_answer.nil?
111
-
112
- $stdin.reopen('/dev/tty')
113
-
114
- default = if default_response.is_a?(String)
115
- default_response =~ /y/i ? true : false
116
- else
117
- default_response
118
- end
119
-
120
- # if global --default is set, answer default
121
- return default if @default_answer
122
-
123
- # if this isn't an interactive shell, answer default
124
- return default unless $stdout.isatty
125
-
126
- # clear the buffer
127
- if ARGV&.length
128
- ARGV.length.times do
129
- ARGV.shift
130
- end
131
- end
132
- system 'stty cbreak'
133
-
134
- cw = white
135
- cbw = boldwhite
136
- cbg = boldgreen
137
- cd = Color.default
138
-
139
- options = unless default.nil?
140
- "#{cw}[#{default ? "#{cbg}Y#{cw}/#{cbw}n" : "#{cbw}y#{cw}/#{cbg}N"}#{cw}]#{cd}"
141
- else
142
- "#{cw}[#{cbw}y#{cw}/#{cbw}n#{cw}]#{cd}"
143
- end
144
- $stdout.syswrite "#{cbw}#{question.sub(/\?$/, '')} #{options}#{cbw}?#{cd} "
145
- res = $stdin.sysread 1
146
- puts
147
- system 'stty cooked'
148
-
149
- res.chomp!
150
- res.downcase!
151
-
152
- return default if res.empty?
153
-
154
- res =~ /y/i ? true : false
155
- end
156
-
157
- def fzf
158
- @fzf ||= install_fzf
159
- end
160
-
161
- def uninstall_fzf
162
- fzf_bin = File.join(File.dirname(__FILE__), '../helpers/fzf/bin/fzf')
163
- FileUtils.rm_f(fzf_bin) if File.exist?(fzf_bin)
164
- Doing.logger.warn('fzf:', "removed #{fzf_bin}")
165
- end
166
-
167
- def which_fzf
168
- fzf_dir = File.join(File.dirname(__FILE__), '../helpers/fzf')
169
- fzf_bin = File.join(fzf_dir, 'bin/fzf')
170
- return fzf_bin if File.exist?(fzf_bin)
171
-
172
- Doing.logger.debug('fzf:', 'Using user-installed fzf')
173
- TTY::Which.which('fzf')
174
- end
175
-
176
- def silence_std(file = '/dev/null')
177
- $stdout = File.new(file, 'w')
178
- $stderr = File.new(file, 'w')
179
- end
180
-
181
- def restore_std
182
- $stdout = STDOUT
183
- $stderr = STDERR
184
- end
185
-
186
- def install_fzf(force: false)
187
- if force
188
- uninstall_fzf
189
- elsif which_fzf
190
- return which_fzf
191
- end
192
-
193
- fzf_dir = File.join(File.dirname(__FILE__), '../helpers/fzf')
194
- FileUtils.mkdir_p(fzf_dir) unless File.directory?(fzf_dir)
195
- fzf_bin = File.join(fzf_dir, 'bin/fzf')
196
- return fzf_bin if File.exist?(fzf_bin)
197
-
198
- prev_level = Doing.logger.level
199
- Doing.logger.adjust_verbosity({ log_level: :info })
200
- Doing.logger.log_now(:warn, 'fzf:', 'Compiling and installing fzf -- this will only happen once')
201
- Doing.logger.log_now(:warn, 'fzf:', 'fzf is copyright Junegunn Choi, MIT License <https://github.com/junegunn/fzf/blob/master/LICENSE>')
202
-
203
- silence_std
204
- `'#{fzf_dir}/install' --bin --no-key-bindings --no-completion --no-update-rc --no-bash --no-zsh --no-fish &> /dev/null`
205
- unless File.exist?(fzf_bin)
206
- restore_std
207
- Doing.logger.log_now(:warn, 'Error installing, trying again as root')
208
- silence_std
209
- `sudo '#{fzf_dir}/install' --bin --no-key-bindings --no-completion --no-update-rc --no-bash --no-zsh --no-fish &> /dev/null`
210
- end
211
- restore_std
212
- unless File.exist?(fzf_bin)
213
- Doing.logger.error('fzf:', 'unable to install fzf. You can install manually and Doing will use the system version.')
214
- Doing.logger.error('fzf:', 'see https://github.com/junegunn/fzf#installation')
215
- raise RuntimeError.new('Error installing fzf, please report at https://github.com/ttscoff/doing/issues')
216
- end
217
-
218
- Doing.logger.info('fzf:', "installed to #{fzf}")
219
- Doing.logger.adjust_verbosity({ log_level: prev_level })
220
- fzf_bin
221
- end
222
-
223
- ##
224
- ## Generate a menu of options and allow user selection
225
- ##
226
- ## @return [String] The selected option
227
- ##
228
- def choose_from(options, prompt: 'Make a selection: ', multiple: false, sorted: true, fzf_args: [])
229
- return nil unless $stdout.isatty
230
-
231
- # fzf_args << '-1' # User is expecting a menu, and even if only one it seves as confirmation
232
- default_args = []
233
- default_args << %(--prompt="#{prompt}")
234
- default_args << "--height=#{options.count + 2}"
235
- default_args << '--info=inline'
236
- default_args << '--multi' if multiple
237
- header = "esc: cancel,#{multiple ? ' tab: multi-select, ctrl-a: select all,' : ''} return: confirm"
238
- default_args << %(--header="#{header}")
239
- default_args.concat(fzf_args)
240
- options.sort! if sorted
241
-
242
- res = `echo #{Shellwords.escape(options.join("\n"))}|#{fzf} #{default_args.join(' ')}`
243
- return false if res.strip.size.zero?
244
-
245
- res
246
- end
247
-
248
- ##
249
- ## Create an interactive menu to select from a set of Items
250
- ##
251
- ## @param items [Array] list of items
252
- ## @param opt Additional options
253
- ##
254
- ## @option opt [Boolean] :include_section Include section name for each item in menu
255
- ## @option opt [String] :header A custom header string
256
- ## @option opt [String] :prompt A custom prompt string
257
- ## @option opt [String] :query Initial query
258
- ## @option opt [Boolean] :show_if_single Show menu even if there's only one option
259
- ## @option opt [Boolean] :menu Show menu
260
- ## @option opt [Boolean] :sort Sort options
261
- ## @option opt [Boolean] :multiple Allow multiple selections
262
- ## @option opt [Symbol] :case (:sensitive, :ignore, :smart)
263
- ##
264
- def choose_from_items(items, **opt)
265
- return items unless $stdout.isatty
266
-
267
- return nil unless items.count.positive?
268
-
269
- case_sensitive = opt.fetch(:case, :smart).normalize_case
270
- header = opt.fetch(:header, 'Arrows: navigate, tab: mark for selection, ctrl-a: select all, enter: commit')
271
- prompt = opt.fetch(:prompt, 'Select entries to act on > ')
272
- query = opt.fetch(:query) { opt.fetch(:search, '') }
273
- include_section = opt.fetch(:include_section, false)
274
-
275
- pad = items.length.to_s.length
276
- options = items.map.with_index do |item, i|
277
- out = [
278
- format("%#{pad}d", i),
279
- ') ',
280
- format('%16s', item.date.strftime('%Y-%m-%d %H:%M')),
281
- ' | ',
282
- item.title
283
- ]
284
- if include_section
285
- out.concat([
286
- ' (',
287
- item.section,
288
- ') '
289
- ])
290
- end
291
- out.join('')
292
- end
293
-
294
- fzf_args = [
295
- %(--header="#{header}"),
296
- %(--prompt="#{prompt.sub(/ *$/, ' ')}"),
297
- opt.fetch(:multiple) ? '--multi' : '--no-multi',
298
- '-0',
299
- '--bind ctrl-a:select-all',
300
- %(-q "#{query}"),
301
- '--info=inline'
302
- ]
303
- fzf_args.push('-1') unless opt.fetch(:show_if_single, false)
304
- fzf_args << case case_sensitive
305
- when :sensitive
306
- '+i'
307
- when :ignore
308
- '-i'
309
- end
310
- fzf_args << '-e' if opt.fetch(:exact, false)
311
-
312
-
313
- unless opt.fetch(:menu)
314
- raise InvalidArgument, "Can't skip menu when no query is provided" unless query && !query.empty?
315
-
316
- fzf_args.concat([%(--filter="#{query}"), opt.fetch(:sort) ? '' : '--no-sort'])
317
- end
318
-
319
- res = `echo #{Shellwords.escape(options.join("\n"))}|#{fzf} #{fzf_args.join(' ')}`
320
- selected = []
321
- res.split(/\n/).each do |item|
322
- idx = item.match(/^ *(\d+)\)/)[1].to_i
323
- selected.push(items[idx])
324
- end
325
-
326
- opt.fetch(:multiple) ? selected : selected[0]
327
- end
328
- end
329
- end
330
- end