rrrex 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +21 -0
- data/README.md +57 -0
- data/Rakefile +79 -0
- data/lib/method_missing_conversion.rb +23 -0
- data/lib/rrrex.rb +1 -0
- data/lib/rrrex/composite_match.rb +16 -0
- data/lib/rrrex/concat_match.rb +10 -0
- data/lib/rrrex/core_ext.rb +3 -0
- data/lib/rrrex/core_ext/fixnum.rb +13 -0
- data/lib/rrrex/core_ext/range.rb +10 -0
- data/lib/rrrex/core_ext/string.rb +23 -0
- data/lib/rrrex/dsl_context.rb +47 -0
- data/lib/rrrex/group_match.rb +20 -0
- data/lib/rrrex/match.rb +52 -0
- data/lib/rrrex/match_data.rb +36 -0
- data/lib/rrrex/not_match.rb +10 -0
- data/lib/rrrex/number_match.rb +17 -0
- data/lib/rrrex/or_match.rb +10 -0
- data/lib/rrrex/range_match.rb +14 -0
- data/lib/rrrex/regexp.rb +14 -0
- data/lib/rrrex/single_atom_match.rb +26 -0
- data/lib/rrrex/string_match.rb +14 -0
- data/lib/rrrex/unescaped_string_match.rb +8 -0
- data/test/match_test.rb +230 -0
- metadata +104 -0
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.
|
data/README.md
ADDED
@@ -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
|
data/Rakefile
ADDED
@@ -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
|
data/lib/rrrex.rb
ADDED
@@ -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,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
|
data/lib/rrrex/match.rb
ADDED
@@ -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,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,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
|
data/lib/rrrex/regexp.rb
ADDED
@@ -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
|
data/test/match_test.rb
ADDED
@@ -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
|
+
|