unparser 0.4.7 → 0.6.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (157) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +40 -9
  3. data/bin/unparser +2 -2
  4. data/lib/unparser/abstract_type.rb +121 -0
  5. data/lib/unparser/adamantium/method_builder.rb +111 -0
  6. data/lib/unparser/adamantium.rb +150 -0
  7. data/lib/unparser/anima/attribute.rb +59 -0
  8. data/lib/unparser/anima/error.rb +23 -0
  9. data/lib/unparser/anima.rb +184 -0
  10. data/lib/unparser/ast/local_variable_scope.rb +6 -76
  11. data/lib/unparser/ast.rb +1 -3
  12. data/lib/unparser/buffer.rb +14 -25
  13. data/lib/unparser/cli.rb +85 -77
  14. data/lib/unparser/{cli/color.rb → color.rb} +4 -14
  15. data/lib/unparser/comments.rb +0 -26
  16. data/lib/unparser/concord.rb +114 -0
  17. data/lib/unparser/constants.rb +4 -53
  18. data/lib/unparser/diff.rb +98 -0
  19. data/lib/unparser/dsl.rb +0 -32
  20. data/lib/unparser/either.rb +153 -0
  21. data/lib/unparser/emitter/alias.rb +2 -8
  22. data/lib/unparser/emitter/args.rb +45 -0
  23. data/lib/unparser/emitter/argument.rb +13 -169
  24. data/lib/unparser/emitter/array.rb +27 -0
  25. data/lib/unparser/emitter/array_pattern.rb +29 -0
  26. data/lib/unparser/emitter/assignment.rb +36 -127
  27. data/lib/unparser/emitter/begin.rb +9 -84
  28. data/lib/unparser/emitter/binary.rb +7 -20
  29. data/lib/unparser/emitter/block.rb +57 -41
  30. data/lib/unparser/emitter/case.rb +6 -48
  31. data/lib/unparser/emitter/case_guard.rb +27 -0
  32. data/lib/unparser/emitter/case_match.rb +40 -0
  33. data/lib/unparser/emitter/cbase.rb +1 -3
  34. data/lib/unparser/emitter/class.rb +6 -26
  35. data/lib/unparser/emitter/const_pattern.rb +24 -0
  36. data/lib/unparser/emitter/def.rb +7 -51
  37. data/lib/unparser/emitter/defined.rb +2 -12
  38. data/lib/unparser/emitter/dstr.rb +22 -0
  39. data/lib/unparser/emitter/dsym.rb +41 -0
  40. data/lib/unparser/emitter/find_pattern.rb +18 -0
  41. data/lib/unparser/emitter/flipflop.rb +11 -10
  42. data/lib/unparser/emitter/float.rb +29 -0
  43. data/lib/unparser/emitter/flow_modifier.rb +15 -53
  44. data/lib/unparser/emitter/for.rb +5 -19
  45. data/lib/unparser/emitter/hash.rb +36 -0
  46. data/lib/unparser/emitter/hash_pattern.rb +67 -0
  47. data/lib/unparser/emitter/hookexe.rb +5 -11
  48. data/lib/unparser/emitter/if.rb +15 -71
  49. data/lib/unparser/emitter/in_match.rb +21 -0
  50. data/lib/unparser/emitter/in_pattern.rb +36 -0
  51. data/lib/unparser/emitter/index.rb +22 -89
  52. data/lib/unparser/emitter/kwargs.rb +13 -0
  53. data/lib/unparser/emitter/kwbegin.rb +31 -0
  54. data/lib/unparser/emitter/lambda.rb +0 -8
  55. data/lib/unparser/emitter/masgn.rb +20 -0
  56. data/lib/unparser/emitter/match.rb +3 -17
  57. data/lib/unparser/emitter/match_alt.rb +23 -0
  58. data/lib/unparser/emitter/match_as.rb +21 -0
  59. data/lib/unparser/emitter/match_pattern.rb +30 -0
  60. data/lib/unparser/emitter/match_pattern_p.rb +20 -0
  61. data/lib/unparser/emitter/match_rest.rb +33 -0
  62. data/lib/unparser/emitter/match_var.rb +19 -0
  63. data/lib/unparser/emitter/mlhs.rb +40 -0
  64. data/lib/unparser/emitter/module.rb +3 -9
  65. data/lib/unparser/emitter/op_assign.rb +14 -29
  66. data/lib/unparser/emitter/pair.rb +33 -0
  67. data/lib/unparser/emitter/pin.rb +19 -0
  68. data/lib/unparser/emitter/primitive.rb +93 -0
  69. data/lib/unparser/emitter/range.rb +35 -0
  70. data/lib/unparser/emitter/regexp.rb +35 -0
  71. data/lib/unparser/emitter/repetition.rb +17 -57
  72. data/lib/unparser/emitter/rescue.rb +1 -97
  73. data/lib/unparser/emitter/root.rb +17 -1
  74. data/lib/unparser/emitter/send.rb +10 -219
  75. data/lib/unparser/emitter/simple.rb +33 -0
  76. data/lib/unparser/emitter/splat.rb +13 -19
  77. data/lib/unparser/emitter/super.rb +1 -29
  78. data/lib/unparser/emitter/undef.rb +1 -9
  79. data/lib/unparser/emitter/variable.rb +1 -31
  80. data/lib/unparser/emitter/xstr.rb +72 -0
  81. data/lib/unparser/emitter/yield.rb +1 -9
  82. data/lib/unparser/emitter.rb +24 -425
  83. data/lib/unparser/equalizer.rb +98 -0
  84. data/lib/unparser/generation.rb +252 -0
  85. data/lib/unparser/node_details/send.rb +65 -0
  86. data/lib/unparser/node_details.rb +21 -0
  87. data/lib/unparser/node_helpers.rb +48 -6
  88. data/lib/unparser/validation.rb +172 -0
  89. data/lib/unparser/writer/binary.rb +99 -0
  90. data/lib/unparser/writer/dynamic_string.rb +211 -0
  91. data/lib/unparser/writer/resbody.rb +40 -0
  92. data/lib/unparser/writer/rescue.rb +43 -0
  93. data/lib/unparser/{emitter → writer}/send/attribute_assignment.rb +11 -26
  94. data/lib/unparser/writer/send/binary.rb +27 -0
  95. data/lib/unparser/writer/send/conditional.rb +25 -0
  96. data/lib/unparser/writer/send/regular.rb +33 -0
  97. data/lib/unparser/{emitter → writer}/send/unary.rb +10 -17
  98. data/lib/unparser/writer/send.rb +115 -0
  99. data/lib/unparser/writer.rb +15 -0
  100. data/lib/unparser.rb +161 -77
  101. metadata +100 -157
  102. data/.circleci/config.yml +0 -49
  103. data/.gitignore +0 -37
  104. data/.rspec +0 -4
  105. data/.rubocop.yml +0 -9
  106. data/Changelog.md +0 -156
  107. data/Gemfile +0 -9
  108. data/Gemfile.lock +0 -181
  109. data/LICENSE +0 -20
  110. data/Rakefile +0 -22
  111. data/config/devtools.yml +0 -2
  112. data/config/flay.yml +0 -3
  113. data/config/flog.yml +0 -2
  114. data/config/mutant.yml +0 -6
  115. data/config/reek.yml +0 -98
  116. data/config/rubocop.yml +0 -122
  117. data/config/yardstick.yml +0 -2
  118. data/lib/unparser/cli/differ.rb +0 -152
  119. data/lib/unparser/cli/source.rb +0 -267
  120. data/lib/unparser/emitter/empty.rb +0 -23
  121. data/lib/unparser/emitter/ensure.rb +0 -37
  122. data/lib/unparser/emitter/literal/array.rb +0 -29
  123. data/lib/unparser/emitter/literal/dynamic.rb +0 -53
  124. data/lib/unparser/emitter/literal/dynamic_body.rb +0 -132
  125. data/lib/unparser/emitter/literal/execute_string.rb +0 -38
  126. data/lib/unparser/emitter/literal/hash.rb +0 -156
  127. data/lib/unparser/emitter/literal/primitive.rb +0 -145
  128. data/lib/unparser/emitter/literal/range.rb +0 -36
  129. data/lib/unparser/emitter/literal/regexp.rb +0 -114
  130. data/lib/unparser/emitter/literal/singleton.rb +0 -26
  131. data/lib/unparser/emitter/literal.rb +0 -10
  132. data/lib/unparser/emitter/meta.rb +0 -16
  133. data/lib/unparser/emitter/redo.rb +0 -25
  134. data/lib/unparser/emitter/resbody.rb +0 -76
  135. data/lib/unparser/emitter/retry.rb +0 -25
  136. data/lib/unparser/emitter/send/binary.rb +0 -57
  137. data/lib/unparser/emitter/send/conditional.rb +0 -40
  138. data/lib/unparser/emitter/send/regular.rb +0 -40
  139. data/lib/unparser/preprocessor.rb +0 -159
  140. data/spec/integration/unparser/corpus_spec.rb +0 -111
  141. data/spec/integrations.yml +0 -92
  142. data/spec/spec_helper.rb +0 -20
  143. data/spec/unit/unparser/buffer/append_spec.rb +0 -24
  144. data/spec/unit/unparser/buffer/append_without_prefix_spec.rb +0 -23
  145. data/spec/unit/unparser/buffer/capture_content_spec.rb +0 -17
  146. data/spec/unit/unparser/buffer/content_spec.rb +0 -38
  147. data/spec/unit/unparser/buffer/fresh_line_spec.rb +0 -20
  148. data/spec/unit/unparser/buffer/indent_spec.rb +0 -20
  149. data/spec/unit/unparser/buffer/nl_spec.rb +0 -16
  150. data/spec/unit/unparser/buffer/unindent_spec.rb +0 -20
  151. data/spec/unit/unparser/comments/consume_spec.rb +0 -22
  152. data/spec/unit/unparser/comments/take_all_spec.rb +0 -19
  153. data/spec/unit/unparser/comments/take_before_spec.rb +0 -46
  154. data/spec/unit/unparser/comments/take_eol_comments_spec.rb +0 -32
  155. data/spec/unit/unparser/emitter/class_methods/handle_spec.rb +0 -17
  156. data/spec/unit/unparser_spec.rb +0 -1849
  157. data/unparser.gemspec +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cc149df7cd940bb78d005b8f3d30635a877c270d37f40cfe8e7d9ba8b305ab8a
