├── var ├── name ├── title ├── created ├── version ├── organization ├── summary ├── copyrights ├── authors ├── description ├── repoistories ├── requirements └── resources ├── lib ├── raml.yml ├── raml │ ├── core_ext.rb │ ├── multi_value.rb │ ├── ripper_parser.rb │ └── eval_parser.rb └── raml.rb ├── Gemfile ├── .gitignore ├── demo ├── applique │ └── document.rb ├── samples │ ├── sample2.rml │ └── sample1.rml ├── eval.rdoc └── read.rdoc ├── .yardopts ├── work └── defunct │ ├── treetop │ ├── syntax │ │ ├── string.treetop │ │ ├── raml.treetop │ │ ├── numbers.treetop │ │ └── interface.treetop │ └── parser.rb │ ├── treetop.rb │ └── raml.rb ├── Rakefile ├── MANIFEST ├── .travis.yml ├── Assembly ├── HISTORY.rdoc ├── .ruby ├── COPYING.rdoc ├── README.rdoc ├── .gemspec └── DEMO.rdoc /var/name: -------------------------------------------------------------------------------- 1 | raml -------------------------------------------------------------------------------- /var/title: -------------------------------------------------------------------------------- 1 | RAML -------------------------------------------------------------------------------- /lib/raml.yml: -------------------------------------------------------------------------------- 1 | ../.ruby -------------------------------------------------------------------------------- /var/created: -------------------------------------------------------------------------------- 1 | 2010-09-21 -------------------------------------------------------------------------------- /var/version: -------------------------------------------------------------------------------- 1 | 0.2.1 2 | -------------------------------------------------------------------------------- /var/organization: -------------------------------------------------------------------------------- 1 | rubyworks -------------------------------------------------------------------------------- /var/summary: -------------------------------------------------------------------------------- 1 | Ruby Syntax Data Language -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gemspec 3 | -------------------------------------------------------------------------------- /var/copyrights: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Rubyworks 2 | -------------------------------------------------------------------------------- /var/authors: -------------------------------------------------------------------------------- 1 | --- 2 | - 7rans 3 | -------------------------------------------------------------------------------- /var/description: -------------------------------------------------------------------------------- 1 | RAML is a Ruby-syntax-based data language. -------------------------------------------------------------------------------- /var/repoistories: -------------------------------------------------------------------------------- 1 | --- 2 | upstream: git://github.com/rubyworks/raml.git 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .reap/digest 2 | .yardoc 3 | doc 4 | log 5 | pkg 6 | tmp 7 | web 8 | -------------------------------------------------------------------------------- /var/requirements: -------------------------------------------------------------------------------- 1 | --- 2 | - blankslate 3 | - detroit (build) 4 | - qed (test) 5 | 6 | -------------------------------------------------------------------------------- /demo/applique/document.rb: -------------------------------------------------------------------------------- 1 | When "Given a RAML document" do |text| 2 | @text = text 3 | end 4 | 5 | -------------------------------------------------------------------------------- /demo/samples/sample2.rml: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | group "development" do 4 | gem "cucumber" 5 | end 6 | 7 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --title "RAML" 2 | --readme README.rdoc 3 | --protected 4 | --private 5 | lib/**/*.rb 6 | - 7 | QED.rdoc 8 | [A-Z]*.* 9 | 10 | -------------------------------------------------------------------------------- /demo/samples/sample1.rml: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | example "this", 10, true 3 | another do 4 | name "Tom" 5 | age 41 6 | weight 229 7 | end 8 | -------------------------------------------------------------------------------- /work/defunct/treetop/syntax/string.treetop: -------------------------------------------------------------------------------- 1 | grammar RAMLString 2 | 3 | rule string 4 | '"' [a-zA-Z] [a-zA-Z0-9]+ '"' 5 | end 6 | 7 | end 8 | 9 | -------------------------------------------------------------------------------- /lib/raml/core_ext.rb: -------------------------------------------------------------------------------- 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 14 | -------------------------------------------------------------------------------- /var/resources: -------------------------------------------------------------------------------- 1 | --- 2 | home: http://rubyworks.github.com/raml 3 | code: http://github.com/rubyworks/raml 4 | docs: http://rubydoc.info/gems/raml/frames 5 | mail: http://groups.google.com/group/rubyworks-mailinglist 6 | gems: http://rubygems.org/gems/raml 7 | #wiki: http://wiki.github.com/rubyworks/raml 8 | -------------------------------------------------------------------------------- /lib/raml/multi_value.rb: -------------------------------------------------------------------------------- 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 13 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | task :default => :test 4 | 5 | desc "run qed demos" 6 | task :test do 7 | system "qed -Ilib qed/" 8 | end 9 | 10 | #desc "generate treetop parser" 11 | #task :treetop do 12 | # Dir.chdir "lib/raml" do 13 | # sh "tt -o parser.rb treetop/raml.treetop" 14 | # end 15 | #end 16 | 17 | -------------------------------------------------------------------------------- /work/defunct/treetop/syntax/raml.treetop: -------------------------------------------------------------------------------- 1 | grammar RAML 2 | 3 | rule config 4 | name space string 5 | end 6 | 7 | rule name 8 | [a-zA-Z] [a-zA-Z0-9]* 9 | end 10 | 11 | rule space 12 | [ \t]+ 13 | end 14 | 15 | rule string 16 | '"' .* '"' 17 | end 18 | 19 | rule eol 20 | "\n" 21 | end 22 | 23 | end 24 | 25 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | #!mast .ruby .yardopts bin lib man qed spec test [A-Z]*.* 2 | .ruby 3 | .yardopts 4 | lib/raml/core_ext.rb 5 | lib/raml/eval_parser.rb 6 | lib/raml/multi_value.rb 7 | lib/raml/ripper_parser.rb 8 | lib/raml.rb 9 | lib/raml.yml 10 | qed/applique/document.rb 11 | qed/eval.rdoc 12 | qed/read.rdoc 13 | qed/samples/sample1.rml 14 | qed/samples/sample2.rml 15 | HISTORY.rdoc 16 | README.rdoc 17 | QED.rdoc 18 | COPYING.rdoc 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | script: "bundle exec qed -v" 3 | rvm: 4 | - 1.9.3 5 | - 1.9.2 6 | - 1.8.7 7 | - ree 8 | - jruby 9 | - rbx-2.0 10 | env: 11 | - RBXOPT="-X19" 12 | - RBXOPT="" 13 | matrix: 14 | exclude: 15 | - rvm: 1.8.7 16 | env: RBXOPT="-X19" 17 | - rvm: 1.9.2 18 | env: RBXOPT="-X19" 19 | - rvm: 1.9.3 20 | env: RBXOPT="-X19" 21 | - rvm: jruby 22 | env: RBXOPT="-X19" 23 | - rvm: ree 24 | env: RBXOPT="-X19" 25 | 26 | -------------------------------------------------------------------------------- /Assembly: -------------------------------------------------------------------------------- 1 | --- 2 | email: 3 | mailto: 4 | - ruby-talk@ruby-lang.org 5 | - rubyworks-mailinglist@googlegroups.com 6 | 7 | qed: 8 | files: demo 9 | 10 | qedoc: 11 | files: demo 12 | title: RAML Demonstrations 13 | output: 14 | - DEMO.rdoc 15 | - web/qed.html 16 | 17 | gem: 18 | active: true 19 | 20 | github: 21 | gh_page: web 22 | 23 | dnote: 24 | output: log/notes.html 25 | 26 | vclog: 27 | output: 28 | - log/changes.html 29 | - log/history.html 30 | 31 | -------------------------------------------------------------------------------- /work/defunct/treetop.rb: -------------------------------------------------------------------------------- 1 | require 'treetop' 2 | 3 | =begin 4 | class RAMLPreparser 5 | 6 | def parse(string) 7 | string.strip 8 | #remove_extra_spaces(remove_comments(string)) 9 | #remove_comments(string.strip) 10 | end 11 | 12 | private 13 | 14 | def remove_comments(string) 15 | string.gsub(/\#.*?($|\Z)/, "") 16 | end 17 | 18 | #def remove_extra_spaces(string) 19 | # string.gsub(/\s+/, " ") 20 | #end 21 | 22 | end 23 | =end 24 | 25 | Treetop.load File.dirname(__FILE__) + "/raml/treetop/raml" 26 | 27 | -------------------------------------------------------------------------------- /work/defunct/treetop/syntax/numbers.treetop: -------------------------------------------------------------------------------- 1 | grammar RAMLNumbers 2 | 3 | rule number 4 | (float / integer) 5 | end 6 | 7 | rule float 8 | ( 9 | (( '+' / '-')? plain_digits '.' plain_digits) / 10 | (( '+' / '-')? plain_digits ('E' / 'e') plain_digits ) / 11 | (( '+' / '-')? plain_digits '.') / 12 | (( '+' / '-')? '.' plain_digits) 13 | ) { 14 | def eval 15 | text_value.to_f 16 | end 17 | } 18 | end 19 | 20 | rule integer 21 | (( '+' / '-' )? plain_digits) { 22 | def eval 23 | text_value.to_i 24 | end 25 | } 26 | end 27 | 28 | rule plain_digits 29 | [0-9] [0-9]* 30 | end 31 | 32 | end 33 | 34 | -------------------------------------------------------------------------------- /work/defunct/treetop/syntax/interface.treetop: -------------------------------------------------------------------------------- 1 | grammar RAMLInterface 2 | include RAMLNumbers 3 | include RAMLString 4 | 5 | rule interface 6 | arguments comma options / arguments / options 7 | end 8 | 9 | rule arguments 10 | argument (comma argument)* 11 | end 12 | 13 | rule argument 14 | ( string / number ) 15 | end 16 | 17 | rule options 18 | option (comma option)* 19 | end 20 | 21 | rule option 22 | key ( ':' / '=>' ) argument 23 | end 24 | 25 | rule key 26 | [a-zA-Z] [a-zA-Z0-9]* 27 | end 28 | 29 | rule block 30 | 'do' 31 | config* 32 | 'end' 33 | end 34 | 35 | rule comma 36 | whitespace ',' whitespace 37 | end 38 | 39 | rule whitespace 40 | [ \t\n]* 41 | end 42 | 43 | rule space 44 | [ \t]+ 45 | end 46 | 47 | end 48 | 49 | -------------------------------------------------------------------------------- /HISTORY.rdoc: -------------------------------------------------------------------------------- 1 | = RELEASE HISTORY 2 | 3 | == 0.2.1 | 2011-10-28 4 | 5 | This is quick fix release, to make sure QED.rdoc document is in gem 6 | for documentation sake on rubydoc.info. 7 | 8 | Changes: 9 | 10 | * Add QED.rdoc to gem. 11 | 12 | 13 | == 0.2.0 | 2011-10-28 14 | 15 | API has changed a bit to allow `RAML.load` to use eval parser unless 16 | the `:eval=>false` option is used. Also added `RAML.read` which does 17 | the same. This release also modernized the build configuration. 18 | 19 | Changes: 20 | 21 | * Ripper provides pure data parser, but not highly robust yet. 22 | * RAML.load uses eval parser unless :eval=>false. 23 | * Add RAML.read method. 24 | * Modernize build configuration. 25 | 26 | == 0.1.0 | 2010-09-28 27 | 28 | A very early release of RAML, presently only supporting an eval-based parser. 29 | Ultimately the intent is to provide a true parser, that can restict the format 30 | to pure data. 31 | 32 | Changes: 33 | 34 | * Initial implementation. 35 | 36 | -------------------------------------------------------------------------------- /.ruby: -------------------------------------------------------------------------------- 1 | --- 2 | source: 3 | - var 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.1 39 | name: raml 40 | description: RAML is a Ruby-syntax-based data language. 41 | organization: rubyworks 42 | date: '2011-11-11' 43 | -------------------------------------------------------------------------------- /COPYING.rdoc: -------------------------------------------------------------------------------- 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 | 32 | -------------------------------------------------------------------------------- /work/defunct/raml.rb: -------------------------------------------------------------------------------- 1 | 2 | class RAML 3 | 4 | # 5 | instance_methods.each{ |m| undef m unless /^__/ =~ m.to_s } 6 | 7 | def self.load(text, data={}) 8 | new(text, data).to_h 9 | end 10 | 11 | # 12 | def initialize(text, data={}) 13 | @text = text 14 | @data = data 15 | instance_eval(text) 16 | end 17 | 18 | # 19 | def to_h 20 | @hash 21 | end 22 | 23 | # 24 | def method_missing(sym, *args, &block) 25 | sym = sym.to_s.downcase 26 | 27 | return @data[sym.to_sym] if @data.key?(sym.to_sym) 28 | 29 | if String === args.first 30 | key = args.first.to_s 31 | srv = sym.to_s 32 | else 33 | key = sym.to_s 34 | srv = false 35 | end 36 | 37 | raise SyntaxError if /[=?!]$/ =~ key 38 | 39 | if block_given? 40 | @hash[key] = Entry.new(@data, &block).to_h 41 | else 42 | @hash[key] = {} 43 | end 44 | 45 | @hash[key]['type_id'] = srv if srv 46 | @hash[key]['active'] = false if FalseClass === args.last 47 | 48 | @hash[key] 49 | end 50 | 51 | # 52 | # 53 | 54 | class Entry 55 | 56 | # 57 | instance_methods.each{ |m| undef m unless /^__/ =~ m.to_s } 58 | 59 | # 60 | def initialize(data={}, &block) 61 | @hash = {} 62 | instance_eval(&block) 63 | end 64 | 65 | # 66 | def to_h ; @hash ; end 67 | 68 | # 69 | def method_missing(sym, *args, &block) 70 | return @data[sym.to_sym] if @data.key?(sym.to_sym) && args.empty? 71 | return @hash[sym.to_s] = args.first 72 | end 73 | 74 | end # class Entry 75 | 76 | end # class RAML 77 | 78 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 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 | {}[http://travis-ci.org/rubyworks/raml] 9 | 10 | 11 | == DESCRIPTION 12 | 13 | RAML is a flexible 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, because 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 a `:safe` option that is used to set the level of evaluation 50 | Ruby is allowed. 51 | 52 | RAML.load(raml, :safe=>true) 53 | 54 | If the option is `false`, which is the default, than evaluation is handled 55 | with `$SAFE=0`. If `true` then $SAFE=4. Safe evaluation is useful when loading 56 | untrusted data files. With `$SAFE=4` most (though not all) security concerns 57 | are mitigated. 58 | 59 | For a complete lock-down on evaluation, allowing only data settings and no other 60 | forms of Ruby evaluation, `RAML.load` supports the `:eval` option. By default 61 | it is set to `true`. By setting it to `false` (but not `nil`), all code 62 | evaluation can be turned off. 63 | 64 | RAML.load(raml, :eval=>false) 65 | 66 | Under the hood the current implementation has two different parsers, a 67 | Ripper based parser and a Kernel.eval based parser. By setting `:eval=>false` 68 | the Ripper parser is used rather than the regular eval parser. 69 | 70 | For additional examples and details on working with RAML files, see 71 | the QED demonstrandum. 72 | 73 | 74 | == IMPORTANT NOTE 75 | 76 | The Ripper based parser is not yet robust and is NOT RECOMMENDED FOR PRODUCTION 77 | USE, as it will parse invalid RAML documents without complaint. The eval parser 78 | on the other hand works well. 79 | 80 | 81 | == SPECIAL THANKS 82 | 83 | Big props to Robert Dober for taking the time to help me improve 84 | the code. 85 | 86 | 87 | == COPYRIGHTS 88 | 89 | Copyright (c) 2010 Rubyworks 90 | 91 | RAML is distributed under the terms of the *FreeBSD* License. 92 | See COPYING.rdoc for details. 93 | 94 | -------------------------------------------------------------------------------- /demo/eval.rdoc: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /demo/read.rdoc: -------------------------------------------------------------------------------- 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 | 110 | -------------------------------------------------------------------------------- /lib/raml.rb: -------------------------------------------------------------------------------- 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 111 | -------------------------------------------------------------------------------- /lib/raml/ripper_parser.rb: -------------------------------------------------------------------------------- 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 152 | -------------------------------------------------------------------------------- /lib/raml/eval_parser.rb: -------------------------------------------------------------------------------- 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. 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 150 | -------------------------------------------------------------------------------- /.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | 5 | module DotRuby 6 | 7 | # 8 | class GemSpec 9 | 10 | # For which revision of .ruby is this gemspec intended? 11 | REVISION = 0 12 | 13 | # 14 | PATTERNS = { 15 | :bin_files => 'bin/*', 16 | :lib_files => 'lib/{**/}*.rb', 17 | :ext_files => 'ext/{**/}extconf.rb', 18 | :doc_files => '*.{txt,rdoc,md,markdown,tt,textile}', 19 | :test_files => '{test/{**/}*_test.rb,spec/{**/}*_spec.rb}' 20 | } 21 | 22 | # 23 | def self.instance 24 | new.to_gemspec 25 | end 26 | 27 | attr :metadata 28 | 29 | attr :manifest 30 | 31 | # 32 | def initialize 33 | @metadata = YAML.load_file('.ruby') 34 | @manifest = Dir.glob('manifest{,.txt}', File::FNM_CASEFOLD).first 35 | 36 | if @metadata['revision'].to_i != REVISION 37 | warn "You have the wrong revision. Trying anyway..." 38 | end 39 | end 40 | 41 | # 42 | def scm 43 | @scm ||= \ 44 | case 45 | when File.directory?('.git') 46 | :git 47 | end 48 | end 49 | 50 | # 51 | def files 52 | @files ||= \ 53 | #glob_files[patterns[:files]] 54 | case 55 | when manifest 56 | File.readlines(manifest). 57 | map{ |line| line.strip }. 58 | reject{ |line| line.empty? || line[0,1] == '#' } 59 | when scm == :git 60 | `git ls-files -z`.split("\0") 61 | else 62 | Dir.glob('{**/}{.*,*}') # TODO: be more specific using standard locations ? 63 | end.select{ |path| File.file?(path) } 64 | end 65 | 66 | # 67 | def glob_files(pattern) 68 | Dir.glob(pattern).select { |path| 69 | File.file?(path) && files.include?(path) 70 | } 71 | end 72 | 73 | # 74 | def patterns 75 | PATTERNS 76 | end 77 | 78 | # 79 | def executables 80 | @executables ||= \ 81 | glob_files(patterns[:bin_files]).map do |path| 82 | File.basename(path) 83 | end 84 | end 85 | 86 | def extensions 87 | @extensions ||= \ 88 | glob_files(patterns[:ext_files]).map do |path| 89 | File.basename(path) 90 | end 91 | end 92 | 93 | # 94 | def name 95 | metadata['name'] || metadata['title'].downcase.gsub(/\W+/,'_') 96 | end 97 | 98 | # 99 | def to_gemspec 100 | Gem::Specification.new do |gemspec| 101 | gemspec.name = name 102 | gemspec.version = metadata['version'] 103 | gemspec.summary = metadata['summary'] 104 | gemspec.description = metadata['description'] 105 | 106 | metadata['authors'].each do |author| 107 | gemspec.authors << author['name'] 108 | 109 | if author.has_key?('email') 110 | if gemspec.email 111 | gemspec.email << author['email'] 112 | else 113 | gemspec.email = [author['email']] 114 | end 115 | end 116 | end 117 | 118 | gemspec.licenses = metadata['copyrights'].map{ |c| c['license'] }.compact 119 | 120 | metadata['requirements'].each do |req| 121 | name = req['name'] 122 | version = req['version'] 123 | groups = req['groups'] || [] 124 | 125 | case version 126 | when /^(.*?)\+$/ 127 | version = ">= #{$1}" 128 | when /^(.*?)\-$/ 129 | version = "< #{$1}" 130 | when /^(.*?)\~$/ 131 | version = "~> #{$1}" 132 | end 133 | 134 | if groups.empty? or groups.include?('runtime') 135 | # populate runtime dependencies 136 | if gemspec.respond_to?(:add_runtime_dependency) 137 | gemspec.add_runtime_dependency(name,*version) 138 | else 139 | gemspec.add_dependency(name,*version) 140 | end 141 | else 142 | # populate development dependencies 143 | if gemspec.respond_to?(:add_development_dependency) 144 | gemspec.add_development_dependency(name,*version) 145 | else 146 | gemspec.add_dependency(name,*version) 147 | end 148 | end 149 | end 150 | 151 | # convert external dependencies into a requirements 152 | if metadata['external_dependencies'] 153 | ##gemspec.requirements = [] unless metadata['external_dependencies'].empty? 154 | metadata['external_dependencies'].each do |req| 155 | gemspec.requirements << req.to_s 156 | end 157 | end 158 | 159 | # determine homepage from resources 160 | homepage = metadata['resources'].find{ |key, url| key =~ /^home/ } 161 | gemspec.homepage = homepage.last if homepage 162 | 163 | gemspec.require_paths = metadata['load_path'] || ['lib'] 164 | gemspec.post_install_message = metadata['install_message'] 165 | 166 | # RubyGems specific metadata 167 | gemspec.files = files 168 | gemspec.extensions = extensions 169 | gemspec.executables = executables 170 | 171 | if Gem::VERSION < '1.7.' 172 | gemspec.default_executable = gemspec.executables.first 173 | end 174 | 175 | gemspec.test_files = glob_files(patterns[:test_files]) 176 | 177 | unless gemspec.files.include?('.document') 178 | gemspec.extra_rdoc_files = glob_files(patterns[:doc_files]) 179 | end 180 | end 181 | end 182 | 183 | end #class GemSpec 184 | 185 | end 186 | 187 | DotRuby::GemSpec.instance 188 | -------------------------------------------------------------------------------- /DEMO.rdoc: -------------------------------------------------------------------------------- 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 | 218 | -------------------------------------------------------------------------------- /work/defunct/treetop/parser.rb: -------------------------------------------------------------------------------- 1 | # Autogenerated from a Treetop grammar. Edits may be lost. 2 | 3 | 4 | module RAML 5 | include Treetop::Runtime 6 | 7 | def root 8 | @root || :space 9 | end 10 | 11 | def _nt_space 12 | start_index = index 13 | if node_cache[:space].has_key?(index) 14 | cached = node_cache[:space][index] 15 | @index = cached.interval.end if cached 16 | return cached 17 | end 18 | 19 | s0, i0 = [], index 20 | loop do 21 | i1 = index 22 | if has_terminal?(" ", false, index) 23 | r2 = instantiate_node(SyntaxNode,input, index...(index + 1)) 24 | @index += 1 25 | else 26 | terminal_parse_failure(" ") 27 | r2 = nil 28 | end 29 | if r2 30 | r1 = r2 31 | else 32 | if has_terminal?("\t", false, index) 33 | r3 = instantiate_node(SyntaxNode,input, index...(index + 1)) 34 | @index += 1 35 | else 36 | terminal_parse_failure("\t") 37 | r3 = nil 38 | end 39 | if r3 40 | r1 = r3 41 | else 42 | @index = i1 43 | r1 = nil 44 | end 45 | end 46 | if r1 47 | s0 << r1 48 | else 49 | break 50 | end 51 | end 52 | if s0.empty? 53 | @index = i0 54 | r0 = nil 55 | else 56 | r0 = instantiate_node(SyntaxNode,input, i0...index, s0) 57 | end 58 | 59 | node_cache[:space][start_index] = r0 60 | 61 | r0 62 | end 63 | 64 | def _nt_eol 65 | start_index = index 66 | if node_cache[:eol].has_key?(index) 67 | cached = node_cache[:eol][index] 68 | @index = cached.interval.end if cached 69 | return cached 70 | end 71 | 72 | if has_terminal?("\n", false, index) 73 | r0 = instantiate_node(SyntaxNode,input, index...(index + 1)) 74 | @index += 1 75 | else 76 | terminal_parse_failure("\n") 77 | r0 = nil 78 | end 79 | 80 | node_cache[:eol][start_index] = r0 81 | 82 | r0 83 | end 84 | 85 | module Config0 86 | def name 87 | elements[0] 88 | end 89 | 90 | def eol 91 | elements[3] 92 | end 93 | end 94 | 95 | def _nt_config 96 | start_index = index 97 | if node_cache[:config].has_key?(index) 98 | cached = node_cache[:config][index] 99 | @index = cached.interval.end if cached 100 | return cached 101 | end 102 | 103 | i0, s0 = index, [] 104 | r1 = _nt_name 105 | s0 << r1 106 | if r1 107 | r3 = _nt_arguments 108 | if r3 109 | r2 = r3 110 | else 111 | r2 = instantiate_node(SyntaxNode,input, index...index) 112 | end 113 | s0 << r2 114 | if r2 115 | r5 = _nt_options 116 | if r5 117 | r4 = r5 118 | else 119 | r4 = instantiate_node(SyntaxNode,input, index...index) 120 | end 121 | s0 << r4 122 | if r4 123 | r6 = _nt_eol 124 | s0 << r6 125 | end 126 | end 127 | end 128 | if s0.last 129 | r0 = instantiate_node(SyntaxNode,input, i0...index, s0) 130 | r0.extend(Config0) 131 | else 132 | @index = i0 133 | r0 = nil 134 | end 135 | 136 | node_cache[:config][start_index] = r0 137 | 138 | r0 139 | end 140 | 141 | module Arguments0 142 | def argument 143 | elements[3] 144 | end 145 | end 146 | 147 | module Arguments1 148 | def argument 149 | elements[1] 150 | end 151 | 152 | end 153 | 154 | def _nt_arguments 155 | start_index = index 156 | if node_cache[:arguments].has_key?(index) 157 | cached = node_cache[:arguments][index] 158 | @index = cached.interval.end if cached 159 | return cached 160 | end 161 | 162 | i0, s0 = index, [] 163 | r2 = _nt_space 164 | if r2 165 | r1 = r2 166 | else 167 | r1 = instantiate_node(SyntaxNode,input, index...index) 168 | end 169 | s0 << r1 170 | if r1 171 | r3 = _nt_argument 172 | s0 << r3 173 | if r3 174 | s4, i4 = [], index 175 | loop do 176 | i5, s5 = index, [] 177 | r7 = _nt_space 178 | if r7 179 | r6 = r7 180 | else 181 | r6 = instantiate_node(SyntaxNode,input, index...index) 182 | end 183 | s5 << r6 184 | if r6 185 | if has_terminal?(',', false, index) 186 | r8 = instantiate_node(SyntaxNode,input, index...(index + 1)) 187 | @index += 1 188 | else 189 | terminal_parse_failure(',') 190 | r8 = nil 191 | end 192 | s5 << r8 193 | if r8 194 | r10 = _nt_space 195 | if r10 196 | r9 = r10 197 | else 198 | r9 = instantiate_node(SyntaxNode,input, index...index) 199 | end 200 | s5 << r9 201 | if r9 202 | r11 = _nt_argument 203 | s5 << r11 204 | end 205 | end 206 | end 207 | if s5.last 208 | r5 = instantiate_node(SyntaxNode,input, i5...index, s5) 209 | r5.extend(Arguments0) 210 | else 211 | @index = i5 212 | r5 = nil 213 | end 214 | if r5 215 | s4 << r5 216 | else 217 | break 218 | end 219 | end 220 | r4 = instantiate_node(SyntaxNode,input, i4...index, s4) 221 | s0 << r4 222 | end 223 | end 224 | if s0.last 225 | r0 = instantiate_node(SyntaxNode,input, i0...index, s0) 226 | r0.extend(Arguments1) 227 | else 228 | @index = i0 229 | r0 = nil 230 | end 231 | 232 | node_cache[:arguments][start_index] = r0 233 | 234 | r0 235 | end 236 | 237 | module Name0 238 | end 239 | 240 | def _nt_name 241 | start_index = index 242 | if node_cache[:name].has_key?(index) 243 | cached = node_cache[:name][index] 244 | @index = cached.interval.end if cached 245 | return cached 246 | end 247 | 248 | i0, s0 = index, [] 249 | if has_terminal?('\G[a-zA-Z]', true, index) 250 | r1 = true 251 | @index += 1 252 | else 253 | r1 = nil 254 | end 255 | s0 << r1 256 | if r1 257 | s2, i2 = [], index 258 | loop do 259 | if has_terminal?('\G[a-zA-Z0-9]', true, index) 260 | r3 = true 261 | @index += 1 262 | else 263 | r3 = nil 264 | end 265 | if r3 266 | s2 << r3 267 | else 268 | break 269 | end 270 | end 271 | r2 = instantiate_node(SyntaxNode,input, i2...index, s2) 272 | s0 << r2 273 | end 274 | if s0.last 275 | r0 = instantiate_node(SyntaxNode,input, i0...index, s0) 276 | r0.extend(Name0) 277 | else 278 | @index = i0 279 | r0 = nil 280 | end 281 | 282 | node_cache[:name][start_index] = r0 283 | 284 | r0 285 | end 286 | 287 | def _nt_argument 288 | start_index = index 289 | if node_cache[:argument].has_key?(index) 290 | cached = node_cache[:argument][index] 291 | @index = cached.interval.end if cached 292 | return cached 293 | end 294 | 295 | i0 = index 296 | r1 = _nt_string 297 | if r1 298 | r0 = r1 299 | else 300 | r2 = _nt_number 301 | if r2 302 | r0 = r2 303 | else 304 | @index = i0 305 | r0 = nil 306 | end 307 | end 308 | 309 | node_cache[:argument][start_index] = r0 310 | 311 | r0 312 | end 313 | 314 | module Options0 315 | def option 316 | elements[3] 317 | end 318 | end 319 | 320 | module Options1 321 | def option 322 | elements[1] 323 | end 324 | 325 | end 326 | 327 | def _nt_options 328 | start_index = index 329 | if node_cache[:options].has_key?(index) 330 | cached = node_cache[:options][index] 331 | @index = cached.interval.end if cached 332 | return cached 333 | end 334 | 335 | i0, s0 = index, [] 336 | r2 = _nt_space 337 | if r2 338 | r1 = r2 339 | else 340 | r1 = instantiate_node(SyntaxNode,input, index...index) 341 | end 342 | s0 << r1 343 | if r1 344 | r3 = _nt_option 345 | s0 << r3 346 | if r3 347 | i5, s5 = index, [] 348 | r7 = _nt_space 349 | if r7 350 | r6 = r7 351 | else 352 | r6 = instantiate_node(SyntaxNode,input, index...index) 353 | end 354 | s5 << r6 355 | if r6 356 | if has_terminal?(',', false, index) 357 | r8 = instantiate_node(SyntaxNode,input, index...(index + 1)) 358 | @index += 1 359 | else 360 | terminal_parse_failure(',') 361 | r8 = nil 362 | end 363 | s5 << r8 364 | if r8 365 | r10 = _nt_space 366 | if r10 367 | r9 = r10 368 | else 369 | r9 = instantiate_node(SyntaxNode,input, index...index) 370 | end 371 | s5 << r9 372 | if r9 373 | r11 = _nt_option 374 | s5 << r11 375 | end 376 | end 377 | end 378 | if s5.last 379 | r5 = instantiate_node(SyntaxNode,input, i5...index, s5) 380 | r5.extend(Options0) 381 | else 382 | @index = i5 383 | r5 = nil 384 | end 385 | if r5 386 | r4 = r5 387 | else 388 | r4 = instantiate_node(SyntaxNode,input, index...index) 389 | end 390 | s0 << r4 391 | end 392 | end 393 | if s0.last 394 | r0 = instantiate_node(SyntaxNode,input, i0...index, s0) 395 | r0.extend(Options1) 396 | else 397 | @index = i0 398 | r0 = nil 399 | end 400 | 401 | node_cache[:options][start_index] = r0 402 | 403 | r0 404 | end 405 | 406 | module Option0 407 | def key 408 | elements[0] 409 | end 410 | 411 | def argument 412 | elements[2] 413 | end 414 | end 415 | 416 | def _nt_option 417 | start_index = index 418 | if node_cache[:option].has_key?(index) 419 | cached = node_cache[:option][index] 420 | @index = cached.interval.end if cached 421 | return cached 422 | end 423 | 424 | i0, s0 = index, [] 425 | r1 = _nt_key 426 | s0 << r1 427 | if r1 428 | i2 = index 429 | if has_terminal?(':', false, index) 430 | r3 = instantiate_node(SyntaxNode,input, index...(index + 1)) 431 | @index += 1 432 | else 433 | terminal_parse_failure(':') 434 | r3 = nil 435 | end 436 | if r3 437 | r2 = r3 438 | else 439 | if has_terminal?('=>', false, index) 440 | r4 = instantiate_node(SyntaxNode,input, index...(index + 2)) 441 | @index += 2 442 | else 443 | terminal_parse_failure('=>') 444 | r4 = nil 445 | end 446 | if r4 447 | r2 = r4 448 | else 449 | @index = i2 450 | r2 = nil 451 | end 452 | end 453 | s0 << r2 454 | if r2 455 | r5 = _nt_argument 456 | s0 << r5 457 | end 458 | end 459 | if s0.last 460 | r0 = instantiate_node(SyntaxNode,input, i0...index, s0) 461 | r0.extend(Option0) 462 | else 463 | @index = i0 464 | r0 = nil 465 | end 466 | 467 | node_cache[:option][start_index] = r0 468 | 469 | r0 470 | end 471 | 472 | module Key0 473 | end 474 | 475 | def _nt_key 476 | start_index = index 477 | if node_cache[:key].has_key?(index) 478 | cached = node_cache[:key][index] 479 | @index = cached.interval.end if cached 480 | return cached 481 | end 482 | 483 | i0, s0 = index, [] 484 | if has_terminal?('\G[a-zA-Z]', true, index) 485 | r1 = true 486 | @index += 1 487 | else 488 | r1 = nil 489 | end 490 | s0 << r1 491 | if r1 492 | s2, i2 = [], index 493 | loop do 494 | if has_terminal?('\G[a-zA-Z0-9]', true, index) 495 | r3 = true 496 | @index += 1 497 | else 498 | r3 = nil 499 | end 500 | if r3 501 | s2 << r3 502 | else 503 | break 504 | end 505 | end 506 | r2 = instantiate_node(SyntaxNode,input, i2...index, s2) 507 | s0 << r2 508 | end 509 | if s0.last 510 | r0 = instantiate_node(SyntaxNode,input, i0...index, s0) 511 | r0.extend(Key0) 512 | else 513 | @index = i0 514 | r0 = nil 515 | end 516 | 517 | node_cache[:key][start_index] = r0 518 | 519 | r0 520 | end 521 | 522 | module Block0 523 | def name 524 | elements[0] 525 | end 526 | 527 | end 528 | 529 | def _nt_block 530 | start_index = index 531 | if node_cache[:block].has_key?(index) 532 | cached = node_cache[:block][index] 533 | @index = cached.interval.end if cached 534 | return cached 535 | end 536 | 537 | i0, s0 = index, [] 538 | r1 = _nt_name 539 | s0 << r1 540 | if r1 541 | i2 = index 542 | r3 = _nt_space 543 | if r3 544 | @index = i2 545 | r2 = instantiate_node(SyntaxNode,input, index...index) 546 | else 547 | r2 = nil 548 | end 549 | s0 << r2 550 | if r2 551 | r5 = _nt_argument_list 552 | if r5 553 | r4 = r5 554 | else 555 | r4 = instantiate_node(SyntaxNode,input, index...index) 556 | end 557 | s0 << r4 558 | if r4 559 | i6 = index 560 | r7 = _nt_space 561 | if r7 562 | @index = i6 563 | r6 = instantiate_node(SyntaxNode,input, index...index) 564 | else 565 | r6 = nil 566 | end 567 | s0 << r6 568 | if r6 569 | if has_terminal?('do', false, index) 570 | r8 = instantiate_node(SyntaxNode,input, index...(index + 2)) 571 | @index += 2 572 | else 573 | terminal_parse_failure('do') 574 | r8 = nil 575 | end 576 | s0 << r8 577 | if r8 578 | s9, i9 = [], index 579 | loop do 580 | r10 = _nt_config 581 | if r10 582 | s9 << r10 583 | else 584 | break 585 | end 586 | end 587 | r9 = instantiate_node(SyntaxNode,input, i9...index, s9) 588 | s0 << r9 589 | if r9 590 | if has_terminal?('end', false, index) 591 | r11 = instantiate_node(SyntaxNode,input, index...(index + 3)) 592 | @index += 3 593 | else 594 | terminal_parse_failure('end') 595 | r11 = nil 596 | end 597 | s0 << r11 598 | end 599 | end 600 | end 601 | end 602 | end 603 | end 604 | if s0.last 605 | r0 = instantiate_node(SyntaxNode,input, i0...index, s0) 606 | r0.extend(Block0) 607 | else 608 | @index = i0 609 | r0 = nil 610 | end 611 | 612 | node_cache[:block][start_index] = r0 613 | 614 | r0 615 | end 616 | 617 | module String0 618 | end 619 | 620 | def _nt_string 621 | start_index = index 622 | if node_cache[:string].has_key?(index) 623 | cached = node_cache[:string][index] 624 | @index = cached.interval.end if cached 625 | return cached 626 | end 627 | 628 | i0, s0 = index, [] 629 | if has_terminal?('"', false, index) 630 | r1 = instantiate_node(SyntaxNode,input, index...(index + 1)) 631 | @index += 1 632 | else 633 | terminal_parse_failure('"') 634 | r1 = nil 635 | end 636 | s0 << r1 637 | if r1 638 | if has_terminal?('\G[a-zA-Z]', true, index) 639 | r2 = true 640 | @index += 1 641 | else 642 | r2 = nil 643 | end 644 | s0 << r2 645 | if r2 646 | s3, i3 = [], index 647 | loop do 648 | if has_terminal?('\G[a-zA-Z0-9]', true, index) 649 | r4 = true 650 | @index += 1 651 | else 652 | r4 = nil 653 | end 654 | if r4 655 | s3 << r4 656 | else 657 | break 658 | end 659 | end 660 | if s3.empty? 661 | @index = i3 662 | r3 = nil 663 | else 664 | r3 = instantiate_node(SyntaxNode,input, i3...index, s3) 665 | end 666 | s0 << r3 667 | if r3 668 | if has_terminal?('"', false, index) 669 | r5 = instantiate_node(SyntaxNode,input, index...(index + 1)) 670 | @index += 1 671 | else 672 | terminal_parse_failure('"') 673 | r5 = nil 674 | end 675 | s0 << r5 676 | end 677 | end 678 | end 679 | if s0.last 680 | r0 = instantiate_node(SyntaxNode,input, i0...index, s0) 681 | r0.extend(String0) 682 | else 683 | @index = i0 684 | r0 = nil 685 | end 686 | 687 | node_cache[:string][start_index] = r0 688 | 689 | r0 690 | end 691 | 692 | def _nt_number 693 | start_index = index 694 | if node_cache[:number].has_key?(index) 695 | cached = node_cache[:number][index] 696 | @index = cached.interval.end if cached 697 | return cached 698 | end 699 | 700 | s0, i0 = [], index 701 | loop do 702 | if has_terminal?('\G[0-9]', true, index) 703 | r1 = true 704 | @index += 1 705 | else 706 | r1 = nil 707 | end 708 | if r1 709 | s0 << r1 710 | else 711 | break 712 | end 713 | end 714 | if s0.empty? 715 | @index = i0 716 | r0 = nil 717 | else 718 | r0 = instantiate_node(SyntaxNode,input, i0...index, s0) 719 | end 720 | 721 | node_cache[:number][start_index] = r0 722 | 723 | r0 724 | end 725 | 726 | end 727 | 728 | class RAMLParser < Treetop::Runtime::CompiledParser 729 | include RAML 730 | end 731 | 732 | 733 | --------------------------------------------------------------------------------