bashly 1.3.8 → 1.4.0.rc2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 411dc6262c3232175e5a968a31ad90b56aa59be9d9b46203c3eb2db14f35d491
4
- data.tar.gz: 2ce20d069c93e1e7081984ff1fb2dd9c7bca530d6fbf127e0bb1c996c0117cc1
3
+ metadata.gz: 77fc095074710d1003657f7ee7360e06e6864c9142d283d8bab88a9776fc737f
4
+ data.tar.gz: fae57e0c6bdf23a09d83bb280a4161b2e4b0de0e46518a44a1c36f902f9993cb
5
5
  SHA512:
6
- metadata.gz: 3bff0a363b5c52c0f54b2b1e413b909256579c0b29ef8a44e88cd73b1803c584c3de26af99fa737edac8cc103a218f418a7a5f463e38b27c416ebf26bbcfe8ef
7
- data.tar.gz: 2691947ed69d6f693625be4ba66a1b9803d01e46f50768d266c5f0f27c60f74242977fb91131443f6bd29161a9810970b542ecef3d3be0e9b3a84641da8dc080
6
+ metadata.gz: a23dd4c92491a97aef54990418b930ae38ce826ede3bf1352c18f99ac8c53d5391e613eef113f5996c2757a60d5c9ebf96fe7e277e69a6a07456d02df35c2b28
7
+ data.tar.gz: 369a787f5851da5ac1b07afe6f7ddb985ebd64fc3ffe08aa5379376b3f8ddbc53e313dc31ba832096361a15ebf5aa973d6fd4dabfe64d7f8c6249799a9a7e1f9
@@ -75,7 +75,7 @@ module Bashly
75
75
  @raw_data ||= begin
76
76
  result = {}
77
77
  Dir["#{docs_dir}/*.yml"].each do |path|
78
- result.merge! YAML.load_file(path)
78
+ result.merge! YAML.trusted_load_file(path)
79
79
  end
80
80
  result
81
81
  end