4
- data.tar.gz: c8a1d3dd4aba58f7fd6a6188341916566857057663d660bd24a2d40f6247158a
3
+ metadata.gz: 43c55e0072230fba02c24266555e1599559396739677e4371b6c9bca4b2a29b1
4
+ data.tar.gz: 4743b675c63ed3cf84ee3c784b70fd5078f8ed4e022e02bde27473a7bf44dbbe
5
5
  SHA512:
6
- metadata.gz: 1cba4e5c9f029441dbca0eb39304478562e76c13023ec22b83dc5eeb58d7640d5c0d83878dad104e45ae6369662f3be0e7141398fb7cf37f6e4932075e305b0b
7
- data.tar.gz: 2c61445c8d43bf01f24bb2f79dd2c49ba96fceef9479c6f1b94a77d7b5eba43fb79f0bbbf7d3995e143b81d208da6ad04852ec761daaf210741f46a049da0893
6
+ metadata.gz: 94bc33d333f3652cbdfa12169432d7a139b568cb72d4ae08994921760d2f7c8348a8d35f717053c059866ce7e640d08c9af6495a5e37132b2b667d1895b889da
7
+ data.tar.gz: ee2ee8e37250de111c419566c74d7b5954b02137f9e6fcb88cf9ff26820583698faf2533e6d01ae5738481bab91797060e2773781e9c93cb8da26efce602c1b0
data/README.md CHANGED
@@ -1,20 +1,24 @@
1
1
  unparser
