ruby-next-core 0.15.3 → 1.0.0.rc.1
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/CHANGELOG.md +28 -0
- data/README.md +118 -48
- data/bin/mspec +11 -0
- data/lib/.rbnext/2.1/ruby-next/commands/nextify.rb +295 -0
- data/lib/.rbnext/2.1/ruby-next/core.rb +10 -2
- data/lib/.rbnext/2.1/ruby-next/language.rb +54 -10
- data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +82 -2
- data/lib/.rbnext/2.3/ruby-next/config.rb +79 -0
- data/lib/.rbnext/2.3/ruby-next/core/data.rb +159 -0
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/2.7/pattern_matching.rb +2 -2
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +6 -32
- data/lib/.rbnext/2.3/ruby-next/utils.rb +3 -22
- data/lib/.rbnext/2.6/ruby-next/core/data.rb +159 -0
- data/lib/.rbnext/2.7/ruby-next/core/data.rb +159 -0
- data/lib/.rbnext/2.7/ruby-next/core.rb +10 -2
- data/lib/.rbnext/2.7/ruby-next/language/paco_parsers/string_literals.rb +109 -0
- data/lib/.rbnext/2.7/ruby-next/language/rewriters/2.7/pattern_matching.rb +2 -2
- data/lib/.rbnext/2.7/ruby-next/language/rewriters/text.rb +132 -0
- data/lib/.rbnext/3.2/ruby-next/commands/base.rb +55 -0
- data/lib/.rbnext/3.2/ruby-next/language/rewriters/2.7/pattern_matching.rb +1095 -0
- data/lib/.rbnext/3.2/ruby-next/rubocop.rb +210 -0
- data/lib/ruby-next/commands/nextify.rb +84 -2
- data/lib/ruby-next/config.rb +27 -0
- data/lib/ruby-next/core/data.rb +159 -0
- data/lib/ruby-next/core/matchdata/deconstruct.rb +9 -0
- data/lib/ruby-next/core/matchdata/deconstruct_keys.rb +20 -0
- data/lib/ruby-next/core/matchdata/named_captures.rb +11 -0
- data/lib/ruby-next/core/refinement/import.rb +44 -36
- data/lib/ruby-next/core/time/deconstruct_keys.rb +30 -0
- data/lib/ruby-next/core.rb +10 -2
- data/lib/ruby-next/irb.rb +2 -2
- data/lib/ruby-next/language/bootsnap.rb +2 -25
- data/lib/ruby-next/language/paco_parser.rb +7 -0
- data/lib/ruby-next/language/paco_parsers/base.rb +47 -0
- data/lib/ruby-next/language/paco_parsers/comments.rb +26 -0
- data/lib/ruby-next/language/paco_parsers/string_literals.rb +109 -0
- data/lib/ruby-next/language/parser.rb +24 -2
- data/lib/ruby-next/language/rewriters/3.0/args_forward_leading.rb +2 -2
- data/lib/ruby-next/language/rewriters/3.1/oneline_pattern_parensless.rb +1 -1
- data/lib/ruby-next/language/rewriters/3.2/anonymous_restargs.rb +104 -0
- data/lib/ruby-next/language/rewriters/abstract.rb +57 -0
- data/lib/ruby-next/language/rewriters/base.rb +6 -32
- data/lib/ruby-next/language/rewriters/edge/it_param.rb +58 -0
- data/lib/ruby-next/language/rewriters/edge.rb +12 -0
- data/lib/ruby-next/language/rewriters/proposed/bind_vars_pattern.rb +3 -0
- data/lib/ruby-next/language/rewriters/proposed/method_reference.rb +9 -20
- data/lib/ruby-next/language/rewriters/text.rb +132 -0
- data/lib/ruby-next/language/runtime.rb +9 -86
- data/lib/ruby-next/language/setup.rb +5 -2
- data/lib/ruby-next/language/unparser.rb +5 -0
- data/lib/ruby-next/language.rb +54 -10
- data/lib/ruby-next/pry.rb +1 -1
- data/lib/ruby-next/rubocop.rb +2 -0
- data/lib/ruby-next/utils.rb +3 -22
- data/lib/ruby-next/version.rb +1 -1
- data/lib/uby-next.rb +2 -2
- metadata +65 -12
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyNext
|
4
|
+
# Mininum Ruby version supported by RubyNext
|
5
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("2.2.0")
|
6
|
+
|
7
|
+
# Where to store transpiled files (relative from the project LOAD_PATH, usually `lib/`)
|
8
|
+
RUBY_NEXT_DIR = ".rbnext"
|
9
|
+
|
10
|
+
# Defines last minor version for every major version
|
11
|
+
LAST_MINOR_VERSIONS = {
|
12
|
+
2 => 8, # 2.8 is required for backward compatibility: some gems already uses it
|
13
|
+
3 => 1
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
LATEST_VERSION = [3, 1].freeze
|
17
|
+
|
18
|
+
# A virtual version number used for proposed features
|
19
|
+
NEXT_VERSION = "1995.next.0"
|
20
|
+
|
21
|
+
class << self
|
22
|
+
# TruffleRuby claims its RUBY_VERSION to be X.Y while not supporting all the features
|
23
|
+
# Currently (23.0.1), it still doesn't support pattern matching, although claims to be "like 3.1".
|
24
|
+
# So, we fallback to 2.6.5 (since we cannot use 2.7).
|
25
|
+
if defined?(TruffleRuby)
|
26
|
+
def current_ruby_version
|
27
|
+
"2.6.5"
|
28
|
+
end
|
29
|
+
else
|
30
|
+
def current_ruby_version
|
31
|
+
::RUBY_VERSION
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns true if we want to use edge syntax
|
36
|
+
def edge_syntax?
|
37
|
+
%w[y true 1].include?(ENV["RUBY_NEXT_EDGE"])
|
38
|
+
end
|
39
|
+
|
40
|
+
def proposed_syntax?
|
41
|
+
%w[y true 1].include?(ENV["RUBY_NEXT_PROPOSED"])
|
42
|
+
end
|
43
|
+
|
44
|
+
def next_ruby_version(version = current_ruby_version)
|
45
|
+
return if version == Gem::Version.new(NEXT_VERSION)
|
46
|
+
|
47
|
+
major, minor = Gem::Version.new(version).segments.map(&:to_i)
|
48
|
+
|
49
|
+
return Gem::Version.new(NEXT_VERSION) if major >= LATEST_VERSION.first && minor >= LATEST_VERSION.last
|
50
|
+
|
51
|
+
nxt =
|
52
|
+
if LAST_MINOR_VERSIONS[major] == minor
|
53
|
+
"#{major + 1}.0.0"
|
54
|
+
else
|
55
|
+
"#{major}.#{minor + 1}.0"
|
56
|
+
end
|
57
|
+
|
58
|
+
Gem::Version.new(nxt)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Load transpile settings from the RC file (nextify command flags)
|
62
|
+
def load_from_rc(path = ".rbnextrc")
|
63
|
+
return unless File.exist?(path)
|
64
|
+
|
65
|
+
require "yaml"
|
66
|
+
|
67
|
+
args = ((((__safe_lvar__ = ((((__safe_lvar__ = ((((__safe_lvar__ = YAML.load_file(path)) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.fetch("nextify", ""))) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.lines)) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.flat_map { |line|
|
68
|
+
line.chomp.split(/\s+/)
|
69
|
+
})
|
70
|
+
|
71
|
+
ENV["RUBY_NEXT_EDGE"] ||= "true" if args.delete("--edge")
|
72
|
+
ENV["RUBY_NEXT_PROPOSED"] ||= "true" if args.delete("--proposed")
|
73
|
+
ENV["RUBY_NEXT_TRANSPILE_MODE"] ||= "rewrite" if args.delete("--transpile-mode=rewrite")
|
74
|
+
ENV["RUBY_NEXT_TRANSPILE_MODE"] ||= "ast" if args.delete("--transpile-mode=ast")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
load_from_rc
|
79
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The code below originates from https://github.com/saturnflyer/polyfill-data
|
4
|
+
|
5
|
+
if !Object.const_defined?(:Data) || !Data.respond_to?(:define)
|
6
|
+
|
7
|
+
# Drop legacy Data class
|
8
|
+
begin
|
9
|
+
Object.send(:remove_const, :Data)
|
10
|
+
rescue
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
class Data < Object
|
15
|
+
using RubyNext
|
16
|
+
|
17
|
+
class << self
|
18
|
+
undef_method :new
|
19
|
+
attr_reader :members
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.define(*args, &block)
|
23
|
+
raise ArgumentError if args.any?(/=/)
|
24
|
+
if block
|
25
|
+
mod = Module.new
|
26
|
+
mod.define_singleton_method(:_) do |klass|
|
27
|
+
klass.class_eval(&block)
|
28
|
+
end
|
29
|
+
arity_converter = mod.method(:_)
|
30
|
+
end
|
31
|
+
klass = ::Class.new(self)
|
32
|
+
|
33
|
+
klass.instance_variable_set(:@members, args.map(&:to_sym).freeze)
|
34
|
+
|
35
|
+
klass.define_singleton_method(:new) do |*new_args, **new_kwargs, &block|
|
36
|
+
init_kwargs = if new_args.any?
|
37
|
+
raise ArgumentError, "unknown arguments #{new_args[members.size..-1].join(", ")}" if new_args.size > members.size
|
38
|
+
members.take(new_args.size).zip(new_args).to_h
|
39
|
+
else
|
40
|
+
new_kwargs
|
41
|
+
end
|
42
|
+
|
43
|
+
allocate.tap do |instance|
|
44
|
+
instance.send(:initialize, **init_kwargs, &block)
|
45
|
+
end.freeze
|
46
|
+
end
|
47
|
+
|
48
|
+
class << klass
|
49
|
+
alias_method :[], :new
|
50
|
+
undef_method :define
|
51
|
+
end
|
52
|
+
|
53
|
+
args.each do |arg|
|
54
|
+
if klass.method_defined?(arg)
|
55
|
+
raise ArgumentError, "duplicate member #{arg}"
|
56
|
+
end
|
57
|
+
klass.define_method(arg) do
|
58
|
+
@attributes[arg]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if arity_converter
|
63
|
+
klass.class_eval(&arity_converter)
|
64
|
+
end
|
65
|
+
|
66
|
+
klass
|
67
|
+
end
|
68
|
+
|
69
|
+
def members
|
70
|
+
self.class.members
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize(**kwargs)
|
74
|
+
kwargs_size = kwargs.size
|
75
|
+
members_size = members.size
|
76
|
+
|
77
|
+
if kwargs_size > members_size
|
78
|
+
extras = kwargs.except(*members).keys
|
79
|
+
|
80
|
+
if extras.size > 1
|
81
|
+
raise ArgumentError, "unknown keywords: #{extras.map { |_1| ":#{_1}" }.join(", ")}"
|
82
|
+
else
|
83
|
+
raise ArgumentError, "unknown keyword: :#{extras.first}"
|
84
|
+
end
|
85
|
+
elsif kwargs_size < members_size
|
86
|
+
missing = members.select { |k| !kwargs.include?(k) }
|
87
|
+
|
88
|
+
if missing.size > 1
|
89
|
+
raise ArgumentError, "missing keywords: #{missing.map { |_1| ":#{_1}" }.join(", ")}"
|
90
|
+
else
|
91
|
+
raise ArgumentError, "missing keyword: :#{missing.first}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
@attributes = members.map { |m| [m, kwargs[m]] }.to_h
|
96
|
+
end
|
97
|
+
|
98
|
+
def deconstruct
|
99
|
+
@attributes.values
|
100
|
+
end
|
101
|
+
|
102
|
+
def deconstruct_keys(array)
|
103
|
+
raise TypeError unless array.is_a?(Array) || array.nil?
|
104
|
+
return @attributes if ((((__safe_lvar__ = array) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.first).nil?
|
105
|
+
|
106
|
+
@attributes.slice(*array)
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_h(&block)
|
110
|
+
@attributes.to_h(&block)
|
111
|
+
end
|
112
|
+
|
113
|
+
def hash
|
114
|
+
to_h.hash
|
115
|
+
end
|
116
|
+
|
117
|
+
def eql?(other)
|
118
|
+
self.class == other.class && hash == other.hash
|
119
|
+
end
|
120
|
+
|
121
|
+
def ==(other)
|
122
|
+
self.class == other.class && to_h == other.to_h
|
123
|
+
end
|
124
|
+
|
125
|
+
def inspect
|
126
|
+
attribute_markers = @attributes.map do |key, value|
|
127
|
+
insect_key = key.to_s.start_with?("@") ? ":#{key}" : key
|
128
|
+
"#{insect_key}=#{value}"
|
129
|
+
end.join(", ")
|
130
|
+
|
131
|
+
display = ["data", self.class.name, attribute_markers].compact.join(" ")
|
132
|
+
|
133
|
+
"#<#{display}>"
|
134
|
+
end
|
135
|
+
alias_method :to_s, :inspect
|
136
|
+
|
137
|
+
def with(**kwargs)
|
138
|
+
return self if kwargs.empty?
|
139
|
+
|
140
|
+
self.class.new(**@attributes.merge(kwargs))
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def marshal_dump
|
146
|
+
@attributes
|
147
|
+
end
|
148
|
+
|
149
|
+
def marshal_load(attributes)
|
150
|
+
@attributes = attributes
|
151
|
+
freeze
|
152
|
+
end
|
153
|
+
|
154
|
+
def initialize_copy(source)
|
155
|
+
super.freeze
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
@@ -185,7 +185,7 @@ module RubyNext
|
|
185
185
|
# rubocop:disable Style/MissingRespondToMissing
|
186
186
|
class Noop < Base
|
187
187
|
# Return node itself, no memoization
|
188
|
-
def method_missing(mid, node, *)
|
188
|
+
def method_missing(mid, node, *__rest__)
|
189
189
|
node
|
190
190
|
end
|
191
191
|
end
|
@@ -1035,7 +1035,7 @@ module RubyNext
|
|
1035
1035
|
s(:send, node, :respond_to?, mid.to_ast_node)
|
1036
1036
|
end
|
1037
1037
|
|
1038
|
-
def respond_to_missing?(mid, *)
|
1038
|
+
def respond_to_missing?(mid, *__rest__)
|
1039
1039
|
return true if mid.to_s.match?(/_(clause|array_element)/)
|
1040
1040
|
super
|
1041
1041
|
end
|
@@ -12,7 +12,7 @@ module RubyNext
|
|
12
12
|
|
13
13
|
MSG
|
14
14
|
|
15
|
-
class Base <
|
15
|
+
class Base < Abstract
|
16
16
|
class LocalsTracker
|
17
17
|
using(Module.new do
|
18
18
|
refine ::Parser::AST::Node do
|
@@ -64,39 +64,15 @@ module RubyNext
|
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
|
-
|
68
|
-
# Returns true if the syntax is not supported
|
69
|
-
# by the current Ruby (performs syntax check, not version check)
|
70
|
-
def unsupported_syntax?
|
71
|
-
save_verbose, $VERBOSE = $VERBOSE, nil
|
72
|
-
eval_mid = Kernel.respond_to?(:eval_without_ruby_next) ? :eval_without_ruby_next : :eval
|
73
|
-
Kernel.send eval_mid, self::SYNTAX_PROBE, nil, __FILE__, __LINE__
|
74
|
-
false
|
75
|
-
rescue SyntaxError, StandardError
|
76
|
-
true
|
77
|
-
ensure
|
78
|
-
$VERBOSE = save_verbose
|
79
|
-
end
|
80
|
-
|
81
|
-
# Returns true if the syntax is supported
|
82
|
-
# by the specified version
|
83
|
-
def unsupported_version?(version)
|
84
|
-
self::MIN_SUPPORTED_VERSION > version
|
85
|
-
end
|
86
|
-
|
87
|
-
private
|
67
|
+
attr_reader :locals
|
88
68
|
|
89
|
-
|
90
|
-
|
91
|
-
end
|
69
|
+
def self.ast?
|
70
|
+
true
|
92
71
|
end
|
93
72
|
|
94
|
-
|
95
|
-
|
96
|
-
def initialize(context)
|
97
|
-
@context = context
|
73
|
+
def initialize(*args)
|
98
74
|
@locals = LocalsTracker.new
|
99
|
-
super
|
75
|
+
super
|
100
76
|
end
|
101
77
|
|
102
78
|
def s(type, *children)
|
@@ -144,8 +120,6 @@ module RubyNext
|
|
144
120
|
|
145
121
|
Unparser.unparse(ast).chomp
|
146
122
|
end
|
147
|
-
|
148
|
-
attr_reader :context
|
149
123
|
end
|
150
124
|
end
|
151
125
|
end
|
@@ -4,28 +4,6 @@ module RubyNext
|
|
4
4
|
module Utils
|
5
5
|
module_function
|
6
6
|
|
7
|
-
if $LOAD_PATH.respond_to?(:resolve_feature_path)
|
8
|
-
def resolve_feature_path(feature)
|
9
|
-
((((__safe_lvar__ = $LOAD_PATH.resolve_feature_path(feature)) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.last)
|
10
|
-
rescue LoadError
|
11
|
-
end
|
12
|
-
else
|
13
|
-
def resolve_feature_path(path)
|
14
|
-
if File.file?(relative = File.expand_path(path))
|
15
|
-
path = relative
|
16
|
-
end
|
17
|
-
|
18
|
-
path = "#{path}.rb" if File.extname(path).empty?
|
19
|
-
|
20
|
-
return path if Pathname.new(path).absolute?
|
21
|
-
|
22
|
-
$LOAD_PATH.find do |lp|
|
23
|
-
lpath = File.join(lp, path)
|
24
|
-
return File.realpath(lpath) if File.file?(lpath)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
7
|
def source_with_lines(source, path)
|
30
8
|
source.lines.map.with_index do |line, i|
|
31
9
|
"#{(i + 1).to_s.rjust(4)}: #{line}"
|
@@ -36,6 +14,7 @@ module RubyNext
|
|
36
14
|
|
37
15
|
# Returns true if modules refinement is supported in current version
|
38
16
|
def refine_modules?
|
17
|
+
save_verbose, $VERBOSE = $VERBOSE, nil
|
39
18
|
@refine_modules ||=
|
40
19
|
begin
|
41
20
|
# Make sure that including modules within refinements works
|
@@ -59,6 +38,8 @@ module RubyNext
|
|
59
38
|
true
|
60
39
|
rescue TypeError, NoMethodError
|
61
40
|
false
|
41
|
+
ensure
|
42
|
+
$VERBOSE = save_verbose
|
62
43
|
end
|
63
44
|
end
|
64
45
|
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The code below originates from https://github.com/saturnflyer/polyfill-data
|
4
|
+
|
5
|
+
if !Object.const_defined?(:Data) || !Data.respond_to?(:define)
|
6
|
+
|
7
|
+
# Drop legacy Data class
|
8
|
+
begin
|
9
|
+
Object.send(:remove_const, :Data)
|
10
|
+
rescue
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
class Data < Object
|
15
|
+
using RubyNext
|
16
|
+
|
17
|
+
class << self
|
18
|
+
undef_method :new
|
19
|
+
attr_reader :members
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.define(*args, &block)
|
23
|
+
raise ArgumentError if args.any?(/=/)
|
24
|
+
if block
|
25
|
+
mod = Module.new
|
26
|
+
mod.define_singleton_method(:_) do |klass|
|
27
|
+
klass.class_eval(&block)
|
28
|
+
end
|
29
|
+
arity_converter = mod.method(:_)
|
30
|
+
end
|
31
|
+
klass = ::Class.new(self)
|
32
|
+
|
33
|
+
klass.instance_variable_set(:@members, args.map(&:to_sym).freeze)
|
34
|
+
|
35
|
+
klass.define_singleton_method(:new) do |*new_args, **new_kwargs, &block|
|
36
|
+
init_kwargs = if new_args.any?
|
37
|
+
raise ArgumentError, "unknown arguments #{new_args[members.size..-1].join(", ")}" if new_args.size > members.size
|
38
|
+
members.take(new_args.size).zip(new_args).to_h
|
39
|
+
else
|
40
|
+
new_kwargs
|
41
|
+
end
|
42
|
+
|
43
|
+
allocate.tap do |instance|
|
44
|
+
instance.send(:initialize, **init_kwargs, &block)
|
45
|
+
end.freeze
|
46
|
+
end
|
47
|
+
|
48
|
+
class << klass
|
49
|
+
alias_method :[], :new
|
50
|
+
undef_method :define
|
51
|
+
end
|
52
|
+
|
53
|
+
args.each do |arg|
|
54
|
+
if klass.method_defined?(arg)
|
55
|
+
raise ArgumentError, "duplicate member #{arg}"
|
56
|
+
end
|
57
|
+
klass.define_method(arg) do
|
58
|
+
@attributes[arg]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if arity_converter
|
63
|
+
klass.class_eval(&arity_converter)
|
64
|
+
end
|
65
|
+
|
66
|
+
klass
|
67
|
+
end
|
68
|
+
|
69
|
+
def members
|
70
|
+
self.class.members
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize(**kwargs)
|
74
|
+
kwargs_size = kwargs.size
|
75
|
+
members_size = members.size
|
76
|
+
|
77
|
+
if kwargs_size > members_size
|
78
|
+
extras = kwargs.except(*members).keys
|
79
|
+
|
80
|
+
if extras.size > 1
|
81
|
+
raise ArgumentError, "unknown keywords: #{extras.map { |_1| ":#{_1}" }.join(", ")}"
|
82
|
+
else
|
83
|
+
raise ArgumentError, "unknown keyword: :#{extras.first}"
|
84
|
+
end
|
85
|
+
elsif kwargs_size < members_size
|
86
|
+
missing = members.select { |k| !kwargs.include?(k) }
|
87
|
+
|
88
|
+
if missing.size > 1
|
89
|
+
raise ArgumentError, "missing keywords: #{missing.map { |_1| ":#{_1}" }.join(", ")}"
|
90
|
+
else
|
91
|
+
raise ArgumentError, "missing keyword: :#{missing.first}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
@attributes = members.map { |m| [m, kwargs[m]] }.to_h
|
96
|
+
end
|
97
|
+
|
98
|
+
def deconstruct
|
99
|
+
@attributes.values
|
100
|
+
end
|
101
|
+
|
102
|
+
def deconstruct_keys(array)
|
103
|
+
raise TypeError unless array.is_a?(Array) || array.nil?
|
104
|
+
return @attributes if array&.first.nil?
|
105
|
+
|
106
|
+
@attributes.slice(*array)
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_h(&block)
|
110
|
+
@attributes.to_h(&block)
|
111
|
+
end
|
112
|
+
|
113
|
+
def hash
|
114
|
+
to_h.hash
|
115
|
+
end
|
116
|
+
|
117
|
+
def eql?(other)
|
118
|
+
self.class == other.class && hash == other.hash
|
119
|
+
end
|
120
|
+
|
121
|
+
def ==(other)
|
122
|
+
self.class == other.class && to_h == other.to_h
|
123
|
+
end
|
124
|
+
|
125
|
+
def inspect
|
126
|
+
attribute_markers = @attributes.map do |key, value|
|
127
|
+
insect_key = key.to_s.start_with?("@") ? ":#{key}" : key
|
128
|
+
"#{insect_key}=#{value}"
|
129
|
+
end.join(", ")
|
130
|
+
|
131
|
+
display = ["data", self.class.name, attribute_markers].compact.join(" ")
|
132
|
+
|
133
|
+
"#<#{display}>"
|
134
|
+
end
|
135
|
+
alias_method :to_s, :inspect
|
136
|
+
|
137
|
+
def with(**kwargs)
|
138
|
+
return self if kwargs.empty?
|
139
|
+
|
140
|
+
self.class.new(**@attributes.merge(kwargs))
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def marshal_dump
|
146
|
+
@attributes
|
147
|
+
end
|
148
|
+
|
149
|
+
def marshal_load(attributes)
|
150
|
+
@attributes = attributes
|
151
|
+
freeze
|
152
|
+
end
|
153
|
+
|
154
|
+
def initialize_copy(source)
|
155
|
+
super.freeze
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The code below originates from https://github.com/saturnflyer/polyfill-data
|
4
|
+
|
5
|
+
if !Object.const_defined?(:Data) || !Data.respond_to?(:define)
|
6
|
+
|
7
|
+
# Drop legacy Data class
|
8
|
+
begin
|
9
|
+
Object.send(:remove_const, :Data)
|
10
|
+
rescue
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
class Data < Object
|
15
|
+
using RubyNext
|
16
|
+
|
17
|
+
class << self
|
18
|
+
undef_method :new
|
19
|
+
attr_reader :members
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.define(*args, &block)
|
23
|
+
raise ArgumentError if args.any?(/=/)
|
24
|
+
if block
|
25
|
+
mod = Module.new
|
26
|
+
mod.define_singleton_method(:_) do |klass|
|
27
|
+
klass.class_eval(&block)
|
28
|
+
end
|
29
|
+
arity_converter = mod.method(:_)
|
30
|
+
end
|
31
|
+
klass = ::Class.new(self)
|
32
|
+
|
33
|
+
klass.instance_variable_set(:@members, args.map(&:to_sym).freeze)
|
34
|
+
|
35
|
+
klass.define_singleton_method(:new) do |*new_args, **new_kwargs, &block|
|
36
|
+
init_kwargs = if new_args.any?
|
37
|
+
raise ArgumentError, "unknown arguments #{new_args[members.size..].join(", ")}" if new_args.size > members.size
|
38
|
+
members.take(new_args.size).zip(new_args).to_h
|
39
|
+
else
|
40
|
+
new_kwargs
|
41
|
+
end
|
42
|
+
|
43
|
+
allocate.tap do |instance|
|
44
|
+
instance.send(:initialize, **init_kwargs, &block)
|
45
|
+
end.freeze
|
46
|
+
end
|
47
|
+
|
48
|
+
class << klass
|
49
|
+
alias_method :[], :new
|
50
|
+
undef_method :define
|
51
|
+
end
|
52
|
+
|
53
|
+
args.each do |arg|
|
54
|
+
if klass.method_defined?(arg)
|
55
|
+
raise ArgumentError, "duplicate member #{arg}"
|
56
|
+
end
|
57
|
+
klass.define_method(arg) do
|
58
|
+
@attributes[arg]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if arity_converter
|
63
|
+
klass.class_eval(&arity_converter)
|
64
|
+
end
|
65
|
+
|
66
|
+
klass
|
67
|
+
end
|
68
|
+
|
69
|
+
def members
|
70
|
+
self.class.members
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize(**kwargs)
|
74
|
+
kwargs_size = kwargs.size
|
75
|
+
members_size = members.size
|
76
|
+
|
77
|
+
if kwargs_size > members_size
|
78
|
+
extras = kwargs.except(*members).keys
|
79
|
+
|
80
|
+
if extras.size > 1
|
81
|
+
raise ArgumentError, "unknown keywords: #{extras.map { |_1| ":#{_1}" }.join(", ")}"
|
82
|
+
else
|
83
|
+
raise ArgumentError, "unknown keyword: :#{extras.first}"
|
84
|
+
end
|
85
|
+
elsif kwargs_size < members_size
|
86
|
+
missing = members.select { |k| !kwargs.include?(k) }
|
87
|
+
|
88
|
+
if missing.size > 1
|
89
|
+
raise ArgumentError, "missing keywords: #{missing.map { |_1| ":#{_1}" }.join(", ")}"
|
90
|
+
else
|
91
|
+
raise ArgumentError, "missing keyword: :#{missing.first}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
@attributes = members.map { |m| [m, kwargs[m]] }.to_h
|
96
|
+
end
|
97
|
+
|
98
|
+
def deconstruct
|
99
|
+
@attributes.values
|
100
|
+
end
|
101
|
+
|
102
|
+
def deconstruct_keys(array)
|
103
|
+
raise TypeError unless array.is_a?(Array) || array.nil?
|
104
|
+
return @attributes if array&.first.nil?
|
105
|
+
|
106
|
+
@attributes.slice(*array)
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_h(&block)
|
110
|
+
@attributes.to_h(&block)
|
111
|
+
end
|
112
|
+
|
113
|
+
def hash
|
114
|
+
to_h.hash
|
115
|
+
end
|
116
|
+
|
117
|
+
def eql?(other)
|
118
|
+
self.class == other.class && hash == other.hash
|
119
|
+
end
|
120
|
+
|
121
|
+
def ==(other)
|
122
|
+
self.class == other.class && to_h == other.to_h
|
123
|
+
end
|
124
|
+
|
125
|
+
def inspect
|
126
|
+
attribute_markers = @attributes.map do |key, value|
|
127
|
+
insect_key = key.to_s.start_with?("@") ? ":#{key}" : key
|
128
|
+
"#{insect_key}=#{value}"
|
129
|
+
end.join(", ")
|
130
|
+
|
131
|
+
display = ["data", self.class.name, attribute_markers].compact.join(" ")
|
132
|
+
|
133
|
+
"#<#{display}>"
|
134
|
+
end
|
135
|
+
alias_method :to_s, :inspect
|
136
|
+
|
137
|
+
def with(**kwargs)
|
138
|
+
return self if kwargs.empty?
|
139
|
+
|
140
|
+
self.class.new(**@attributes.merge(kwargs))
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def marshal_dump
|
146
|
+
@attributes
|
147
|
+
end
|
148
|
+
|
149
|
+
def marshal_load(attributes)
|
150
|
+
@attributes = attributes
|
151
|
+
freeze
|
152
|
+
end
|
153
|
+
|
154
|
+
def initialize_copy(source)
|
155
|
+
super.freeze
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|