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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 21e418840b351f5e697c472127300d7733ca1c20bdf4be321ae1832133447767
4
- data.tar.gz: c130412892dcb6a1db4835c3ef9fa9738f2a181ede78ccca6a7a13336a348e8c
3
+ metadata.gz: 7eda3912beee61a5384b60d5011749c10c7c46df2ad76d34c26cbb2bf2cc3fae
4
+ data.tar.gz: 2721620e86f7ff2d5be383551ef9a9c6b961f3a2695ce3eae84e56246a658129
5
5
  SHA512:
6
- metadata.gz: 4a25a3e71df443024af556d20ca97f9a627874ee5857a4badfa2c7bd228159732fac0060715b198acc5648879c25baf8c0b10496f6cb0303fa41841cf06521ad
7
- data.tar.gz: 8dec6ae089dbf574a7687177993da010f2569677217032e95079e007598c04e0acbb35d967667532331187d6ab25c81346c5073454dda7665e8864fc0e31e42a
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.5
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 MRI-2.5.x.
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::Flat, Concord.new(:node, :controller), Enumerable
81
+ include Adamantium, Concord.new(:node, :controller), Enumerable
83
82
 
84
83
  # Return new instance
85
84
  #