bashly 1.3.8 → 1.4.0.rc1

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: c4417e2acaa7177450ab206c368039c46db5e2e3839b015c7dac235d23d3899a
4
+ data.tar.gz: e62782cd28d229c36adcc4be6d09243b693f8c1536fadcfb98ceaddb203fbc49
5
5
  SHA512:
6
- metadata.gz: 3bff0a363b5c52c0f54b2b1e413b909256579c0b29ef8a44e88cd73b1803c584c3de26af99fa737edac8cc103a218f418a7a5f463e38b27c416ebf26bbcfe8ef
7
- data.tar.gz: 2691947ed69d6f693625be4ba66a1b9803d01e46f50768d266c5f0f27c60f74242977fb91131443f6bd29161a9810970b542ecef3d3be0e9b3a84641da8dc080
6
+ metadata.gz: f2827e99a65bbfc2cdaa77b2533400d6a85d7af72cbb35471654bb2ce840cef897f76e19c51504027559405a410cb46d150c2b2680d9b471526a94ee29dc4083
7
+ data.tar.gz: 1cbe9b74014155e5cbd920fa29249a4102afea7738334b811583d5bdc1ef9064a1595d87d238bc350dd46b4dbe32926546a341956b1f417b521f5d459947ba1c
@@ -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,188 @@
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)
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_command child, inherited_global_groups: child_global_groups
40
+ end
41
+ end
42
+
43
+ def pattern_for(command, option_groups)
44
+ parts = [command_path(command)]
45
+ parts.concat(option_groups.map { |group| "[#{group} options]" })
46
+ parts.concat positional_tokens(command)
47
+ parts.join ' '
48
+ end
49
+
50
+ def command_path(command)
51
+ command_chain(command).map.with_index do |item, index|
52
+ index.zero? ? item.name : item.aliases.join('|')
53
+ end.join ' '
54
+ end
55
+
56
+ def command_chain(command)
57
+ result = []
58
+ current = command
59
+ while current
60
+ result.unshift current
61
+ current = current.parent_command
62
+ end
63
+ result
64
+ end
65
+
66
+ def add_local_options(command)
67
+ entries = fixed_option_entries(command) + flag_option_entries(command.visible_flags, command)
68
+ return if entries.empty?
69
+
70
+ name = group_name command
71
+ @options[name] = entries
72
+ name
73
+ end
74
+
75
+ def add_global_options(command)
76
+ entries = flag_option_entries command.visible_flags, command
77
+ return if entries.empty?
78
+
79
+ name = "#{group_name(command)}_global"
80
+ @options[name] = entries
81
+ name
82
+ end
83
+
84
+ def fixed_option_entries(command)
85
+ return [] if !command.root_command? && command.catch_all.catch_help?
86
+
87
+ entries = %w[--help|-h]
88
+ entries << '--version|-v' if command.root_command? && @with_version
89
+ entries
90
+ end
91
+
92
+ def flag_option_entries(flags, command)
93
+ flags.map do |flag|
94
+ token_name = flag_token_name flag, command
95
+ flag.completion_option_entry token_name
96
+ end
97
+ end
98
+
99
+ def flag_token_name(flag, command)
100
+ return unless flag.arg || flag.allowed || flag.completions
101
+
102
+ register_token flag.arg || flag.name, command, flag_source(flag)
103
+ end
104
+
105
+ def positional_tokens(command)
106
+ command.args.map do |arg|
107
+ token_name = register_token arg.name, command, arg_source(arg, command)
108
+ suffix = arg.repeatable ? '...' : nil
109
+ "<#{token_name}>#{suffix}"
110
+ end
111
+ end
112
+
113
+ def flag_source(flag)
114
+ return static_source(flag.allowed) if flag.allowed
115
+ return completion_source(flag.completions) if flag.completions
116
+
117
+ nil
118
+ end
119
+
120
+ def arg_source(arg, command)
121
+ return static_source(arg.allowed) if arg.allowed
122
+ return completion_source(arg.completions) if arg.completions
123
+ return completion_source(command.completions) if command.completions
124
+
125
+ nil
126
+ end
127
+
128
+ def static_source(values)
129
+ Array(values).compact.map { |value| escape_static_source value }
130
+ end
131
+
132
+ def completion_source(values)
133
+ Array(values).compact.map do |value|
134
+ string = value.to_s
135
+ builtin = string[BUILTIN_PATTERN, 1]
136
+
137
+ if builtin
138
+ "+#{builtin}"
139
+ else
140
+ escape_static_source string
141
+ end
142
+ end
143
+ end
144
+
145
+ def escape_static_source(value)
146
+ string = value.to_s
147
+ string.start_with?('+') ? "+#{string}" : string
148
+ end
149
+
150
+ def register_token(base_name, command, source)
151
+ preferred = token_name base_name
152
+ return preferred if register_token_name preferred, source
153
+
154
+ scoped = token_name "#{group_name command}_#{base_name}"
155
+ return scoped if register_token_name scoped, source
156
+
157
+ suffix = 2
158
+ loop do
159
+ candidate = "#{scoped}_#{suffix}"
160
+ return candidate if register_token_name candidate, source
161
+
162
+ suffix += 1
163
+ end
164
+ end
165
+
166
+ def register_token_name(name, source)
167
+ if @token_sources.has_key? name
168
+ return false unless @token_sources[name] == source
169
+ else
170
+ @token_sources[name] = source
171
+ @tokens[name] = source
172
+ end
173
+
174
+ true
175
+ end
176
+
177
+ def group_name(command)
178
+ token_name command.root_command? ? 'root' : command.action_name
179
+ end
180
+
181
+ def token_name(value)
182
+ value.to_s
183
+ .gsub(/[^a-zA-Z0-9]+/, '_')
184
+ .gsub(/\A_+|_+\z/, '')
185
+ .downcase
186
+ end
187
+ end
188
+ end