doing 2.1.40 → 2.1.41
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -1
- data/CHANGELOG.md +22 -0
- data/Gemfile.lock +1 -1
- data/Rakefile +4 -4
- data/bin/commands/changes.rb +1 -1
- data/bin/commands/tag_dir.rb +49 -15
- data/{Dockerfile → docker/Dockerfile} +3 -1
- data/{Dockerfile-2.6 → docker/Dockerfile-2.6} +2 -2
- data/{Dockerfile-2.7 → docker/Dockerfile-2.7} +2 -2
- data/{Dockerfile-3.0 → docker/Dockerfile-3.0} +2 -2
- data/{bash_profile → docker/bash_profile} +0 -0
- data/{inputrc → docker/inputrc} +0 -0
- data/docs/doc/Array.html +84 -2
- data/docs/doc/BooleanTermParser/Clause.html +1 -1
- data/docs/doc/BooleanTermParser/Operator.html +1 -1
- data/docs/doc/BooleanTermParser/Query.html +1 -1
- data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
- data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
- data/docs/doc/BooleanTermParser.html +1 -1
- data/docs/doc/Doing/ArrayNestedHash.html +198 -0
- data/docs/doc/Doing/ArrayTags.html +424 -0
- data/docs/doc/Doing/CSVExport.html +266 -0
- data/docs/doc/Doing/CalendarImport.html +232 -0
- data/docs/doc/Doing/Change.html +617 -0
- data/docs/doc/Doing/Changes.html +468 -0
- data/docs/doc/Doing/ChronifyArray.html +347 -0
- data/docs/doc/Doing/ChronifyNumeric.html +271 -0
- data/docs/doc/Doing/ChronifyString.html +682 -0
- data/docs/doc/Doing/Color.html +2 -2
- data/docs/doc/Doing/Completion/BashCompletions.html +445 -0
- data/docs/doc/Doing/Completion/FishCompletions.html +445 -0
- data/docs/doc/Doing/Completion/StringUtils.html +229 -0
- data/docs/doc/Doing/Completion/ZshCompletions.html +445 -0
- data/docs/doc/Doing/Completion.html +17 -3
- data/docs/doc/Doing/Configuration.html +1 -1
- data/docs/doc/Doing/DayOneRenderer.html +383 -0
- data/docs/doc/Doing/DayoneExport.html +290 -0
- data/docs/doc/Doing/DoingImport.html +391 -0
- data/docs/doc/Doing/Entry.html +381 -0
- data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
- data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
- data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
- data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
- data/docs/doc/Doing/Errors/HistoryLimitError.html +1 -1
- data/docs/doc/Doing/Errors/InvalidPlugin.html +1 -1
- data/docs/doc/Doing/Errors/MissingBackupFile.html +1 -1
- data/docs/doc/Doing/Errors/NoResults.html +1 -1
- data/docs/doc/Doing/Errors/PluginException.html +1 -1
- data/docs/doc/Doing/Errors/UserCancelled.html +1 -1
- data/docs/doc/Doing/Errors/WrongCommand.html +1 -1
- data/docs/doc/Doing/Errors.html +1 -1
- data/docs/doc/Doing/HTMLExport.html +256 -0
- data/docs/doc/Doing/Hooks.html +1 -1
- data/docs/doc/Doing/Item.html +47 -3
- data/docs/doc/Doing/ItemDates.html +564 -0
- data/docs/doc/Doing/ItemQuery.html +614 -0
- data/docs/doc/Doing/ItemState.html +387 -0
- data/docs/doc/Doing/ItemTags.html +498 -0
- data/docs/doc/Doing/Items.html +460 -11
- data/docs/doc/Doing/JSONExport.html +222 -0
- data/docs/doc/Doing/Logger.html +1 -1
- data/docs/doc/Doing/MarkdownExport.html +266 -0
- data/docs/doc/Doing/MarkdownRenderer.html +383 -0
- data/docs/doc/Doing/Note.html +16 -3
- data/docs/doc/Doing/Pager.html +1 -1
- data/docs/doc/Doing/Plugins.html +1 -1
- data/docs/doc/Doing/Prompt.html +31 -682
- data/docs/doc/Doing/PromptChoose.html +484 -0
- data/docs/doc/Doing/PromptFZF.html +391 -0
- data/docs/doc/Doing/PromptInput.html +572 -0
- data/docs/doc/Doing/PromptSTD.html +293 -0
- data/docs/doc/Doing/PromptYN.html +237 -0
- data/docs/doc/Doing/Section.html +58 -2
- data/docs/doc/Doing/StringHighlight.html +533 -0
- data/docs/doc/Doing/StringNormalize.html +929 -0
- data/docs/doc/Doing/StringQuery.html +725 -0
- data/docs/doc/Doing/StringTags.html +884 -0
- data/docs/doc/Doing/StringTransform.html +565 -0
- data/docs/doc/Doing/StringTruncate.html +448 -0
- data/docs/doc/Doing/StringURL.html +409 -0
- data/docs/doc/Doing/SymbolNormalize.html +341 -0
- data/docs/doc/Doing/TaskPaperExport.html +222 -0
- data/docs/doc/Doing/TemplateExport.html +249 -0
- data/docs/doc/Doing/TemplateString.html +101 -2
- data/docs/doc/Doing/TimingImport.html +285 -0
- data/docs/doc/Doing/Types.html +1 -1
- data/docs/doc/Doing/Util/Backup.html +9 -7
- data/docs/doc/Doing/Util.html +2 -2
- data/docs/doc/Doing/Version.html +523 -0
- data/docs/doc/Doing/WWID/WWIDUtil.html +510 -0
- data/docs/doc/Doing/WWID.html +4377 -217
- data/docs/doc/Doing/WWIDDisplay.html +865 -0
- data/docs/doc/Doing/WWIDEditor.html +466 -0
- data/docs/doc/Doing/WWIDFileTools.html +359 -0
- data/docs/doc/Doing/WWIDFilter.html +466 -0
- data/docs/doc/Doing/WWIDGuess.html +299 -0
- data/docs/doc/Doing/WWIDInteractive.html +752 -0
- data/docs/doc/Doing/WWIDModify.html +1078 -0
- data/docs/doc/Doing/WWIDTags.html +302 -0
- data/docs/doc/Doing/WWIDTimers.html +359 -0
- data/docs/doc/Doing/WWIDUtil.html +510 -0
- data/docs/doc/Doing.html +9 -6
- data/docs/doc/FalseClass.html +1 -1
- data/docs/doc/GLI/Commands/Help.html +1 -1
- data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
- data/docs/doc/GLI/Commands.html +1 -1
- data/docs/doc/GLI.html +1 -1
- data/docs/doc/Hash.html +1 -1
- data/docs/doc/Numeric.html +23 -78
- data/docs/doc/Object.html +1 -1
- data/docs/doc/PhraseParser/Operator.html +1 -1
- data/docs/doc/PhraseParser/PhraseClause.html +1 -1
- data/docs/doc/PhraseParser/Query.html +1 -1
- data/docs/doc/PhraseParser/QueryParser.html +1 -1
- data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
- data/docs/doc/PhraseParser/TermClause.html +1 -1
- data/docs/doc/PhraseParser.html +1 -1
- data/docs/doc/Status.html +1 -1
- data/docs/doc/String.html +58 -633
- data/docs/doc/Symbol.html +9 -224
- data/docs/doc/Time.html +119 -13
- data/docs/doc/TrueClass.html +1 -1
- data/docs/doc/_index.html +324 -8
- data/docs/doc/class_list.html +1 -1
- data/docs/doc/file.README.html +1 -1
- data/docs/doc/index.html +1 -1
- data/docs/doc/method_list.html +2326 -542
- data/docs/doc/top-level-namespace.html +2 -2
- data/doing.rdoc +13 -3
- data/lib/completion/_doing.zsh +1 -1
- data/lib/completion/doing.bash +2 -2
- data/lib/completion/doing.fish +3 -1
- data/lib/doing/array/array.rb +16 -12
- data/lib/doing/array/nested_hash.rb +1 -1
- data/lib/doing/array/tags.rb +6 -5
- data/lib/doing/changelog/changelog.rb +6 -0
- data/lib/doing/chronify/array.rb +1 -3
- data/lib/doing/chronify/chronify.rb +12 -0
- data/lib/doing/chronify/numeric.rb +3 -2
- data/lib/doing/chronify/string.rb +1 -1
- data/lib/doing/completion/completion_string.rb +25 -0
- data/lib/doing/completion.rb +1 -1
- data/lib/doing/good.rb +8 -0
- data/lib/doing/item/dates.rb +1 -1
- data/lib/doing/{item.rb → item/item.rb} +10 -5
- data/lib/doing/item/query.rb +1 -1
- data/lib/doing/item/state.rb +1 -1
- data/lib/doing/item/tags.rb +1 -1
- data/lib/doing/items/filter.rb +67 -0
- data/lib/doing/items/items.rb +57 -0
- data/lib/doing/items/modify.rb +36 -0
- data/lib/doing/items/sections.rb +83 -0
- data/lib/doing/items/util.rb +74 -0
- data/lib/doing/normalize.rb +10 -2
- data/lib/doing/plugins/export/markdown_export.rb +4 -2
- data/lib/doing/plugins/import/doing_import.rb +1 -1
- data/lib/doing/prompt/choose.rb +118 -0
- data/lib/doing/prompt/fzf.rb +84 -0
- data/lib/doing/prompt/input.rb +129 -0
- data/lib/doing/prompt/prompt.rb +41 -0
- data/lib/doing/prompt/std.rb +32 -0
- data/lib/doing/prompt/yn.rb +64 -0
- data/lib/doing/section.rb +4 -0
- data/lib/doing/string/highlight.rb +1 -1
- data/lib/doing/string/query.rb +1 -1
- data/lib/doing/string/string.rb +18 -7
- data/lib/doing/string/tags.rb +14 -3
- data/lib/doing/string/transform.rb +1 -1
- data/lib/doing/string/truncate.rb +1 -1
- data/lib/doing/string/url.rb +1 -1
- data/lib/doing/time.rb +19 -1
- data/lib/doing/util_backup.rb +2 -2
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid/display.rb +357 -360
- data/lib/doing/wwid/editor.rb +173 -176
- data/lib/doing/wwid/filetools.rb +156 -159
- data/lib/doing/wwid/filter.rb +191 -183
- data/lib/doing/wwid/guess.rb +58 -60
- data/lib/doing/wwid/interactive.rb +332 -330
- data/lib/doing/wwid/modify.rb +509 -512
- data/lib/doing/wwid/tags.rb +38 -41
- data/lib/doing/wwid/timers.rb +293 -296
- data/lib/doing/{wwid.rb → wwid/wwid.rb} +32 -23
- data/lib/doing/wwid/wwidutil.rb +79 -82
- data/lib/doing.rb +5 -5
- data/lib/helpers/threaded_tests.rb +1 -0
- metadata +76 -14
- data/lib/doing/changelog.rb +0 -6
- data/lib/doing/completion/string.rb +0 -17
- data/lib/doing/items.rb +0 -221
- data/lib/doing/prompt.rb +0 -330
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doing
|
4
|
+
class Items < Array
|
5
|
+
# # Create a deep copy of Items
|
6
|
+
# def clone
|
7
|
+
# Marshal.load(Marshal.dump(self))
|
8
|
+
# end
|
9
|
+
|
10
|
+
def delete(item)
|
11
|
+
deleted = nil
|
12
|
+
each_with_index do |i, idx|
|
13
|
+
if i.equal?(item, match_section: true)
|
14
|
+
deleted = delete_at(idx)
|
15
|
+
break
|
16
|
+
end
|
17
|
+
end
|
18
|
+
deleted
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
## Get all tags on Items in self
|
23
|
+
##
|
24
|
+
## @return [Array] array of tags
|
25
|
+
##
|
26
|
+
def all_tags
|
27
|
+
each_with_object([]) do |entry, tags|
|
28
|
+
tags.concat(entry.tags).sort!.uniq!
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
## Return Items containing items that don't exist in
|
34
|
+
## receiver
|
35
|
+
##
|
36
|
+
## @param items [Items] Receiver
|
37
|
+
##
|
38
|
+
## @return [Hash] Hash of added and deleted items
|
39
|
+
##
|
40
|
+
def diff(items)
|
41
|
+
a = clone
|
42
|
+
b = items.clone
|
43
|
+
|
44
|
+
a.delete_if do |item|
|
45
|
+
if b.include?(item)
|
46
|
+
b.delete(item)
|
47
|
+
true
|
48
|
+
else
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
{ added: b, deleted: a }
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
## Remove duplicated entries. Duplicate entries must have matching start date, title, note, and section
|
57
|
+
##
|
58
|
+
## @return [Items] Items array with duplicate entries removed
|
59
|
+
##
|
60
|
+
def dedup(match_section: true)
|
61
|
+
unique = Items.new
|
62
|
+
each do |item|
|
63
|
+
unique.push(item) unless unique.include?(item, match_section: match_section)
|
64
|
+
end
|
65
|
+
|
66
|
+
unique
|
67
|
+
end
|
68
|
+
|
69
|
+
# @see #dedup
|
70
|
+
def dedup!(match_section: true)
|
71
|
+
replace dedup(match_section: match_section)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/doing/normalize.rb
CHANGED
@@ -4,7 +4,7 @@ module Doing
|
|
4
4
|
##
|
5
5
|
## String to symbol conversion
|
6
6
|
##
|
7
|
-
|
7
|
+
module StringNormalize
|
8
8
|
##
|
9
9
|
## Convert tag sort string to a qualified type
|
10
10
|
##
|
@@ -160,7 +160,7 @@ module Doing
|
|
160
160
|
##
|
161
161
|
## Symbol helpers
|
162
162
|
##
|
163
|
-
|
163
|
+
module SymbolNormalize
|
164
164
|
def normalize_tag_sort(default = :name)
|
165
165
|
to_s.normalize_tag_sort
|
166
166
|
end
|
@@ -186,3 +186,11 @@ module Doing
|
|
186
186
|
end
|
187
187
|
end
|
188
188
|
end
|
189
|
+
|
190
|
+
class ::String
|
191
|
+
include Doing::StringNormalize
|
192
|
+
end
|
193
|
+
|
194
|
+
class ::Symbol
|
195
|
+
include Doing::SymbolNormalize
|
196
|
+
end
|
@@ -5,6 +5,7 @@
|
|
5
5
|
# author: Brett Terpstra
|
6
6
|
# url: https://brettterpstra.com
|
7
7
|
module Doing
|
8
|
+
# @private
|
8
9
|
class MarkdownRenderer
|
9
10
|
attr_accessor :items, :page_title, :totals
|
10
11
|
|
@@ -15,12 +16,13 @@ module Doing
|
|
15
16
|
end
|
16
17
|
|
17
18
|
def get_binding
|
18
|
-
binding
|
19
|
+
binding
|
19
20
|
end
|
20
21
|
end
|
21
22
|
|
23
|
+
# Markdown Export Plugin
|
22
24
|
class MarkdownExport
|
23
|
-
include
|
25
|
+
include Util
|
24
26
|
|
25
27
|
def self.settings
|
26
28
|
{
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doing
|
4
|
+
# Methods for creating interactive menus of options and items
|
5
|
+
module PromptChoose
|
6
|
+
##
|
7
|
+
## Generate a menu of options and allow user selection
|
8
|
+
##
|
9
|
+
## @return [String] The selected option
|
10
|
+
##
|
11
|
+
## @param options [Array] The options from which to choose
|
12
|
+
## @param prompt [String] The prompt
|
13
|
+
## @param multiple [Boolean] If true, allow multiple selections
|
14
|
+
## @param sorted [Boolean] If true, sort selections alphanumerically
|
15
|
+
## @param fzf_args [Array] Additional fzf arguments
|
16
|
+
##
|
17
|
+
def choose_from(options, prompt: 'Make a selection: ', multiple: false, sorted: true, fzf_args: [])
|
18
|
+
return nil unless $stdout.isatty
|
19
|
+
|
20
|
+
# fzf_args << '-1' # User is expecting a menu, and even if only one it seves as confirmation
|
21
|
+
default_args = []
|
22
|
+
default_args << %(--prompt="#{prompt}")
|
23
|
+
default_args << "--height=#{options.count + 2}"
|
24
|
+
default_args << '--info=inline'
|
25
|
+
default_args << '--multi' if multiple
|
26
|
+
header = "esc: cancel,#{multiple ? ' tab: multi-select, ctrl-a: select all,' : ''} return: confirm"
|
27
|
+
default_args << %(--header="#{header}")
|
28
|
+
default_args.concat(fzf_args)
|
29
|
+
options.sort! if sorted
|
30
|
+
|
31
|
+
res = `echo #{Shellwords.escape(options.join("\n"))}|#{fzf} #{default_args.join(' ')}`
|
32
|
+
return false if res.strip.size.zero?
|
33
|
+
|
34
|
+
res
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
## Create an interactive menu to select from a set of Items
|
39
|
+
##
|
40
|
+
## @param items [Array] list of items
|
41
|
+
## @param opt Additional options
|
42
|
+
##
|
43
|
+
## @option opt [Boolean] :include_section Include section name for each item in menu
|
44
|
+
## @option opt [String] :header A custom header string
|
45
|
+
## @option opt [String] :prompt A custom prompt string
|
46
|
+
## @option opt [String] :query Initial query
|
47
|
+
## @option opt [Boolean] :show_if_single Show menu even if there's only one option
|
48
|
+
## @option opt [Boolean] :menu Show menu
|
49
|
+
## @option opt [Boolean] :sort Sort options
|
50
|
+
## @option opt [Boolean] :multiple Allow multiple selections
|
51
|
+
## @option opt [Symbol] :case (:sensitive, :ignore, :smart)
|
52
|
+
##
|
53
|
+
def choose_from_items(items, **opt)
|
54
|
+
return items unless $stdout.isatty
|
55
|
+
|
56
|
+
return nil unless items.count.positive?
|
57
|
+
|
58
|
+
case_sensitive = opt.fetch(:case, :smart).normalize_case
|
59
|
+
header = opt.fetch(:header, 'Arrows: navigate, tab: mark for selection, ctrl-a: select all, enter: commit')
|
60
|
+
prompt = opt.fetch(:prompt, 'Select entries to act on > ')
|
61
|
+
query = opt.fetch(:query) { opt.fetch(:search, '') }
|
62
|
+
include_section = opt.fetch(:include_section, false)
|
63
|
+
|
64
|
+
pad = items.length.to_s.length
|
65
|
+
options = items.map.with_index do |item, i|
|
66
|
+
out = [
|
67
|
+
format("%#{pad}d", i),
|
68
|
+
') ',
|
69
|
+
format('%16s', item.date.strftime('%Y-%m-%d %H:%M')),
|
70
|
+
' | ',
|
71
|
+
item.title
|
72
|
+
]
|
73
|
+
if include_section
|
74
|
+
out.concat([
|
75
|
+
' (',
|
76
|
+
item.section,
|
77
|
+
') '
|
78
|
+
])
|
79
|
+
end
|
80
|
+
out.join('')
|
81
|
+
end
|
82
|
+
|
83
|
+
fzf_args = [
|
84
|
+
%(--header="#{header}"),
|
85
|
+
%(--prompt="#{prompt.sub(/ *$/, ' ')}"),
|
86
|
+
opt.fetch(:multiple) ? '--multi' : '--no-multi',
|
87
|
+
'-0',
|
88
|
+
'--bind ctrl-a:select-all',
|
89
|
+
%(-q "#{query}"),
|
90
|
+
'--info=inline'
|
91
|
+
]
|
92
|
+
fzf_args.push('-1') unless opt.fetch(:show_if_single, false)
|
93
|
+
fzf_args << case case_sensitive
|
94
|
+
when :sensitive
|
95
|
+
'+i'
|
96
|
+
when :ignore
|
97
|
+
'-i'
|
98
|
+
end
|
99
|
+
fzf_args << '-e' if opt.fetch(:exact, false)
|
100
|
+
|
101
|
+
|
102
|
+
unless opt.fetch(:menu)
|
103
|
+
raise InvalidArgument, "Can't skip menu when no query is provided" unless query && !query.empty?
|
104
|
+
|
105
|
+
fzf_args.concat([%(--filter="#{query}"), opt.fetch(:sort) ? '' : '--no-sort'])
|
106
|
+
end
|
107
|
+
|
108
|
+
res = `echo #{Shellwords.escape(options.join("\n"))}|#{fzf} #{fzf_args.join(' ')}`
|
109
|
+
selected = []
|
110
|
+
res.split(/\n/).each do |item|
|
111
|
+
idx = item.match(/^ *(\d+)\)/)[1].to_i
|
112
|
+
selected.push(items[idx])
|
113
|
+
end
|
114
|
+
|
115
|
+
opt.fetch(:multiple) ? selected : selected[0]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doing
|
4
|
+
# Methods for working installing/using FuzzyFileFinder
|
5
|
+
module PromptFZF
|
6
|
+
##
|
7
|
+
## Get path to fzf binary, installing if needed
|
8
|
+
##
|
9
|
+
## @return [String] Path to fzf binary
|
10
|
+
##
|
11
|
+
def fzf
|
12
|
+
@fzf ||= install_fzf
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
## Remove fzf binary
|
17
|
+
##
|
18
|
+
def uninstall_fzf
|
19
|
+
fzf_bin = File.join(File.dirname(__FILE__), '../../helpers/fzf/bin/fzf')
|
20
|
+
FileUtils.rm_f(fzf_bin) if File.exist?(fzf_bin)
|
21
|
+
Doing.logger.warn('fzf:', "removed #{fzf_bin}")
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
## Return the path to the fzf binary
|
26
|
+
##
|
27
|
+
## @return [String] Path to fzf
|
28
|
+
##
|
29
|
+
def which_fzf
|
30
|
+
fzf_dir = File.join(File.dirname(__FILE__), '../../helpers/fzf')
|
31
|
+
fzf_bin = File.join(fzf_dir, 'bin/fzf')
|
32
|
+
return fzf_bin if File.exist?(fzf_bin)
|
33
|
+
|
34
|
+
Doing.logger.debug('fzf:', 'Using user-installed fzf')
|
35
|
+
TTY::Which.which('fzf')
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
## Install fzf on the current system. Installs to a
|
40
|
+
## subdirectory of the gem
|
41
|
+
##
|
42
|
+
## @param force [Boolean] If true, reinstall if
|
43
|
+
## needed
|
44
|
+
##
|
45
|
+
## @return [String] Path to fzf binary
|
46
|
+
##
|
47
|
+
def install_fzf(force: false)
|
48
|
+
if force
|
49
|
+
uninstall_fzf
|
50
|
+
elsif which_fzf
|
51
|
+
return which_fzf
|
52
|
+
end
|
53
|
+
|
54
|
+
fzf_dir = File.join(File.dirname(__FILE__), '../../helpers/fzf')
|
55
|
+
FileUtils.mkdir_p(fzf_dir) unless File.directory?(fzf_dir)
|
56
|
+
fzf_bin = File.join(fzf_dir, 'bin/fzf')
|
57
|
+
return fzf_bin if File.exist?(fzf_bin)
|
58
|
+
|
59
|
+
prev_level = Doing.logger.level
|
60
|
+
Doing.logger.adjust_verbosity({ log_level: :info })
|
61
|
+
Doing.logger.log_now(:warn, 'fzf:', 'Compiling and installing fzf -- this will only happen once')
|
62
|
+
Doing.logger.log_now(:warn, 'fzf:', 'fzf is copyright Junegunn Choi, MIT License <https://github.com/junegunn/fzf/blob/master/LICENSE>')
|
63
|
+
|
64
|
+
silence_std
|
65
|
+
`'#{fzf_dir}/install' --bin --no-key-bindings --no-completion --no-update-rc --no-bash --no-zsh --no-fish &> /dev/null`
|
66
|
+
unless File.exist?(fzf_bin)
|
67
|
+
restore_std
|
68
|
+
Doing.logger.log_now(:warn, 'Error installing, trying again as root')
|
69
|
+
silence_std
|
70
|
+
`sudo '#{fzf_dir}/install' --bin --no-key-bindings --no-completion --no-update-rc --no-bash --no-zsh --no-fish &> /dev/null`
|
71
|
+
end
|
72
|
+
restore_std
|
73
|
+
unless File.exist?(fzf_bin)
|
74
|
+
Doing.logger.error('fzf:', 'unable to install fzf. You can install manually and Doing will use the system version.')
|
75
|
+
Doing.logger.error('fzf:', 'see https://github.com/junegunn/fzf#installation')
|
76
|
+
raise RuntimeError.new('Error installing fzf, please report at https://github.com/ttscoff/doing/issues')
|
77
|
+
end
|
78
|
+
|
79
|
+
Doing.logger.info('fzf:', "installed to #{fzf}")
|
80
|
+
Doing.logger.adjust_verbosity({ log_level: prev_level })
|
81
|
+
fzf_bin
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doing
|
4
|
+
# Methods for requesting user text input
|
5
|
+
module PromptInput
|
6
|
+
##
|
7
|
+
## Request single-line input
|
8
|
+
##
|
9
|
+
## @param prompt [String] The prompt
|
10
|
+
## @param default_response [String] The default
|
11
|
+
## response returned if
|
12
|
+
## :default_answer is
|
13
|
+
## true
|
14
|
+
##
|
15
|
+
## @return [String] The user response
|
16
|
+
##
|
17
|
+
## @deprecated Use {#read_line} instead
|
18
|
+
##
|
19
|
+
def enter_text(prompt, default_response: '')
|
20
|
+
$stdin.reopen('/dev/tty')
|
21
|
+
return default_response if @default_answer
|
22
|
+
|
23
|
+
print "#{yellow(prompt).sub(/:?$/, ':')} #{reset}"
|
24
|
+
$stdin.gets.strip
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
## Request single-line input using Readline. Allows
|
29
|
+
## for control sequences and tab completions
|
30
|
+
##
|
31
|
+
## @param prompt [String] The prompt
|
32
|
+
## @param completions [Array] Array of tab
|
33
|
+
## completions
|
34
|
+
## @param default_response [String] The default
|
35
|
+
## response returned if
|
36
|
+
## :default_answer is
|
37
|
+
## true
|
38
|
+
##
|
39
|
+
## @return [String] User input string
|
40
|
+
##
|
41
|
+
def read_line(prompt: 'Enter text', completions: [], default_response: '')
|
42
|
+
$stdin.reopen('/dev/tty')
|
43
|
+
return default_response if @default_answer
|
44
|
+
|
45
|
+
unless completions.empty?
|
46
|
+
completions.sort!
|
47
|
+
comp = proc { |s| completions.grep(/^#{Regexp.escape(s)}/) }
|
48
|
+
Readline.completion_append_character = ' '
|
49
|
+
Readline.completion_proc = comp
|
50
|
+
end
|
51
|
+
|
52
|
+
begin
|
53
|
+
Readline.readline("#{yellow(prompt).sub(/:?$/, ':')} #{reset}", true).strip
|
54
|
+
rescue Interrupt
|
55
|
+
raise UserCancelled
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
## Request multi-line input using Readline. Allows for
|
61
|
+
## control sequences and tab completion
|
62
|
+
##
|
63
|
+
## @param prompt [String] The prompt
|
64
|
+
## @param completions [Array] Array of tab
|
65
|
+
## completions
|
66
|
+
## @param default_response [String] The default
|
67
|
+
## response returned if
|
68
|
+
## :default_answer is
|
69
|
+
## true
|
70
|
+
##
|
71
|
+
## @return [String] Multi-line result, joined with newlines
|
72
|
+
##
|
73
|
+
def read_lines(prompt: 'Enter text', completions: [], default_response: '')
|
74
|
+
$stdin.reopen('/dev/tty')
|
75
|
+
return default_response if @default_answer
|
76
|
+
|
77
|
+
completions.sort!
|
78
|
+
comp = proc { |s| completions.grep(/^#{Regexp.escape(s)}/) }
|
79
|
+
Readline.completion_append_character = ' '
|
80
|
+
Readline.completion_proc = comp
|
81
|
+
puts format(['%<promptcolor>s%<prompt>s %<textcolor>sEnter a blank line',
|
82
|
+
'(%<keycolor>sreturn twice%<textcolor>s)',
|
83
|
+
'to end editing and save,',
|
84
|
+
'%<keycolor>sCTRL-C%<textcolor>s to cancel%<reset>s'].join(' '),
|
85
|
+
{ promptcolor: boldgreen, prompt: prompt.sub(/:?$/, ':'),
|
86
|
+
textcolor: yellow, keycolor: boldwhite, reset: reset })
|
87
|
+
|
88
|
+
res = []
|
89
|
+
|
90
|
+
begin
|
91
|
+
while (line = Readline.readline('> ', true))
|
92
|
+
break if line.strip.empty?
|
93
|
+
|
94
|
+
res << line.chomp
|
95
|
+
end
|
96
|
+
rescue Interrupt
|
97
|
+
raise UserCancelled
|
98
|
+
end
|
99
|
+
|
100
|
+
res.join("\n").strip
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
## Request multi-line input
|
105
|
+
##
|
106
|
+
## @param prompt [String] The prompt
|
107
|
+
## @param default_response [String] The default
|
108
|
+
## response, returned if
|
109
|
+
## :default_answer is
|
110
|
+
## true
|
111
|
+
##
|
112
|
+
## @deprecated Use {#read_lines} instead
|
113
|
+
def request_lines(prompt: 'Enter text', default_response: '')
|
114
|
+
$stdin.reopen('/dev/tty')
|
115
|
+
return default_response if @default_answer
|
116
|
+
|
117
|
+
ask_note = []
|
118
|
+
reader = TTY::Reader.new(interrupt: -> { raise Errors::UserCancelled }, track_history: false)
|
119
|
+
puts "#{boldgreen(prompt.sub(/:?$/, ':'))} #{yellow('Hit return for a new line, ')}#{boldwhite('enter a blank line (')}#{boldyellow('return twice')}#{boldwhite(') to end editing')}"
|
120
|
+
loop do
|
121
|
+
res = reader.read_line(green('> '))
|
122
|
+
break if res.strip.empty?
|
123
|
+
|
124
|
+
ask_note.push(res)
|
125
|
+
end
|
126
|
+
ask_note.join("\n").strip
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'choose'
|
4
|
+
require_relative 'fzf'
|
5
|
+
require_relative 'input'
|
6
|
+
require_relative 'std'
|
7
|
+
require_relative 'yn'
|
8
|
+
|
9
|
+
module Doing
|
10
|
+
# Terminal Prompt methods
|
11
|
+
module Prompt
|
12
|
+
class << self
|
13
|
+
attr_writer :force_answer, :default_answer
|
14
|
+
|
15
|
+
include Color
|
16
|
+
include PromptSTD
|
17
|
+
include PromptInput
|
18
|
+
include PromptYN
|
19
|
+
include PromptFZF
|
20
|
+
include PromptChoose
|
21
|
+
|
22
|
+
##
|
23
|
+
## Value to return if prompt is skipped
|
24
|
+
##
|
25
|
+
## @return Force answer value
|
26
|
+
##
|
27
|
+
def force_answer
|
28
|
+
@force_answer ||= nil
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
## If true, always return the default answer without prompting
|
33
|
+
##
|
34
|
+
## @return [Boolean] default answer
|
35
|
+
##
|
36
|
+
def default_answer
|
37
|
+
@default_answer ||= false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doing
|
4
|
+
# STDOUT and STDERR methods
|
5
|
+
module PromptSTD
|
6
|
+
##
|
7
|
+
## Clear the terminal screen
|
8
|
+
##
|
9
|
+
def clear_screen(msg = nil)
|
10
|
+
puts "\e[H\e[2J" if $stdout.tty?
|
11
|
+
puts msg if msg.good?
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
## Redirect STDOUT and STDERR to /dev/null or file
|
16
|
+
##
|
17
|
+
## @param file [String] a file path to redirect to
|
18
|
+
##
|
19
|
+
def silence_std(file = '/dev/null')
|
20
|
+
$stdout = File.new(file, 'w')
|
21
|
+
$stderr = File.new(file, 'w')
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
## Restore silenced STDOUT and STDERR
|
26
|
+
##
|
27
|
+
def restore_std
|
28
|
+
$stdout = STDOUT
|
29
|
+
$stderr = STDERR
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doing
|
4
|
+
# Request Yes/No answers on command line
|
5
|
+
module PromptYN
|
6
|
+
##
|
7
|
+
## Ask a yes or no question in the terminal
|
8
|
+
##
|
9
|
+
## @param question [String] The question
|
10
|
+
## to ask
|
11
|
+
## @param default_response [Boolean] default
|
12
|
+
## response if no input
|
13
|
+
##
|
14
|
+
## @return [Boolean] yes or no
|
15
|
+
##
|
16
|
+
def yn(question, default_response: false)
|
17
|
+
return @force_answer == :yes ? true : false unless @force_answer.nil?
|
18
|
+
|
19
|
+
$stdin.reopen('/dev/tty')
|
20
|
+
|
21
|
+
default = if default_response.is_a?(String)
|
22
|
+
default_response =~ /y/i ? true : false
|
23
|
+
else
|
24
|
+
default_response
|
25
|
+
end
|
26
|
+
|
27
|
+
# if global --default is set, answer default
|
28
|
+
return default if @default_answer
|
29
|
+
|
30
|
+
# if this isn't an interactive shell, answer default
|
31
|
+
return default unless $stdout.isatty
|
32
|
+
|
33
|
+
# clear the buffer
|
34
|
+
if ARGV&.length
|
35
|
+
ARGV.length.times do
|
36
|
+
ARGV.shift
|
37
|
+
end
|
38
|
+
end
|
39
|
+
system 'stty cbreak'
|
40
|
+
|
41
|
+
cw = white
|
42
|
+
cbw = boldwhite
|
43
|
+
cbg = boldgreen
|
44
|
+
cd = Color.default
|
45
|
+
|
46
|
+
options = unless default.nil?
|
47
|
+
"#{cw}[#{default ? "#{cbg}Y#{cw}/#{cbw}n" : "#{cbw}y#{cw}/#{cbg}N"}#{cw}]#{cd}"
|
48
|
+
else
|
49
|
+
"#{cw}[#{cbw}y#{cw}/#{cbw}n#{cw}]#{cd}"
|
50
|
+
end
|
51
|
+
$stdout.syswrite "#{cbw}#{question.sub(/\?$/, '')} #{options}#{cbw}?#{cd} "
|
52
|
+
res = $stdin.sysread 1
|
53
|
+
puts
|
54
|
+
system 'stty cooked'
|
55
|
+
|
56
|
+
res.chomp!
|
57
|
+
res.downcase!
|
58
|
+
|
59
|
+
return default if res.empty?
|
60
|
+
|
61
|
+
res =~ /y/i ? true : false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/doing/section.rb
CHANGED
data/lib/doing/string/query.rb
CHANGED
data/lib/doing/string/string.rb
CHANGED
@@ -1,8 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'highlight'
|
4
|
+
require_relative 'query'
|
5
|
+
require_relative 'tags'
|
6
|
+
require_relative 'transform'
|
7
|
+
require_relative 'truncate'
|
8
|
+
require_relative 'url'
|
9
|
+
|
3
10
|
class ::String
|
4
11
|
include Doing::Color
|
12
|
+
include Doing::StringHighlight
|
13
|
+
include Doing::StringQuery
|
14
|
+
include Doing::StringTags
|
15
|
+
include Doing::StringTransform
|
16
|
+
include Doing::StringTruncate
|
17
|
+
include Doing::StringURL
|
5
18
|
|
19
|
+
##
|
20
|
+
## Force UTF-8 encoding if available
|
21
|
+
##
|
22
|
+
## @return [String] UTF-8 encoded string
|
23
|
+
##
|
6
24
|
def utf8
|
7
25
|
if String.method_defined? :force_encoding
|
8
26
|
dup.force_encoding('utf-8')
|
@@ -11,10 +29,3 @@ class ::String
|
|
11
29
|
end
|
12
30
|
end
|
13
31
|
end
|
14
|
-
|
15
|
-
require_relative 'highlight'
|
16
|
-
require_relative 'query'
|
17
|
-
require_relative 'tags'
|
18
|
-
require_relative 'transform'
|
19
|
-
require_relative 'truncate'
|
20
|
-
require_relative 'url'
|