raml 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.ruby ADDED
@@ -0,0 +1,42 @@
1
+ ---
2
+ source:
3
+ - meta
4
+ authors:
5
+ - name: 7rans
6
+ email: transfire@gmail.com
7
+ copyrights:
8
+ - holder: Rubyworks
9
+ year: '2010'
10
+ replacements: []
11
+ alternatives: []
12
+ requirements:
13
+ - name: blankslate
14
+ - name: detroit
15
+ groups:
16
+ - build
17
+ development: true
18
+ - name: qed
19
+ groups:
20
+ - test
21
+ development: true
22
+ dependencies: []
23
+ conflicts: []
24
+ repositories: []
25
+ resources:
26
+ home: http://rubyworks.github.com/raml
27
+ code: http://github.com/rubyworks/raml
28
+ docs: http://rubydoc.info/gems/raml/frames
29
+ mail: http://groups.google.com/group/rubyworks-mailinglist
30
+ gems: http://rubygems.org/gems/raml
31
+ extra: {}
32
+ load_path:
33
+ - lib
34
+ revision: 0
35
+ created: '2010-09-21'
36
+ summary: Ruby Syntax Data Language
37
+ title: RAML
38
+ version: 0.2.0
39
+ name: raml
40
+ description: RAML is a Ruby-syntax-based data language.
41
+ organization: rubyworks
42
+ date: '2011-10-28'
@@ -0,0 +1,8 @@
1
+ --title "RAML"
2
+ --readme README.rdoc
3
+ --protected
4
+ --private
5
+ lib/**/*.rb
6
+ -
7
+ [A-Z]*.*
8
+
@@ -0,0 +1,31 @@
1
+ = COPYRIGHT NOTICES
2
+
3
+ == RAML
4
+
5
+ Copyright:: (c) 2010 Thomas Sawyer, Rubyworks
6
+ License:: BSD-2-Clause
7
+ Website:: http://rubyworks.github.com/raml
8
+
9
+ Copyright 2011 Thomas Sawyer. All rights reserved.
10
+
11
+ Redistribution and use in source and binary forms, with or without
12
+ modification, are permitted provided that the following conditions are met:
13
+
14
+ 1. Redistributions of source code must retain the above copyright notice,
15
+ this list of conditions and the following disclaimer.
16
+
17
+ 2. Redistributions in binary form must reproduce the above copyright
18
+ notice, this list of conditions and the following disclaimer in the
19
+ documentation and/or other materials provided with the distribution.
20
+
21
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
22
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
23
+ AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24
+ COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26
+ NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
28
+ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
30
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+
@@ -0,0 +1,12 @@
1
+ = RELEASE HISTORY
2
+
3
+ == 0.1.0 | 2010-09-28
4
+
5
+ A very early release of RAML, presently only supporting an eval-based parser.
6
+ Ultimately the intent is to provide a true parser, that can restict the format
7
+ to pure data.
8
+
9
+ Changes:
10
+
11
+ * Initial implementation.
12
+
@@ -0,0 +1,217 @@
1
+ = RAML.eval
2
+
3
+ The RAML.eval() method parses a RAML document using the Kernel.eval.
4
+ In this way Ruby scripting can still be utilized within the document.
5
+
6
+ Require the RAML library.
7
+
8
+ require 'raml'
9
+
10
+ Given a RAML document:
11
+
12
+ website "http://rubygems.org"
13
+
14
+ We can load the text via the #load method. (Note above document text has
15
+ been placed in the @text variable.)
16
+
17
+ data = RAML.eval(@text)
18
+
19
+ data.assert == {:website=>"http://rubygems.org"}
20
+
21
+ One of the nicer features of RAML derives from Ruby's block notation, allowing
22
+ for nested entries.
23
+
24
+ Given a RAML document:
25
+
26
+ resources do
27
+ home "http://rubyworks.github.com/raml"
28
+ docs "http://rubyworks.github.com/raml/docs/api"
29
+ wiki "http://wiki.rubyworks.github.com/raml"
30
+ end
31
+
32
+ We get a two layer hash.
33
+
34
+ data = RAML.eval(@text)
35
+
36
+ data[:resources][:home].assert == "http://rubyworks.github.com/raml"
37
+ data[:resources][:docs].assert == "http://rubyworks.github.com/raml/docs/api"
38
+ data[:resources][:wiki].assert == "http://wiki.rubyworks.github.com/raml"
39
+
40
+ RAML is also considers the content of a block. If it is a scalar entry,
41
+ such as a String, then that will be assigned to the key.
42
+
43
+ Given a RAML document:
44
+
45
+ description %{
46
+ This is a description.
47
+ It can have multiple lines.
48
+ RAML handles this just fine,
49
+ because Ruby does too.
50
+ }
51
+
52
+ Loading this document, description will contain the text as expected.
53
+
54
+ data = RAML.eval(@text)
55
+
56
+ text = data[:description].sub(/\s+/, ' ').strip
57
+
58
+ text.assert.start_with?("This is")
59
+ text.assert.end_with?("does too.")
60
+
61
+ It is only unfortunate that Ruby doesn't have a margin controlled string
62
+ notation (e.g. `%L{ }`) so that post processing with `sub()` would not
63
+ be neccessary.
64
+
65
+ RAML has some options that makes it more flexible than many other data
66
+ lanaguages. For instance, it can allow for multi-key entries.
67
+
68
+ Given a RAML document:
69
+
70
+ source "http://rubygems.org"
71
+ gem "facets", "~> 2.8"
72
+ gem "ansi", "~> 1.1"
73
+
74
+ We simply need to inform the loader to allow identical keys.
75
+
76
+ data = RAML.eval(@text, :multikey=>true)
77
+
78
+ data.assert == {
79
+ :source=>"http://rubygems.org",
80
+ :gem=>[["facets", "~> 2.8"],["ansi", "~> 1.1"]]
81
+ }
82
+
83
+ If we did not turn on the multi-key option, then the last `gem` entry
84
+ would have simply overwritten the former.
85
+
86
+ data = RAML.eval(@text)
87
+
88
+ data.assert == {
89
+ :source=>"http://rubygems.org",
90
+ :gem=>["ansi", "~> 1.1"]
91
+ }
92
+
93
+ Not let's show-off the benefit of using RAML.eval instead of RAML.load.
94
+
95
+ Given a RAML document:
96
+
97
+ sum 1 + 1
98
+
99
+ We will see that the value of `sum` will be evaluated as 2.
100
+
101
+ data = RAML.eval(@text)
102
+
103
+ data.assert == {:sum=>2}
104
+
105
+
106
+ = RAML.read
107
+
108
+ The RAML.read() method parses a RAML document using Ripper.
109
+ In this way a RAML document is treated purely as data and cannot
110
+ contain any Ruby scripting.
111
+
112
+ Require the RAML library.
113
+
114
+ require 'raml'
115
+
116
+ Given a RAML document:
117
+
118
+ website "http://rubygems.org"
119
+
120
+ We can load the text via the #read method. (Note above document text has
121
+ been placed in the @text variable.)
122
+
123
+ data = RAML.read(@text)
124
+
125
+ data.assert == {:website=>"http://rubygems.org"}
126
+
127
+ One of the nicer features of RAML derives from Ruby's block notation, allowing
128
+ for nested entries.
129
+
130
+ Given a RAML document:
131
+
132
+ resources do
133
+ home "http://rubyworks.github.com/raml"
134
+ docs "http://rubyworks.github.com/raml/docs/api"
135
+ wiki "http://wiki.rubyworks.github.com/raml"
136
+ end
137
+
138
+ We get a two layer hash.
139
+
140
+ data = RAML.read(@text)
141
+
142
+ data[:resources][:home].assert == "http://rubyworks.github.com/raml"
143
+ data[:resources][:docs].assert == "http://rubyworks.github.com/raml/docs/api"
144
+ data[:resources][:wiki].assert == "http://wiki.rubyworks.github.com/raml"
145
+
146
+ RAML is also considers the content of a block. If it is a scalar entry,
147
+ such as a String, then that will be assigned to the key.
148
+
149
+ Given a RAML document:
150
+
151
+ description %{
152
+ This is a description.
153
+ It can have multiple lines.
154
+ RAML handles this just fine,
155
+ because Ruby does too.
156
+ }
157
+
158
+ Loading this document, description will contain the text as expected.
159
+
160
+ data = RAML.read(@text)
161
+
162
+ text = data[:description].sub(/\s+/, ' ').strip
163
+
164
+ text.assert.start_with?("This is")
165
+ text.assert.end_with?("does too.")
166
+
167
+ It is only unfortunate that Ruby doesn't have a margin controlled string
168
+ notation (e.g. `%L{ }`) so that post processing with `sub()` would not
169
+ be neccessary.
170
+
171
+ RAML has some options that makes it more flexible than many other data
172
+ lanaguages. For instance, it can allow for multi-key entries.
173
+
174
+ Given a RAML document:
175
+
176
+ source "http://rubygems.org"
177
+ gem "facets", "~> 2.8"
178
+ gem "ansi", "~> 1.1"
179
+
180
+ We simply need to inform the reader to allow identical keys.
181
+
182
+ data = RAML.read(@text, :multikey=>true)
183
+
184
+ data.assert == {
185
+ :source=>"http://rubygems.org",
186
+ :gem=>[["facets", "~> 2.8"],["ansi", "~> 1.1"]]
187
+ }
188
+
189
+ If we did not turn on the multi-key option, then the last `gem` entry
190
+ would have simply overwritten the former.
191
+
192
+ data = RAML.read(@text)
193
+
194
+ data.assert == {
195
+ :source=>"http://rubygems.org",
196
+ :gem=>["ansi", "~> 1.1"]
197
+ }
198
+
199
+ Not let's show-off the benefit of using RAML.read instead of RAML.eval.
200
+
201
+ Given a RAML document:
202
+
203
+ sum "word".upcase
204
+
205
+ We will see that the result of calling `#upcase` CANNOT be evaluated and
206
+ will raise an error.
207
+
208
+ expect Exception do
209
+ data = RAML.read(@text)
210
+ end if RUBY_VERSION >= '1.9'
211
+
212
+ Note, this last assertion is only true for Ruby 1.9+, becuase Ripper is
213
+ not supported by older versions of Ruby.
214
+
215
+
216
+
217
+
@@ -0,0 +1,92 @@
1
+ = Ruby Ambidextrous Meta-Language
2
+
3
+ {Homepage}[http://rubyworks.github.com/raml] |
4
+ {Source Code}[http://github.com/rubyworks/raml] |
5
+ {Mailing List}[http://googlegroups.com/group/rubyworks-mailinglist]
6
+ {IRC}[irc://chat.us.freenode.net/rubyworks]
7
+
8
+ {<img src="http://travis-ci.org/rubyworks/raml.png" />}[http://travis-ci.org/rubyworks/raml]
9
+
10
+
11
+ == DESCRIPTION
12
+
13
+ RAML is a flexable data format suitable for a variety of uses, such
14
+ as configuration files.
15
+
16
+ Admittedly, "Ruby Ambidextrous Meta-Language" is a funny name. But
17
+ nonetheless fitting, becuase unlike YAML, RAML can handle a wider
18
+ variety of data format design needs, even limited markup formats.
19
+ Unlike YAML, RAML is not a serialization language.
20
+
21
+
22
+ == SYNOPSIS
23
+
24
+ A RAML document is Ruby code operating under a set of open domain language rules.
25
+ An example RAML document looks like this:
26
+
27
+ source "http://rubygems.org"
28
+ example "this", 10, true
29
+ another do
30
+ name "Tonto"
31
+ age 42
32
+ weight 229
33
+ end
34
+
35
+ Loading this document in via RAML would produce the following Hash:
36
+
37
+ {:source=>"http://rubygems.org",
38
+ :example=>["this", 10, true],
39
+ :another=>{:name=>"Tonto", :age=>42, :weight=>229}}
40
+
41
+ Loading is handled by the `RAML.load` method. The method can take a string,
42
+
43
+ RAML.load("name 'Bob'")
44
+
45
+ Or an IO object,
46
+
47
+ RAML.load(File.new('foo.rml'))
48
+
49
+ The method also takes an `:safe` option that is used to set the level evaluation
50
+ Ruby is allowed. If the option is `false`, the default, than evaluation is handled
51
+ with Ruby `$SAFE=0`. If `true` the $SAFE=4.
52
+
53
+ RAML.load(raml, :safe=>true)
54
+
55
+ Safe evaluation is useful when loading untrusted data files. With `$SAFE=4`
56
+ most (though not all) security concerns are mitigated.
57
+
58
+ For a complete lockdown on evaluation, allowing only data setting and no other
59
+ forms of Ruby evaluation, `RAML.load` supports the `:eval` option. By default
60
+ it is `true`. By setting it to `false` (not `nil`), all code evaluation
61
+ can be turned off.
62
+
63
+ RAML.load(raml, :eval=>false)
64
+
65
+ Under the hood the current implementation has two different parsers, a
66
+ Ripper-based parser and a Kernel.eval based parser. By setting `:eval=>false`
67
+ the Ripper parser is used rather than the regular eval parser.
68
+
69
+ For additional examples and details on working with RAML files, see
70
+ the QED demonstrandum.
71
+
72
+
73
+ == IMPORTANT NOTE
74
+
75
+ The Ripper based parser is not yet robust and is NOT RECOMMENDED FOR PRODUCTION
76
+ USE, as it will parse invalid RAML documents without complaint. The eval parser
77
+ on the other hand works well.
78
+
79
+
80
+ == SPECIAL THANKS
81
+
82
+ Big props to <b>Robert Dober</b> for taking the time to help me improve
83
+ the code.
84
+
85
+
86
+ == COPYRIGHTS
87
+
88
+ Copyright (c) 2010 Rubyworks
89
+
90
+ RAML is distributed under the terms of the *FreeBSD* License.
91
+ See COPYING.rdoc for details.
92
+
@@ -0,0 +1,110 @@
1
+ require 'raml/eval_parser'
2
+
3
+ if RUBY_VERSION > '1.9'
4
+ require 'raml/ripper_parser'
5
+ end
6
+
7
+ module RAML
8
+
9
+ # Load a RAML document. Like `eval()` but parses the document
10
+ # via Ripper, ensuring a pure data format.
11
+ #
12
+ # IMPORTANT: Ruby 1.8.x and older does not support Ripper.
13
+ # In this case RAML falls back to using `eval()` with $SAFE = 4.
14
+ #
15
+ # Arguments
16
+ #
17
+ # io - a String, File or any object that responds to #read.
18
+ #
19
+ # Options
20
+ #
21
+ # :eval - false for data-only parser
22
+ # :safe - true sets $SAFE=4
23
+ # :keep - public methods to keep in scope
24
+ # :scope - an object to act as the evaluation context
25
+ # :multikey - handle duplicate keys
26
+ #
27
+ # Returns [Hash] data parsed from document.
28
+ def self.load(io, options={})
29
+ if FalseClass === options[:eval]
30
+ read(io, options)
31
+ else
32
+ eval(io, options)
33
+ end
34
+ end
35
+
36
+ # Evaluate a RAML document. Like `load()` but parses the document via #eval.
37
+ # (same as load with :eval=>true). This can be done a $SAFE level 4 by
38
+ # setting the :safe option to +true+.
39
+ #
40
+ # Arguments
41
+ #
42
+ # io - a String, File or any object that responds to #read.
43
+ #
44
+ # Options
45
+ #
46
+ # :safe - true/false
47
+ # :keep - public methods to keep in scope
48
+ # :scope - an object to act as the evaluation context
49
+ # :multikey - handle duplicate keys
50
+ #
51
+ # Returns [Hash] data parsed from document.
52
+ def self.eval(io, options={})
53
+ code, file = io(io)
54
+ parser = RAML::EvalParser.new(options)
55
+ parser.parse(code, file)
56
+ end
57
+
58
+ # Read in a RAML document. Like `load()` but parses the document via Ripper
59
+ # (same as load with :eval=>false). This only work in Ruby 1.9+, otherwise it
60
+ # falls back to the eval parer with `:safe=>true`.
61
+ #
62
+ # Arguments
63
+ #
64
+ # io - a String, File or any object that responds to #read.
65
+ #
66
+ # Options
67
+ #
68
+ # :keep - public methods to keep in scope
69
+ # :scope - an object to act as the evaluation context
70
+ # :multikey - handle duplicate keys
71
+ #
72
+ # Returns [Hash] data parsed from document.
73
+ def self.read(io, options={})
74
+ code, file = io(io)
75
+ if RUBY_VERSION > '1.9'
76
+ parser = RAML::RipperParser.new(options)
77
+ else
78
+ options[:safe] = true
79
+ parser = RAML::EvalParser.new(options)
80
+ end
81
+ parser.parse(code, file)
82
+ end
83
+
84
+ private
85
+
86
+ # Take a String, File, IO or any object that respons to #read
87
+ # and return the string or result of calling #read and the
88
+ # file name if any and "(eval)" if not.
89
+ #
90
+ # Arguments
91
+ #
92
+ # io - a String, File or any object that responds to #read.
93
+ #
94
+ # Returns [String, String] text of string or file and file name or "(eval)".
95
+ def self.io(io)
96
+ case io
97
+ when String
98
+ file = '(eval)'
99
+ code = io
100
+ when File
101
+ file = io.path
102
+ code = io.read
103
+ else #IO
104
+ file = '(eval)'
105
+ code = io.read
106
+ end
107
+ return code, file
108
+ end
109
+
110
+ end
@@ -0,0 +1,42 @@
1
+ ---
2
+ source:
3
+ - meta
4
+ authors:
5
+ - name: 7rans
6
+ email: transfire@gmail.com
7
+ copyrights:
8
+ - holder: Rubyworks
9
+ year: '2010'
10
+ replacements: []
11
+ alternatives: []
12
+ requirements:
13
+ - name: blankslate
14
+ - name: detroit
15
+ groups:
16
+ - build
17
+ development: true
18
+ - name: qed
19
+ groups:
20
+ - test
21
+ development: true
22
+ dependencies: []
23
+ conflicts: []
24
+ repositories: []
25
+ resources:
26
+ home: http://rubyworks.github.com/raml
27
+ code: http://github.com/rubyworks/raml
28
+ docs: http://rubydoc.info/gems/raml/frames
29
+ mail: http://groups.google.com/group/rubyworks-mailinglist
30
+ gems: http://rubygems.org/gems/raml
31
+ extra: {}
32
+ load_path:
33
+ - lib
34
+ revision: 0
35
+ created: '2010-09-21'
36
+ summary: Ruby Syntax Data Language
37
+ title: RAML
38
+ version: 0.2.0
39
+ name: raml
40
+ description: RAML is a Ruby-syntax-based data language.
41
+ organization: rubyworks
42
+ date: '2011-10-28'
@@ -0,0 +1,13 @@
1
+ class String
2
+
3
+ # Combine with string with a newline between each.
4
+ #
5
+ # Examples
6
+ #
7
+ # "foo" ^ "bar" #=> "foo\nbar"
8
+ #
9
+ def ^(string)
10
+ self + "\n" + string.to_s
11
+ end
12
+
13
+ end
@@ -0,0 +1,149 @@
1
+ require 'thread'
2
+ require 'raml/multi_value'
3
+
4
+ unless defined?(::BasicObject)
5
+ require 'blankslate'
6
+ BasicObject = BlankSlate
7
+ end
8
+
9
+ module RAML
10
+
11
+ # The EvalParser parses RAML documents using standard Ruby evaluation.
12
+ # This has some limitations, but also some benefits.
13
+ #
14
+ # Unlike the pure data evaluator, the ruby evaluator can be instructed to
15
+ # keep methods available for access, as well as use a custom scope.
16
+ #
17
+ class EvalParser
18
+
19
+ #
20
+ SAFE = 4
21
+
22
+ # Need tainted data store.
23
+ HASH = {}.taint
24
+
25
+ #
26
+ attr :options
27
+
28
+ # You can pass in an object to act as the scope. All non-essential public
29
+ # and private Object methods will be removed from the scope unless a `keep`
30
+ # string or regex matches the name. Protected methods are also kept intact.
31
+ def initialize(options={})
32
+ @options = options
33
+
34
+ self.safe = options[:safe]
35
+ #self.file = options[:file]
36
+ self.keep = options[:keep]
37
+ self.scope = options[:scope] || BasicObject.new
38
+ self.multi_key = options[:multikey]
39
+ end
40
+
41
+ #
42
+ def safe?
43
+ @safe
44
+ end
45
+
46
+ #
47
+ def safe=(boolean)
48
+ @safe = !!boolean
49
+ end
50
+
51
+ # Returns 4 is safe is true, otherwise 0.
52
+ def safe_level
53
+ safe? ? 4 : 0
54
+ end
55
+
56
+ # Returns Array<Regexp>.
57
+ attr_reader :keep
58
+
59
+ def keep=(list)
60
+ @keep = [list].compact.flatten
61
+ end
62
+
63
+ ## Returns [String] file name.
64
+ #attr_accessor :file
65
+
66
+ # Returns [Boolean] true/false.
67
+ def multi_key?
68
+ @multi_key
69
+ end
70
+
71
+ #
72
+ def multi_key=(bool)
73
+ @multi_key = !!bool
74
+ end
75
+
76
+ # Returns [BasicObject,Object] scope object.
77
+ attr_reader :scope
78
+
79
+ # Sets the scope object while preparing it for use as the evaluation
80
+ # context.
81
+ def scope=(object)
82
+ @scope ||= (
83
+ #qua_class = (class << object; self; end)
84
+ #methods = [object.public_methods, Object.private_instance_methods].flatten
85
+ #methods.each do |m|
86
+ # next if /^(__|instance_|singleton_method_|binding$|method_missing$|extend$|initialize$|object_id$|p$)/ =~ m.to_s
87
+ # next if keep.any?{ |k| k === m.to_s }
88
+ # qua_class.__send__(:undef_method, m)
89
+ #end
90
+ parser = self
91
+ object.instance_eval{ @__parser__ = parser }
92
+ #object.instance_eval{ extend MethodMissing }
93
+ MethodMissing.__send__(:extend_object, object)
94
+ object
95
+ )
96
+ end
97
+
98
+ #
99
+ def parse(code=nil, file=nil, &block)
100
+ data = HASH.dup
101
+
102
+ scope.instance_eval{ @__data__ = data}
103
+
104
+ result = nil
105
+
106
+ thread = Thread.new do
107
+ $SAFE = safe_level unless $SAFE == safe_level
108
+ result = if block
109
+ scope.instance_eval(&block)
110
+ else
111
+ scope.instance_eval(code, file)
112
+ end
113
+ end
114
+
115
+ thread.join
116
+
117
+ return result if data.empty? # good idea?
118
+ return data
119
+ end
120
+
121
+ #
122
+ module MethodMissing
123
+ #
124
+ def method_missing(name, *args, &block)
125
+ return @__data__[name] if args.empty? and !block
126
+ if block
127
+ val = EvalParser.new(@__parser__.options).parse(&block)
128
+ else
129
+ val = args.size == 1 ? args.first : args
130
+ end
131
+ if @__parser__.multi_key?
132
+ if @__data__.key?(name)
133
+ unless MultiValue === @__data__[name]
134
+ @__data__[name] = MultiValue.new(@__data__[name])
135
+ end
136
+ @__data__[name] << val
137
+ else
138
+ @__data__[name] = val
139
+ end
140
+ else
141
+ @__data__[name] = val
142
+ val
143
+ end
144
+ end
145
+ end
146
+
147
+ end
148
+
149
+ end
@@ -0,0 +1,12 @@
1
+ module RAML
2
+
3
+ # The MultiValue class is simply an Array. It is used to distinguish
4
+ # an Array value from a multi-key entry.
5
+ class MultiValue < Array
6
+ #
7
+ def initialize(*elements)
8
+ replace(elements)
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,151 @@
1
+ require 'ripper'
2
+ require 'pp'
3
+ require 'raml/multi_value'
4
+
5
+ module RAML
6
+
7
+ # RAML document parser utilizoing Ripper.
8
+ #
9
+ # NOTE: This code is probably far from robust and is almost certainly not
10
+ # best the approach to implmentation. But your humble author is no expert
11
+ # on Ripper or parsers in general.
12
+ #
13
+ # FIXME: This class needs work. I currently handles basic cases, but will
14
+ # incorrectly parse complex cases.
15
+ #
16
+ # FIXME: Add non hash block value support.
17
+ class RipperParser
18
+
19
+ def initialize(options={})
20
+ @options = options
21
+
22
+ self.multi_key = options[:multikey]
23
+ end
24
+
25
+ # Returns [Boolean] true/false.
26
+ def multi_key?
27
+ @multi_key
28
+ end
29
+
30
+ # Set multi-key option.
31
+ def multi_key=(bool)
32
+ @multi_key = !!bool
33
+ end
34
+
35
+ # Parse the RAML document via Ripper.
36
+ def parse(code, file=nil)
37
+ tree = Ripper::SexpBuilder.new(code).parse
38
+
39
+ @k, @v = nil, []
40
+
41
+ d = clean(tree)
42
+ set(d)
43
+
44
+ return d
45
+ end
46
+
47
+ private
48
+
49
+ #
50
+ def clean(tree, data={})
51
+ d = data
52
+
53
+ tag, *rest = *tree
54
+
55
+ #show(tag, rest)
56
+
57
+ case tag.to_s
58
+ when "@ident"
59
+ set(d)
60
+ @k = rest.shift
61
+ when "@kw"
62
+ case rest.shift
63
+ when "nil" then @v << nil
64
+ when "true" then @v << true
65
+ when "false" then @v << false
66
+ end
67
+ when "@int"
68
+ @v << rest.shift.to_i
69
+ when "@float"
70
+ @v << rest.shift.to_f
71
+ when /^@/
72
+ @v << rest.shift
73
+ when "do_block"
74
+ h = [d, @k, @v]
75
+ @k, @v = nil, []
76
+ n = {}
77
+ rest.each do |r|
78
+ clean(r, n)
79
+ end
80
+ set(n)
81
+ d, @k, @v = *h
82
+ @v << n
83
+ set(d)
84
+ when '.'
85
+ raise SyntaxError, "evaluations forbidden"
86
+ else
87
+ rest.each do |r|
88
+ clean(r, d)
89
+ end
90
+ end
91
+
92
+ return d
93
+ end
94
+
95
+ #
96
+ def set(data)
97
+ return unless @k
98
+ if multi_key?
99
+ set_multi_key(data)
100
+ else
101
+ set_key(data)
102
+ end
103
+ end
104
+
105
+ #
106
+ def set_key(data)
107
+ key = @k.to_sym
108
+ case @v.size
109
+ when 0
110
+ data[key] = nil
111
+ when 1
112
+ data[key] = @v.first
113
+ else
114
+ data[key] = @v
115
+ end
116
+ @k, @v = nil, []
117
+ end
118
+
119
+ #
120
+ def set_multi_key(data)
121
+ key = @k.to_sym
122
+
123
+ if data.key?(key)
124
+ unless MultiValue === data[key]
125
+ data[key] = MultiValue.new(data[key])
126
+ end
127
+
128
+ case @v.size
129
+ when 1
130
+ data[key] << @v.first
131
+ else
132
+ data[key] << @v
133
+ end
134
+ @k, @v = nil, []
135
+ else
136
+ set_key(data)
137
+ end
138
+ end
139
+
140
+ # Used for development purposes only.
141
+ def show(name, args)
142
+ if @options[:debug]
143
+ puts "#{name}:"
144
+ p args
145
+ puts
146
+ end
147
+ end
148
+
149
+ end
150
+
151
+ end
@@ -0,0 +1,4 @@
1
+ When "Given a RAML document" do |text|
2
+ @text = text
3
+ end
4
+
@@ -0,0 +1,104 @@
1
+ = RAML.eval
2
+
3
+ The RAML.eval() method parses a RAML document using the Kernel.eval.
4
+ In this way Ruby scripting can still be utilized within the document.
5
+
6
+ Require the RAML library.
7
+
8
+ require 'raml'
9
+
10
+ Given a RAML document:
11
+
12
+ website "http://rubygems.org"
13
+
14
+ We can load the text via the #load method. (Note above document text has
15
+ been placed in the @text variable.)
16
+
17
+ data = RAML.eval(@text)
18
+
19
+ data.assert == {:website=>"http://rubygems.org"}
20
+
21
+ One of the nicer features of RAML derives from Ruby's block notation, allowing
22
+ for nested entries.
23
+
24
+ Given a RAML document:
25
+
26
+ resources do
27
+ home "http://rubyworks.github.com/raml"
28
+ docs "http://rubyworks.github.com/raml/docs/api"
29
+ wiki "http://wiki.rubyworks.github.com/raml"
30
+ end
31
+
32
+ We get a two layer hash.
33
+
34
+ data = RAML.eval(@text)
35
+
36
+ data[:resources][:home].assert == "http://rubyworks.github.com/raml"
37
+ data[:resources][:docs].assert == "http://rubyworks.github.com/raml/docs/api"
38
+ data[:resources][:wiki].assert == "http://wiki.rubyworks.github.com/raml"
39
+
40
+ RAML is also considers the content of a block. If it is a scalar entry,
41
+ such as a String, then that will be assigned to the key.
42
+
43
+ Given a RAML document:
44
+
45
+ description %{
46
+ This is a description.
47
+ It can have multiple lines.
48
+ RAML handles this just fine,
49
+ because Ruby does too.
50
+ }
51
+
52
+ Loading this document, description will contain the text as expected.
53
+
54
+ data = RAML.eval(@text)
55
+
56
+ text = data[:description].sub(/\s+/, ' ').strip
57
+
58
+ text.assert.start_with?("This is")
59
+ text.assert.end_with?("does too.")
60
+
61
+ It is only unfortunate that Ruby doesn't have a margin controlled string
62
+ notation (e.g. `%L{ }`) so that post processing with `sub()` would not
63
+ be neccessary.
64
+
65
+ RAML has some options that makes it more flexible than many other data
66
+ lanaguages. For instance, it can allow for multi-key entries.
67
+
68
+ Given a RAML document:
69
+
70
+ source "http://rubygems.org"
71
+ gem "facets", "~> 2.8"
72
+ gem "ansi", "~> 1.1"
73
+
74
+ We simply need to inform the loader to allow identical keys.
75
+
76
+ data = RAML.eval(@text, :multikey=>true)
77
+
78
+ data.assert == {
79
+ :source=>"http://rubygems.org",
80
+ :gem=>[["facets", "~> 2.8"],["ansi", "~> 1.1"]]
81
+ }
82
+
83
+ If we did not turn on the multi-key option, then the last `gem` entry
84
+ would have simply overwritten the former.
85
+
86
+ data = RAML.eval(@text)
87
+
88
+ data.assert == {
89
+ :source=>"http://rubygems.org",
90
+ :gem=>["ansi", "~> 1.1"]
91
+ }
92
+
93
+ Not let's show-off the benefit of using RAML.eval instead of RAML.load.
94
+
95
+ Given a RAML document:
96
+
97
+ sum 1 + 1
98
+
99
+ We will see that the value of `sum` will be evaluated as 2.
100
+
101
+ data = RAML.eval(@text)
102
+
103
+ data.assert == {:sum=>2}
104
+
@@ -0,0 +1,109 @@
1
+ = RAML.read
2
+
3
+ The RAML.read() method parses a RAML document using Ripper.
4
+ In this way a RAML document is treated purely as data and cannot
5
+ contain any Ruby scripting.
6
+
7
+ Require the RAML library.
8
+
9
+ require 'raml'
10
+
11
+ Given a RAML document:
12
+
13
+ website "http://rubygems.org"
14
+
15
+ We can load the text via the #read method. (Note above document text has
16
+ been placed in the @text variable.)
17
+
18
+ data = RAML.read(@text)
19
+
20
+ data.assert == {:website=>"http://rubygems.org"}
21
+
22
+ One of the nicer features of RAML derives from Ruby's block notation, allowing
23
+ for nested entries.
24
+
25
+ Given a RAML document:
26
+
27
+ resources do
28
+ home "http://rubyworks.github.com/raml"
29
+ docs "http://rubyworks.github.com/raml/docs/api"
30
+ wiki "http://wiki.rubyworks.github.com/raml"
31
+ end
32
+
33
+ We get a two layer hash.
34
+
35
+ data = RAML.read(@text)
36
+
37
+ data[:resources][:home].assert == "http://rubyworks.github.com/raml"
38
+ data[:resources][:docs].assert == "http://rubyworks.github.com/raml/docs/api"
39
+ data[:resources][:wiki].assert == "http://wiki.rubyworks.github.com/raml"
40
+
41
+ RAML is also considers the content of a block. If it is a scalar entry,
42
+ such as a String, then that will be assigned to the key.
43
+
44
+ Given a RAML document:
45
+
46
+ description %{
47
+ This is a description.
48
+ It can have multiple lines.
49
+ RAML handles this just fine,
50
+ because Ruby does too.
51
+ }
52
+
53
+ Loading this document, description will contain the text as expected.
54
+
55
+ data = RAML.read(@text)
56
+
57
+ text = data[:description].sub(/\s+/, ' ').strip
58
+
59
+ text.assert.start_with?("This is")
60
+ text.assert.end_with?("does too.")
61
+
62
+ It is only unfortunate that Ruby doesn't have a margin controlled string
63
+ notation (e.g. `%L{ }`) so that post processing with `sub()` would not
64
+ be neccessary.
65
+
66
+ RAML has some options that makes it more flexible than many other data
67
+ lanaguages. For instance, it can allow for multi-key entries.
68
+
69
+ Given a RAML document:
70
+
71
+ source "http://rubygems.org"
72
+ gem "facets", "~> 2.8"
73
+ gem "ansi", "~> 1.1"
74
+
75
+ We simply need to inform the reader to allow identical keys.
76
+
77
+ data = RAML.read(@text, :multikey=>true)
78
+
79
+ data.assert == {
80
+ :source=>"http://rubygems.org",
81
+ :gem=>[["facets", "~> 2.8"],["ansi", "~> 1.1"]]
82
+ }
83
+
84
+ If we did not turn on the multi-key option, then the last `gem` entry
85
+ would have simply overwritten the former.
86
+
87
+ data = RAML.read(@text)
88
+
89
+ data.assert == {
90
+ :source=>"http://rubygems.org",
91
+ :gem=>["ansi", "~> 1.1"]
92
+ }
93
+
94
+ Not let's show-off the benefit of using RAML.read instead of RAML.eval.
95
+
96
+ Given a RAML document:
97
+
98
+ sum "word".upcase
99
+
100
+ We will see that the result of calling `#upcase` CANNOT be evaluated and
101
+ will raise an error.
102
+
103
+ expect Exception do
104
+ data = RAML.read(@text)
105
+ end if RUBY_VERSION >= '1.9'
106
+
107
+ Note, this last assertion is only true for Ruby 1.9+, becuase Ripper is
108
+ not supported by older versions of Ruby.
109
+
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+ example "this", 10, true
3
+ another do
4
+ name "Tom"
5
+ age 41
6
+ weight 229
7
+ end
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+
3
+ group "development" do
4
+ gem "cucumber"
5
+ end
6
+
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: raml
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - 7rans
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-10-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: blankslate
16
+ requirement: &22824260 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *22824260
25
+ - !ruby/object:Gem::Dependency
26
+ name: detroit
27
+ requirement: &22823580 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *22823580
36
+ - !ruby/object:Gem::Dependency
37
+ name: qed
38
+ requirement: &22822940 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *22822940
47
+ description: RAML is a Ruby-syntax-based data language.
48
+ email:
49
+ - transfire@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files:
53
+ - HISTORY.rdoc
54
+ - README.rdoc
55
+ - QED.rdoc
56
+ - COPYING.rdoc
57
+ files:
58
+ - .ruby
59
+ - .yardopts
60
+ - lib/raml/core_ext.rb
61
+ - lib/raml/eval_parser.rb
62
+ - lib/raml/multi_value.rb
63
+ - lib/raml/ripper_parser.rb
64
+ - lib/raml.rb
65
+ - lib/raml.yml
66
+ - qed/applique/document.rb
67
+ - qed/eval.rdoc
68
+ - qed/read.rdoc
69
+ - qed/samples/sample1.rml
70
+ - qed/samples/sample2.rml
71
+ - HISTORY.rdoc
72
+ - README.rdoc
73
+ - QED.rdoc
74
+ - COPYING.rdoc
75
+ homepage: http://rubyworks.github.com/raml
76
+ licenses: []
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 1.8.10
96
+ signing_key:
97
+ specification_version: 3
98
+ summary: Ruby Syntax Data Language
99
+ test_files: []