unparser 0.5.6 → 0.6.2
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/README.md +14 -2
- data/lib/unparser/abstract_type.rb +121 -0
- data/lib/unparser/adamantium/method_builder.rb +111 -0
- data/lib/unparser/adamantium.rb +150 -0
- data/lib/unparser/anima/attribute.rb +59 -0
- data/lib/unparser/anima/error.rb +23 -0
- data/lib/unparser/anima.rb +184 -0
- data/lib/unparser/ast.rb +1 -2
- data/lib/unparser/buffer.rb +2 -2
- data/lib/unparser/cli.rb +1 -0
- data/lib/unparser/color.rb +1 -1
- data/lib/unparser/concord.rb +114 -0
- data/lib/unparser/diff.rb +1 -1
- data/lib/unparser/either.rb +153 -0
- data/lib/unparser/emitter/flow_modifier.rb +6 -0
- data/lib/unparser/emitter/hash_pattern.rb +1 -1
- data/lib/unparser/emitter/index.rb +1 -1
- data/lib/unparser/emitter/op_assign.rb +2 -2
- data/lib/unparser/emitter/primitive.rb +2 -2
- data/lib/unparser/emitter/simple.rb +2 -2
- data/lib/unparser/emitter.rb +1 -1
- data/lib/unparser/equalizer.rb +98 -0
- data/lib/unparser/node_details/send.rb +5 -2
- data/lib/unparser/node_details.rb +1 -1
- data/lib/unparser/node_helpers.rb +3 -1
- data/lib/unparser/validation.rb +3 -3
- data/lib/unparser/writer/binary.rb +1 -1
- data/lib/unparser/writer/dynamic_string.rb +10 -12
- data/lib/unparser/writer/rescue.rb +1 -1
- data/lib/unparser/writer/send/unary.rb +1 -1
- data/lib/unparser/writer/send.rb +9 -5
- data/lib/unparser.rb +31 -13
- metadata +17 -106
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7eda3912beee61a5384b60d5011749c10c7c46df2ad76d34c26cbb2bf2cc3fae
|
4
|
+
data.tar.gz: 2721620e86f7ff2d5be383551ef9a9c6b961f3a2695ce3eae84e56246a658129
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c6a25eebeab70213690d84e50e3a161950e2a025d88c693094387299108ce191d671078c8aa03ab059f82aaae0b6d21ced684830aa85f8a4696772a88a01273c
|
7
|
+
data.tar.gz: a5252a53a3dd3cb1f77d99d12a500f1fb22a191ffa59d9cf15c2cc65daa21fb86805574466c174111914b805346c2fc054850d44b1f8b27da70d5995e8236bcf
|
data/README.md
CHANGED
@@ -10,7 +10,7 @@ The following constraints apply:
|
|
10
10
|
|
11
11
|
* No support for macruby extensions
|
12
12
|
* Only support for the [modern AST](https://github.com/whitequark/parser/#usage) format
|
13
|
-
* Only support for Ruby >= 2.
|
13
|
+
* Only support for Ruby >= 2.6
|
14
14
|
|
15
15
|
Notable Users:
|
16
16
|
|
@@ -110,7 +110,7 @@ If you need to generate Ruby Syntax outside of this band feel free to contact me
|
|
110
110
|
Testing:
|
111
111
|
--------
|
112
112
|
|
113
|
-
Unparser currently successfully round trips almost all ruby code around. Using
|
113
|
+
Unparser currently successfully round trips almost all ruby code around. Using Ruby >= 2.6.
|
114
114
|
If there is a non round trippable example that is NOT subjected to known [Limitations](#limitations).
|
115
115
|
please report a bug.
|
116
116
|
|
@@ -168,6 +168,18 @@ People
|
|
168
168
|
|
169
169
|
Various people contributed to this repository. See [Contributors](https://github.com/mbj/unparser/graphs/contributors).
|
170
170
|
|
171
|
+
Included Libraries
|
172
|
+
------------------
|
173
|
+
|
174
|
+
For dependency reduction reasons unparser ships vendored (and reduced) versions of:
|
175
|
+
|
176
|
+
* [abstract_type](https://github.com/mbj/concord) -> Unparser::AbstractType
|
177
|
+
* [adamantium](https://github.com/dkubb/adamantium) -> Unparser::Adamantium
|
178
|
+
* [anima](https://github.com/mbj/concord) -> Unparser::Anima
|
179
|
+
* [concord](https://github.com/mbj/concord) -> Unparser::Concord
|
180
|
+
* [memoizable](https://github.com/dkubb/memoizable) -> Unparser::Adamantium
|
181
|
+
* [mprelude](https://github.com/dkubb/memoizable) -> Unparser::Either
|
182
|
+
|
171
183
|
Contributing
|
172
184
|
-------------
|
173
185
|
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Unparser
|
4
|
+
# Module to allow class and methods to be abstract
|
5
|
+
#
|
6
|
+
# Original code before vendoring and reduction from: https://github.com/dkubb/abstract_type.
|
7
|
+
module AbstractType
|
8
|
+
|
9
|
+
# Hook called when module is included
|
10
|
+
#
|
11
|
+
# @param [Module] descendant
|
12
|
+
# the module or class including AbstractType
|
13
|
+
#
|
14
|
+
# @return [undefined]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
def self.included(descendant)
|
18
|
+
super
|
19
|
+
create_new_method(descendant)
|
20
|
+
descendant.extend(AbstractMethodDeclarations)
|
21
|
+
end
|
22
|
+
|
23
|
+
private_class_method :included
|
24
|
+
|
25
|
+
# Define the new method on the abstract type
|
26
|
+
#
|
27
|
+
# Ensures that the instance cannot be of the abstract type
|
28
|
+
# and must be a descendant.
|
29
|
+
#
|
30
|
+
# @param [Class] abstract_class
|
31
|
+
#
|
32
|
+
# @return [undefined]
|
33
|
+
#
|
34
|
+
# @api private
|
35
|
+
def self.create_new_method(abstract_class)
|
36
|
+
abstract_class.define_singleton_method(:new) do |*args, &block|
|
37
|
+
if equal?(abstract_class)
|
38
|
+
fail NotImplementedError, "#{self} is an abstract type"
|
39
|
+
else
|
40
|
+
super(*args, &block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private_class_method :create_new_method
|
46
|
+
|
47
|
+
module AbstractMethodDeclarations
|
48
|
+
|
49
|
+
# Create abstract instance methods
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# class Foo
|
53
|
+
# include AbstractType
|
54
|
+
#
|
55
|
+
# # Create an abstract instance method
|
56
|
+
# abstract_method :some_method
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# @param [Array<#to_s>] names
|
60
|
+
#
|
61
|
+
# @return [self]
|
62
|
+
#
|
63
|
+
# @api public
|
64
|
+
def abstract_method(*names)
|
65
|
+
names.each(&method(:create_abstract_instance_method))
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
# Create abstract singleton methods
|
70
|
+
#
|
71
|
+
# @example
|
72
|
+
# class Foo
|
73
|
+
# include AbstractType
|
74
|
+
#
|
75
|
+
# # Create an abstract instance method
|
76
|
+
# abstract_singleton_method :some_method
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# @param [Array<#to_s>] names
|
80
|
+
#
|
81
|
+
# @return [self]
|
82
|
+
#
|
83
|
+
# @api private
|
84
|
+
def abstract_singleton_method(*names)
|
85
|
+
names.each(&method(:create_abstract_singleton_method))
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# Create abstract singleton method
|
92
|
+
#
|
93
|
+
# @param [#to_s] name
|
94
|
+
# the name of the method to create
|
95
|
+
#
|
96
|
+
# @return [undefined]
|
97
|
+
#
|
98
|
+
# @api private
|
99
|
+
def create_abstract_singleton_method(name)
|
100
|
+
define_singleton_method(name) do |*|
|
101
|
+
fail NotImplementedError, "#{self}.#{name} is not implemented"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Create abstract instance method
|
106
|
+
#
|
107
|
+
# @param [#to_s] name
|
108
|
+
# the name of the method to create
|
109
|
+
#
|
110
|
+
# @return [undefined]
|
111
|
+
#
|
112
|
+
# @api private
|
113
|
+
def create_abstract_instance_method(name)
|
114
|
+
define_method(name) do |*|
|
115
|
+
fail NotImplementedError, "#{self.class}##{name} is not implemented"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end # AbstractMethodDeclarations
|
120
|
+
end # AbstractType
|
121
|
+
end # Unparser
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Unparser
|
4
|
+
module Adamantium
|
5
|
+
# Build the memoized method
|
6
|
+
class MethodBuilder
|
7
|
+
|
8
|
+
# Raised when the method arity is invalid
|
9
|
+
class InvalidArityError < ArgumentError
|
10
|
+
|
11
|
+
# Initialize an invalid arity exception
|
12
|
+
#
|
13
|
+
# @param [Module] descendant
|
14
|
+
# @param [Symbol] method
|
15
|
+
# @param [Integer] arity
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
def initialize(descendant, method, arity)
|
19
|
+
super("Cannot memoize #{descendant}##{method}, its arity is #{arity}")
|
20
|
+
end
|
21
|
+
|
22
|
+
end # InvalidArityError
|
23
|
+
|
24
|
+
# Raised when a block is passed to a memoized method
|
25
|
+
class BlockNotAllowedError < ArgumentError
|
26
|
+
|
27
|
+
# Initialize a block not allowed exception
|
28
|
+
#
|
29
|
+
# @param [Module] descendant
|
30
|
+
# @param [Symbol] method
|
31
|
+
#
|
32
|
+
# @api private
|
33
|
+
def initialize(descendant, method)
|
34
|
+
super("Cannot pass a block to #{descendant}##{method}, it is memoized")
|
35
|
+
end
|
36
|
+
|
37
|
+
end # BlockNotAllowedError
|
38
|
+
|
39
|
+
# Initialize an object to build a memoized method
|
40
|
+
#
|
41
|
+
# @param [Module] descendant
|
42
|
+
# @param [Symbol] method_name
|
43
|
+
#
|
44
|
+
# @return [undefined]
|
45
|
+
#
|
46
|
+
# @api private
|
47
|
+
def initialize(descendant, method_name)
|
48
|
+
@descendant = descendant
|
49
|
+
@method_name = method_name
|
50
|
+
@original_visibility = visibility
|
51
|
+
@original_method = @descendant.instance_method(@method_name)
|
52
|
+
assert_arity(@original_method.arity)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Build a new memoized method
|
56
|
+
#
|
57
|
+
# @example
|
58
|
+
# method_builder.call # => creates new method
|
59
|
+
#
|
60
|
+
# @return [UnboundMethod]
|
61
|
+
#
|
62
|
+
# @api public
|
63
|
+
def call
|
64
|
+
remove_original_method
|
65
|
+
create_memoized_method
|
66
|
+
set_method_visibility
|
67
|
+
@original_method
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def assert_arity(arity)
|
73
|
+
if arity.nonzero?
|
74
|
+
fail InvalidArityError.new(@descendant, @method_name, arity)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def remove_original_method
|
79
|
+
name = @method_name
|
80
|
+
@descendant.module_eval { undef_method(name) }
|
81
|
+
end
|
82
|
+
|
83
|
+
def create_memoized_method
|
84
|
+
name = @method_name
|
85
|
+
method = @original_method
|
86
|
+
@descendant.module_eval do
|
87
|
+
define_method(name) do |&block|
|
88
|
+
fail BlockNotAllowedError.new(self.class, name) if block
|
89
|
+
|
90
|
+
memoized_method_cache.fetch(name) do
|
91
|
+
method.bind(self).call.freeze
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def set_method_visibility
|
98
|
+
@descendant.__send__(@original_visibility, @method_name)
|
99
|
+
end
|
100
|
+
|
101
|
+
def visibility
|
102
|
+
if @descendant.private_method_defined?(@method_name) then :private
|
103
|
+
elsif @descendant.protected_method_defined?(@method_name) then :protected
|
104
|
+
else
|
105
|
+
:public
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end # MethodBuilder
|
110
|
+
end # Adamantium
|
111
|
+
end # Unparser
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Unparser
|
4
|
+
# Allows objects to be made immutable
|
5
|
+
#
|
6
|
+
# Original code before vendoring and reduction from: https://github.com/dkubb/adamantium.
|
7
|
+
module Adamantium
|
8
|
+
module InstanceMethods
|
9
|
+
# A noop #dup for immutable objects
|
10
|
+
#
|
11
|
+
# @return [self]
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
def dup
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
# Freeze the object
|
19
|
+
#
|
20
|
+
# @return [Object]
|
21
|
+
#
|
22
|
+
# @api public
|
23
|
+
def freeze
|
24
|
+
memoized_method_cache
|
25
|
+
super()
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def memoized_method_cache
|
31
|
+
@memoized_method_cache ||= Memory.new({})
|
32
|
+
end
|
33
|
+
|
34
|
+
end # InstanceMethods
|
35
|
+
|
36
|
+
# Storage for memoized methods
|
37
|
+
class Memory
|
38
|
+
|
39
|
+
# Initialize the memory storage for memoized methods
|
40
|
+
#
|
41
|
+
# @return [undefined]
|
42
|
+
#
|
43
|
+
# @api private
|
44
|
+
def initialize(values)
|
45
|
+
@values = values
|
46
|
+
@monitor = Monitor.new
|
47
|
+
freeze
|
48
|
+
end
|
49
|
+
|
50
|
+
# Fetch the value from memory, or evaluate if it does not exist
|
51
|
+
#
|
52
|
+
# @param [Symbol] name
|
53
|
+
#
|
54
|
+
# @yieldreturn [Object]
|
55
|
+
# the value to memoize
|
56
|
+
#
|
57
|
+
# @api public
|
58
|
+
def fetch(name)
|
59
|
+
@values.fetch(name) do # check for the key
|
60
|
+
@monitor.synchronize do # acquire a lock if the key is not found
|
61
|
+
@values.fetch(name) do # recheck under lock
|
62
|
+
@values[name] = yield # set the value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end # Memory
|
68
|
+
|
69
|
+
# Methods mixed in to adamantium classes
|
70
|
+
module ClassMethods
|
71
|
+
|
72
|
+
# Instantiate a new frozen object
|
73
|
+
#
|
74
|
+
# @return [Object]
|
75
|
+
#
|
76
|
+
# @api public
|
77
|
+
def new(*)
|
78
|
+
super.freeze
|
79
|
+
end
|
80
|
+
|
81
|
+
end # ClassMethods
|
82
|
+
|
83
|
+
# Methods mixed in to adamantium modules
|
84
|
+
module ModuleMethods
|
85
|
+
|
86
|
+
# Memoize a list of methods
|
87
|
+
#
|
88
|
+
# @param [Array<#to_s>] methods
|
89
|
+
# a list of methods to memoize
|
90
|
+
#
|
91
|
+
# @return [self]
|
92
|
+
#
|
93
|
+
# @api public
|
94
|
+
def memoize(*methods)
|
95
|
+
methods.each(&method(:memoize_method))
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
# Test if method is memoized
|
100
|
+
#
|
101
|
+
# @param [Symbol] name
|
102
|
+
#
|
103
|
+
# @return [Bool]
|
104
|
+
def memoized?(method_name)
|
105
|
+
memoized_methods.key?(method_name)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Return unmemoized instance method
|
109
|
+
#
|
110
|
+
# @param [Symbol] name
|
111
|
+
#
|
112
|
+
# @return [UnboundMethod]
|
113
|
+
# the memoized method
|
114
|
+
#
|
115
|
+
# @raise [NameError]
|
116
|
+
# raised if the method is unknown
|
117
|
+
#
|
118
|
+
# @api public
|
119
|
+
def unmemoized_instance_method(method_name)
|
120
|
+
memoized_methods.fetch(method_name) do
|
121
|
+
fail ArgumentError, "##{method_name} is not memoized"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def memoize_method(method_name)
|
128
|
+
if memoized_methods.key?(method_name)
|
129
|
+
fail ArgumentError, "##{method_name} is already memoized"
|
130
|
+
end
|
131
|
+
|
132
|
+
memoized_methods[method_name] = MethodBuilder.new(self, method_name).call
|
133
|
+
end
|
134
|
+
|
135
|
+
def memoized_methods
|
136
|
+
@memoized_methods ||= {}
|
137
|
+
end
|
138
|
+
|
139
|
+
end # ModuleMethods
|
140
|
+
|
141
|
+
def self.included(descendant)
|
142
|
+
descendant.class_eval do
|
143
|
+
include InstanceMethods
|
144
|
+
extend ModuleMethods
|
145
|
+
extend ClassMethods if instance_of?(Class)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
private_class_method :included
|
149
|
+
end # Adamantium
|
150
|
+
end # Unparser
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Unparser
|
4
|
+
class Anima
|
5
|
+
# An attribute
|
6
|
+
class Attribute
|
7
|
+
include Adamantium, Equalizer.new(:name)
|
8
|
+
|
9
|
+
# Initialize attribute
|
10
|
+
#
|
11
|
+
# @param [Symbol] name
|
12
|
+
def initialize(name)
|
13
|
+
@name = name
|
14
|
+
@instance_variable_name = :"@#{name}"
|
15
|
+
end
|
16
|
+
|
17
|
+
# Return attribute name
|
18
|
+
#
|
19
|
+
# @return [Symbol]
|
20
|
+
attr_reader :name
|
21
|
+
|
22
|
+
# Return instance variable name
|
23
|
+
#
|
24
|
+
# @return [Symbol]
|
25
|
+
attr_reader :instance_variable_name
|
26
|
+
|
27
|
+
# Load attribute
|
28
|
+
#
|
29
|
+
# @param [Object] object
|
30
|
+
# @param [Hash] attributes
|
31
|
+
#
|
32
|
+
# @return [self]
|
33
|
+
def load(object, attributes)
|
34
|
+
set(object, attributes.fetch(name))
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get attribute value from object
|
38
|
+
#
|
39
|
+
# @param [Object] object
|
40
|
+
#
|
41
|
+
# @return [Object]
|
42
|
+
def get(object)
|
43
|
+
object.public_send(name)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Set attribute value in object
|
47
|
+
#
|
48
|
+
# @param [Object] object
|
49
|
+
# @param [Object] value
|
50
|
+
#
|
51
|
+
# @return [self]
|
52
|
+
def set(object, value)
|
53
|
+
object.instance_variable_set(instance_variable_name, value)
|
54
|
+
|
55
|
+
self
|
56
|
+
end
|
57
|
+
end # Attribute
|
58
|
+
end # Anima
|
59
|
+
end # Unparser
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Unparser
|
4
|
+
class Anima
|
5
|
+
# Abstract base class for anima errors
|
6
|
+
class Error < RuntimeError
|
7
|
+
FORMAT = '%s attributes missing: %s, unknown: %s'.freeze
|
8
|
+
private_constant(*constants(false))
|
9
|
+
|
10
|
+
# Initialize object
|
11
|
+
#
|
12
|
+
# @param [Class] klass
|
13
|
+
# the class being initialized
|
14
|
+
# @param [Enumerable<Symbol>] missing
|
15
|
+
# @param [Enumerable<Symbol>] unknown
|
16
|
+
#
|
17
|
+
# @return [undefined]
|
18
|
+
def initialize(klass, missing, unknown)
|
19
|
+
super(format(FORMAT, klass, missing, unknown))
|
20
|
+
end
|
21
|
+
end # Error
|
22
|
+
end # Anima
|
23
|
+
end # Unparser
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Unparser
|
4
|
+
# Original code before vendoring and reduction from: https://github.com/mbj/anima.
|
5
|
+
class Anima < Module
|
6
|
+
include Adamantium, Equalizer.new(:attributes)
|
7
|
+
|
8
|
+
# Return names
|
9
|
+
#
|
10
|
+
# @return [AttributeSet]
|
11
|
+
attr_reader :attributes
|
12
|
+
|
13
|
+
# Initialize object
|
14
|
+
#
|
15
|
+
# @return [undefined]
|
16
|
+
#
|
17
|
+
# rubocop:disable Lint/MissingSuper
|
18
|
+
def initialize(*names)
|
19
|
+
@attributes = names.uniq.map(&Attribute.public_method(:new)).freeze
|
20
|
+
end
|
21
|
+
# rubocop:enable Lint/MissingSuper
|
22
|
+
|
23
|
+
# Return new anima with attributes added
|
24
|
+
#
|
25
|
+
# @return [Anima]
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# anima = Anima.new(:foo)
|
29
|
+
# anima.add(:bar) # equals Anima.new(:foo, :bar)
|
30
|
+
#
|
31
|
+
def add(*names)
|
32
|
+
new(attribute_names + names)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return new anima with attributes removed
|
36
|
+
#
|
37
|
+
# @return [Anima]
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# anima = Anima.new(:foo, :bar)
|
41
|
+
# anima.remove(:bar) # equals Anima.new(:foo)
|
42
|
+
#
|
43
|
+
def remove(*names)
|
44
|
+
new(attribute_names - names)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return attributes hash for instance
|
48
|
+
#
|
49
|
+
# @param [Object] object
|
50
|
+
#
|
51
|
+
# @return [Hash]
|
52
|
+
def attributes_hash(object)
|
53
|
+
attributes.each_with_object({}) do |attribute, attributes_hash|
|
54
|
+
attributes_hash[attribute.name] = attribute.get(object)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Return attribute names
|
59
|
+
#
|
60
|
+
# @return [Enumerable<Symbol>]
|
61
|
+
def attribute_names
|
62
|
+
attributes.map(&:name)
|
63
|
+
end
|
64
|
+
memoize :attribute_names
|
65
|
+
|
66
|
+
# Initialize instance
|
67
|
+
#
|
68
|
+
# @param [Object] object
|
69
|
+
#
|
70
|
+
# @param [Hash] attribute_hash
|
71
|
+
#
|
72
|
+
# @return [self]
|
73
|
+
def initialize_instance(object, attribute_hash)
|
74
|
+
assert_known_attributes(object.class, attribute_hash)
|
75
|
+
attributes.each do |attribute|
|
76
|
+
attribute.load(object, attribute_hash)
|
77
|
+
end
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
# Static instance methods for anima infected classes
|
82
|
+
module InstanceMethods
|
83
|
+
# Initialize an anima infected object
|
84
|
+
#
|
85
|
+
# @param [#to_h] attributes
|
86
|
+
# a hash that matches anima defined attributes
|
87
|
+
#
|
88
|
+
# @return [undefined]
|
89
|
+
#
|
90
|
+
# rubocop:disable Lint/MissingSuper
|
91
|
+
def initialize(attributes)
|
92
|
+
self.class.anima.initialize_instance(self, attributes)
|
93
|
+
end
|
94
|
+
# rubocop:enable Lint/MissingSuper
|
95
|
+
|
96
|
+
# Return a hash representation of an anima infected object
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# anima.to_h # => { :foo => : bar }
|
100
|
+
#
|
101
|
+
# @return [Hash]
|
102
|
+
#
|
103
|
+
# @api public
|
104
|
+
def to_h
|
105
|
+
self.class.anima.attributes_hash(self)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Return updated instance
|
109
|
+
#
|
110
|
+
# @example
|
111
|
+
# klass = Class.new do
|
112
|
+
# include Anima.new(:foo, :bar)
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
# foo = klass.new(:foo => 1, :bar => 2)
|
116
|
+
# updated = foo.with(:foo => 3)
|
117
|
+
# updated.foo # => 3
|
118
|
+
# updated.bar # => 2
|
119
|
+
#
|
120
|
+
# @param [Hash] attributes
|
121
|
+
#
|
122
|
+
# @return [Anima]
|
123
|
+
#
|
124
|
+
# @api public
|
125
|
+
def with(attributes)
|
126
|
+
self.class.new(to_h.update(attributes))
|
127
|
+
end
|
128
|
+
end # InstanceMethods
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
# Infect the instance with anima
|
133
|
+
#
|
134
|
+
# @param [Class, Module] scope
|
135
|
+
#
|
136
|
+
# @return [undefined]
|
137
|
+
def included(descendant)
|
138
|
+
descendant.instance_exec(self, attribute_names) do |anima, names|
|
139
|
+
# Define anima method
|
140
|
+
define_singleton_method(:anima) { anima }
|
141
|
+
|
142
|
+
# Define instance methods
|
143
|
+
include InstanceMethods
|
144
|
+
|
145
|
+
# Define attribute readers
|
146
|
+
attr_reader(*names)
|
147
|
+
|
148
|
+
# Define equalizer
|
149
|
+
include Equalizer.new(*names)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Fail unless keys in +attribute_hash+ matches #attribute_names
|
154
|
+
#
|
155
|
+
# @param [Class] klass
|
156
|
+
# the class being initialized
|
157
|
+
#
|
158
|
+
# @param [Hash] attribute_hash
|
159
|
+
# the attributes to initialize +object+ with
|
160
|
+
#
|
161
|
+
# @return [undefined]
|
162
|
+
#
|
163
|
+
# @raise [Error]
|
164
|
+
def assert_known_attributes(klass, attribute_hash)
|
165
|
+
keys = attribute_hash.keys
|
166
|
+
|
167
|
+
unknown = keys - attribute_names
|
168
|
+
missing = attribute_names - keys
|
169
|
+
|
170
|
+
unless unknown.empty? && missing.empty?
|
171
|
+
fail Error.new(klass, missing, unknown)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Return new instance
|
176
|
+
#
|
177
|
+
# @param [Enumerable<Symbol>] attributes
|
178
|
+
#
|
179
|
+
# @return [Anima]
|
180
|
+
def new(attributes)
|
181
|
+
self.class.new(*attributes)
|
182
|
+
end
|
183
|
+
end # Anima
|
184
|
+
end # Unparser
|
data/lib/unparser/ast.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
module Unparser
|
4
4
|
# Namespace for AST processing tools
|
5
5
|
module AST
|
6
|
-
|
7
6
|
FIRST_CHILD = ->(node) { node.children.first }.freeze
|
8
7
|
TAUTOLOGY = ->(_node) { true }.freeze
|
9
8
|
|
@@ -79,7 +78,7 @@ module Unparser
|
|
79
78
|
|
80
79
|
# AST enumerator
|
81
80
|
class Enumerator
|
82
|
-
include Adamantium
|
81
|
+
include Adamantium, Concord.new(:node, :controller), Enumerable
|
83
82
|
|
84
83
|
# Return new instance
|
85
84
|
#
|