@@ -0,0 +1,231 @@
1
+ module Bashly
2
+ class CompletionBuilder
3
+ BUILTIN_PATTERN = /\A<([^>]+)>\z/
4
+
5
+ def initialize(command, with_version: true)
6
+ @command = command
7
+ @with_version = with_version
8
+ @patterns = []
9
+ @options = {}
10
+ @tokens = {}
11
+ @token_sources = {}
12
+ end
13
+
14
+ def call
15
+ add_command @command, inherited_global_groups: []
16
+
17
+ result = { 'patterns' => @patterns }
18
+ result['options'] = @options if @options.any?
19
+ result['tokens'] = @tokens if @tokens.any?
20
+ result
21
+ end
22
+
23
+ private
24
+
25
+ def add_command(command, inherited_global_groups:)
26
+ local_group = add_local_options command
27
+ pattern_groups = inherited_global_groups.dup
28
+ pattern_groups << local_group if local_group
29
+
30
+ @patterns << pattern_for(command, pattern_groups) unless visible_default_command(command)
31
+
32
+ child_global_groups = inherited_global_groups.dup
33
+ if command.global_flags?
34
+ global_group = add_global_options command
35
+ child_global_groups << global_group if global_group
36
+ end
37
+
38
+ command.visible_commands.each do |child|
39
+ add_default_command_pattern command, child, pattern_groups
40
+ add_command child, inherited_global_groups: child_global_groups
41
+ end
42
+ end
43
+
44
+ def pattern_for(command, option_groups)
45
+ parts = [command_path(command)]
46
+ parts.concat(option_groups.map { |group| "[#{group} options]" })
47
+ parts.concat positional_tokens(command)
48
+ parts.join ' '
49
+ end
50
+
51
+ def add_default_command_pattern(parent, command, parent_option_groups)
52
+ return unless command.default
53
+
54
+ default_group = add_default_options parent, command, parent_option_groups
55
+ option_groups = default_group ? [default_group] : []
56
+
57
+ @patterns << pattern_for_default_command(parent, command, option_groups)
58
+ end
59
+
60
+ def add_default_options(parent, command, parent_option_groups)
61
+ local_group = add_local_options command
62
+ group_names = parent_option_groups.dup
63
+ group_names << local_group if local_group
64
+
65
+ entries = group_names.flat_map { |name| @options[name] || [] }.uniq
66
+ return if entries.empty?
67
+
68
+ name = token_name "#{group_name(parent)}_#{group_name(command)}_default"
69
+ @options[name] = entries
70
+ name
71
+ end
72
+
73
+ def pattern_for_default_command(parent, command, option_groups)
74
+ parts = [command_path(parent)]
75
+ parts.concat(option_groups.map { |group| "[#{group} options]" })
76
+ parts.concat positional_tokens(
77
+ command,
78
+ first_source_extra: static_source(parent.visible_command_aliases)
79
+ )
80
+ parts.join ' '
81
+ end
82
+
83
+ def command_path(command)
84
+ command_chain(command).map.with_index do |item, index|
85
+ index.zero? ? item.name : item.aliases.join('|')
86
+ end.join ' '
87
+ end
88
+
89
+ def command_chain(command)
90
+ result = []
91
+ current = command
92
+ while current
93
+ result.unshift current
94
+ current = current.parent_command
95
+ end
96
+ result
97
+ end
98
+
99
+ def add_local_options(command)
100
+ entries = fixed_option_entries(command) + flag_option_entries(command.visible_flags, command)
101
+ return if entries.empty?
102
+
103
+ name = group_name command
104
+ @options[name] = entries
105
+ name
106
+ end
107
+
108
+ def add_global_options(command)
109
+ entries = flag_option_entries command.visible_flags, command
110
+ return if entries.empty?
111
+
112
+ name = "#{group_name(command)}_global"
113
+ @options[name] = entries
114
+ name
115
+ end
116
+
117
+ def fixed_option_entries(command)
118
+ return [] if !command.root_command? && command.catch_all.catch_help?
119
+
120
+ entries = %w[--help|-h]
121
+ entries << '--version|-v' if command.root_command? && @with_version
122
+ entries
123
+ end
124
+
125
+ def flag_option_entries(flags, command)
126
+ flags.map do |flag|
127
+ token_name = flag_token_name flag, command
128
+ flag.completion_option_entry token_name
129
+ end
130
+ end
131
+
132
+ def flag_token_name(flag, command)
133
+ return unless flag.arg || flag.allowed || flag.completions
134
+
135
+ register_token flag.arg || flag.name, command, flag_source(flag)
136
+ end
137
+
138
+ def positional_tokens(command, first_source_extra: nil)
139
+ command.args.map.with_index do |arg, index|
140
+ source = arg_source arg, command
141
+ source = merge_sources(first_source_extra, source) if index.zero? && first_source_extra
142
+ token_name = register_token arg.name, command, source
143
+ suffix = arg.repeatable ? '...' : nil
144
+ "<#{token_name}>#{suffix}"
145
+ end
146
+ end
147
+
148
+ def merge_sources(*sources)
149
+ sources.compact.flatten.uniq
150
+ end
151
+
152
+ def flag_source(flag)
153
+ return static_source(flag.allowed) if flag.allowed
154
+ return completion_source(flag.completions) if flag.completions
155
+
156
+ nil
157
+ end
158
+
159
+ def arg_source(arg, command)
160
+ return static_source(arg.allowed) if arg.allowed
161
+ return completion_source(arg.completions) if arg.completions
162
+ return completion_source(command.completions) if command.completions
163
+
164
+ nil
165
+ end
166
+
167
+ def static_source(values)
168
+ Array(values).compact.map { |value| escape_static_source value }
169
+ end
170
+
171
+ def completion_source(values)
172
+ Array(values).compact.map do |value|
173
+ string = value.to_s
174
+ builtin = string[BUILTIN_PATTERN, 1]
175
+
176
+ if builtin
177
+ "+#{builtin}"
178
+ else
179
+ escape_static_source string
180
+ end
181
+ end
182
+ end
183
+
184
+ def escape_static_source(value)
185
+ string = value.to_s
186
+ string.start_with?('+') ? "+#{string}" : string
187
+ end
188
+
189
+ def register_token(base_name, command, source)
190
+ preferred = token_name base_name
191
+ return preferred if register_token_name preferred, source
192
+
193
+ scoped = token_name "#{group_name command}_#{base_name}"
194
+ return scoped if register_token_name scoped, source
195
+
196
+ suffix = 2
197
+ loop do
198
+ candidate = "#{scoped}_#{suffix}"
199
+ return candidate if register_token_name candidate, source
200
+
201
+ suffix += 1
202
+ end
203
+ end
204
+
205
+ def register_token_name(name, source)
206
+ if @token_sources.has_key? name
207
+ return false unless @token_sources[name] == source
208
+ else
209
+ @token_sources[name] = source
210
+ @tokens[name] = source
211
+ end
212
+
213
+ true
214
+ end
215
+
216
+ def group_name(command)
217
+ token_name command.root_command? ? 'root' : command.action_name
218
+ end
219
+
220
+ def visible_default_command(command)
221
+ command.visible_commands.find(&:default)
222
+ end
223
+
224
+ def token_name(value)
225
+ value.to_s
226
+ .gsub(/[^a-zA-Z0-9]+/, '_')
227
+ .gsub(/\A_+|_+\z/, '')
228
+ .downcase
229
+ end
230
+ end
231
+ end