unparser 0.5.3 → 0.6.0

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.
@@ -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
@@ -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
@@ -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
  #
@@ -3,7 +3,7 @@
3
3
  module Unparser
4
4
  # Class to colorize strings
5
5
  class Color
6
- include Adamantium::Flat, Concord.new(:code)
6
+ include Adamantium, Concord.new(:code)
7
7
 
8
8
  # Format text with color
9
9
  #
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unparser
4
+ # A mixin to define a composition
5
+ #
6
+ # Original code before vendoring and reduction from: https://github.com/mbj/concord.
7
+ class Concord < Module
8
+ include Adamantium, Equalizer.new(:names)
9
+
10
+ # The maximum number of objects the hosting class is composed of
11
+ MAX_NR_OF_OBJECTS = 3
12
+
13
+ # Return names
14
+ #
15
+ # @return [Enumerable<Symbol>]
16
+ #
17
+ # @api private
18
+ #
19
+ attr_reader :names
20
+
21
+ private
22
+
23
+ # Initialize object
24
+ #
25
+ # @return [undefined]
26
+ #
27
+ # @api private
28
+ #
29
+ # rubocop:disable Lint/MissingSuper
30
+ def initialize(*names)
31
+ if names.length > MAX_NR_OF_OBJECTS
32
+ fail "Composition of more than #{MAX_NR_OF_OBJECTS} objects is not allowed"
33
+ end
34
+
35
+ @names = names
36
+ define_initialize
37
+ define_readers
38
+ define_equalizer
39
+ end
40
+ # rubocop:enable Lint/MissingSuper
41
+
42
+ # Define equalizer
43
+ #
44
+ # @return [undefined]
45
+ #
46
+ # @api private
47
+ #
48
+ def define_equalizer
49
+ include(Equalizer.new(*names))
50
+ end
51
+
52
+ # Define readers
53
+ #
54
+ # @return [undefined]
55
+ #
56
+ # @api private
57
+ #
58
+ def define_readers
59
+ attribute_names = names
60
+ attr_reader(*attribute_names)
61
+
62
+ protected(*attribute_names) if attribute_names.any?
63
+ end
64
+
65
+ # Define initialize method
66
+ #
67
+ # @return [undefined]
68
+ #
69
+ # @api private
70
+ #
71
+ #
72
+ def define_initialize
73
+ ivars = instance_variable_names
74
+ size = names.size
75
+
76
+ define_method :initialize do |*args|
77
+ args_size = args.size
78
+ unless args_size.equal?(size)
79
+ fail ArgumentError, "wrong number of arguments (#{args_size} for #{size})"
80
+ end
81
+
82
+ ivars.zip(args) { |ivar, arg| instance_variable_set(ivar, arg) }
83
+ end
84
+ end
85
+
86
+ # Return instance variable names
87
+ #
88
+ # @return [String]
89
+ #
90
+ # @api private
91
+ #
92
+ def instance_variable_names
93
+ names.map { |name| "@#{name}" }
94
+ end
95
+
96
+ # Mixin for public attribute readers
97
+ class Public < self
98
+
99
+ # Hook called when module is included
100
+ #
101
+ # @param [Class,Module] descendant
102
+ #
103
+ # @return [undefined]
104
+ #
105
+ # @api private
106
+ #
107
+ def included(descendant)
108
+ names.each do |name|
109
+ descendant.__send__(:public, name)
110
+ end
111
+ end
112
+ end # Public
113
+ end # Concord
114
+ end # Unparser
@@ -3,7 +3,7 @@
3
3
  module Unparser
4
4
  # Class to create diffs from source code
5
5
  class Diff
6
- include Adamantium::Flat, Concord.new(:old, :new)
6
+ include Adamantium, Concord.new(:old, :new)
7
7
 
8
8
  ADDITION = '+'
9
9
  DELETION = '-'
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Unparser
4
+ module RequireBlock
5
+
6
+ private
7
+
8
+ # Raise error unless block is provided
9
+ #
10
+ # @raise [MissingBlockError]
11
+ # if no block is given
12
+ #
13
+ # @return [self]
14
+ def require_block
15
+ fail LocalJumpError unless block_given?
16
+
17
+ self
18
+ end
19
+ end # RequireBLock
20
+
21
+ class Either
22
+ include(
23
+ Adamantium,
24
+ Concord.new(:value),
25
+ RequireBlock
26
+ )
27
+
28
+ # Execute block and wrap error in left
29
+ #
30
+ # @param [Class<Exception>] exception
31
+ #
32
+ # @return [Either<Exception, Object>]
33
+ def self.wrap_error(*exceptions)
34
+ Right.new(yield)
35
+ rescue *exceptions => error
36
+ Left.new(error)
37
+ end
38
+
39
+ # Test for left constructor
40
+ #
41
+ # @return [Boolean]
42
+ def left?
43
+ instance_of?(Left)
44
+ end
45
+
46
+ # Test for right constructor
47
+ #
48
+ # @return [Boolean]
49
+ def right?
50
+ instance_of?(Right)
51
+ end
52
+
53
+ class Left < self
54
+ # Evaluate functor block
55
+ #
56
+ # @return [Either::Left<Object>]
57
+ def fmap(&block)
58
+ require_block(&block)
59
+ end
60
+
61
+ # Evaluate applicative block
62
+ #
63
+ # @return [Either::Left<Object>]
64
+ def bind(&block)
65
+ require_block(&block)
66
+ end
67
+
68
+ # Unwrap value from left
69
+ #
70
+ # @return [Object]
71
+ def from_left
72
+ value
73
+ end
74
+
75
+ # Unwrap value from right
76
+ #
77
+ # @return [Object]
78
+ #
79
+ def from_right
80
+ if block_given?
81
+ yield(value)
82
+ else
83
+ fail "Expected right value, got #{inspect}"
84
+ end
85
+ end
86
+
87
+ # Map over left value
88
+ #
89
+ # @return [Either::Right<Object>]
90
+ def lmap
91
+ Left.new(yield(value))
92
+ end
93
+
94
+ # Evaluate left side of branch
95
+ #
96
+ # @param [#call] left
97
+ # @param [#call] _right
98
+ def either(left, _right)
99
+ left.call(value)
100
+ end
101
+ end # Left
102
+
103
+ class Right < self
104
+ # Evaluate functor block
105
+ #
106
+ # @return [Either::Right<Object>]
107
+ def fmap
108
+ Right.new(yield(value))
109
+ end
110
+
111
+ # Evaluate applicative block
112
+ #
113
+ # @return [Either<Object>]
114
+ def bind
115
+ yield(value)
116
+ end
117
+
118
+ # Unwrap value from left
119
+ #
120
+ # @return [Object]
121
+ #
122
+ def from_left
123
+ if block_given?
124
+ yield(value)
125
+ else
126
+ fail "Expected left value, got #{inspect}"
127
+ end
128
+ end
129
+
130
+ # Unwrap value from right
131
+ #
132
+ # @return [Object]
133
+ def from_right
134
+ value
135
+ end
136
+
137
+ # Map over left value
138
+ #
139
+ # @return [Either::Right<Object>]
140
+ def lmap(&block)
141
+ require_block(&block)
142
+ end
143
+
144
+ # Evaluate right side of branch
145
+ #
146
+ # @param [#call] _left
147
+ # @param [#call] right
148
+ def either(_left, right)
149
+ right.call(value)
150
+ end
151
+ end # Right
152
+ end # Either
153
+ end # Unparser