pax_global_header00006660000000000000000000000064134062117170014514gustar00rootroot0000000000000052 comment=260115d68175f8abdf814c829eb03c060424523f ruby-raabro-1.1.6/000077500000000000000000000000001340621171700137465ustar00rootroot00000000000000ruby-raabro-1.1.6/CHANGELOG.md000066400000000000000000000027031340621171700155610ustar00rootroot00000000000000 # raabro CHANGELOG.md ## raabro 1.1.6 released 2018-06-22 * Remove unused `add` var, gh-2, thanks to https://github.com/utilum ## raabro 1.1.5 released 2017-08-19 * Default name to nil for Tree#subgather, #gather, #sublookup, and #lookup ## raabro 1.1.4 released 2017-08-17 * fail with ArgumentError if Raabro.pp input is not a Raabro::Tree * parse(x, error: true) will produce an error message `[ line, column, offset, err_message, err_visual ]` ## raabro 1.1.3 released 2016-07-11 * display `>nonstring(14)<` in Raabro.pp * add "tears" to Raabro.pp ## raabro 1.1.2 released 2016-04-04 * add Raabro.pp(tree) ## raabro 1.1.1 released 2016-04-03 * Tree#clast ## raabro 1.1.0 released 2016-02-09 * many improvements * unlock custom `rewrite(t)` ## raabro 1.0.5 released 2015-09-25 * allow for .parse(s, debug: 1 to 3) * drop complications in _narrow(parser) ## raabro 1.0.4 released 2015-09-24 * provide a default .rewrite implementation ## raabro 1.0.3 released 2015-09-23 * drop the shrink! concept ## raabro 1.0.2 released 2015-09-23 * don't let parse() shrink tree when prune: false * let parse() return tree anyway when prune: false * add parse(s, rewrite: false) option ## raabro 1.0.1 released 2015-09-23 * take last parser as default :root * provide default .parse for modules including Raabro ## raabro 1.0.0 released 2015-09-23 * first complete (hopefully) release ## raabro 0.9.0 * initial push to RubyGems ruby-raabro-1.1.6/CREDITS.md000066400000000000000000000004741340621171700153720ustar00rootroot00000000000000 # raabro credits * Utilum https://github.com/utilum removed unused var * John Mettraux https://github.com/jmettraux author and maintainer ## aabro and jaabro credits Thanks to all the people who contributed to aabro and jaabro as well. * https://github.com/flon-io/aabro * https://github.com/jmettraux/jaabro ruby-raabro-1.1.6/LICENSE.txt000066400000000000000000000021171340621171700155720ustar00rootroot00000000000000 Copyright (c) 2015-2018, John Mettraux, jmettraux@gmail.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Made in Japan. ruby-raabro-1.1.6/Makefile000066400000000000000000000016401340621171700154070ustar00rootroot00000000000000 ## gem tasks ## NAME = \ $(shell ruby -e "s = eval(File.read(Dir['*.gemspec'][0])); puts s.name") VERSION = \ $(shell ruby -e "s = eval(File.read(Dir['*.gemspec'][0])); puts s.version") count_lines: find lib -name "*.rb" | xargs cat | ruby -e "p STDIN.readlines.count { |l| l = l.strip; l[0, 1] != '#' && l != '' }" find spec -name "*_spec.rb" | xargs cat | ruby -e "p STDIN.readlines.count { |l| l = l.strip; l[0, 1] != '#' && l != '' }" cl: count_lines gemspec_validate: @echo "---" ruby -e "s = eval(File.read(Dir['*.gemspec'].first)); p s.validate" @echo "---" name: gemspec_validate @echo "$(NAME) $(VERSION)" cw: find lib -name "*.rb" -exec ruby -cw {} \; build: gemspec_validate gem build $(NAME).gemspec mkdir -p pkg mv $(NAME)-$(VERSION).gem pkg/ push: build gem push pkg/$(NAME)-$(VERSION).gem spec: bundle exec rspec test: spec .PHONY: count_lines gemspec_validate name cw build push spec ruby-raabro-1.1.6/README.md000066400000000000000000000165061340621171700152350ustar00rootroot00000000000000 # raabro [![Build Status](https://secure.travis-ci.org/floraison/raabro.svg)](http://travis-ci.org/floraison/raabro) [![Gem Version](https://badge.fury.io/rb/raabro.svg)](http://badge.fury.io/rb/raabro) A very dumb PEG parser library. Son to [aabro](https://github.com/flon-io/aabro), grandson to [neg](https://github.com/jmettraux/neg), grand-grandson to [parslet](https://github.com/kschiess/parslet). There is also a javascript version [jaabro](https://github.com/jmettraux/jaabro). ## a sample parser/rewriter You use raabro by providing the parsing rules, then some rewrite rules. The parsing rules make use of the raabro basic parsers `seq`, `alt`, `str`, `rex`, `eseq`, ... The rewrite rules match names passed as first argument to the basic parsers to rewrite the resulting parse trees. ```ruby require 'raabro' module Fun include Raabro # parse # # Last function is the root, "i" stands for "input". def pa(i); rex(nil, i, /\(\s*/); end def pz(i); rex(nil, i, /\)\s*/); end def com(i); rex(nil, i, /,\s*/); end def num(i); rex(:num, i, /-?[0-9]+\s*/); end def args(i); eseq(nil, i, :pa, :exp, :com, :pz); end def funame(i); rex(nil, i, /[a-z][a-z0-9]*/); end def fun(i); seq(:fun, i, :funame, :args); end def exp(i); alt(nil, i, :fun, :num); end # rewrite # # Names above (:num, :fun, ...) get a rewrite_xxx function. # "t" stands for "tree". # # The trees with a nil name are handled by rewrite_(tree) a default # rewrite function def rewrite_num(t); t.string.to_i; end def rewrite_fun(t) [ t.children[0].string ] + t.children[1].odd_children.collect { |a| rewrite(a) } end end p Fun.parse('mul(1, 2)') # => ["mul", 1, 2] p Fun.parse('mul(1, add(-2, 3))') # => ["mul", 1, ["add", -2, 3]] p Fun.parse('mul (1, 2)') # => nil (doesn't accept a space after the function name) ``` This sample is available at: [doc/readme0.rb](doc/readme0.rb). ## custom rewrite() By default, a parser gets a `rewrite(t)` that looks at the parse tree node names and calls the corresponding `rewrite_{node_name}()`. It's OK to provide a custom `rewrite(t)` function. ```ruby module Hello include Raabro def hello(i); str(:hello, i, 'hello'); end def rewrite(t) [ :ok, t.string ] end end ``` ## basic parsers One makes a parser by composing basic parsers, for example: ```ruby def args(i); eseq(:args, i, :pa, :exp, :com, :pz); end def funame(i); rex(:funame, i, /[a-z][a-z0-9]*/); end def fun(i); seq(:fun, i, :funame, :args); end ``` where the `fun` parser is a sequence combining the `funame` parser then the `args` one. `:fun` (the first argument to the basic parser `seq`) will be the name of the resulting (local) parse tree. Below is a list of the basic parsers provided by Raabro. The first parameter to the basic parser is the name used by rewrite rules. The second parameter is a `Raabro::Input` instance, mostly a wrapped string. ```ruby def str(name, input, string) # matching a string def rex(name, input, regex_or_string) # matching a regexp # no need for ^ or \A, checks the match occurs at current offset def seq(name, input, *parsers) # a sequence of parsers def alt(name, input, *parsers) # tries the parsers returns as soon as one succeeds def altg(name, input, *parsers) # tries all the parsers, returns with the longest match def rep(name, input, parser, min, max=0) # repeats the the wrapped parser def ren(name, input, parser) # renames the output of the wrapped parser def jseq(name, input, eltpa, seppa) # # seq(name, input, eltpa, seppa, eltpa, seppa, eltpa, seppa, ...) # # a sequence of `eltpa` parsers separated (joined) by `seppa` parsers def eseq(name, input, startpa, eltpa, seppa, endpa) # # seq(name, input, startpa, eltpa, seppa, eltpa, seppa, ..., endpa) # # a sequence of `eltpa` parsers separated (joined) by `seppa` parsers # preceded by a `startpa` parser and followed by a `endpa` parser ``` ## the `seq` parser and its quantifiers `seq` is special, it understands "quantifiers": `'?'`, `'+'` or `'*'`. They make behave `seq` a bit like a classical regex. ```ruby module CartParser include Raabro def fruit(i) rex(:fruit, i, /(tomato|apple|orange)/) end def vegetable(i) rex(:vegetable, i, /(potato|cabbage|carrot)/) end def cart(i) seq(:cart, i, :fruit, '*', :vegetable, '*') end # zero or more fruits followed by zero or more vegetables end ``` (Yes, this sample parser parses string like "appletomatocabbage", it's not very useful, but I hope you get the point about `.seq`) ## trees An instance of `Raabro::Tree` is passed to `rewrite()` and `rewrite_{name}()` functions. The most useful methods of this class are: ```ruby class Raabro::Tree # Look for the first child or sub-child with the given name. # If the given name is nil, looks for the first child with a name (not nil). # def sublookup(name=nil) # Gathers all the children or sub-children with the given name. # If the given name is nil, gathers all the children with a name (not nil). # When a child matches, does not pursue gathering from the children of the # matching child. # def subgather(name=nil) end ``` I'm using "child or sub-child" instead of "descendant" because once a child or sub-child matches, those methods do not consider the children or sub-children of that matching entity. Here is a closeup on the rewrite functions of the sample parser at [doc/readme1.rb](doc/readme1.rb) (extracted from an early version of [floraison/dense](https://github.com/floraison/dense)): ```ruby require 'raabro' module PathParser include Raabro # (...) def rewrite_name(t); t.string; end def rewrite_off(t); t.string.to_i; end def rewrite_index(t); rewrite(t.sublookup); end def rewrite_path(t); t.subgather(:index).collect { |tt| rewrite(tt) }; end end ``` Where `rewrite_index(t)` returns the result of the rewrite of the first of its children that has a name and `rewrite_path(t)` collects the result of the rewrite of all of its children that have the "index" name. ## errors By default, a parser will return nil when it cannot successfully parse the input. For example, given the above [`Fun` parser](#a-sample-parserrewriter), parsing some truncated input would yield `nil`: ```ruby tree = Sample::Fun.parse('f(a, b') # yields `nil`... ``` One can reparse with `error: true` and receive an error array with the parse error details: ```ruby err = Sample::Fun.parse('f(a, b', error: true) # yields: # [ line, column, offest, error_message, error_visual ] [ 1, 4, 3, 'parsing failed .../:exp/:fun/:arg', "f(a, b\n ^---" ] ``` The last string in the error array looks like when printed out: ``` f(a, b ^--- ``` ### error when not all is consumed Consider the following toy parser: ```ruby module ToPlus include Raabro # parse def to_plus(input); rep(:tos, input, :to, 1); end # rewrite def rewrite(t); [ :ok, t.string ]; end end ``` ```ruby Sample::ToPlus.parse('totota') # yields nil since all the input was not parsed, "ta" is remaining Sample::ToPlus.parse('totota', all: false) # yields [ :ok, "toto" ] # and doesn't care about the remaining input "ta" Sample::ToPlus.parse('totota', error: true) # yields [ 1, 5, 4, "parsing failed, not all input was consumed", "totota\n ^---" ] ``` The last string in the error array looks like when printed out: ``` totota ^--- ``` ## LICENSE MIT, see [LICENSE.txt](LICENSE.txt) ruby-raabro-1.1.6/lib/000077500000000000000000000000001340621171700145145ustar00rootroot00000000000000ruby-raabro-1.1.6/lib/raabro.rb000066400000000000000000000321551340621171700163150ustar00rootroot00000000000000 module Raabro VERSION = '1.1.6' class Input attr_accessor :string, :offset attr_reader :options def initialize(string, offset=0, options={}) @string = string @offset = offset.is_a?(Hash) ? 0 : offset @options = offset.is_a?(Hash) ? offset : options end def match(str_or_regex) if str_or_regex.is_a?(Regexp) m = @string[@offset..-1].match(str_or_regex) m && (m.offset(0).first == 0) ? m[0].length : false else # String or whatever responds to #to_s s = str_or_regex.to_s l = s.length @string[@offset, l] == s ? l : false end end def tring(l=-1) l < 0 ? @string[@offset..l] : @string[@offset, l] end def at(i) @string[i, 1] end end class Tree attr_accessor :name, :input attr_accessor :result # ((-1 error,)) 0 nomatch, 1 success attr_accessor :offset, :length attr_accessor :parter, :children def initialize(name, parter, input) @result = 0 @name = name @parter = parter @input = input @offset = input.offset @length = 0 @children = [] end def c0; @children[0]; end def c1; @children[1]; end def c2; @children[2]; end def c3; @children[3]; end def c4; @children[4]; end def clast; @children.last; end def empty? @result == 1 && @length == 0 end def successful_children @children.select { |c| c.result == 1 } end def prune! @children = successful_children end def string @input.string[@offset, @length] end def nonstring(l=7) @input.string[@offset, l] end def lookup(name=nil) name = name ? name.to_s : nil return self if @name && name == nil return self if @name.to_s == name sublookup(name) end def sublookup(name=nil) @children.each { |c| if n = c.lookup(name); return n; end } nil end def gather(name=nil, acc=[]) name = name ? name.to_s : nil if (@name && name == nil) || (@name.to_s == name) acc << self else subgather(name, acc) end acc end def subgather(name=nil, acc=[]) @children.each { |c| c.gather(name, acc) } acc end def to_a(opts={}) opts = Array(opts).inject({}) { |h, e| h[e] = true; h } \ unless opts.is_a?(Hash) cn = if opts[:leaves] && (@result == 1) && @children.empty? string elsif opts[:children] != false @children.collect { |e| e.to_a(opts) } else @children.length end [ @name, @result, @offset, @length, @note, @parter, cn ] end def to_s(depth=0, io=StringIO.new) io.print "\n" if depth > 0 io.print ' ' * depth io.print "#{@result} #{@name.inspect} #{@offset},#{@length}" io.print result == 1 && children.size == 0 ? ' ' + string.inspect : '' @children.each { |c| c.to_s(depth + 1, io) } depth == 0 ? io.string : nil end def odd_children cs = []; @children.each_with_index { |c, i| cs << c if i.odd? }; cs end def even_children cs = []; @children.each_with_index { |c, i| cs << c if i.even? }; cs end def extract_error #Raabro.pp(self, colors: true) err_tree, stack = lookup_error || lookup_all_error line, column = line_and_column(err_tree.offset) err_message = if stack path = stack .compact.reverse.take(3).reverse .collect(&:inspect).join('/') "parsing failed .../#{path}" else "parsing failed, not all input was consumed" end visual = visual(line, column) [ line, column, err_tree.offset, err_message, visual ] end def lookup_error(stack=[]) #print "le(): "; Raabro.pp(self, colors: true) return nil if @result != 0 return [ self, stack ] if @children.empty? @children.each { |c| es = c.lookup_error(stack.dup.push(self.name)) return es if es } nil end # Not "lookup all errors" but "lookup all error", in other # words lookup the point up until which the parser stopped (not # consuming all the input) # def lookup_all_error #print "lae(): "; Raabro.pp(self, colors: true) @children.each { |c| return [ c, nil ] if c.result == 0 } @children.reverse.each { |c| es = c.lookup_all_error; return es if es } nil end def line_and_column(offset) line = 1 column = 0 (0..offset).each do |off| column += 1 next unless @input.at(off) == "\n" line += 1 column = 0 end [ line, column ] end def visual(line, column) @input.string.split("\n")[line - 1] + "\n" + ' ' * (column - 1) + '^---' end end module ModuleMethods def _match(name, input, parter, regex_or_string) r = Raabro::Tree.new(name, parter, input) if l = input.match(regex_or_string) r.result = 1 r.length = l input.offset += l end r end def str(name, input, string) _match(name, input, :str, string) end def rex(name, input, regex_or_string) _match(name, input, :rex, Regexp.new(regex_or_string)) end def _quantify(parser) return nil if parser.is_a?(Symbol) && respond_to?(parser) # so that :plus and co can be overriden case parser when '?', :q, :qmark then [ 0, 1 ] when '*', :s, :star then [ 0, 0 ] when '+', :p, :plus then [ 1, 0 ] else nil end end def _narrow(parser) fail ArgumentError.new("lone quantifier #{parser}") if _quantify(parser) method(parser.to_sym) end def _parse(parser, input) #p [ caller.length, parser, input.tring ] #r = _narrow(parser).call(input) #p [ caller.length, parser, input.tring, r.to_a(children: false) ] #r _narrow(parser).call(input) end def seq(name, input, *parsers) r = ::Raabro::Tree.new(name, :seq, input) start = input.offset c = nil loop do pa = parsers.shift break unless pa if q = _quantify(parsers.first) parsers.shift c = rep(nil, input, pa, *q) r.children.concat(c.children) else c = _parse(pa, input) r.children << c end break if c.result != 1 end if c && c.result == 1 r.result = 1 r.length = input.offset - start else input.offset = start end r end def alt(name, input, *parsers) greedy = if parsers.last == true || parsers.last == false parsers.pop else false end r = ::Raabro::Tree.new(name, greedy ? :altg : :alt, input) start = input.offset c = nil parsers.each do |pa| cc = _parse(pa, input) r.children << cc input.offset = start if greedy if cc.result == 1 && cc.length >= (c ? c.length : -1) c.result = 0 if c c = cc else cc.result = 0 end else c = cc break if c.result == 1 end end if c && c.result == 1 r.result = 1 r.length = c.length input.offset = start + r.length end r.prune! if input.options[:prune] r end def altg(name, input, *parsers) alt(name, input, *parsers, true) end def rep(name, input, parser, min, max=0) min = 0 if min == nil || min < 0 max = nil if max.nil? || max < 1 r = ::Raabro::Tree.new(name, :rep, input) start = input.offset count = 0 loop do c = _parse(parser, input) r.children << c break if c.result != 1 count += 1 break if c.length < 1 break if max && count == max end if count >= min && (max == nil || count <= max) r.result = 1 r.length = input.offset - start else input.offset = start end r.prune! if input.options[:prune] r end def ren(name, input, parser) r = _parse(parser, input) r.name = name r end alias rename ren def all(name, input, parser) start = input.offset length = input.string.length - input.offset r = ::Raabro::Tree.new(name, :all, input) c = _parse(parser, input) r.children << c if c.length < length input.offset = start else r.result = 1 r.length = c.length end r end def eseq(name, input, startpa, eltpa, seppa=nil, endpa=nil) jseq = false if seppa.nil? && endpa.nil? jseq = true seppa = eltpa; eltpa = startpa; startpa = nil end start = input.offset r = ::Raabro::Tree.new(name, jseq ? :jseq : :eseq, input) r.result = 1 c = nil if startpa c = _parse(startpa, input) r.children << c r.result = 0 if c.result != 1 end if r.result == 1 i = 0 loop do st = i > 0 ? _parse(seppa, input) : nil et = st == nil || st.result == 1 ? _parse(eltpa, input) : nil break if st && et && st.empty? && et.result == 0 break if st && et && st.empty? && et.empty? r.children << st if st r.children << et if et break if et == nil break if et.result != 1 i = i + 1 end r.result = 0 if jseq && i == 0 end if r.result == 1 && endpa c = _parse(endpa, input) r.children << c r.result = 0 if c.result != 1 end if r.result == 1 r.length = input.offset - start else input.offset = start end r.prune! if input.options[:prune] r end alias jseq eseq attr_accessor :last def method_added(name) m = method(name) return unless m.arity == 1 return unless m.parameters[0][1] == :i || m.parameters[0][1] == :input @last = name.to_sym end def parse(input, opts={}) d = opts[:debug].to_i opts[:rewrite] = false if d > 0 opts[:all] = false if d > 1 opts[:prune] = false if d > 2 opts[:prune] = true unless opts.has_key?(:prune) root = self.respond_to?(:root) ? :root : @last t = if opts[:all] == false _parse(root, Raabro::Input.new(input, opts)) else all(nil, Raabro::Input.new(input, opts), root) end return reparse_for_error(input, opts, t) if opts[:error] && t.result != 1 return nil if opts[:prune] != false && t.result != 1 t = t.children.first if t.parter == :all return rewrite(t) if opts[:rewrite] != false t end def reparse_for_error(input, opts, t) t = opts[:prune] == false ? t : parse(input, opts.merge(error: false, rewrite: false, prune: false)) #Raabro.pp(t, colours: true) t.extract_error end def rewrite_(tree) t = tree.lookup(nil) t ? rewrite(t) : nil end def rewrite(tree) return !! methods.find { |m| m.to_s.match(/^rewrite_/) } if tree == 0 # return true when "rewrite_xxx" methods seem to have been provided send("rewrite_#{tree.name}", tree) end def make_includable def self.included(target) target.instance_eval do extend ::Raabro::ModuleMethods extend self end end end end extend ModuleMethods make_includable # Black 0;30 Dark Gray 1;30 # Blue 0;34 Light Blue 1;34 # Green 0;32 Light Green 1;32 # Cyan 0;36 Light Cyan 1;36 # Red 0;31 Light Red 1;31 # Purple 0;35 Light Purple 1;35 # Brown 0;33 Yellow 1;33 # Light Gray 0;37 White 1;37 def self.pp(tree, depth=0, opts={}) fail ArgumentError.new( 'tree is not an instance of Raabro::Tree' ) unless tree.is_a?(Raabro::Tree) depth, opts = 0, depth if depth.is_a?(Hash) _rs, _dg, _gn, _yl, _bl, _lg = (opts[:colors] || opts[:colours] || $stdout.tty?) ? [ "", "", "", "", "", "" ] : [ '', '', '', '', '', '' ] lc = tree.result == 1 ? _gn : _dg nc = tree.result == 1 ? _bl : _lg nc = lc if tree.name == nil sc = tree.result == 1 ? _yl : _dg str = if tree.children.size == 0 " #{sc}#{tree.string.length == 0 ? "#{_dg} >#{tree.nonstring(14).inspect[1..-2]}<" : tree.string.inspect}" else '' end print "#{_dg}t---\n" if depth == 0 #print "#{' ' * depth}" depth.times do |i| pipe = i % 3 == 0 ? ': ' : '| ' print i.even? ? "#{_dg}#{pipe} " : ' ' end print "#{lc}#{tree.result}" print " #{nc}#{tree.name.inspect} #{lc}#{tree.offset},#{tree.length}" print str print "#{_rs}\n" tree.children.each { |c| self.pp(c, depth + 1, opts) } if depth == 0 print _dg print "input ln: #{tree.input.string.length}, tree ln: #{tree.length} " print "---t\n" print _rs end end end ruby-raabro-1.1.6/raabro.gemspec000066400000000000000000000015721340621171700165660ustar00rootroot00000000000000 Gem::Specification.new do |s| s.name = 'raabro' s.version = File.read( File.expand_path('../lib/raabro.rb', __FILE__) ).match(/ VERSION *= *['"]([^'"]+)/)[1] s.platform = Gem::Platform::RUBY s.authors = [ 'John Mettraux' ] s.email = [ 'jmettraux+flor@gmail.com' ] s.homepage = 'http://github.com/floraison/raabro' #s.rubyforge_project = 'rufus' s.license = 'MIT' s.summary = 'a very dumb PEG parser library' s.description = %{ A very dumb PEG parser library, with a horrible interface. }.strip #s.files = `git ls-files`.split("\n") s.files = Dir[ 'README.{md,txt}', 'CHANGELOG.{md,txt}', 'CREDITS.{md,txt}', 'LICENSE.{md,txt}', 'Makefile', 'lib/**/*.rb', #'spec/**/*.rb', 'test/**/*.rb', "#{s.name}.gemspec", ] #s.add_runtime_dependency 'tzinfo' s.add_development_dependency 'rspec', '~> 3.7' s.require_path = 'lib' end