raml 0.2.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/.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: []