ruby-next-core 0.15.3 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -0
- data/README.md +127 -54
- 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 +59 -12
- data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +83 -3
- data/lib/.rbnext/2.3/ruby-next/config.rb +79 -0
- data/lib/.rbnext/2.3/ruby-next/core/data.rb +163 -0
- data/lib/.rbnext/2.3/ruby-next/language/eval.rb +4 -4
- 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 +163 -0
- data/lib/.rbnext/2.7/ruby-next/core/data.rb +163 -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 +85 -3
- data/lib/ruby-next/config.rb +29 -2
- data/lib/ruby-next/core/data.rb +163 -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/eval.rb +4 -4
- 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 +31 -6
- 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.1/shorthand_hash.rb +2 -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 +59 -12
- 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 +63 -10
@@ -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 => 4
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
LATEST_VERSION = [3, 4].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,163 @@
|
|
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 self.inherited(subclass)
|
70
|
+
subclass.instance_variable_set(:@members, members)
|
71
|
+
end
|
72
|
+
|
73
|
+
def members
|
74
|
+
self.class.members
|
75
|
+
end
|
76
|
+
|
77
|
+
def initialize(**kwargs)
|
78
|
+
kwargs_size = kwargs.size
|
79
|
+
members_size = members.size
|
80
|
+
|
81
|
+
if kwargs_size > members_size
|
82
|
+
extras = kwargs.except(*members).keys
|
83
|
+
|
84
|
+
if extras.size > 1
|
85
|
+
raise ArgumentError, "unknown keywords: #{extras.map { |_1| ":#{_1}" }.join(", ")}"
|
86
|
+
else
|
87
|
+
raise ArgumentError, "unknown keyword: :#{extras.first}"
|
88
|
+
end
|
89
|
+
elsif kwargs_size < members_size
|
90
|
+
missing = members.select { |k| !kwargs.include?(k) }
|
91
|
+
|
92
|
+
if missing.size > 1
|
93
|
+
raise ArgumentError, "missing keywords: #{missing.map { |_1| ":#{_1}" }.join(", ")}"
|
94
|
+
else
|
95
|
+
raise ArgumentError, "missing keyword: :#{missing.first}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
@attributes = members.map { |m| [m, kwargs[m]] }.to_h
|
100
|
+
end
|
101
|
+
|
102
|
+
def deconstruct
|
103
|
+
@attributes.values
|
104
|
+
end
|
105
|
+
|
106
|
+
def deconstruct_keys(array)
|
107
|
+
raise TypeError unless array.is_a?(Array) || array.nil?
|
108
|
+
return @attributes if ((((__safe_lvar__ = array) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.first).nil?
|
109
|
+
|
110
|
+
@attributes.slice(*array)
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_h(&block)
|
114
|
+
@attributes.to_h(&block)
|
115
|
+
end
|
116
|
+
|
117
|
+
def hash
|
118
|
+
to_h.hash
|
119
|
+
end
|
120
|
+
|
121
|
+
def eql?(other)
|
122
|
+
self.class == other.class && hash == other.hash
|
123
|
+
end
|
124
|
+
|
125
|
+
def ==(other)
|
126
|
+
self.class == other.class && to_h == other.to_h
|
127
|
+
end
|
128
|
+
|
129
|
+
def inspect
|
130
|
+
attribute_markers = @attributes.map do |key, value|
|
131
|
+
insect_key = key.to_s.start_with?("@") ? ":#{key}" : key
|
132
|
+
"#{insect_key}=#{value}"
|
133
|
+
end.join(", ")
|
134
|
+
|
135
|
+
display = ["data", self.class.name, attribute_markers].compact.join(" ")
|
136
|
+
|
137
|
+
"#<#{display}>"
|
138
|
+
end
|
139
|
+
alias_method :to_s, :inspect
|
140
|
+
|
141
|
+
def with(**kwargs)
|
142
|
+
return self if kwargs.empty?
|
143
|
+
|
144
|
+
self.class.new(**@attributes.merge(kwargs))
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def marshal_dump
|
150
|
+
@attributes
|
151
|
+
end
|
152
|
+
|
153
|
+
def marshal_load(attributes)
|
154
|
+
@attributes = attributes
|
155
|
+
freeze
|
156
|
+
end
|
157
|
+
|
158
|
+
def initialize_copy(source)
|
159
|
+
super.freeze
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
@@ -11,7 +11,7 @@ module RubyNext
|
|
11
11
|
using: ((((__safe_lvar__ = bind) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.receiver) == TOPLEVEL_BINDING.receiver || ((((__safe_lvar__ = ((((__safe_lvar__ = bind) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.receiver)) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.is_a?(Module))
|
12
12
|
)
|
13
13
|
RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
|
14
|
-
super
|
14
|
+
super(new_source, bind, *args)
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -25,7 +25,7 @@ module RubyNext
|
|
25
25
|
source = args.shift
|
26
26
|
new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
|
27
27
|
RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
|
28
|
-
super
|
28
|
+
super(new_source, *args)
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
@@ -39,7 +39,7 @@ module RubyNext
|
|
39
39
|
new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
|
40
40
|
|
41
41
|
RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
|
42
|
-
super
|
42
|
+
super(new_source, *args)
|
43
43
|
end
|
44
44
|
|
45
45
|
def class_eval(*args, &block)
|
@@ -48,7 +48,7 @@ module RubyNext
|
|
48
48
|
source = args.shift
|
49
49
|
new_source = ::RubyNext::Language::Runtime.transform(source, using: false)
|
50
50
|
RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
|
51
|
-
super
|
51
|
+
super(new_source, *args)
|
52
52
|
end
|
53
53
|
end
|
54
54
|
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,163 @@
|
|
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 self.inherited(subclass)
|
70
|
+
subclass.instance_variable_set(:@members, members)
|
71
|
+
end
|
72
|
+
|
73
|
+
def members
|
74
|
+
self.class.members
|
75
|
+
end
|
76
|
+
|
77
|
+
def initialize(**kwargs)
|
78
|
+
kwargs_size = kwargs.size
|
79
|
+
members_size = members.size
|
80
|
+
|
81
|
+
if kwargs_size > members_size
|
82
|
+
extras = kwargs.except(*members).keys
|
83
|
+
|
84
|
+
if extras.size > 1
|
85
|
+
raise ArgumentError, "unknown keywords: #{extras.map { |_1| ":#{_1}" }.join(", ")}"
|
86
|
+
else
|
87
|
+
raise ArgumentError, "unknown keyword: :#{extras.first}"
|
88
|
+
end
|
89
|
+
elsif kwargs_size < members_size
|
90
|
+
missing = members.select { |k| !kwargs.include?(k) }
|
91
|
+
|
92
|
+
if missing.size > 1
|
93
|
+
raise ArgumentError, "missing keywords: #{missing.map { |_1| ":#{_1}" }.join(", ")}"
|
94
|
+
else
|
95
|
+
raise ArgumentError, "missing keyword: :#{missing.first}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
@attributes = members.map { |m| [m, kwargs[m]] }.to_h
|
100
|
+
end
|
101
|
+
|
102
|
+
def deconstruct
|
103
|
+
@attributes.values
|
104
|
+
end
|
105
|
+
|
106
|
+
def deconstruct_keys(array)
|
107
|
+
raise TypeError unless array.is_a?(Array) || array.nil?
|
108
|
+
return @attributes if array&.first.nil?
|
109
|
+
|
110
|
+
@attributes.slice(*array)
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_h(&block)
|
114
|
+
@attributes.to_h(&block)
|
115
|
+
end
|
116
|
+
|
117
|
+
def hash
|
118
|
+
to_h.hash
|
119
|
+
end
|
120
|
+
|
121
|
+
def eql?(other)
|
122
|
+
self.class == other.class && hash == other.hash
|
123
|
+
end
|
124
|
+
|
125
|
+
def ==(other)
|
126
|
+
self.class == other.class && to_h == other.to_h
|
127
|
+
end
|
128
|
+
|
129
|
+
def inspect
|
130
|
+
attribute_markers = @attributes.map do |key, value|
|
131
|
+
insect_key = key.to_s.start_with?("@") ? ":#{key}" : key
|
132
|
+
"#{insect_key}=#{value}"
|
133
|
+
end.join(", ")
|
134
|
+
|
135
|
+
display = ["data", self.class.name, attribute_markers].compact.join(" ")
|
136
|
+
|
137
|
+
"#<#{display}>"
|
138
|
+
end
|
139
|
+
alias_method :to_s, :inspect
|
140
|
+
|
141
|
+
def with(**kwargs)
|
142
|
+
return self if kwargs.empty?
|
143
|
+
|
144
|
+
self.class.new(**@attributes.merge(kwargs))
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def marshal_dump
|
150
|
+
@attributes
|
151
|
+
end
|
152
|
+
|
153
|
+
def marshal_load(attributes)
|
154
|
+
@attributes = attributes
|
155
|
+
freeze
|
156
|
+
end
|
157
|
+
|
158
|
+
def initialize_copy(source)
|
159
|
+
super.freeze
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|