rubymacros 0.1.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.
data/COPYING.LGPL ADDED
@@ -0,0 +1,165 @@
1
+ GNU LESSER GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+
9
+ This version of the GNU Lesser General Public License incorporates
10
+ the terms and conditions of version 3 of the GNU General Public
11
+ License, supplemented by the additional permissions listed below.
12
+
13
+ 0. Additional Definitions.
14
+
15
+ As used herein, "this License" refers to version 3 of the GNU Lesser
16
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
17
+ General Public License.
18
+
19
+ "The Library" refers to a covered work governed by this License,
20
+ other than an Application or a Combined Work as defined below.
21
+
22
+ An "Application" is any work that makes use of an interface provided
23
+ by the Library, but which is not otherwise based on the Library.
24
+ Defining a subclass of a class defined by the Library is deemed a mode
25
+ of using an interface provided by the Library.
26
+
27
+ A "Combined Work" is a work produced by combining or linking an
28
+ Application with the Library. The particular version of the Library
29
+ with which the Combined Work was made is also called the "Linked
30
+ Version".
31
+
32
+ The "Minimal Corresponding Source" for a Combined Work means the
33
+ Corresponding Source for the Combined Work, excluding any source code
34
+ for portions of the Combined Work that, considered in isolation, are
35
+ based on the Application, and not on the Linked Version.
36
+
37
+ The "Corresponding Application Code" for a Combined Work means the
38
+ object code and/or source code for the Application, including any data
39
+ and utility programs needed for reproducing the Combined Work from the
40
+ Application, but excluding the System Libraries of the Combined Work.
41
+
42
+ 1. Exception to Section 3 of the GNU GPL.
43
+
44
+ You may convey a covered work under sections 3 and 4 of this License
45
+ without being bound by section 3 of the GNU GPL.
46
+
47
+ 2. Conveying Modified Versions.
48
+
49
+ If you modify a copy of the Library, and, in your modifications, a
50
+ facility refers to a function or data to be supplied by an Application
51
+ that uses the facility (other than as an argument passed when the
52
+ facility is invoked), then you may convey a copy of the modified
53
+ version:
54
+
55
+ a) under this License, provided that you make a good faith effort to
56
+ ensure that, in the event an Application does not supply the
57
+ function or data, the facility still operates, and performs
58
+ whatever part of its purpose remains meaningful, or
59
+
60
+ b) under the GNU GPL, with none of the additional permissions of
61
+ this License applicable to that copy.
62
+
63
+ 3. Object Code Incorporating Material from Library Header Files.
64
+
65
+ The object code form of an Application may incorporate material from
66
+ a header file that is part of the Library. You may convey such object
67
+ code under terms of your choice, provided that, if the incorporated
68
+ material is not limited to numerical parameters, data structure
69
+ layouts and accessors, or small macros, inline functions and templates
70
+ (ten or fewer lines in length), you do both of the following:
71
+
72
+ a) Give prominent notice with each copy of the object code that the
73
+ Library is used in it and that the Library and its use are
74
+ covered by this License.
75
+
76
+ b) Accompany the object code with a copy of the GNU GPL and this license
77
+ document.
78
+
79
+ 4. Combined Works.
80
+
81
+ You may convey a Combined Work under terms of your choice that,
82
+ taken together, effectively do not restrict modification of the
83
+ portions of the Library contained in the Combined Work and reverse
84
+ engineering for debugging such modifications, if you also do each of
85
+ the following:
86
+
87
+ a) Give prominent notice with each copy of the Combined Work that
88
+ the Library is used in it and that the Library and its use are
89
+ covered by this License.
90
+
91
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
92
+ document.
93
+
94
+ c) For a Combined Work that displays copyright notices during
95
+ execution, include the copyright notice for the Library among
96
+ these notices, as well as a reference directing the user to the
97
+ copies of the GNU GPL and this license document.
98
+
99
+ d) Do one of the following:
100
+
101
+ 0) Convey the Minimal Corresponding Source under the terms of this
102
+ License, and the Corresponding Application Code in a form
103
+ suitable for, and under terms that permit, the user to
104
+ recombine or relink the Application with a modified version of
105
+ the Linked Version to produce a modified Combined Work, in the
106
+ manner specified by section 6 of the GNU GPL for conveying
107
+ Corresponding Source.
108
+
109
+ 1) Use a suitable shared library mechanism for linking with the
110
+ Library. A suitable mechanism is one that (a) uses at run time
111
+ a copy of the Library already present on the user's computer
112
+ system, and (b) will operate properly with a modified version
113
+ of the Library that is interface-compatible with the Linked
114
+ Version.
115
+
116
+ e) Provide Installation Information, but only if you would otherwise
117
+ be required to provide such information under section 6 of the
118
+ GNU GPL, and only to the extent that such information is
119
+ necessary to install and execute a modified version of the
120
+ Combined Work produced by recombining or relinking the
121
+ Application with a modified version of the Linked Version. (If
122
+ you use option 4d0, the Installation Information must accompany
123
+ the Minimal Corresponding Source and Corresponding Application
124
+ Code. If you use option 4d1, you must provide the Installation
125
+ Information in the manner specified by section 6 of the GNU GPL
126
+ for conveying Corresponding Source.)
127
+
128
+ 5. Combined Libraries.
129
+
130
+ You may place library facilities that are a work based on the
131
+ Library side by side in a single library together with other library
132
+ facilities that are not Applications and are not covered by this
133
+ License, and convey such a combined library under terms of your
134
+ choice, if you do both of the following:
135
+
136
+ a) Accompany the combined library with a copy of the same work based
137
+ on the Library, uncombined with any other library facilities,
138
+ conveyed under the terms of this License.
139
+
140
+ b) Give prominent notice with the combined library that part of it
141
+ is a work based on the Library, and explaining where to find the
142
+ accompanying uncombined form of the same work.
143
+
144
+ 6. Revised Versions of the GNU Lesser General Public License.
145
+
146
+ The Free Software Foundation may publish revised and/or new versions
147
+ of the GNU Lesser General Public License from time to time. Such new
148
+ versions will be similar in spirit to the present version, but may
149
+ differ in detail to address new problems or concerns.
150
+
151
+ Each version is given a distinguishing version number. If the
152
+ Library as you received it specifies that a certain numbered version
153
+ of the GNU Lesser General Public License "or any later version"
154
+ applies to it, you have the option of following the terms and
155
+ conditions either of that published version or of any later version
156
+ published by the Free Software Foundation. If the Library as you
157
+ received it does not specify a version number of the GNU Lesser
158
+ General Public License, you may choose any version of the GNU Lesser
159
+ General Public License ever published by the Free Software Foundation.
160
+
161
+ If the Library as you received it specifies that a proxy can decide
162
+ whether future versions of the GNU Lesser General Public License shall
163
+ apply, that proxy's public statement of acceptance of any version is
164
+ permanent authorization for you to choose that version for the
165
+ Library.
data/Manifest.txt ADDED
@@ -0,0 +1,21 @@
1
+ COPYING.LGPL
2
+ Manifest.txt
3
+ README.txt
4
+ rubymacros.vpj
5
+ Rakefile
6
+ test/test_form.rb
7
+ test/test_expand.rb
8
+ lib/macro.rb
9
+ lib/macro/form.rb
10
+ lib/macro/version.rb
11
+ example/andand.rb
12
+ example/assert.rb
13
+ example/assert_wrap.rb
14
+ example/__dir__.rb
15
+ example/__dir___wrap.rb
16
+ example/loop.rb
17
+ example/loop_wrap.rb
18
+ example/simple.rb
19
+ example/simple_wrap.rb
20
+ example/with.rb
21
+ example/with_wrap.rb
data/README.txt ADDED
@@ -0,0 +1,119 @@
1
+ = RubyMacros
2
+ * http://rubymacros.rubyforge.org
3
+ * http://rubyforge.org/projects/rubymacros
4
+
5
+ == DESCRIPTION:
6
+ RubyMacros is a lisp-like macro pre-processor for Ruby. More than just a
7
+ purely textual substitution scheme, RubyMacros can manipulate and morph
8
+ Ruby parse trees (in the form of RedParse Nodes) at parse time in just about
9
+ any way you see fit.
10
+
11
+ Macros are programmed in ruby itself. And since parse trees are represented
12
+ in RedParse format, they're easier to use (programatically) and more object-
13
+ oriented than other available ruby parsetree formats. (RedParse Node format
14
+ is actually designed to be straightforward to use and to represent the
15
+ structure of ruby source code very closely.)
16
+
17
+ == Benefits:
18
+ * Powerful and easy metaprogramming
19
+ * Create better DSLs
20
+ * Manipulate syntax trees to suit yourself
21
+ * Access local variables and other caller context unavailable to methods
22
+ * Macros as inline methods: should be slightly faster than equivalent methods
23
+
24
+ == Drawbacks:
25
+ Although in theory already as powerful as lisp macros, the current
26
+ implementation has a number of problems which added together make it merely
27
+ a proof of concept or toy at this point:
28
+ * pre-processing is very, very slow (because of RedParse)
29
+ * macro calls must be inside some sort of method;
30
+ * straight out macro calls at the top level won't work
31
+ * macros can't have blocks or receivers
32
+ * some ruby syntax is unsupported in files using macros
33
+ * files using macros must be loaded via Macro.require;
34
+ * Kernel#require will not recognize macros
35
+ * RedParse Node tree format will be changing slightly
36
+ * macros cannot be scoped
37
+ * no variable (or other) hygiene
38
+
39
+ == Requirements:
40
+ RubyMacros requires RedParse.
41
+
42
+ == Install:
43
+ gem install rubymacros
44
+
45
+ == Examples:
46
+ macro simple(a,b)
47
+ :(^a+^b)
48
+ end
49
+ def simple_user
50
+ p simple(1,2) #prints 3
51
+ end
52
+
53
+ #loop as a macro, should be a bit faster than the #loop method
54
+ macro loop(body)
55
+ :(while true
56
+ ^body
57
+ end
58
+ )
59
+ end
60
+
61
+ #for more examples, see the examples/ directory
62
+
63
+ == New Syntax:
64
+ I have invented 3 new syntactical constructions in order to allow reasonably
65
+ easy to use macros. Macros themselves look just like methods except that
66
+ 'macro' instead of 'def' is used to start the macro definition off. A form
67
+ literal is an expression surrounded by ':(' and ')'. The form escape operator
68
+ is '^'. '^' is a unary operator of fairly high precedence.
69
+
70
+ == Forms and Form Escapes:
71
+ Forms are an essential adjunct to macros. Forms represent quoted source
72
+ code, which has been parsed but not evaled yet. When a form literal is
73
+ executed, it returns a RedParse::Node representing the parse tree for the
74
+ enclosed source code. Within a form literal, a ^, used as a unary operator,
75
+ will escape the expression it controls, so that instead of being part of the
76
+ form's data, it is executed at the same time as the form literal, and the
77
+ result of an escaped expression (which should be a Node) is interpolated
78
+ into the form at that point. The whole effect is much like that of string
79
+ interpolations (#{}) inside string literals.
80
+
81
+ == How Macros Work
82
+ Typically, macros return a single form literal, which contains form escape
83
+ expressions within it which make use of the macro's parameters. However,
84
+ macro bodies may contain anything at all; more complicated macros will
85
+ likely not contain any forms. (Likewise, form literals may be used outside
86
+ macros, but the utility of doing so may be minimal.)
87
+
88
+ At parse time (well, really at method definition time, but in effect it's
89
+ much the same thing) method bodies are scanned for callsites which have the
90
+ names of known macros. When such a call is found, it is expanded as follows.
91
+ The parsetrees for the arguments to the callsite are passed as arguments to
92
+ the macro. The macro is expected to return a parsetree, which replaces the
93
+ macro callsite in the parsetree which contained it.
94
+
95
+
96
+
97
+ == Known Problems
98
+ * need to insert extra parens around form params and macro texts
99
+ * a variety of parsetrees are kept around forever for no good reason
100
+ * a few warnings and disabled tests in unit tests
101
+ * however, huge rediculous piles of RedParse warnings when running 'rake test'
102
+
103
+ == License:
104
+ Copyright (C) 2008 Caleb Clausen
105
+
106
+ This program is free software: you can redistribute it and/or modify
107
+ it under the terms of the GNU Lesser General Public License as published by
108
+ the Free Software Foundation, either version 3 of the License, or
109
+ (at your option) any later version.
110
+
111
+ This program is distributed in the hope that it will be useful,
112
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
113
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
114
+ GNU Lesser General Public License for more details.
115
+
116
+ You should have received a copy of the GNU Lesser General Public License
117
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
118
+
119
+
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ # Copyright (C) 2008 Caleb Clausen
2
+ # Distributed under the terms of Ruby's license.
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require 'lib/macro/version.rb'
6
+
7
+
8
+ readme=open("README.txt")
9
+ readme.readline("\n== DESCRIPTION:")
10
+ readme.readline("\n\n")
11
+ desc=readme.readline("\n\n")
12
+
13
+ hoe=Hoe.new("rubymacros", Macro::VERSION) do |_|
14
+ _.author = "Caleb Clausen"
15
+ _.email = "rubymacros-owner @at@ inforadical .dot. net"
16
+ _.url = ["http://rubymacros.rubyforge.org/", "http://rubyforge.org/projects/rubymacros/"]
17
+ _.extra_deps << ['redparse', '>= 0.8.0']
18
+ _.test_globs=["test/*"]
19
+ _.description=desc
20
+ _.summary=desc[/\A[^.]+\./]
21
+ # _.spec_extras={:bindir=>''}
22
+ _.rdoc_pattern=/\A(README\.txt|lib\/.*\.rb)\Z/
23
+ _.remote_rdoc_dir="/"
24
+ end
25
+
26
+
@@ -0,0 +1,7 @@
1
+ macro __DIR__
2
+ :(File.dirname(__FILE__))
3
+ end
4
+
5
+ def diruser
6
+ p __DIR__
7
+ end
@@ -0,0 +1,4 @@
1
+ require 'macro'
2
+ Macro.require 'example/__dir__.rb'
3
+
4
+ diruser
data/example/andand.rb ADDED
@@ -0,0 +1,3 @@
1
+ macro andand(a,b)
2
+ :(^a and ^b)
3
+ end
data/example/assert.rb ADDED
@@ -0,0 +1,42 @@
1
+ #this file uses macros! won't parse in normal ruby
2
+ $Debug=1
3
+ if $Debug
4
+ macro assert(cond)
5
+ if RedParse::OpNode===cond and /\A[=!]=\Z/===cond.op
6
+ left,op,right=*cond
7
+ :(fail 'expected '+^left.unparse({})+"(==#{^left}) to be "+
8
+ ^op+" "+^right.unparse({})+"(==#{^right})" unless ^cond)
9
+ else
10
+ :(fail "expected #{:(^^cond)}, but was not true" unless ^cond)
11
+ end
12
+ end
13
+ else
14
+ macro assert(cond)
15
+ end
16
+ end
17
+
18
+
19
+ #assert_equal... bah, who needs it?
20
+
21
+ def test_assert
22
+ a=1
23
+ b=2
24
+
25
+ assert a #ok
26
+ assert a!=b #ok
27
+
28
+ begin
29
+ assert(a==b) #oops, fails. msg="expected a(==1) to be == b(==2)"
30
+ rescue Exception=>e
31
+ assert("expected a(==1) to be == b(==2)"== e.message) #better be ok
32
+ else fail
33
+ end
34
+
35
+ begin
36
+ assert(nil) #oops, fails. msg="expected nil, but was not true"
37
+ rescue Exception=>e
38
+ assert("expected nil, but was not true"== e.message) #better be ok
39
+ #ok, that message didn't make a lot of sense...
40
+ else fail
41
+ end
42
+ end
@@ -0,0 +1,4 @@
1
+ require 'macro'
2
+ Macro.require 'example/assert'
3
+
4
+ test_assert
data/example/loop.rb ADDED
@@ -0,0 +1,23 @@
1
+ =begin doesn't work yet
2
+ macro loop(&body)
3
+ :(while true
4
+ ^body
5
+ )
6
+ end
7
+ =end
8
+
9
+ macro loop(body)
10
+ :(while true
11
+ ^body
12
+ end
13
+ )
14
+ end
15
+
16
+ def loop_user
17
+ i=0
18
+ loop(
19
+ p i
20
+ i+=1
21
+ break if i>=10
22
+ )
23
+ end
@@ -0,0 +1,4 @@
1
+ require 'macro'
2
+ Macro.require 'example/loop.rb'
3
+
4
+ loop_user
data/example/simple.rb ADDED
@@ -0,0 +1,4 @@
1
+ macro simple(a,b) :(^a+^b) end
2
+ def simple_user
3
+ p simple(1,2)
4
+ end
@@ -0,0 +1,4 @@
1
+ require 'macro'
2
+ Macro.require 'example/simple'
3
+
4
+ simple_user
data/example/with.rb ADDED
@@ -0,0 +1,56 @@
1
+ =begin doesn't work yet
2
+
3
+ #change the default receiver within the block from self to new_default
4
+ macro with(new_default,&block)
5
+ block.walk{|parent,i,subi,node|
6
+ if RedParse::CallNode===node and node.receiver.nil?
7
+ node.receiver=new_default
8
+ end
9
+ true
10
+ }
11
+ return block
12
+ end
13
+
14
+ #used like this:
15
+ class Foo
16
+ def bar
17
+ @quux=999
18
+ p with "baz" do
19
+ [
20
+ @quux, #=>999, not nil
21
+ size #=>3, not 99
22
+ ]
23
+ end
24
+ end
25
+
26
+ def size; 99 end
27
+ end
28
+ Foo.new.bar
29
+
30
+
31
+ =end
32
+
33
+ #change the default receiver within the block from self to new_default
34
+ macro with(new_default,block)
35
+ block.walk{|parent,i,subi,node|
36
+ if RedParse::CallNode===node and node.receiver.nil?
37
+ node.receiver=new_default
38
+ end
39
+ true
40
+ }
41
+ return block
42
+ end
43
+
44
+ #used like this:
45
+ class Foo
46
+ def bar
47
+ @quux=999
48
+ p with "baz", [
49
+ @quux, #=>999, not nil
50
+ size #=>3, not 99
51
+ ]
52
+ end
53
+
54
+ def size; 99 end
55
+ end
56
+ Foo.new.bar
@@ -0,0 +1,3 @@
1
+ require 'macro'
2
+ Macro.require 'example/with'
3
+
data/lib/macro/form.rb ADDED
@@ -0,0 +1,146 @@
1
+ =begin
2
+ rubymacros - a macro preprocessor for ruby
3
+ Copyright (C) 2008 Caleb Clausen
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Lesser General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Lesser General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Lesser General Public License
16
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ =end
18
+
19
+ #warn '$LOAD_PATH hacked up to include latest redparse'
20
+ #$: << "../redparse/lib"
21
+
22
+ require "redparse"
23
+ require "macro"
24
+ class Macro
25
+ class FormNode < RedParse::ValueNode
26
+ param_names :text
27
+ def initialize(colon,text)
28
+ if RedParse::VarLikeNode===text
29
+ @transform=HashLiteralNode[]
30
+ super text
31
+ return
32
+ end
33
+
34
+ fail unless ParenedNode===text && text.size==1
35
+ text=text.body
36
+
37
+ super text
38
+ rebuild_transform
39
+ end
40
+
41
+ def rebuild_transform
42
+ @transform=HashLiteralNode[]
43
+ @parameters=[]
44
+
45
+ walkers=proc{|rcvr,wraplayers| #curry
46
+ rcvr.walk{|*args|
47
+ node=args.last
48
+ case node
49
+ when FormParameterNode
50
+ target=node.wraplevel
51
+ fail if wraplayers > target
52
+ if wraplayers==target #skip this parameter if it doesn't have enough wrappers
53
+ @parameters << node #remember parameter (and implicitly, location)
54
+
55
+ nil# and stop further recursion
56
+ else
57
+ true
58
+ end
59
+ when FormNode
60
+ #walk form with same walker we're using now, except an extra layer of form parameters
61
+ #must be present for them to be considered 'our' parameters
62
+ walkers[node.text,wraplayers+1]
63
+ nil #don't recurse in this node again, we just did it
64
+ else true
65
+ end
66
+ } if rcvr.respond_to? :walk
67
+ }
68
+ walkers[text,1]
69
+
70
+ @parameters.each{|orig_escd|
71
+ escd=orig_escd
72
+ escd=escd.val while FormParameterNode===escd
73
+ @transform.push LiteralNode[orig_escd.__id__], escd
74
+ }
75
+
76
+ return self
77
+ end
78
+
79
+ def each_parameter(&block)
80
+ @parameters.each(&block) if @parameters
81
+ end
82
+
83
+ def deep_copy transform={}
84
+ super(transform).rebuild_transform
85
+ end
86
+
87
+ def unparse o
88
+ ":("+text.unparse(o)+")"
89
+ end
90
+
91
+ def reify transform
92
+ transform.each_pair{|k,v|
93
+ transform[k]=Macro.quote(v) unless Node===v or VarNameToken===v
94
+ }
95
+ deep_copy(transform)
96
+ end
97
+
98
+ def parsetree
99
+ parses_like.parsetree
100
+ end
101
+
102
+
103
+
104
+ module ::Macro::Names
105
+ COUNT=[0]
106
+ def self.request form
107
+ result="Number_#{COUNT[0]+=1}"
108
+ const_set result, form
109
+ return result
110
+ end
111
+ end
112
+
113
+ #CallSiteNode=RedParse::CallSiteNode
114
+ #ConstantNode=RedParse::ConstantNode
115
+ def parses_like
116
+ CallSiteNode[CallSiteNode[ConstantNode[nil,"Macro","Names",formname], "reify", [@transform],nil,nil], "text", nil,nil,nil]
117
+ #:(::Macro::Names::^(formname).reify.text)
118
+ end
119
+
120
+ def formname
121
+ @formname ||= ::Macro::Names.request(self)
122
+ end
123
+ end
124
+
125
+
126
+ class FormParameterNode < RedParse::ValueNode #not to appear in final parsetree?
127
+ param_names :val
128
+
129
+ def initialize(*args)
130
+ super(args.last)
131
+ end
132
+
133
+ def unparse o
134
+ "^"+val.unparse(o)
135
+ end
136
+
137
+ def wraplevel
138
+ return val.wraplevel+1 if FormParameterNode===val
139
+ return 1
140
+ end
141
+
142
+ def inspect
143
+ val.unparse({})
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,22 @@
1
+ =begin
2
+ rubymacros - a macro preprocessor for ruby
3
+ Copyright (C) 2008 Caleb Clausen
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Lesser General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Lesser General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Lesser General Public License
16
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ =end
18
+
19
+
20
+ class Macro
21
+ VERSION="0.1.0"
22
+ end