ruby-next-core 0.14.0 → 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 +70 -0
- data/README.md +163 -56
- 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 +12 -4
- data/lib/.rbnext/2.1/ruby-next/language.rb +62 -12
- data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +97 -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/args_forward.rb +134 -0
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/2.7/pattern_matching.rb +122 -47
- 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 +12 -4
- 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 +1095 -0
- 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/cli.rb +10 -15
- data/lib/ruby-next/commands/nextify.rb +99 -3
- data/lib/ruby-next/config.rb +31 -4
- 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/proc/compose.rb +0 -1
- 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 +11 -3
- data/lib/ruby-next/irb.rb +24 -0
- 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/2.5/rescue_within_block.rb +41 -0
- data/lib/ruby-next/language/rewriters/2.7/args_forward.rb +57 -0
- data/lib/ruby-next/language/rewriters/2.7/pattern_matching.rb +120 -45
- 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 +62 -12
- data/lib/ruby-next/pry.rb +90 -0
- 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/irb.rb +3 -0
- data/lib/uby-next/pry.rb +3 -0
- data/lib/uby-next.rb +2 -2
- metadata +70 -10
@@ -182,11 +182,10 @@ module RubyNext
|
|
182
182
|
end
|
183
183
|
end
|
184
184
|
|
185
|
-
# rubocop:disable Style/MethodMissingSuper
|
186
185
|
# rubocop:disable Style/MissingRespondToMissing
|
187
186
|
class Noop < Base
|
188
187
|
# Return node itself, no memoization
|
189
|
-
def method_missing(mid, node, *)
|
188
|
+
def method_missing(mid, node, *__rest__)
|
190
189
|
node
|
191
190
|
end
|
192
191
|
end
|
@@ -243,6 +242,7 @@ module RubyNext
|
|
243
242
|
|
244
243
|
@deconstructed_keys = {}
|
245
244
|
@predicates = Predicates::CaseIn.new
|
245
|
+
@lvars = []
|
246
246
|
|
247
247
|
matchee_ast =
|
248
248
|
s(:begin, s(:lvasgn, MATCHEE, node.children[0]))
|
@@ -272,6 +272,7 @@ module RubyNext
|
|
272
272
|
|
273
273
|
@deconstructed_keys = {}
|
274
274
|
@predicates = Predicates::Noop.new
|
275
|
+
@lvars = []
|
275
276
|
|
276
277
|
matchee =
|
277
278
|
s(:begin, s(:lvasgn, MATCHEE, node.children[0]))
|
@@ -282,10 +283,12 @@ module RubyNext
|
|
282
283
|
arr: MATCHEE_ARR,
|
283
284
|
hash: MATCHEE_HASH
|
284
285
|
) do
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
286
|
+
with_declared_locals do
|
287
|
+
send(
|
288
|
+
:"#{node.children[1].type}_clause",
|
289
|
+
node.children[1]
|
290
|
+
)
|
291
|
+
end.then do |node|
|
289
292
|
s(:begin,
|
290
293
|
s(:or,
|
291
294
|
node,
|
@@ -311,6 +314,7 @@ module RubyNext
|
|
311
314
|
|
312
315
|
@deconstructed_keys = {}
|
313
316
|
@predicates = Predicates::Noop.new
|
317
|
+
@lvars = []
|
314
318
|
|
315
319
|
matchee =
|
316
320
|
s(:begin, s(:lvasgn, MATCHEE, node.children[0]))
|
@@ -321,10 +325,12 @@ module RubyNext
|
|
321
325
|
arr: MATCHEE_ARR,
|
322
326
|
hash: MATCHEE_HASH
|
323
327
|
) do
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
+
with_declared_locals do
|
329
|
+
send(
|
330
|
+
:"#{node.children[1].type}_clause",
|
331
|
+
node.children[1]
|
332
|
+
)
|
333
|
+
end
|
328
334
|
end
|
329
335
|
|
330
336
|
node.updated(
|
@@ -395,13 +401,15 @@ module RubyNext
|
|
395
401
|
def build_when_clause(clause)
|
396
402
|
predicates.reset!
|
397
403
|
[
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
404
|
+
with_declared_locals do
|
405
|
+
with_guard(
|
406
|
+
send(
|
407
|
+
:"#{clause.children[0].type}_clause",
|
408
|
+
clause.children[0]
|
409
|
+
),
|
410
|
+
clause.children[1] # guard
|
411
|
+
)
|
412
|
+
end,
|
405
413
|
process(clause.children[2] || s(:nil)) # expression
|
406
414
|
].then do |children|
|
407
415
|
s(:when, *children)
|
@@ -417,15 +425,15 @@ module RubyNext
|
|
417
425
|
s(:begin,
|
418
426
|
s(:and,
|
419
427
|
node,
|
420
|
-
send(:"#{pattern.type}_clause", pattern)))
|
428
|
+
send(:"#{pattern.type}_clause", pattern, right)))
|
421
429
|
end
|
422
430
|
end
|
423
431
|
|
424
|
-
def match_alt_clause(node)
|
432
|
+
def match_alt_clause(node, matchee = s(:lvar, locals[:matchee]))
|
425
433
|
children = locals.with(ALTERNATION_MARKER => true) do
|
426
434
|
node.children.map.with_index do |child, i|
|
427
435
|
predicates.terminate! if i == 1
|
428
|
-
send :"#{child.type}_clause", child
|
436
|
+
send :"#{child.type}_clause", child, matchee
|
429
437
|
end
|
430
438
|
end
|
431
439
|
s(:begin, s(:or, *children))
|
@@ -442,7 +450,7 @@ module RubyNext
|
|
442
450
|
var = node.children[0]
|
443
451
|
return s(:true) if var == :_
|
444
452
|
|
445
|
-
check_match_var_alternation!(var)
|
453
|
+
check_match_var_alternation!(var)
|
446
454
|
|
447
455
|
s(:begin,
|
448
456
|
s(:or,
|
@@ -541,11 +549,10 @@ module RubyNext
|
|
541
549
|
def array_find(head, *nodes, tail)
|
542
550
|
index = s(:lvar, :__i__)
|
543
551
|
|
544
|
-
match_vars = []
|
545
|
-
|
546
552
|
head_match =
|
547
553
|
unless head.children.empty?
|
548
|
-
|
554
|
+
# we only need to call this to track the lvar usage
|
555
|
+
build_var_assignment(head.children[0].children[0])
|
549
556
|
|
550
557
|
arr_take = s(:send,
|
551
558
|
s(:lvar, locals[:arr]),
|
@@ -557,16 +564,19 @@ module RubyNext
|
|
557
564
|
|
558
565
|
tail_match =
|
559
566
|
unless tail.children.empty?
|
560
|
-
|
567
|
+
# we only need to call this to track the lvar usage
|
568
|
+
build_var_assignment(tail.children[0].children[0])
|
561
569
|
|
562
570
|
match_var_clause(tail.children[0], arr_slice(index + nodes.size, -1))
|
563
571
|
end
|
564
572
|
|
565
573
|
nodes.each do |node|
|
566
574
|
if node.type == :match_var
|
567
|
-
|
575
|
+
# we only need to call this to track the lvar usage
|
576
|
+
build_var_assignment(node.children[0])
|
568
577
|
elsif node.type == :match_as
|
569
|
-
|
578
|
+
# we only need to call this to track the lvar usage
|
579
|
+
build_var_assignment(node.children[1].children[0])
|
570
580
|
end
|
571
581
|
end
|
572
582
|
|
@@ -594,19 +604,7 @@ module RubyNext
|
|
594
604
|
s(:args,
|
595
605
|
s(:arg, :_),
|
596
606
|
s(:arg, :__i__)),
|
597
|
-
pattern)
|
598
|
-
next block if match_vars.empty?
|
599
|
-
|
600
|
-
# We need to declare match vars outside of `find` block
|
601
|
-
locals_declare = s(:begin, s(:masgn,
|
602
|
-
s(:mlhs, *match_vars),
|
603
|
-
s(:nil)))
|
604
|
-
|
605
|
-
s(:begin,
|
606
|
-
s(:or,
|
607
|
-
locals_declare,
|
608
|
-
block))
|
609
|
-
end
|
607
|
+
pattern)
|
610
608
|
end
|
611
609
|
|
612
610
|
def array_match_rest(index, node, *tail)
|
@@ -649,6 +647,14 @@ module RubyNext
|
|
649
647
|
end
|
650
648
|
end
|
651
649
|
|
650
|
+
def find_pattern_array_element(node, index)
|
651
|
+
element = arr_item_at(index)
|
652
|
+
locals.with(arr: locals[:arr, index]) do
|
653
|
+
predicates.push :"i#{index}"
|
654
|
+
find_pattern_clause(node, element).tap { predicates.pop }
|
655
|
+
end
|
656
|
+
end
|
657
|
+
|
652
658
|
def hash_pattern_array_element(node, index)
|
653
659
|
element = arr_item_at(index)
|
654
660
|
locals.with(hash: locals[:arr, index]) do
|
@@ -657,6 +663,14 @@ module RubyNext
|
|
657
663
|
end
|
658
664
|
end
|
659
665
|
|
666
|
+
def const_pattern_array_element(node, index)
|
667
|
+
element = arr_item_at(index)
|
668
|
+
locals.with(arr: locals[:arr, index]) do
|
669
|
+
predicates.push :"i#{index}"
|
670
|
+
const_pattern_clause(node, element).tap { predicates.pop }
|
671
|
+
end
|
672
|
+
end
|
673
|
+
|
660
674
|
def match_alt_array_element(node, index)
|
661
675
|
children = node.children.map do |child, i|
|
662
676
|
send :"#{child.type}_array_element", child, index
|
@@ -665,11 +679,19 @@ module RubyNext
|
|
665
679
|
end
|
666
680
|
|
667
681
|
def match_var_array_element(node, index)
|
668
|
-
|
682
|
+
element = arr_item_at(index)
|
683
|
+
locals.with(arr: locals[:arr, index]) do
|
684
|
+
predicates.push :"i#{index}"
|
685
|
+
match_var_clause(node, element).tap { predicates.pop }
|
686
|
+
end
|
669
687
|
end
|
670
688
|
|
671
689
|
def match_as_array_element(node, index)
|
672
|
-
|
690
|
+
element = arr_item_at(index)
|
691
|
+
locals.with(arr: locals[:arr, index]) do
|
692
|
+
predicates.push :"i#{index}"
|
693
|
+
match_as_clause(node, element).tap { predicates.pop }
|
694
|
+
end
|
673
695
|
end
|
674
696
|
|
675
697
|
def pin_array_element(node, index)
|
@@ -829,6 +851,24 @@ module RubyNext
|
|
829
851
|
end
|
830
852
|
end
|
831
853
|
|
854
|
+
def find_pattern_hash_element(node, key)
|
855
|
+
element = hash_value_at(key)
|
856
|
+
key_index = deconstructed_key(key)
|
857
|
+
locals.with(arr: locals[:hash, key_index]) do
|
858
|
+
predicates.push :"k#{key_index}"
|
859
|
+
find_pattern_clause(node, element).tap { predicates.pop }
|
860
|
+
end
|
861
|
+
end
|
862
|
+
|
863
|
+
def const_pattern_hash_element(node, key)
|
864
|
+
element = hash_value_at(key)
|
865
|
+
key_index = deconstructed_key(key)
|
866
|
+
locals.with(hash: locals[:hash, key_index]) do
|
867
|
+
predicates.push :"k#{key_index}"
|
868
|
+
const_pattern_clause(node, element).tap { predicates.pop }
|
869
|
+
end
|
870
|
+
end
|
871
|
+
|
832
872
|
def hash_element(head, *tail)
|
833
873
|
send("#{head.type}_hash_element", head).then do |node|
|
834
874
|
next node if tail.empty?
|
@@ -869,12 +909,22 @@ module RubyNext
|
|
869
909
|
end
|
870
910
|
|
871
911
|
def match_as_hash_element(node, key)
|
872
|
-
|
912
|
+
element = hash_value_at(key)
|
913
|
+
key_index = deconstructed_key(key)
|
914
|
+
locals.with(hash: locals[:hash, key_index]) do
|
915
|
+
predicates.push :"k#{key_index}"
|
916
|
+
match_as_clause(node, element).tap { predicates.pop }
|
917
|
+
end
|
873
918
|
end
|
874
919
|
|
875
920
|
def match_var_hash_element(node, key = nil)
|
876
921
|
key ||= node.children[0]
|
877
|
-
|
922
|
+
element = hash_value_at(key)
|
923
|
+
key_index = deconstructed_key(key)
|
924
|
+
locals.with(hash: locals[:hash, key_index]) do
|
925
|
+
predicates.push :"k#{key_index}"
|
926
|
+
match_var_clause(node, element).tap { predicates.pop }
|
927
|
+
end
|
878
928
|
end
|
879
929
|
|
880
930
|
def match_nil_pattern_hash_element(node, _key = nil)
|
@@ -948,6 +998,24 @@ module RubyNext
|
|
948
998
|
end
|
949
999
|
end
|
950
1000
|
|
1001
|
+
def with_declared_locals
|
1002
|
+
lvars.clear
|
1003
|
+
node = yield
|
1004
|
+
|
1005
|
+
return node if lvars.empty?
|
1006
|
+
|
1007
|
+
# We need to declare match lvars outside of the outer `find` block,
|
1008
|
+
# so we do that for that whole pattern
|
1009
|
+
locals_declare = s(:begin, s(:masgn,
|
1010
|
+
s(:mlhs, *lvars.uniq.map { |_1| s(:lvasgn, _1) }),
|
1011
|
+
s(:nil)))
|
1012
|
+
|
1013
|
+
s(:begin,
|
1014
|
+
s(:or,
|
1015
|
+
locals_declare,
|
1016
|
+
node))
|
1017
|
+
end
|
1018
|
+
|
951
1019
|
def no_matching_pattern
|
952
1020
|
raise_error(
|
953
1021
|
:NoMatchingPatternError,
|
@@ -967,7 +1035,7 @@ module RubyNext
|
|
967
1035
|
s(:send, node, :respond_to?, mid.to_ast_node)
|
968
1036
|
end
|
969
1037
|
|
970
|
-
def respond_to_missing?(mid, *)
|
1038
|
+
def respond_to_missing?(mid, *__rest__)
|
971
1039
|
return true if mid.to_s.match?(/_(clause|array_element)/)
|
972
1040
|
super
|
973
1041
|
end
|
@@ -982,13 +1050,17 @@ module RubyNext
|
|
982
1050
|
|
983
1051
|
private
|
984
1052
|
|
985
|
-
attr_reader :deconstructed_keys, :predicates
|
1053
|
+
attr_reader :deconstructed_keys, :predicates, :lvars
|
986
1054
|
|
987
1055
|
# Raise SyntaxError if match-var is used within alternation
|
988
1056
|
# https://github.com/ruby/ruby/blob/672213ef1ca2b71312084057e27580b340438796/compile.c#L5900
|
989
1057
|
def check_match_var_alternation!(name)
|
990
1058
|
return unless locals.key?(ALTERNATION_MARKER)
|
991
1059
|
|
1060
|
+
if name.is_a?(::Parser::AST::Node)
|
1061
|
+
raise ::SyntaxError, "illegal variable in alternative pattern (#{name.children.first})"
|
1062
|
+
end
|
1063
|
+
|
992
1064
|
return if name.start_with?("_")
|
993
1065
|
|
994
1066
|
raise ::SyntaxError, "illegal variable in alternative pattern (#{name})"
|
@@ -1008,7 +1080,10 @@ module RubyNext
|
|
1008
1080
|
|
1009
1081
|
# Value could be omitted for mass assignment
|
1010
1082
|
def build_var_assignment(var, value = nil)
|
1011
|
-
|
1083
|
+
unless var.is_a?(::Parser::AST::Node)
|
1084
|
+
lvars << var
|
1085
|
+
return s(:lvasgn, *[var, value].compact)
|
1086
|
+
end
|
1012
1087
|
|
1013
1088
|
asign_type = :"#{var.type.to_s[0]}vasgn"
|
1014
1089
|
|
@@ -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 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
|