2
2
  ========
3
3
 
4
- [![Build Status](https://secure.travis-ci.org/mbj/unparser.svg?branch=master)](http://travis-ci.org/mbj/unparser)
5
- [![Code Climate](https://codeclimate.com/github/mbj/unparser.svg)](https://codeclimate.com/github/mbj/unparser)
4
+ ![CI](https://github.com/mbj/unparser/workflows/CI/badge.svg)
6
5
  [![Gem Version](https://img.shields.io/gem/v/unparser.svg)](https://rubygems.org/gems/unparser)
7
6
 
8
- Generate equivalent source for ASTs from whitequarks [parser](https://github.com/whitequark/parser).
7
+ Generate equivalent source for ASTs from [parser](https://github.com/whitequark/parser).
9
8
 
10
9
  The following constraints apply:
11
10
 
12
11
  * No support for macruby extensions
13
12
  * Only support for the [modern AST](https://github.com/whitequark/parser/#usage) format
14
- * Only support for Ruby >= 2.5
13
+ * Only support for Ruby >= 2.7
15
14
 
16
- It serves well for [mutant](https://github.com/mbj/mutant) mutators and the in-memory vendoring for self hosting,
17
- and other tooling.
15
+ Notable Users:
16
+
17
+ * [mutant](https://github.com/mbj/mutant) - Code review engine via mutation testing.
18
+ * [ruby-next](https://github.com/ruby-next/ruby-next) - Ruby Syntax Backports.
19
+ * May other [reverse-dependencies](https://rubygems.org/gems/unparser/reverse_dependencies).
20
+
21
+ (if you want your tool to be mentioned here please PR the addition with a TLDR of your use case).
18
22
 
19
23
  Public API:
20
24
  -----------
@@ -87,16 +91,26 @@ RUBY
87
91
 
88
92
  generated = Unparser.unparse(node) # ["foo", "bar"], NOT %w[foo bar] !
89
93
 
90
- code == generated # false, not identical code
94
+ code == generated # false, not identical code
91
95
  Unparser.parse(generated) == node # true, but identical AST
92
96
  ```
93
97
 
94
98
  Summary: unparser does not reproduce your source! It produces equivalent source.
95
99
 
100
+ Ruby Versions:
101
+ --------------
102
+
103
+ Unparsers primay reason for existance is mutant and its
104
+ supported [Ruby-Versions](https://github.com/mbj/mutant#ruby-versions).
105
+
106
+ Basically: All non EOL MRI releases.
107
+
108
+ If you need to generate Ruby Syntax outside of this band feel free to contact me (email in gemspec).
109
+
96
110
  Testing:
97
111
  --------
98
112
 
99
- 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.
100
114
  If there is a non round trippable example that is NOT subjected to known [Limitations](#limitations).
101
115
  please report a bug.
102
116
 
@@ -154,6 +168,18 @@ People
154
168
 
155
169
  Various people contributed to this repository. See [Contributors](https://github.com/mbj/unparser/graphs/contributors).
156
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
+
157
183
  Contributing
158
184
  -------------
159
185
 
@@ -161,10 +187,15 @@ Contributing
161
187
  * Make your feature addition or bug fix.
162
188
  * Add tests for it. This is important so I don't break it in a
163
189
  future version unintentionally.
164
- * Commit, do not mess with Rakefile or version
190
+ * Commit, do not mess with version
165
191
  (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
166
192
  * Send me a pull request. Bonus points for topic branches.
167
193
 
194
+ Known Users
195
+ -------------
196
+
197
+ * [RailsRocket](https://www.railsrocket.app) - A no-code app builder that creates Rails apps
198
+
168
199
  License
169
200
  -------
170
201
 
data/bin/unparser CHANGED
@@ -2,9 +2,9 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  trap('INT') do |status|
5
- exit! 128 + status
5
+ exit! status + 128
6
6
  end
7
7
 
8
- require 'unparser/cli'
8
+ require 'unparser'
9
9
 
10
10
  exit Unparser::CLI.run(ARGV)
@@ -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