rrrex 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2011 Ian Young
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,57 @@
1
+ Really Readable Regular Expressions
2
+ ================
3
+
4
+ rrrex is a new syntax for regular expressions. It trades compactness for readability by real humans, and picks up a couple nice perks
5
+ along the way.
6
+
7
+ Crash Course
8
+ ============
9
+
10
+ "The string you'd like to search".rmatch? { "string" }
11
+
12
+ "abc".rmatch? { "ab" + "c" }
13
+
14
+ "abc".rmatch? { "xyz".or "abc" }
15
+
16
+ You don't have to worry about escaping special characters in your strings any more:
17
+ "{symbols} .*&+ [galore]".rmatch? { "{symbols} .*&+ [galore]" }
18
+
19
+ You can combine operations and get the expected precedence:
20
+ "abc".rmatch? { "ab" + ( "z".or "c" ) }
21
+
22
+ Repetition:
23
+ "aaabc".rmatch? { 1.or_more "a" }
24
+ "aaabc".rmatch? { 5.or_less "a" }
25
+ "aaabc".rmatch? { 3.exactly "a" }
26
+ "aaabc".rmatch? { (1..5).of "a" }
27
+ These are equivalent:
28
+ "aaabc".rmatch? { 0.or_more "a" }
29
+ "aaabc".rmatch? { any "a" }
30
+ And these are equivalent:
31
+ "aaabc".rmatch? { 1.or_more "a" }
32
+ "aaabc".rmatch? { some "a" }
33
+
34
+ Special character sets:
35
+ "abc1234.&*".rmatch? { 10.exactly any_char }
36
+ "abc1234".rmatch? { 3.exactly letter }
37
+ "abc1234".rmatch? { 4.exactly digit }
38
+ "abc_123".rmatch? { 7.exactly word_char }
39
+ " ".rmatch? { whitespace }
40
+ Or create your own:
41
+ "abc".rmatch? { 3.exactly "a".."c" }
42
+
43
+ Two types of negation:
44
+ "x".rmatch? { word_char.not "x" } # => nil
45
+ "y".rmatch? { word_char.not "x" }
46
+ "x".rmatch? { _not "x" } # => nil
47
+ "y".rmatch? { _not "x" }
48
+
49
+ Groups:
50
+ match = "1234567890 Central Processing".rmatch? do
51
+ group :serial do
52
+ some digit
53
+ end + some whitespace + group :source do
54
+ any any_char
55
+ end
56
+ end
57
+ match
@@ -0,0 +1,79 @@
1
+ require "rubygems"
2
+ require "rake/gempackagetask"
3
+ require "rake/rdoctask"
4
+
5
+ require "rake/testtask"
6
+ Rake::TestTask.new do |t|
7
+ t.libs << "test"
8
+ t.test_files = FileList["test/**/*_test.rb"]
9
+ t.verbose = true
10
+ end
11
+
12
+
13
+ task :default => ["test"]
14
+
15
+ spec = Gem::Specification.new do |s|
16
+
17
+ s.name = "rrrex"
18
+ s.version = "0.1.0"
19
+ s.author = "Ian Young"
20
+ s.email = "ian.greenleaf+github@gmail.com"
21
+
22
+ s.summary = "Really Readable Regexps"
23
+ s.description = <<-EOF
24
+ Rrrex is a new syntax for regular expressions.
25
+ Less compact, but human-readable. By regular humans.
26
+ EOF
27
+
28
+ s.files = Dir[ 'lib/**/*.rb', 'test/**/*', '[A-Z]*' ]
29
+
30
+ s.has_rdoc = true
31
+ s.extra_rdoc_files = %w(README.md)
32
+ s.rdoc_options = %w(--main README.md)
33
+
34
+ s.add_development_dependency("mocha")
35
+ end
36
+
37
+ Rake::GemPackageTask.new(spec) do |pkg|
38
+ pkg.gem_spec = spec
39
+ end
40
+
41
+ desc "Build the gemspec file #{spec.name}.gemspec"
42
+ task :gemspec do
43
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
44
+ File.open(file, "w") {|f| f << spec.to_ruby }
45
+ end
46
+
47
+ task :package => :gemspec
48
+
49
+ Rake::RDocTask.new do |rd|
50
+ rd.main = "README.md"
51
+ rd.rdoc_files.include("README.md", "lib/**/*.rb")
52
+ rd.rdoc_dir = "rdoc"
53
+ end
54
+
55
+ desc 'Clear out RDoc and generated packages'
56
+ task :clean => [:clobber_rdoc, :clobber_package] do
57
+ rm "#{spec.name}.gemspec"
58
+ end
59
+
60
+ desc 'Tag the repository in git with gem version number'
61
+ task :tag => [:gemspec, :package] do
62
+ if `git diff --cached`.empty?
63
+ if `git tag`.split("\n").include?("v#{spec.version}")
64
+ raise "Version #{spec.version} has already been released"
65
+ end
66
+ `git add #{File.expand_path("../#{spec.name}.gemspec", __FILE__)}`
67
+ `git commit -m "Released version #{spec.version}"`
68
+ `git tag v#{spec.version}`
69
+ `git push --tags`
70
+ `git push`
71
+ else
72
+ raise "Unstaged changes still waiting to be committed"
73
+ end
74
+ end
75
+
76
+ desc "Tag and publish the gem to rubygems.org"
77
+ task :publish => :tag do
78
+ `gem push pkg/#{spec.name}-#{spec.version}.gem`
79
+ end
@@ -0,0 +1,23 @@
1
+ module MethodMissingConversion
2
+ def self.included receiver
3
+ receiver.extend ClassMethods
4
+ end
5
+
6
+ module ClassMethods
7
+ def sends_methods_to methods, convert_to
8
+ define_method :method_missing_helper do |name, args, block|
9
+ if methods.include? name
10
+ convert_to.new( self ).send name, *args
11
+ else
12
+ method_missing_without_regexp name, *args, &block
13
+ end
14
+ end
15
+ alias_method :method_missing_without_regexp, :method_missing
16
+ alias_method :method_missing, :method_missing_with_regexp
17
+ end
18
+ end
19
+
20
+ def method_missing_with_regexp( name, *args, &block )
21
+ method_missing_helper name, args, block
22
+ end
23
+ end
@@ -0,0 +1 @@
1
+ require 'rrrex/core_ext'
@@ -0,0 +1,16 @@
1
+ require 'rrrex/composite_match'
2
+ module Rrrex
3
+ module CompositeMatch
4
+ def initialize(*args)
5
+ @atoms = args.collect do |a|
6
+ input a
7
+ end
8
+ end
9
+
10
+ def group_names
11
+ @atoms.inject( [] ) do |memo,a|
12
+ memo + a.group_names
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ require 'rrrex/match'
2
+ require 'rrrex/composite_match'
3
+ module Rrrex
4
+ class ConcatMatch < Match
5
+ include CompositeMatch
6
+ def to_regexp_string
7
+ wrap @atoms.map {|p| p.to_regexp_string }.join ""
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ require 'rrrex/core_ext/fixnum'
2
+ require 'rrrex/core_ext/range'
3
+ require 'rrrex/core_ext/string'
@@ -0,0 +1,13 @@
1
+ class Fixnum
2
+ def exactly( atom )
3
+ Rrrex::NumberMatch.new atom, self, self
4
+ end
5
+
6
+ def or_more( atom )
7
+ Rrrex::NumberMatch.new atom, self, nil
8
+ end
9
+
10
+ def or_less( atom )
11
+ Rrrex::NumberMatch.new atom, nil, self
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ require 'method_missing_conversion'
2
+ require 'rrrex/range_match'
3
+ class Range
4
+ include MethodMissingConversion
5
+ sends_methods_to [ :or, :+ ], Rrrex::RangeMatch
6
+
7
+ def of( atom )
8
+ Rrrex::NumberMatch.new atom, self.begin, self.end
9
+ end
10
+ end
@@ -0,0 +1,23 @@
1
+ require 'method_missing_conversion'
2
+ require 'rrrex/string_match'
3
+ require 'rrrex/match'
4
+ require 'rrrex/dsl_context'
5
+ class String
6
+ include MethodMissingConversion
7
+ sends_methods_to [ :or ], Rrrex::StringMatch
8
+
9
+ def plus_with_regexp( str2 )
10
+ if str2.kind_of? Rrrex::Match
11
+ Rrrex::StringMatch.new( self ) + str2
12
+ else
13
+ self.plus_without_regexp str2
14
+ end
15
+ end
16
+ alias_method :plus_without_regexp, :+
17
+ alias_method :+, :plus_with_regexp
18
+
19
+ def rmatch?( &block )
20
+ pattern = Rrrex::Match.convert Rrrex::DslContext.module_exec &block
21
+ pattern.match self
22
+ end
23
+ end
@@ -0,0 +1,47 @@
1
+ require 'rrrex/unescaped_string_match'
2
+ require 'rrrex/group_match'
3
+ require 'rrrex/number_match'
4
+ module Rrrex
5
+ module DslContext
6
+
7
+ WORD_CHAR = '\w'
8
+ DIGIT = '\d'
9
+ WHITESPACE = '\s'
10
+ LETTER = '[[:alpha:]]'
11
+ ANY_CHAR = '.'
12
+
13
+ constants.each do |const|
14
+ ( class << self; self; end ).instance_eval do
15
+ define_method const.downcase.to_sym do
16
+ UnescapedStringMatch.new const_get( const )
17
+ end
18
+ end
19
+ end
20
+
21
+ def self.any r
22
+ NumberMatch.new r, 0, nil
23
+ end
24
+
25
+ def self.some r
26
+ NumberMatch.new r, 1, nil
27
+ end
28
+
29
+ def self._not r
30
+ any_char.not r
31
+ end
32
+
33
+ def self.group( name_or_atom=nil, atom=nil, &block )
34
+ if name_or_atom.kind_of? Symbol
35
+ name = name_or_atom
36
+ atom = atom || DslContext.module_exec( &block )
37
+ elsif name_or_atom.kind_of? Hash
38
+ name = name_or_atom.keys.first
39
+ atom = name_or_atom[ name ]
40
+ else
41
+ name = nil
42
+ atom = name_or_atom
43
+ end
44
+ GroupMatch.new atom, name
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,20 @@
1
+ require 'rrrex/match'
2
+ require 'rrrex/single_atom_match'
3
+ module Rrrex
4
+ class GroupMatch < Match
5
+ include SingleAtomMatch
6
+ def initialize atom, name
7
+ @name = name
8
+ super atom
9
+ end
10
+
11
+ def to_regexp_string
12
+ "(#{atom.to_regexp_string})"
13
+ end
14
+
15
+ def group_names
16
+ names = @atom.group_names || []
17
+ names.unshift @name
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,52 @@
1
+ module Rrrex
2
+ class Match
3
+ end
4
+ end
5
+ require 'rrrex/regexp'
6
+ require 'rrrex/string_match'
7
+ require 'rrrex/range_match'
8
+ require 'rrrex/or_match'
9
+ require 'rrrex/concat_match'
10
+ require 'rrrex/not_match'
11
+ module Rrrex
12
+ class Match
13
+ def self.convert( atom )
14
+ if atom.kind_of? Match
15
+ atom
16
+ elsif atom.kind_of? Range
17
+ RangeMatch.new atom
18
+ else
19
+ StringMatch.new atom
20
+ end
21
+ end
22
+
23
+ def wrap( s )
24
+ "(?:#{s})"
25
+ end
26
+
27
+ def match(str)
28
+ Regexp.new( self ).match str
29
+ end
30
+
31
+ def or(atom)
32
+ OrMatch.new self, atom
33
+ end
34
+
35
+ def +(p)
36
+ ConcatMatch.new self, p
37
+ end
38
+
39
+ def not(atom)
40
+ ConcatMatch.new NotMatch.new( atom ), self
41
+ end
42
+
43
+ def group_names
44
+ []
45
+ end
46
+
47
+ protected
48
+ def input( atom )
49
+ self.class.convert atom
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,36 @@
1
+ module Rrrex
2
+ class MatchData
3
+ def initialize( atom, match_data )
4
+ @atom = atom
5
+ @match_data = match_data
6
+ end
7
+
8
+ def []( i )
9
+ if i.is_a? Symbol
10
+ named_groups[ i ]
11
+ else
12
+ @match_data[ i ]
13
+ end
14
+ end
15
+
16
+ def named_groups
17
+ @named_groups ||=
18
+ begin
19
+ result = {}
20
+ names = @atom.group_names
21
+ names.each_index do |i|
22
+ result[ names[ i ] ] = @match_data[ i + 1 ]
23
+ end
24
+ result
25
+ end
26
+ end
27
+
28
+ def to_a
29
+ @match_data.to_a
30
+ end
31
+
32
+ def method_missing( name, *args )
33
+ @match_data.send name, *args
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,10 @@
1
+ require 'rrrex/match'
2
+ require 'rrrex/single_atom_match'
3
+ module Rrrex
4
+ class NotMatch < Match
5
+ include SingleAtomMatch
6
+ def to_regexp_string
7
+ "(?!#{atom.to_regexp_string})"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ require 'rrrex/match'
2
+ require 'rrrex/single_atom_match'
3
+ module Rrrex
4
+ class NumberMatch < Match
5
+ include SingleAtomMatch
6
+ def initialize( a, min, max )
7
+ super a
8
+ @min = min
9
+ @max = max
10
+ end
11
+
12
+ def to_regexp_string
13
+ # Subtle: when nil, we want min to convert to 0, but max to convert to ""
14
+ wrap atom.to_regexp_string + "{#{@min.to_i},#{@max}}"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ require 'rrrex/match'
2
+ require 'rrrex/composite_match'
3
+ module Rrrex
4
+ class OrMatch < Match
5
+ include CompositeMatch
6
+ def to_regexp_string
7
+ wrap @atoms.map {|p| p.to_regexp_string }.join "|"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ require 'rrrex/match'
2
+ require 'rrrex/single_atom_match'
3
+ module Rrrex
4
+ class RangeMatch < Match
5
+ include SingleAtomMatch
6
+ def initialize( range )
7
+ @range = range
8
+ end
9
+
10
+ def to_regexp_string
11
+ wrap "[#{@range.first}-#{@range.last}]"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ require 'rrrex/match_data'
2
+ module Rrrex
3
+ class Regexp < ::Regexp
4
+ def initialize( r )
5
+ super r.to_regexp_string
6
+ @atom = r
7
+ end
8
+
9
+ def match( str )
10
+ result = super( str )
11
+ MatchData.new @atom, result unless result.nil?
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,26 @@
1
+ module Rrrex
2
+ module SingleAtomMatch
3
+
4
+ attr_reader :atom
5
+
6
+ def initialize( a )
7
+ self.atom = a
8
+ end
9
+
10
+ def atom=( a )
11
+ @atom = input a
12
+ end
13
+
14
+ def to_regexp_string
15
+ wrap atom.to_regexp_string
16
+ end
17
+
18
+ def group_names
19
+ if @atom.respond_to? :group_names
20
+ @atom.group_names if @atom.respond_to? :group_names
21
+ else
22
+ []
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,14 @@
1
+ require 'rrrex/match'
2
+ require 'rrrex/single_atom_match'
3
+ module Rrrex
4
+ class StringMatch < Match
5
+ include SingleAtomMatch
6
+ def initialize( a )
7
+ @atom = a
8
+ end
9
+
10
+ def to_regexp_string
11
+ wrap Regexp.escape atom
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ require 'rrrex/string_match'
2
+ module Rrrex
3
+ class UnescapedStringMatch < StringMatch
4
+ def to_regexp_string
5
+ wrap atom
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,230 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'mocha'
4
+ require 'rrrex'
5
+
6
+ class MatchTest < Test::Unit::TestCase
7
+
8
+ def test_match_simple_string
9
+ [ ["a", "a"], ["bc", "babb bc"], ["úñícode", "i like úñícode"] ].each do |pattern,string|
10
+ m = Rrrex::StringMatch.new pattern
11
+ assert m.match string
12
+ end
13
+ end
14
+
15
+ def test_dont_match_simple_string
16
+ [ ["a", "b"], ["bc", "bac def"], ["úñícode", "i like unicode"] ].each do |pattern,string|
17
+ m = Rrrex::StringMatch.new pattern
18
+ assert_nil m.match string
19
+ end
20
+ end
21
+
22
+ def test_inline_match_triggers_module
23
+ rxp_stub = stub "Rrrex::Match", { :match => true }
24
+ Rrrex::StringMatch.expects(:new).with("oo").returns(rxp_stub)
25
+ "foobar".rmatch? do "oo" end
26
+ end
27
+
28
+ def test_match_simple_string_inline
29
+ assert_match "oo", "foobar" do "oo" end
30
+ end
31
+
32
+ def test_dont_match_simple_string_inline
33
+ assert_no_match "foobar" do "xy" end
34
+ end
35
+
36
+ def test_special_characters_escaped_in_string
37
+ assert_no_match "foobar" do "o+" end
38
+ assert_match "o+", "+hello+" do "o+" end
39
+ end
40
+
41
+ def test_match_or
42
+ assert_match "foo", "foobar" do "xy".or "foo" end
43
+ end
44
+
45
+ def test_match_any
46
+ assert_match "x", "x" do any "x" end
47
+ assert_match "aaaaa", "aaaaab" do any "a" end
48
+ assert_match "abab", "ababaab" do any "ab" end
49
+ assert_match "", "xxxx" do any "y" end
50
+ end
51
+
52
+ def test_match_some
53
+ assert_match "x", "x" do some "x" end
54
+ assert_match "aaaaa", "aaaaab" do some "a" end
55
+ assert_match "abab", "ababaab" do some "ab" end
56
+ assert_no_match "xxxx" do some "y" end
57
+ end
58
+
59
+ def test_match_not
60
+ assert_match "x", "x" do _not "y" end
61
+ assert_no_match "x" do _not "x" end
62
+ assert_match "cdef", "abcdefab" do some _not( "a".or "b" ) end
63
+ assert_match "defa", "abcdefaab" do some _not( "ab".or "b".or "c" ) end
64
+ end
65
+
66
+ def test_match_lookahead_not
67
+ assert_match "y", "xy" do letter.not "x" end
68
+ assert_match "1234", "123456789" do some( digit.not "5" ) end
69
+ assert_match "1234", "1234abc" do some( digit.not "5" ) end
70
+ assert_match "abb", "abcabb" do ( "ab" + letter ).not "abc" end
71
+ assert_no_match "abbbc" do "a" + ( (1..6).of "a" ).not( "aaa" ) + "c" end
72
+ assert_match "21", "123321" do 2.or_more digit.not( "12".or "3" ) end
73
+ end
74
+
75
+ def test_match_concat
76
+ assert_match "foobar", "foobar" do "foo" + "bar" end
77
+ assert_match "foobarbaz", "foobarbazbar" do "foo" + "bar" + "baz" end
78
+ end
79
+
80
+ def test_match_num_exactly
81
+ assert_match "oo", "foobar" do 2.exactly "o" end
82
+ assert_match "oo", "foooobar" do 2.exactly "o" end
83
+ assert_no_match "foobar" do 3.exactly "o" end
84
+ assert_match "foobar", "foobar" do "f" + 2.exactly( "o" ) + "bar" end
85
+ assert_no_match "foobar" do "f" + 1.exactly( "o" ) + "bar" end
86
+ end
87
+
88
+ def test_match_num_or_more
89
+ assert_match "oo", "foobar" do 2.or_more "o" end
90
+ assert_match "foooo", "foooobar" do "f" + 2.or_more( "o" ) end
91
+ assert_no_match "foobar" do 3.or_more "o" end
92
+ end
93
+
94
+ def test_match_num_or_less
95
+ assert_match "xx", "xx" do 2.or_less "x" end
96
+ assert_match "xx", "xxxxxxxx" do 2.or_less "x" end
97
+ assert_match "xx", "xx" do 100.or_less "x" end
98
+ assert_match "foobar", "foobar" do "f" + 2.or_less( "o" ) + "bar" end
99
+ assert_match "fbar", "fbar" do "f" + 2.or_less( "o" ) + "bar" end
100
+ assert_no_match "foooobar" do "f" + 3.or_less( "o" ) + "bar" end
101
+ assert_match "", "xxxxx" do 3.or_less "y" end
102
+ end
103
+
104
+ def test_range_of_matches
105
+ assert_match "xx", "xx" do (2..4).of "x" end
106
+ assert_match "xxx", "xxx" do (2..100).of "x" end
107
+ assert_match "xxxx", "xxxxxxxx" do (2..4).of "x" end
108
+ assert_no_match "foobar" do "f" + (3..4).of( "o" ) + "bar" end
109
+ assert_no_match "foooooooobar" do "f" + (3..4).of( "o" ) + "bar" end
110
+ assert_no_match "xxxx" do (1..100).of( "y" ) + "bar" end
111
+ end
112
+
113
+ def test_char_range
114
+ assert_match "b", "b" do "a".."c" end
115
+ assert_no_match "b" do "A".."C" end
116
+ assert_match "abc", "abcdefg" do 1.or_more "a".."c" end
117
+ assert_match "123", "123456789" do (1..4).of 1..3 end
118
+ assert_match "x8", "ax87" do 1.or_more( ("q".."z").or(8..9) ) end
119
+ assert_match "az", "az" do ("a".."c") + ("w".."z") end
120
+ end
121
+
122
+ def test_grouping
123
+ assert_match "foobar", "foobar" do "foo" + ( "xyz".or "bar" ) end
124
+ assert_match "bar", "foobar" do ( "xyz" + "foo" ).or "bar" end
125
+ assert_match "fo", "foobar" do ( "xyz" + "foo" ).or( "xyz".or "fo" ).or( "foo" + "xyz" ) end
126
+ end
127
+
128
+ def test_dont_add_extra_backreferences
129
+ mdata = "foobar".rmatch? do "foo" + ( "xyz".or "bar" ) end
130
+ assert_equal 1, mdata.length
131
+ end
132
+
133
+ def test_any_characters
134
+ assert_match "f", "foobar" do any_char end
135
+ chars = "#\t?/\<>.,;:\"'!@\#$%^&*()[]{} bar"
136
+ assert_match chars, chars do some any_char end
137
+ assert_no_match "\n" do any_char end
138
+ end
139
+
140
+ def test_word_characters
141
+ assert_match "f", "foobar" do word_char end
142
+ assert_match "foo_bar2", "### foo_bar2 baz bar" do some word_char end
143
+ assert_no_match '?/\<>.,;:"\'!@#$%^&*()[]{}' do word_char end
144
+ assert_no_match 'a,b,c,d' do 2.or_more word_char end
145
+ end
146
+
147
+ def test_digit_characters
148
+ assert_match "1", "12345" do digit end
149
+ assert_match "654321", "### abc654321baz123" do some digit end
150
+ assert_no_match 'abc_DEF *&".' do digit end
151
+ assert_no_match '1,2,3' do 2.or_more digit end
152
+ end
153
+
154
+ def test_letter_characters
155
+ assert_match "f", "foobar" do letter end
156
+ assert_match "foo", "### foo_bar2 baz bar" do some letter end
157
+ assert_no_match '?/."()123456_' do letter end
158
+ assert_no_match 'a1b2c3' do 2.or_more letter end
159
+ end
160
+
161
+ def test_whitespace_characters
162
+ assert_match " ", " " do whitespace end
163
+ assert_match " \t ", "### \t baz bar" do some whitespace end
164
+ assert_no_match 'abc_123-+=().?!' do whitespace end
165
+ assert_no_match 'a b c d' do 2.or_more whitespace end
166
+ end
167
+
168
+ def test_numeric_group
169
+ assert_match_backreferences ["a", "a"], "abc" do group "a" end
170
+ assert_match_backreferences ["ab", "a", "b"], "abc" do group( "a" ) + group( "b" ) end
171
+ assert_match_backreferences ["ab", "ab", "b"], "abc" do group( "a" + group( "b" ) ) end
172
+ assert_match_backreferences ["abcde", "abcde"], "abcde" do group( any word_char ) end
173
+ assert_match_backreferences ["abcde", "e"], "abcde" do any group( word_char ) end
174
+ assert_match_backreferences ["a", "a", nil], "abc" do group( "a" ).or group( "b" ) end
175
+ end
176
+
177
+ def test_named_groups
178
+ assert_match_named_groups( { :my_a => "a" }, "abc" ) do group :my_a, "a" end
179
+ assert_match_named_groups( { :my_b => "b" }, "abc" ) do "a" + group( :my_b, "b" ) + "c" end
180
+ assert_match_named_groups( { :a => "a", :b => nil }, "abc" ) do group( :a, "a" ).or group( :b, "b" ) end
181
+ assert_match_named_groups( { :a => "ab", :b => "ab" }, "abc" ) do group( :a, group( :b, "ab" ) ) end
182
+ assert_match_named_groups( { :a => "ab", :b => "b" }, "abc" ) do group( :a, "a" + group( :b, "b" ) ) end
183
+ assert_match_named_groups( { :word => "abcde" }, "abcde" ) do group( :word, any( word_char ) ) end
184
+ assert_match_named_groups( { :letter => "e" }, "abcde" ) do any group( :letter, word_char ) end
185
+ end
186
+
187
+ def test_named_groups_block_syntax
188
+ assert_match_named_groups( { :my_a => "a" }, "abc" ) do group :my_a do "a" end end
189
+ assert_match_named_groups( { :my_a => "a" }, "abc" ) do group( :my_a ) { "a" } end
190
+ assert_match_named_groups( { :full_match => "abc", :last_part => "bc" }, "abc" ) do
191
+ group :full_match do
192
+ "a" + group( :last_part ) do
193
+ "bz".or "bc".or "b"
194
+ end
195
+ end
196
+ end
197
+ end
198
+
199
+ def test_named_groups_cached
200
+ assert( matches = "a".rmatch? do group :a, "a" end )
201
+ Rrrex::GroupMatch.any_instance.expects( :group_names ).times( 1 ).returns( [] )
202
+ matches[ :a ]
203
+ matches[ :a ]
204
+ matches.named_groups
205
+ matches.named_groups
206
+ end
207
+
208
+ def assert_no_match( string, &block )
209
+ assert_nil( string.rmatch?( &block ) )
210
+ end
211
+
212
+ def assert_match( expected, string, &block )
213
+ assert( matches = string.rmatch?( &block ) )
214
+ assert_equal expected, matches[0]
215
+ end
216
+
217
+ def assert_match_backreferences( expected, string, &block )
218
+ assert( matches = string.rmatch?( &block ) )
219
+ assert_equal expected, matches.to_a
220
+ end
221
+
222
+ def assert_match_named_groups( expected, string, &block )
223
+ assert( matches = string.rmatch?( &block ) )
224
+ assert_equal expected, matches.named_groups
225
+ expected.each do |k,v|
226
+ assert_equal v, matches[ k ]
227
+ end
228
+ end
229
+
230
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rrrex
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Ian Young
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-03-11 00:00:00 -08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: mocha
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ description: " Rrrex is a new syntax for regular expressions.\n Less compact, but human-readable. By regular humans.\n"
36
+ email: ian.greenleaf+github@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.md
43
+ files:
44
+ - lib/rrrex.rb
45
+ - lib/rrrex/match_data.rb
46
+ - lib/rrrex/or_match.rb
47
+ - lib/rrrex/single_atom_match.rb
48
+ - lib/rrrex/regexp.rb
49
+ - lib/rrrex/dsl_context.rb
50
+ - lib/rrrex/match.rb
51
+ - lib/rrrex/not_match.rb
52
+ - lib/rrrex/group_match.rb
53
+ - lib/rrrex/number_match.rb
54
+ - lib/rrrex/core_ext.rb
55
+ - lib/rrrex/range_match.rb
56
+ - lib/rrrex/unescaped_string_match.rb
57
+ - lib/rrrex/string_match.rb
58
+ - lib/rrrex/core_ext/string.rb
59
+ - lib/rrrex/core_ext/fixnum.rb
60
+ - lib/rrrex/core_ext/range.rb
61
+ - lib/rrrex/composite_match.rb
62
+ - lib/rrrex/concat_match.rb
63
+ - lib/method_missing_conversion.rb
64
+ - test/match_test.rb
65
+ - LICENSE
66
+ - Rakefile
67
+ - README.md
68
+ has_rdoc: true
69
+ homepage:
70
+ licenses: []
71
+
72
+ post_install_message:
73
+ rdoc_options:
74
+ - --main
75
+ - README.md
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ hash: 3
84
+ segments:
85
+ - 0
86
+ version: "0"
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 3
93
+ segments:
94
+ - 0
95
+ version: "0"
96
+ requirements: []
97
+
98
+ rubyforge_project:
99
+ rubygems_version: 1.3.7
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: Really Readable Regexps
103
+ test_files: []
104
+