bundler 4.0.21 → 4.1.0.beta1
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 +4 -4
- data/{lib/bundler/vendor/connection_pool/LICENSE → MIT.txt} +8 -6
- data/bundler.gemspec +23 -6
- data/lib/bundler/build_metadata.rb +2 -2
- data/lib/bundler/checksum.rb +40 -25
- data/lib/bundler/ci_detector.rb +1 -1
- data/lib/bundler/cli/add.rb +3 -4
- data/lib/bundler/cli/cache.rb +4 -0
- data/lib/bundler/cli/clean.rb +5 -0
- data/lib/bundler/cli/common.rb +37 -1
- data/lib/bundler/cli/config.rb +4 -2
- data/lib/bundler/cli/gem.rb +0 -17
- data/lib/bundler/cli/install.rb +4 -5
- data/lib/bundler/cli/lock.rb +1 -2
- data/lib/bundler/cli/outdated.rb +8 -3
- data/lib/bundler/cli/pristine.rb +2 -1
- data/lib/bundler/cli/update.rb +21 -12
- data/lib/bundler/cli.rb +14 -11
- data/lib/bundler/definition.rb +61 -24
- data/lib/bundler/dependency.rb +4 -0
- data/lib/bundler/dsl.rb +77 -11
- data/lib/bundler/endpoint_specification.rb +51 -14
- data/lib/bundler/env.rb +7 -24
- data/lib/bundler/errors.rb +34 -5
- data/lib/bundler/fetcher/compact_index.rb +14 -5
- data/lib/bundler/fetcher/connection_pools.rb +150 -0
- data/lib/bundler/fetcher/downloader.rb +31 -16
- data/lib/bundler/fetcher.rb +8 -47
- data/lib/bundler/injector.rb +3 -3
- data/lib/bundler/inline.rb +44 -4
- data/lib/bundler/installer/parallel_installer.rb +25 -30
- data/lib/bundler/installer.rb +20 -6
- data/lib/bundler/lazy_specification.rb +44 -17
- data/lib/bundler/lockfile_generator.rb +24 -2
- data/lib/bundler/lockfile_parser.rb +43 -5
- data/lib/bundler/man/bundle-add.1 +7 -4
- data/lib/bundler/man/bundle-add.1.ronn +8 -4
- data/lib/bundler/man/bundle-binstubs.1 +1 -1
- data/lib/bundler/man/bundle-cache.1 +2 -2
- data/lib/bundler/man/bundle-cache.1.ronn +2 -2
- data/lib/bundler/man/bundle-check.1 +1 -1
- data/lib/bundler/man/bundle-clean.1 +1 -1
- data/lib/bundler/man/bundle-config.1 +38 -6
- data/lib/bundler/man/bundle-config.1.ronn +143 -10
- data/lib/bundler/man/bundle-console.1 +1 -1
- data/lib/bundler/man/bundle-doctor.1 +1 -1
- data/lib/bundler/man/bundle-env.1 +1 -1
- data/lib/bundler/man/bundle-exec.1 +1 -1
- data/lib/bundler/man/bundle-fund.1 +1 -1
- data/lib/bundler/man/bundle-gem.1 +1 -1
- data/lib/bundler/man/bundle-help.1 +1 -1
- data/lib/bundler/man/bundle-info.1 +1 -1
- data/lib/bundler/man/bundle-init.1 +1 -1
- data/lib/bundler/man/bundle-install.1 +2 -2
- data/lib/bundler/man/bundle-install.1.ronn +4 -4
- data/lib/bundler/man/bundle-issue.1 +1 -1
- data/lib/bundler/man/bundle-licenses.1 +1 -1
- data/lib/bundler/man/bundle-list.1 +1 -1
- data/lib/bundler/man/bundle-lock.1 +3 -3
- data/lib/bundler/man/bundle-lock.1.ronn +5 -5
- data/lib/bundler/man/bundle-open.1 +1 -1
- data/lib/bundler/man/bundle-outdated.1 +1 -1
- data/lib/bundler/man/bundle-platform.1 +1 -1
- data/lib/bundler/man/bundle-plugin.1 +1 -1
- data/lib/bundler/man/bundle-pristine.1 +1 -1
- data/lib/bundler/man/bundle-remove.1 +1 -1
- data/lib/bundler/man/bundle-show.1 +1 -1
- data/lib/bundler/man/bundle-update.1 +3 -3
- data/lib/bundler/man/bundle-update.1.ronn +3 -5
- data/lib/bundler/man/bundle-version.1 +1 -1
- data/lib/bundler/man/bundle.1 +1 -1
- data/lib/bundler/man/gemfile.5 +45 -1
- data/lib/bundler/man/gemfile.5.ronn +53 -0
- data/lib/bundler/match_metadata.rb +20 -0
- data/lib/bundler/match_platform.rb +25 -2
- data/lib/bundler/match_remote_metadata.rb +21 -6
- data/lib/bundler/materialization.rb +2 -1
- data/lib/bundler/override.rb +69 -0
- data/lib/bundler/plugin/dsl.rb +3 -5
- data/lib/bundler/plugin/events.rb +55 -13
- data/lib/bundler/plugin/index.rb +87 -11
- data/lib/bundler/plugin/installer/path.rb +1 -1
- data/lib/bundler/plugin/installer.rb +13 -5
- data/lib/bundler/plugin.rb +58 -30
- data/lib/bundler/remote_specification.rb +6 -3
- data/lib/bundler/resolver/base.rb +2 -1
- data/lib/bundler/resolver/incompatibility.rb +1 -1
- data/lib/bundler/resolver.rb +73 -23
- data/lib/bundler/ruby_version.rb +0 -2
- data/lib/bundler/rubygems_ext.rb +71 -1
- data/lib/bundler/rubygems_gem_installer.rb +41 -2
- data/lib/bundler/rubygems_integration.rb +7 -2
- data/lib/bundler/runtime.rb +69 -2
- data/lib/bundler/self_manager.rb +9 -15
- data/lib/bundler/settings.rb +373 -30
- data/lib/bundler/shared_helpers.rb +2 -1
- data/lib/bundler/source/git/git_proxy.rb +2 -10
- data/lib/bundler/source/git.rb +15 -6
- data/lib/bundler/source/path/installer.rb +1 -1
- data/lib/bundler/source/rubygems/remote.rb +10 -5
- data/lib/bundler/source/rubygems.rb +48 -12
- data/lib/bundler/spec_set.rb +16 -4
- data/lib/bundler/stub_specification.rb +2 -1
- data/lib/bundler/templates/newgem/Gemfile.tt +4 -4
- data/lib/bundler/templates/newgem/newgem.gemspec.tt +3 -3
- data/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt +1 -1
- data/lib/bundler/templates/newgem/test/minitest/test_newgem.rb.tt +1 -1
- data/lib/bundler/ui/shell.rb +0 -4
- data/lib/bundler/ui/silent.rb +0 -4
- data/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb +35 -2
- data/lib/bundler/vendor/thor/lib/thor/base.rb +2 -1
- data/lib/bundler/vendor/thor/lib/thor/shell/basic.rb +14 -6
- data/lib/bundler/vendor/thor/lib/thor/shell/color.rb +2 -0
- data/lib/bundler/vendor/thor/lib/thor/version.rb +1 -1
- data/lib/bundler/vendor/thor/lib/thor.rb +34 -1
- data/lib/bundler/vendored_pub_grub.rb +5 -2
- data/lib/bundler/vendored_securerandom.rb +11 -8
- data/lib/bundler/vendored_uri.rb +14 -14
- data/lib/bundler/version.rb +1 -1
- data/lib/bundler/worker.rb +3 -2
- data/lib/bundler.rb +1 -1
- data/lib/rubygems/compact_index_client/cache.rb +122 -0
- data/lib/rubygems/compact_index_client/cache_file.rb +147 -0
- data/lib/rubygems/compact_index_client/http_fetcher.rb +88 -0
- data/lib/rubygems/compact_index_client/parser.rb +90 -0
- data/lib/rubygems/compact_index_client/updater.rb +113 -0
- data/lib/rubygems/compact_index_client.rb +111 -0
- data/lib/rubygems/cooldown_settings.rb +57 -0
- data/lib/rubygems/credential_store/native/linux.rb +97 -0
- data/lib/rubygems/credential_store/native/macos.rb +88 -0
- data/lib/rubygems/credential_store/native/windows.rb +130 -0
- data/lib/rubygems/credential_store.rb +332 -0
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/assignment.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/basic_package_source.rb +7 -7
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/failure_writer.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/incompatibility.rb +4 -4
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/package.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/partial_solution.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/rubygems.rb +3 -3
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/solve_failure.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/static_package_source.rb +3 -3
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/strategy.rb +2 -2
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/term.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_constraint.rb +3 -3
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_range.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_solver.rb +2 -2
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_union.rb +1 -1
- data/lib/rubygems/vendor/pub_grub/lib/pub_grub.rb +57 -0
- data/lib/{bundler → rubygems}/vendor/securerandom/lib/securerandom.rb +10 -6
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/common.rb +119 -119
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/file.rb +13 -13
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ftp.rb +24 -24
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/generic.rb +132 -132
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/http.rb +18 -18
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/https.rb +4 -4
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ldap.rb +14 -14
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ldaps.rb +4 -4
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/mailto.rb +21 -21
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/rfc2396_parser.rb +39 -39
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/rfc3986_parser.rb +14 -14
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/version.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ws.rb +12 -12
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/wss.rb +4 -4
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri.rb +35 -31
- data/lib/rubygems/yaml_serializer.rb +880 -0
- metadata +77 -68
- data/CHANGELOG.md +0 -5784
- data/LICENSE.md +0 -22
- data/README.md +0 -58
- data/lib/bundler/compact_index_client/cache.rb +0 -107
- data/lib/bundler/compact_index_client/cache_file.rb +0 -148
- data/lib/bundler/compact_index_client/parser.rb +0 -87
- data/lib/bundler/compact_index_client/updater.rb +0 -105
- data/lib/bundler/compact_index_client.rb +0 -92
- data/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb +0 -227
- data/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb +0 -3
- data/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb +0 -56
- data/lib/bundler/vendor/connection_pool/lib/connection_pool.rb +0 -230
- data/lib/bundler/vendor/net-http-persistent/README.rdoc +0 -82
- data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/connection.rb +0 -41
- data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/pool.rb +0 -65
- data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb +0 -80
- data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb +0 -1153
- data/lib/bundler/vendor/pub_grub/lib/pub_grub.rb +0 -31
- data/lib/bundler/vendored_persistent.rb +0 -11
- data/lib/bundler/yaml_serializer.rb +0 -98
- /data/lib/{bundler → rubygems}/vendor/pub_grub/LICENSE.txt +0 -0
- /data/lib/{bundler → rubygems}/vendor/securerandom/COPYING +0 -0
- /data/lib/{bundler → rubygems}/vendor/uri/COPYING +0 -0
|
@@ -0,0 +1,880 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Skip reloading when an identical copy (e.g. the one shipped inside the Bundler
|
|
4
|
+
# gem) was already required from a different path, to avoid redefinition warnings.
|
|
5
|
+
return if defined?(Gem::YAMLSerializer)
|
|
6
|
+
|
|
7
|
+
unless defined?(Psych::VERSION)
|
|
8
|
+
module Psych
|
|
9
|
+
class Exception < ::RuntimeError; end
|
|
10
|
+
class SyntaxError < Exception; end
|
|
11
|
+
class DisallowedClass < Exception; end
|
|
12
|
+
class BadAlias < Exception; end
|
|
13
|
+
class AliasesNotEnabled < BadAlias; end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module Gem
|
|
18
|
+
module YAMLSerializer
|
|
19
|
+
Scalar = Struct.new(:value, :tag, :anchor, keyword_init: true)
|
|
20
|
+
|
|
21
|
+
Mapping = Struct.new(:pairs, :tag, :anchor, keyword_init: true) do
|
|
22
|
+
def initialize(pairs: [], tag: nil, anchor: nil)
|
|
23
|
+
super
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
Sequence = Struct.new(:items, :tag, :anchor, keyword_init: true) do
|
|
28
|
+
def initialize(items: [], tag: nil, anchor: nil)
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
AliasRef = Struct.new(:name, keyword_init: true)
|
|
34
|
+
|
|
35
|
+
class Parser
|
|
36
|
+
# A plain (unquoted) mapping key followed by ":". "#" inside a key is
|
|
37
|
+
# literal unless preceded by a space (which would start a comment).
|
|
38
|
+
MAPPING_KEY_RE = /^((?:[^#:\s]|:[^ ])(?:[^#:]|:[^ ]|(?<=[^ ])#)*):(?:[ ]+(.*))?$/
|
|
39
|
+
# A quoted mapping key followed by ":". A whole-line quoted scalar
|
|
40
|
+
# cannot match because its closing quote is at the end of the line.
|
|
41
|
+
QUOTED_KEY_RE = /^("(?:[^"\\]|\\.)*"|'(?:[^']|'')*'):(?:[ ]+(.*))?$/
|
|
42
|
+
MAX_NESTING_DEPTH = 1_000
|
|
43
|
+
|
|
44
|
+
STRING_UNESCAPES = {
|
|
45
|
+
"\\\\" => "\\",
|
|
46
|
+
"\\\"" => "\"",
|
|
47
|
+
"\\0" => "\0",
|
|
48
|
+
"\\a" => "\a",
|
|
49
|
+
"\\b" => "\b",
|
|
50
|
+
"\\t" => "\t",
|
|
51
|
+
"\\n" => "\n",
|
|
52
|
+
"\\v" => "\v",
|
|
53
|
+
"\\f" => "\f",
|
|
54
|
+
"\\r" => "\r",
|
|
55
|
+
"\\e" => "\e",
|
|
56
|
+
}.freeze
|
|
57
|
+
|
|
58
|
+
def initialize(source)
|
|
59
|
+
@lines = source.split(/\r?\n/)
|
|
60
|
+
@anchors = {}
|
|
61
|
+
@depth = 0
|
|
62
|
+
strip_document_prefix
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def parse
|
|
66
|
+
return nil if @lines.empty?
|
|
67
|
+
|
|
68
|
+
root = nil
|
|
69
|
+
while @lines.any?
|
|
70
|
+
before = @lines.size
|
|
71
|
+
node = parse_node(-1)
|
|
72
|
+
@lines.shift if @lines.size == before && @lines.any?
|
|
73
|
+
|
|
74
|
+
if root.is_a?(Mapping) && node.is_a?(Mapping)
|
|
75
|
+
root.pairs.concat(node.pairs)
|
|
76
|
+
elsif root.nil?
|
|
77
|
+
root = node
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
root
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def strip_document_prefix
|
|
86
|
+
return if @lines.empty?
|
|
87
|
+
return unless @lines[0]&.start_with?("---")
|
|
88
|
+
|
|
89
|
+
if @lines[0].strip == "---"
|
|
90
|
+
@lines.shift
|
|
91
|
+
else
|
|
92
|
+
@lines[0] = @lines[0].sub(/^---\s*/, "")
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def parse_node(base_indent)
|
|
97
|
+
@depth += 1
|
|
98
|
+
raise_max_nesting! if @depth > MAX_NESTING_DEPTH
|
|
99
|
+
|
|
100
|
+
skip_blank_and_comments
|
|
101
|
+
return nil if @lines.empty?
|
|
102
|
+
|
|
103
|
+
line = @lines[0]
|
|
104
|
+
stripped = line.lstrip
|
|
105
|
+
indent = line.size - stripped.size
|
|
106
|
+
return nil if indent < base_indent
|
|
107
|
+
|
|
108
|
+
return parse_alias_ref if stripped.start_with?("*")
|
|
109
|
+
|
|
110
|
+
anchor = consume_anchor
|
|
111
|
+
|
|
112
|
+
if anchor
|
|
113
|
+
line = @lines[0]
|
|
114
|
+
stripped = line.lstrip
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
if stripped.start_with?("- ") || stripped == "-"
|
|
118
|
+
parse_sequence(indent, anchor)
|
|
119
|
+
elsif QUOTED_KEY_RE.match?(stripped)
|
|
120
|
+
# A mapping whose key is quoted, e.g. '"have: colon": value'.
|
|
121
|
+
parse_mapping(indent, anchor)
|
|
122
|
+
elsif (stripped.start_with?("\"") && stripped.end_with?("\"")) ||
|
|
123
|
+
(stripped.start_with?("'") && stripped.end_with?("'"))
|
|
124
|
+
# A whole-line quoted scalar, e.g. '"system: foo: bar"'. It may
|
|
125
|
+
# contain ": ", so it must be checked before MAPPING_KEY_RE.
|
|
126
|
+
parse_plain_scalar(indent, anchor)
|
|
127
|
+
elsif stripped =~ MAPPING_KEY_RE && !stripped.start_with?("!ruby/object:")
|
|
128
|
+
parse_mapping(indent, anchor)
|
|
129
|
+
elsif stripped.start_with?("!ruby/object:")
|
|
130
|
+
parse_tagged_node(indent, anchor)
|
|
131
|
+
elsif stripped.start_with?("|")
|
|
132
|
+
modifier = stripped[1..].to_s.strip
|
|
133
|
+
@lines.shift
|
|
134
|
+
register_anchor(anchor, Scalar.new(value: parse_block_scalar(indent, modifier)))
|
|
135
|
+
else
|
|
136
|
+
parse_plain_scalar(indent, anchor)
|
|
137
|
+
end
|
|
138
|
+
ensure
|
|
139
|
+
@depth -= 1
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def parse_sequence(indent, anchor)
|
|
143
|
+
items = []
|
|
144
|
+
while @lines.any?
|
|
145
|
+
line = @lines[0]
|
|
146
|
+
stripped = line.lstrip
|
|
147
|
+
break unless line.size - stripped.size == indent &&
|
|
148
|
+
(stripped.start_with?("- ") || stripped == "-")
|
|
149
|
+
content = @lines.shift.lstrip[1..].strip
|
|
150
|
+
item_anchor, content = extract_item_anchor(content)
|
|
151
|
+
item = parse_sequence_item(content, indent)
|
|
152
|
+
items << register_anchor(item_anchor, item)
|
|
153
|
+
end
|
|
154
|
+
register_anchor(anchor, Sequence.new(items: items))
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def parse_sequence_item(content, indent)
|
|
158
|
+
if content.start_with?("*")
|
|
159
|
+
parse_inline_alias(content)
|
|
160
|
+
elsif content.empty?
|
|
161
|
+
@lines.any? && current_indent > indent ? parse_node(indent) : nil
|
|
162
|
+
elsif content.start_with?("!ruby/object:")
|
|
163
|
+
parse_tagged_content(content.strip, indent)
|
|
164
|
+
elsif content.start_with?("!binary ")
|
|
165
|
+
parse_binary_value(content, indent)
|
|
166
|
+
elsif content.start_with?("-")
|
|
167
|
+
@lines.unshift("#{" " * (indent + 2)}#{content}")
|
|
168
|
+
parse_node(indent)
|
|
169
|
+
elsif QUOTED_KEY_RE.match?(content) ||
|
|
170
|
+
(content =~ MAPPING_KEY_RE && !content.start_with?("!ruby/object:"))
|
|
171
|
+
@lines.unshift("#{" " * (indent + 2)}#{content}")
|
|
172
|
+
parse_node(indent)
|
|
173
|
+
elsif content.start_with?("|")
|
|
174
|
+
Scalar.new(value: parse_block_scalar(indent, content[1..].to_s.strip))
|
|
175
|
+
else
|
|
176
|
+
parse_inline_scalar(content, indent)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def parse_mapping(indent, anchor)
|
|
181
|
+
pairs = []
|
|
182
|
+
while @lines.any?
|
|
183
|
+
line = @lines[0]
|
|
184
|
+
stripped = line.lstrip
|
|
185
|
+
break unless line.size - stripped.size == indent
|
|
186
|
+
|
|
187
|
+
if (match = QUOTED_KEY_RE.match(stripped))
|
|
188
|
+
key = coerce(match[1])
|
|
189
|
+
elsif (match = MAPPING_KEY_RE.match(stripped)) && !stripped.start_with?("!ruby/object:")
|
|
190
|
+
key = match[1].strip
|
|
191
|
+
key = decode_binary_tag(key) if key.start_with?("!binary ")
|
|
192
|
+
else
|
|
193
|
+
break
|
|
194
|
+
end
|
|
195
|
+
@lines.shift
|
|
196
|
+
val = strip_comment(match[2].to_s.strip)
|
|
197
|
+
|
|
198
|
+
val_anchor, val = consume_value_anchor(val)
|
|
199
|
+
value = parse_mapping_value(val, indent)
|
|
200
|
+
value = register_anchor(val_anchor, value) if val_anchor
|
|
201
|
+
|
|
202
|
+
pairs << [Scalar.new(value: key), value]
|
|
203
|
+
end
|
|
204
|
+
register_anchor(anchor, Mapping.new(pairs: pairs))
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def parse_mapping_value(val, indent)
|
|
208
|
+
if val.start_with?("*")
|
|
209
|
+
parse_inline_alias(val)
|
|
210
|
+
elsif val.start_with?("!ruby/object:")
|
|
211
|
+
parse_tagged_content(val.strip, indent)
|
|
212
|
+
elsif val.start_with?("!binary ")
|
|
213
|
+
parse_binary_value(val, indent)
|
|
214
|
+
elsif val.empty?
|
|
215
|
+
next_stripped = nil
|
|
216
|
+
next_indent = nil
|
|
217
|
+
if @lines.any?
|
|
218
|
+
next_stripped = @lines[0].lstrip
|
|
219
|
+
next_indent = @lines[0].size - next_stripped.size
|
|
220
|
+
end
|
|
221
|
+
if next_stripped &&
|
|
222
|
+
(next_stripped.start_with?("- ") || next_stripped == "-") &&
|
|
223
|
+
next_indent == indent
|
|
224
|
+
parse_node(indent)
|
|
225
|
+
else
|
|
226
|
+
parse_node(indent + 1)
|
|
227
|
+
end
|
|
228
|
+
elsif val == "[]"
|
|
229
|
+
Sequence.new
|
|
230
|
+
elsif val == "{}"
|
|
231
|
+
Mapping.new
|
|
232
|
+
elsif val.start_with?("|")
|
|
233
|
+
Scalar.new(value: parse_block_scalar(indent, val[1..].to_s.strip))
|
|
234
|
+
else
|
|
235
|
+
parse_inline_scalar(val, indent)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def parse_tagged_node(indent, anchor)
|
|
240
|
+
tag = @lines.shift.strip
|
|
241
|
+
nested = parse_node(indent)
|
|
242
|
+
apply_tag(nested, tag, anchor)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def parse_tagged_content(tag, indent)
|
|
246
|
+
nested = parse_node(indent)
|
|
247
|
+
apply_tag(nested, tag, nil)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def apply_tag(node, tag, anchor)
|
|
251
|
+
if node.is_a?(Mapping)
|
|
252
|
+
node.tag = tag
|
|
253
|
+
node.anchor = anchor
|
|
254
|
+
node
|
|
255
|
+
else
|
|
256
|
+
Mapping.new(pairs: [[Scalar.new(value: "value"), node]], tag: tag, anchor: anchor)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def parse_block_scalar(base_indent, modifier)
|
|
261
|
+
parts = []
|
|
262
|
+
block_indent = nil
|
|
263
|
+
|
|
264
|
+
while @lines.any?
|
|
265
|
+
line = @lines[0]
|
|
266
|
+
if line.strip.empty?
|
|
267
|
+
parts << "\n"
|
|
268
|
+
@lines.shift
|
|
269
|
+
else
|
|
270
|
+
line_indent = line.size - line.lstrip.size
|
|
271
|
+
break if line_indent <= base_indent
|
|
272
|
+
block_indent ||= line_indent
|
|
273
|
+
parts << @lines.shift[block_indent..].to_s << "\n"
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
res = parts.join
|
|
278
|
+
res.chomp! if modifier == "-" && res.end_with?("\n")
|
|
279
|
+
res
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def parse_plain_scalar(indent, anchor)
|
|
283
|
+
result = coerce(@lines.shift.strip)
|
|
284
|
+
return register_anchor(anchor, result) if result.is_a?(Mapping) || result.is_a?(Sequence)
|
|
285
|
+
|
|
286
|
+
while result.is_a?(String) && @lines.any? &&
|
|
287
|
+
!@lines[0].strip.empty? && current_indent > indent
|
|
288
|
+
result << " " << @lines.shift.strip
|
|
289
|
+
end
|
|
290
|
+
register_anchor(anchor, Scalar.new(value: result))
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def parse_inline_scalar(val, indent)
|
|
294
|
+
result = coerce(val)
|
|
295
|
+
return result if result.is_a?(Mapping) || result.is_a?(Sequence)
|
|
296
|
+
|
|
297
|
+
while result.is_a?(String) && @lines.any? &&
|
|
298
|
+
!@lines[0].strip.empty? && current_indent > indent
|
|
299
|
+
result << " " << @lines.shift.strip
|
|
300
|
+
end
|
|
301
|
+
Scalar.new(value: result)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def coerce(val, depth = 0)
|
|
305
|
+
raise_max_nesting! if depth > MAX_NESTING_DEPTH
|
|
306
|
+
|
|
307
|
+
val = val.sub(/^! /, "") if val.start_with?("! ")
|
|
308
|
+
|
|
309
|
+
if val =~ /^"(.*)"$/
|
|
310
|
+
$1.gsub(/\\(?:["\\0abtnvfre]|x\h{2})/) do |m|
|
|
311
|
+
STRING_UNESCAPES[m] || m[2..].to_i(16).chr(Encoding::UTF_8)
|
|
312
|
+
end
|
|
313
|
+
elsif val =~ /^'(.*)'$/
|
|
314
|
+
$1.gsub(/''/, "'")
|
|
315
|
+
elsif val == "true"
|
|
316
|
+
true
|
|
317
|
+
elsif val == "false"
|
|
318
|
+
false
|
|
319
|
+
elsif ["~", "null"].include?(val)
|
|
320
|
+
nil
|
|
321
|
+
elsif val == "{}"
|
|
322
|
+
Mapping.new
|
|
323
|
+
elsif val =~ /^\[(.*)\]$/
|
|
324
|
+
inner = $1.strip
|
|
325
|
+
return Sequence.new if inner.empty?
|
|
326
|
+
items = inner.split(/\s*,\s*/).reject(&:empty?).map {|e| Scalar.new(value: coerce(e, depth + 1)) }
|
|
327
|
+
Sequence.new(items: items)
|
|
328
|
+
elsif /\A\d{4}-\d{2}-\d{2}([ T]\d{2}:\d{2}:\d{2})?/.match?(val)
|
|
329
|
+
begin
|
|
330
|
+
Time.new(val)
|
|
331
|
+
rescue ArgumentError
|
|
332
|
+
# date-only format like "2024-06-15" is not supported by Time.new
|
|
333
|
+
if /\A(\d{4})-(\d{2})-(\d{2})\z/.match(val)
|
|
334
|
+
Time.utc($1.to_i, $2.to_i, $3.to_i)
|
|
335
|
+
else
|
|
336
|
+
val
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
elsif /^-?\d+$/.match?(val)
|
|
340
|
+
val.to_i
|
|
341
|
+
else
|
|
342
|
+
val
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def decode_binary_tag(str)
|
|
347
|
+
content = str.sub(/\A!binary\s+/, "")
|
|
348
|
+
content = $1 if content =~ /\A"(.*)"\z/ || content =~ /\A'(.*)'\z/
|
|
349
|
+
content.unpack1("m")
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def parse_binary_value(val, indent)
|
|
353
|
+
rest = val.sub(/\A!binary\s+/, "")
|
|
354
|
+
if rest.start_with?("|")
|
|
355
|
+
content = parse_block_scalar(indent, rest[1..].to_s.strip)
|
|
356
|
+
Scalar.new(value: content.unpack1("m"))
|
|
357
|
+
else
|
|
358
|
+
Scalar.new(value: decode_binary_tag(val))
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def parse_alias_ref
|
|
363
|
+
AliasRef.new(name: @lines.shift.lstrip[1..].strip)
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def parse_inline_alias(content)
|
|
367
|
+
AliasRef.new(name: content[1..].strip)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def current_indent
|
|
371
|
+
line = @lines[0]
|
|
372
|
+
line.size - line.lstrip.size
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def consume_anchor
|
|
376
|
+
line = @lines[0]
|
|
377
|
+
stripped = line.lstrip
|
|
378
|
+
return nil unless stripped.start_with?("&") && stripped =~ /^&(\S+)\s+/
|
|
379
|
+
|
|
380
|
+
anchor = $1
|
|
381
|
+
@lines[0] = line.sub(/&#{Regexp.escape(anchor)}\s+/, "")
|
|
382
|
+
anchor
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def extract_item_anchor(content)
|
|
386
|
+
return [nil, content] unless content =~ /^&(\S+)/
|
|
387
|
+
|
|
388
|
+
anchor = $1
|
|
389
|
+
[anchor, content.sub(/^&#{Regexp.escape(anchor)}\s*/, "")]
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def consume_value_anchor(val)
|
|
393
|
+
return [nil, val] unless val =~ /^&(\S+)\s+/
|
|
394
|
+
|
|
395
|
+
anchor = $1
|
|
396
|
+
[anchor, val.sub(/^&#{Regexp.escape(anchor)}\s+/, "")]
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def register_anchor(name, node)
|
|
400
|
+
if name
|
|
401
|
+
@anchors[name] = node
|
|
402
|
+
node.anchor = name if node.respond_to?(:anchor=)
|
|
403
|
+
end
|
|
404
|
+
node
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def raise_max_nesting!
|
|
408
|
+
message = "exceeded maximum nesting depth (#{MAX_NESTING_DEPTH})"
|
|
409
|
+
if defined?(Psych::VERSION)
|
|
410
|
+
raise Psych::SyntaxError.new(nil, 0, 0, 0, message, nil)
|
|
411
|
+
else
|
|
412
|
+
raise Psych::SyntaxError, message
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def skip_blank_and_comments
|
|
417
|
+
while @lines.any?
|
|
418
|
+
line = @lines[0]
|
|
419
|
+
stripped = line.lstrip
|
|
420
|
+
break unless stripped.empty? || stripped.start_with?("#")
|
|
421
|
+
@lines.shift
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def strip_comment(val)
|
|
426
|
+
return val unless val.include?("#")
|
|
427
|
+
return "" if val.lstrip.start_with?("#")
|
|
428
|
+
|
|
429
|
+
in_single = false
|
|
430
|
+
in_double = false
|
|
431
|
+
escape = false
|
|
432
|
+
|
|
433
|
+
val.each_char.with_index do |ch, i|
|
|
434
|
+
if escape
|
|
435
|
+
escape = false
|
|
436
|
+
next
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
if in_single
|
|
440
|
+
in_single = false if ch == "'"
|
|
441
|
+
elsif in_double
|
|
442
|
+
if ch == "\\"
|
|
443
|
+
escape = true
|
|
444
|
+
elsif ch == '"'
|
|
445
|
+
in_double = false
|
|
446
|
+
end
|
|
447
|
+
else
|
|
448
|
+
case ch
|
|
449
|
+
when "'" then in_single = true
|
|
450
|
+
when '"' then in_double = true
|
|
451
|
+
when "#"
|
|
452
|
+
# A "#" starts a comment only when preceded by whitespace.
|
|
453
|
+
return val[0...i].rstrip if [" ", "\t"].include?(val[i - 1])
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
val
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
class Builder
|
|
463
|
+
VALID_OPS = %w[= != > < >= <= ~>].freeze
|
|
464
|
+
ARRAY_FIELDS = %w[files test_files executables extra_rdoc_files].freeze
|
|
465
|
+
MAX_ALIAS_RESOLUTIONS = 1_000
|
|
466
|
+
|
|
467
|
+
def initialize(permitted_classes: [], permitted_symbols: [], aliases: true)
|
|
468
|
+
@permitted_classes = permitted_classes.map {|c| "!ruby/object:#{c}" }
|
|
469
|
+
@permitted_symbols = permitted_symbols
|
|
470
|
+
@aliases = aliases
|
|
471
|
+
@anchor_values = {}
|
|
472
|
+
@alias_count = 0
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
def build(node)
|
|
476
|
+
return nil if node.nil?
|
|
477
|
+
|
|
478
|
+
result = build_node(node)
|
|
479
|
+
|
|
480
|
+
if result.is_a?(Hash) && result[:tag] == "!ruby/object:Gem::Specification"
|
|
481
|
+
build_specification(result)
|
|
482
|
+
else
|
|
483
|
+
result
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
private
|
|
488
|
+
|
|
489
|
+
def build_node(node)
|
|
490
|
+
case node
|
|
491
|
+
when nil then nil
|
|
492
|
+
when AliasRef then resolve_alias(node)
|
|
493
|
+
when Scalar then store_anchor(node.anchor, node.value)
|
|
494
|
+
when Mapping then build_mapping(node)
|
|
495
|
+
when Sequence then store_anchor(node.anchor, node.items.map {|item| build_node(item) })
|
|
496
|
+
else node # already a Ruby object
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def resolve_alias(node)
|
|
501
|
+
raise Psych::AliasesNotEnabled unless @aliases
|
|
502
|
+
@alias_count += 1
|
|
503
|
+
if @alias_count > MAX_ALIAS_RESOLUTIONS
|
|
504
|
+
raise Psych::BadAlias, "exceeded maximum alias resolutions (#{MAX_ALIAS_RESOLUTIONS})"
|
|
505
|
+
end
|
|
506
|
+
unless @anchor_values.key?(node.name)
|
|
507
|
+
klass = defined?(Psych::AnchorNotDefined) ? Psych::AnchorNotDefined : Psych::BadAlias
|
|
508
|
+
raise klass, "An alias referenced an unknown anchor: #{node.name}"
|
|
509
|
+
end
|
|
510
|
+
@anchor_values.fetch(node.name)
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
def store_anchor(name, value)
|
|
514
|
+
@anchor_values[name] = value if name
|
|
515
|
+
value
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def build_mapping(node)
|
|
519
|
+
validate_tag!(node.tag) if node.tag
|
|
520
|
+
|
|
521
|
+
result = case node.tag
|
|
522
|
+
when "!ruby/object:Gem::Version"
|
|
523
|
+
build_version(node)
|
|
524
|
+
when "!ruby/object:Gem::Platform"
|
|
525
|
+
build_platform(node)
|
|
526
|
+
when "!ruby/object:Gem::Requirement", "!ruby/object:Gem::Version::Requirement"
|
|
527
|
+
build_requirement(node)
|
|
528
|
+
when "!ruby/object:Gem::Dependency"
|
|
529
|
+
build_dependency(node)
|
|
530
|
+
when nil
|
|
531
|
+
build_hash(node)
|
|
532
|
+
when "!ruby/object:Gem::Specification"
|
|
533
|
+
hash = build_hash(node)
|
|
534
|
+
hash[:tag] = node.tag
|
|
535
|
+
hash
|
|
536
|
+
else
|
|
537
|
+
raise ArgumentError, "undefined class/module #{node.tag.sub("!ruby/object:", "")}"
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
store_anchor(node.anchor, result)
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
def build_hash(node)
|
|
544
|
+
result = {}
|
|
545
|
+
node.pairs.each do |key_node, value_node|
|
|
546
|
+
key = key_node.is_a?(Scalar) ? key_node.value.to_s : build_node(key_node).to_s
|
|
547
|
+
value = build_node(value_node)
|
|
548
|
+
|
|
549
|
+
if ARRAY_FIELDS.include?(key)
|
|
550
|
+
value = normalize_array_field(value)
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
result[key] = value
|
|
554
|
+
end
|
|
555
|
+
result
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
def build_version(node)
|
|
559
|
+
hash = pairs_to_hash(node)
|
|
560
|
+
Gem::Version.new((hash["version"] || hash["value"]).to_s)
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
PLATFORM_FIELDS = %w[cpu os version].freeze
|
|
564
|
+
PLATFORM_ALLOWED_IVARS = %w[cpu os version value].freeze
|
|
565
|
+
|
|
566
|
+
def build_platform(node)
|
|
567
|
+
hash = pairs_to_hash(node)
|
|
568
|
+
if (hash.keys & PLATFORM_FIELDS).any?
|
|
569
|
+
Gem::Platform.new([hash["cpu"], hash["os"], hash["version"]])
|
|
570
|
+
elsif hash["value"].is_a?(Array)
|
|
571
|
+
# Malformed platform (e.g. sequence instead of mapping).
|
|
572
|
+
# Return the raw value so yaml_initialize handles it like Psych does.
|
|
573
|
+
hash["value"]
|
|
574
|
+
else
|
|
575
|
+
plat = Gem::Platform.allocate
|
|
576
|
+
hash.each do |k, v|
|
|
577
|
+
plat.instance_variable_set(:"@#{k}", v) if PLATFORM_ALLOWED_IVARS.include?(k)
|
|
578
|
+
end
|
|
579
|
+
plat
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
def build_requirement(node)
|
|
584
|
+
r = Gem::Requirement.allocate
|
|
585
|
+
hash = pairs_to_hash(node)
|
|
586
|
+
reqs = hash["requirements"] || hash["value"]
|
|
587
|
+
|
|
588
|
+
if reqs.is_a?(Array) && !reqs.empty?
|
|
589
|
+
safe_reqs = []
|
|
590
|
+
reqs.each do |item|
|
|
591
|
+
if item.is_a?(Array) && item.size == 2
|
|
592
|
+
op = item[0].to_s
|
|
593
|
+
ver = item[1]
|
|
594
|
+
if VALID_OPS.include?(op)
|
|
595
|
+
version_obj = ver.is_a?(Gem::Version) ? ver : Gem::Version.new(ver.to_s)
|
|
596
|
+
safe_reqs << [op, version_obj]
|
|
597
|
+
end
|
|
598
|
+
elsif item.is_a?(String)
|
|
599
|
+
parsed = Gem::Requirement.parse(item)
|
|
600
|
+
safe_reqs << parsed
|
|
601
|
+
end
|
|
602
|
+
rescue Gem::Requirement::BadRequirementError, Gem::Version::BadVersionError
|
|
603
|
+
# Skip malformed items silently
|
|
604
|
+
end
|
|
605
|
+
reqs = safe_reqs unless safe_reqs.empty?
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
r.instance_variable_set(:@requirements, reqs)
|
|
609
|
+
r
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
def build_dependency(node)
|
|
613
|
+
hash = pairs_to_hash(node)
|
|
614
|
+
d = Gem::Dependency.allocate
|
|
615
|
+
d.instance_variable_set(:@name, hash["name"])
|
|
616
|
+
|
|
617
|
+
d.instance_variable_set(:@requirement, hash["requirement"] || hash["version_requirements"])
|
|
618
|
+
|
|
619
|
+
raw_type = hash["type"]
|
|
620
|
+
if raw_type
|
|
621
|
+
name = raw_type.to_s.sub(/^:/, "")
|
|
622
|
+
validate_symbol!(name)
|
|
623
|
+
type = name.to_sym
|
|
624
|
+
else
|
|
625
|
+
type = :runtime
|
|
626
|
+
end
|
|
627
|
+
d.instance_variable_set(:@type, type)
|
|
628
|
+
|
|
629
|
+
d.instance_variable_set(:@prerelease, ["true", true].include?(hash["prerelease"]))
|
|
630
|
+
d.instance_variable_set(:@version_requirements, d.instance_variable_get(:@requirement))
|
|
631
|
+
d
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
def build_specification(hash)
|
|
635
|
+
spec = Gem::Specification.allocate
|
|
636
|
+
|
|
637
|
+
normalize_specification_version!(hash)
|
|
638
|
+
normalize_array_fields!(hash)
|
|
639
|
+
|
|
640
|
+
spec.yaml_initialize("!ruby/object:Gem::Specification", hash)
|
|
641
|
+
spec
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
def pairs_to_hash(node)
|
|
645
|
+
result = {}
|
|
646
|
+
node.pairs.each do |key_node, value_node|
|
|
647
|
+
key = key_node.is_a?(Scalar) ? key_node.value.to_s : build_node(key_node).to_s
|
|
648
|
+
result[key] = build_node(value_node)
|
|
649
|
+
end
|
|
650
|
+
result
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
def validate_tag!(tag)
|
|
654
|
+
return if @permitted_classes.include?(tag)
|
|
655
|
+
raise_disallowed_class!(tag)
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
def raise_disallowed_class!(tag)
|
|
659
|
+
if defined?(Psych::VERSION)
|
|
660
|
+
raise Psych::DisallowedClass.new("load", tag)
|
|
661
|
+
else
|
|
662
|
+
raise Psych::DisallowedClass, "Tried to load unspecified class: #{tag}"
|
|
663
|
+
end
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
def validate_symbol!(name)
|
|
667
|
+
return if @permitted_symbols.empty? || @permitted_symbols.include?(name)
|
|
668
|
+
|
|
669
|
+
label = ":#{name}"
|
|
670
|
+
if defined?(Psych::VERSION)
|
|
671
|
+
raise Psych::DisallowedClass.new("load", label)
|
|
672
|
+
else
|
|
673
|
+
raise Psych::DisallowedClass, "Tried to load unspecified class: #{label}"
|
|
674
|
+
end
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
def normalize_specification_version!(hash)
|
|
678
|
+
val = hash["specification_version"]
|
|
679
|
+
return unless val && !val.is_a?(Integer)
|
|
680
|
+
hash["specification_version"] = val.to_i if val.is_a?(String) && /\A\d+\z/.match?(val)
|
|
681
|
+
end
|
|
682
|
+
|
|
683
|
+
def normalize_array_fields!(hash)
|
|
684
|
+
ARRAY_FIELDS.each do |field|
|
|
685
|
+
hash[field] = normalize_array_field(hash[field]) if hash[field]
|
|
686
|
+
end
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
def normalize_array_field(value)
|
|
690
|
+
if value.is_a?(Hash)
|
|
691
|
+
value.values.flatten.compact
|
|
692
|
+
elsif !value.is_a?(Array) && value
|
|
693
|
+
[value].flatten.compact
|
|
694
|
+
else
|
|
695
|
+
value
|
|
696
|
+
end
|
|
697
|
+
end
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
class Emitter
|
|
701
|
+
STRING_ESCAPES = {
|
|
702
|
+
"\\" => "\\\\",
|
|
703
|
+
"\"" => "\\\"",
|
|
704
|
+
"\0" => "\\0",
|
|
705
|
+
"\a" => "\\a",
|
|
706
|
+
"\b" => "\\b",
|
|
707
|
+
"\t" => "\\t",
|
|
708
|
+
"\n" => "\\n",
|
|
709
|
+
"\v" => "\\v",
|
|
710
|
+
"\f" => "\\f",
|
|
711
|
+
"\r" => "\\r",
|
|
712
|
+
"\e" => "\\e",
|
|
713
|
+
}.freeze
|
|
714
|
+
|
|
715
|
+
def emit(obj)
|
|
716
|
+
"---#{emit_node(obj, 0)}"
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
private
|
|
720
|
+
|
|
721
|
+
def emit_node(obj, indent, quote: false)
|
|
722
|
+
case obj
|
|
723
|
+
when Gem::Specification then emit_specification(obj, indent)
|
|
724
|
+
when Gem::Version then emit_version(obj, indent)
|
|
725
|
+
when Gem::Platform then emit_platform(obj, indent)
|
|
726
|
+
when Gem::Requirement then emit_requirement(obj, indent)
|
|
727
|
+
when Gem::Dependency then emit_dependency(obj, indent)
|
|
728
|
+
when Hash then emit_hash(obj, indent)
|
|
729
|
+
when Array then emit_array(obj, indent)
|
|
730
|
+
when Time then emit_time(obj)
|
|
731
|
+
when String then emit_string(obj, indent, quote: quote)
|
|
732
|
+
when NilClass
|
|
733
|
+
"\n"
|
|
734
|
+
when Numeric, Symbol, TrueClass, FalseClass
|
|
735
|
+
" #{obj.inspect}\n"
|
|
736
|
+
else
|
|
737
|
+
" #{quote_string(obj.to_s)}\n"
|
|
738
|
+
end
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
def emit_specification(spec, indent)
|
|
742
|
+
parts = [" !ruby/object:Gem::Specification\n"]
|
|
743
|
+
parts << "#{pad(indent)}name:#{emit_node(spec.name, indent + 2)}"
|
|
744
|
+
parts << "#{pad(indent)}version:#{emit_node(spec.version, indent + 2)}"
|
|
745
|
+
parts << "#{pad(indent)}platform: #{spec.platform}\n"
|
|
746
|
+
if spec.platform.to_s != spec.original_platform.to_s
|
|
747
|
+
parts << "#{pad(indent)}original_platform: #{spec.original_platform}\n"
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
attributes = Gem::Specification.attribute_names.map(&:to_s).sort - %w[name version platform]
|
|
751
|
+
attributes.each do |name|
|
|
752
|
+
val = spec.instance_variable_get("@#{name}")
|
|
753
|
+
next if val.nil?
|
|
754
|
+
parts << "#{pad(indent)}#{name}:#{emit_node(val, indent + 2)}"
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
res = parts.join
|
|
758
|
+
res << "\n" unless res.end_with?("\n")
|
|
759
|
+
res
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
def emit_version(ver, indent)
|
|
763
|
+
" !ruby/object:Gem::Version\n" \
|
|
764
|
+
"#{pad(indent)}version: #{emit_node(ver.version.to_s, indent + 2).lstrip}"
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
def emit_platform(plat, indent)
|
|
768
|
+
" !ruby/object:Gem::Platform\n" \
|
|
769
|
+
"#{pad(indent)}cpu:#{emit_node(plat.cpu, indent + 2)}" \
|
|
770
|
+
"#{pad(indent)}os:#{emit_node(plat.os, indent + 2)}" \
|
|
771
|
+
"#{pad(indent)}version:#{emit_node(plat.version, indent + 2)}"
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
def emit_requirement(req, indent)
|
|
775
|
+
" !ruby/object:Gem::Requirement\n" \
|
|
776
|
+
"#{pad(indent)}requirements:#{emit_node(req.requirements, indent + 2)}"
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
def emit_dependency(dep, indent)
|
|
780
|
+
[
|
|
781
|
+
" !ruby/object:Gem::Dependency\n",
|
|
782
|
+
"#{pad(indent)}name: #{emit_node(dep.name, indent + 2).lstrip}",
|
|
783
|
+
"#{pad(indent)}requirement:#{emit_node(dep.requirement, indent + 2)}",
|
|
784
|
+
"#{pad(indent)}type: #{emit_node(dep.type, indent + 2).lstrip}",
|
|
785
|
+
"#{pad(indent)}prerelease: #{emit_node(dep.prerelease?, indent + 2).lstrip}",
|
|
786
|
+
"#{pad(indent)}version_requirements:#{emit_node(dep.requirement, indent + 2)}",
|
|
787
|
+
].join
|
|
788
|
+
end
|
|
789
|
+
|
|
790
|
+
def emit_hash(hash, indent)
|
|
791
|
+
if hash.empty?
|
|
792
|
+
" {}\n"
|
|
793
|
+
else
|
|
794
|
+
parts = ["\n"]
|
|
795
|
+
hash.each do |k, v|
|
|
796
|
+
is_symbol = k.is_a?(Symbol) || (k.is_a?(String) && k.start_with?(":"))
|
|
797
|
+
key_str = k.is_a?(Symbol) ? k.inspect : k.to_s
|
|
798
|
+
key_str = quote_string(key_str) if !is_symbol && needs_quoting?(key_str)
|
|
799
|
+
parts << "#{pad(indent)}#{key_str}:#{emit_node(v, indent + 2, quote: is_symbol)}"
|
|
800
|
+
end
|
|
801
|
+
parts.join
|
|
802
|
+
end
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
def emit_array(arr, indent)
|
|
806
|
+
if arr.empty?
|
|
807
|
+
" []\n"
|
|
808
|
+
else
|
|
809
|
+
parts = ["\n"]
|
|
810
|
+
arr.each do |v|
|
|
811
|
+
parts << "#{pad(indent)}-#{emit_node(v, indent + 2)}"
|
|
812
|
+
end
|
|
813
|
+
parts.join
|
|
814
|
+
end
|
|
815
|
+
end
|
|
816
|
+
|
|
817
|
+
def emit_time(time)
|
|
818
|
+
" #{time.utc.strftime("%Y-%m-%d %H:%M:%S.%N Z")}\n"
|
|
819
|
+
end
|
|
820
|
+
|
|
821
|
+
def emit_string(str, indent, quote: false)
|
|
822
|
+
# A CR cannot be represented in a block scalar because parsing
|
|
823
|
+
# normalizes CRLF line breaks to LF, so quote and escape instead.
|
|
824
|
+
if str.include?("\n") && !str.include?("\r")
|
|
825
|
+
emit_block_scalar(str, indent)
|
|
826
|
+
elsif needs_quoting?(str, quote)
|
|
827
|
+
" #{quote_string(str)}\n"
|
|
828
|
+
else
|
|
829
|
+
" #{str}\n"
|
|
830
|
+
end
|
|
831
|
+
end
|
|
832
|
+
|
|
833
|
+
def emit_block_scalar(str, indent)
|
|
834
|
+
parts = [str.end_with?("\n") ? " |\n" : " |-\n"]
|
|
835
|
+
str.each_line do |line|
|
|
836
|
+
parts << "#{pad(indent + 2)}#{line}"
|
|
837
|
+
end
|
|
838
|
+
res = parts.join
|
|
839
|
+
res << "\n" unless res.end_with?("\n")
|
|
840
|
+
res
|
|
841
|
+
end
|
|
842
|
+
|
|
843
|
+
def needs_quoting?(str, quote = false)
|
|
844
|
+
quote || str.empty? || str != str.strip || str =~ /[[:cntrl:]]/ ||
|
|
845
|
+
str =~ /^[!*&:@%$"'|`]/ || str =~ /^-?\d+(\.\d+)?$/ || str =~ /^[<>=-]/ ||
|
|
846
|
+
str == "true" || str == "false" || str == "nil" || str == "null" || str == "~" ||
|
|
847
|
+
str.include?(":") || str.include?("#") || str.include?("[") || str.include?("]") ||
|
|
848
|
+
str.include?("{") || str.include?("}") || str.include?(",")
|
|
849
|
+
end
|
|
850
|
+
|
|
851
|
+
def quote_string(str)
|
|
852
|
+
%("#{str.gsub(/[\\"]|[[:cntrl:]]/) {|c| STRING_ESCAPES[c] || format("\\x%02X", c.ord) }}")
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
def pad(indent)
|
|
856
|
+
" " * indent
|
|
857
|
+
end
|
|
858
|
+
end
|
|
859
|
+
|
|
860
|
+
module_function
|
|
861
|
+
|
|
862
|
+
def dump(obj)
|
|
863
|
+
Emitter.new.emit(obj)
|
|
864
|
+
end
|
|
865
|
+
|
|
866
|
+
def load(str, permitted_classes: [], permitted_symbols: [], aliases: true)
|
|
867
|
+
raise TypeError, "no implicit conversion of nil into String" if str.nil?
|
|
868
|
+
return nil if str.empty?
|
|
869
|
+
|
|
870
|
+
ast = Parser.new(str).parse
|
|
871
|
+
return nil if ast.nil?
|
|
872
|
+
|
|
873
|
+
Builder.new(
|
|
874
|
+
permitted_classes: permitted_classes,
|
|
875
|
+
permitted_symbols: permitted_symbols,
|
|
876
|
+
aliases: aliases
|
|
877
|
+
).build(ast)
|
|
878
|
+
end
|
|
879
|
+
end
|
|
880
|
+
end
|