joker 0.0.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.markdown +5 -0
- data/README.markdown +8 -3
- data/Rakefile +5 -18
- data/VERSION +1 -1
- data/ext/joker_native/Joker.c +72 -0
- data/ext/joker_native/Joker.h +53 -0
- data/ext/joker_native/Wildcard.c +22 -0
- data/ext/joker_native/Wildcard.h +40 -0
- data/ext/joker_native/compile.c +158 -0
- data/ext/joker_native/compile.h +16 -0
- data/ext/joker_native/extconf.rb +6 -0
- data/ext/joker_native/match.c +272 -0
- data/ext/joker_native/match.h +21 -0
- data/lib/joker.rb +6 -70
- data/tasks/c_extensions.rake +29 -0
- data/tasks/jeweler.rake +28 -0
- data/tasks/rdoc.rake +9 -0
- data/tasks/shortcuts.rake +22 -0
- data/tasks/test.rake +6 -0
- data/test/c/test_compile.c +215 -0
- data/test/c/test_match.c +131 -0
- data/test/{test_joker.rb → ruby/test_joker.rb} +0 -18
- metadata +19 -3
@@ -0,0 +1,21 @@
|
|
1
|
+
#ifndef MATCH_H_GUARD
|
2
|
+
#define MATCH_H_GUARD
|
3
|
+
|
4
|
+
#include "Wildcard.h"
|
5
|
+
#include "stdbool.h"
|
6
|
+
|
7
|
+
|
8
|
+
/*
|
9
|
+
* Matches a given Wildcard against a given string and returns
|
10
|
+
* true on success and false otherwise.
|
11
|
+
*
|
12
|
+
*/
|
13
|
+
bool Wildcard_match(
|
14
|
+
Wildcard * wildcard, // The wildcard to match
|
15
|
+
const char * cstring, // The string to match against
|
16
|
+
const long int len, // The length of the string
|
17
|
+
bool casefold); // Whether to ignore character case
|
18
|
+
|
19
|
+
|
20
|
+
#endif
|
21
|
+
|
data/lib/joker.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
# Joker is a simple Wildcard implementation that works much like Regexps.
|
3
3
|
#
|
4
4
|
|
5
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'joker_native')
|
6
|
+
|
5
7
|
#
|
6
8
|
# Implements wildcards for Ruby. Modeled after the Regexp class.
|
7
9
|
#
|
@@ -54,6 +56,7 @@ class Wildcard
|
|
54
56
|
#
|
55
57
|
# Creates a new Wildcard from the given string.
|
56
58
|
# If casefold is true, the Wildcard will ignore case.
|
59
|
+
# TODO
|
57
60
|
#
|
58
61
|
def initialize( wildcard_string, casefold = false )
|
59
62
|
@source = wildcard_string
|
@@ -71,7 +74,7 @@ class Wildcard
|
|
71
74
|
# special meaning in a Wildcard.
|
72
75
|
#
|
73
76
|
def quote( string )
|
74
|
-
string.gsub(%r{[\\?*\[]}) { |char| "\\#{char}" }
|
77
|
+
string.gsub(%r{[\\?*\[\]]}) { |char| "\\#{char}" }
|
75
78
|
end
|
76
79
|
|
77
80
|
alias_method :[], :new
|
@@ -81,7 +84,7 @@ class Wildcard
|
|
81
84
|
end
|
82
85
|
|
83
86
|
def inspect
|
84
|
-
%{Wildcard[#{
|
87
|
+
%{Wildcard[#{self.source.inspect}]#{self.casefold ? 'i' : ''}}
|
85
88
|
end
|
86
89
|
|
87
90
|
#
|
@@ -94,33 +97,6 @@ class Wildcard
|
|
94
97
|
self =~ $_
|
95
98
|
end
|
96
99
|
|
97
|
-
#
|
98
|
-
# Matches the Wildcard against the given string.
|
99
|
-
#
|
100
|
-
# NOTE: Since a wildcard has to match the whole string,
|
101
|
-
# this method only returns true or false, not the position
|
102
|
-
# of the match.
|
103
|
-
#
|
104
|
-
# Wildcard['*fairy*'] =~ 'I love fairycake' #=> true
|
105
|
-
# 'I love fairycake' =~ Wildcard['*dairy*'] #=> false
|
106
|
-
#
|
107
|
-
def =~( string )
|
108
|
-
!!(@regexp =~ string)
|
109
|
-
end
|
110
|
-
|
111
|
-
#
|
112
|
-
# The case operator. Allows you to use Wildcards in case
|
113
|
-
# expressions:
|
114
|
-
#
|
115
|
-
# case 'I love fairycake'
|
116
|
-
# when Wildcard['*fairy*'] then puts 'fairy!'
|
117
|
-
# else puts 'no fairy...'
|
118
|
-
# end
|
119
|
-
#
|
120
|
-
def ===( object )
|
121
|
-
!!(@regexp =~ object)
|
122
|
-
end
|
123
|
-
|
124
100
|
#
|
125
101
|
# Compares to Wildcards for equality.
|
126
102
|
#
|
@@ -129,51 +105,11 @@ class Wildcard
|
|
129
105
|
#
|
130
106
|
def eql?( that )
|
131
107
|
return false unless that.is_a?(Wildcard)
|
132
|
-
|
108
|
+
self.source == that.source && self.casefold == that.casefold
|
133
109
|
end
|
134
110
|
|
135
111
|
alias_method :==, :eql?
|
136
112
|
alias_method :casefold?, :casefold
|
137
113
|
|
138
|
-
private
|
139
|
-
|
140
|
-
#
|
141
|
-
# Converts the wildcard string into a Regexp.
|
142
|
-
# A simple parser, I just threw it in there, no
|
143
|
-
# optimizations.
|
144
|
-
#
|
145
|
-
def compile
|
146
|
-
ptr = 0
|
147
|
-
compiled = '^'
|
148
|
-
while ptr < @source.length
|
149
|
-
snip = @source[ptr..-1]
|
150
|
-
if snip.scan(%r{^\\\\}).first
|
151
|
-
compiled << '\\\\'
|
152
|
-
ptr += 2
|
153
|
-
elsif snip.scan(%r{^\\\?}).first
|
154
|
-
compiled << '\\?'
|
155
|
-
ptr += 2
|
156
|
-
elsif snip.scan(%r{^\\\*}).first
|
157
|
-
compiled << '\\*'
|
158
|
-
ptr += 2
|
159
|
-
elsif snip.scan(%r{^\?}).first
|
160
|
-
compiled << '.'
|
161
|
-
ptr += 1
|
162
|
-
elsif snip.scan(%r{^\*}).first
|
163
|
-
compiled << '.*'
|
164
|
-
ptr += 1
|
165
|
-
elsif group = snip.scan(%r{^\[(?:\\\]|[^\]])+\]}).first
|
166
|
-
ptr += group.length
|
167
|
-
group = group[1..-2] # remove []
|
168
|
-
group = group.gsub(%r{\\\]}) { ']' }
|
169
|
-
compiled << '[' << Regexp.quote(group) << ']'
|
170
|
-
else
|
171
|
-
compiled << Regexp.quote(@source[ptr..ptr])
|
172
|
-
ptr += 1
|
173
|
-
end
|
174
|
-
end
|
175
|
-
compiled + '$'
|
176
|
-
end
|
177
|
-
|
178
114
|
end
|
179
115
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rake/extensiontask'
|
2
|
+
require 'rake/extensiontesttask'
|
3
|
+
|
4
|
+
extension_task =
|
5
|
+
Rake::ExtensionTask.new('joker_native', $gemspec) do |ext|
|
6
|
+
ext.cross_compile = true
|
7
|
+
ext.cross_platform = 'x86-mswin32'
|
8
|
+
ext.test_files = FileList['test/c/*']
|
9
|
+
end
|
10
|
+
|
11
|
+
CLEAN.include 'lib/**/*.so'
|
12
|
+
|
13
|
+
|
14
|
+
# workaround for rake-compiler which needs the gemspec to have a
|
15
|
+
# version and yaml-dump-loads the
|
16
|
+
# gemspec which leads to errors since procs can't be loaded
|
17
|
+
Rake::Task.tasks.each do |task_name|
|
18
|
+
case task_name.to_s
|
19
|
+
when /^native/
|
20
|
+
task_name.prerequisites.unshift("gemspec", "fix_rake_compiler_gemspec_dump")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
task :fix_rake_compiler_gemspec_dump do
|
25
|
+
%w{files extra_rdoc_files test_files}.each do |accessor|
|
26
|
+
$gemspec.send(accessor).instance_eval { @exclude_procs = Array.new }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
data/tasks/jeweler.rake
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
task :release do
|
3
|
+
sh "vim HISTORY.markdown"
|
4
|
+
sh "vim README.markdown"
|
5
|
+
sh "git commit -a -m 'prerelease adjustments'; true"
|
6
|
+
end
|
7
|
+
|
8
|
+
task :build => :gemspec
|
9
|
+
|
10
|
+
require 'jeweler'
|
11
|
+
jeweler_tasks = Jeweler::Tasks.new do |gem|
|
12
|
+
gem.name = 'joker'
|
13
|
+
gem.summary = 'Joker is a simple wildcard implementation that works much like Regexps'
|
14
|
+
gem.description = gem.summary
|
15
|
+
gem.email = 'karottenreibe@gmail.com'
|
16
|
+
gem.homepage = 'http://karottenreibe.github.com/joker'
|
17
|
+
gem.authors = ['Fabian Streitel']
|
18
|
+
gem.rubyforge_project = 'k-gems'
|
19
|
+
gem.extensions = FileList['ext/**/extconf.rb']
|
20
|
+
|
21
|
+
gem.files.include('lib/joker_native.*') # add native stuff
|
22
|
+
end
|
23
|
+
|
24
|
+
$gemspec = jeweler_tasks.gemspec
|
25
|
+
|
26
|
+
Jeweler::RubyforgeTasks.new
|
27
|
+
Jeweler::GemcutterTasks.new
|
28
|
+
|
data/tasks/rdoc.rake
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
desc("Build linux and windows specific gems")
|
3
|
+
task :gems do
|
4
|
+
sh "rake clean build:native"
|
5
|
+
sh "rake clean build:cross"
|
6
|
+
sh "rake clean build"
|
7
|
+
end
|
8
|
+
|
9
|
+
task "build:native" => [:no_extconf, :native, :build] do
|
10
|
+
file = "pkg/joker-#{`cat VERSION`.chomp}.gem"
|
11
|
+
mv file, "#{file.ext}-i686-linux.gem"
|
12
|
+
end
|
13
|
+
|
14
|
+
task "build:cross" => [:no_extconf, :cross, :native, :build] do
|
15
|
+
file = "pkg/joker-#{`cat VERSION`.chomp}.gem"
|
16
|
+
mv file, "#{file.ext}-x86-mingw32.gem"
|
17
|
+
end
|
18
|
+
|
19
|
+
task :no_extconf do
|
20
|
+
$gemspec.extensions = []
|
21
|
+
end
|
22
|
+
|
data/tasks/test.rake
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
#include <stdarg.h>
|
2
|
+
#include <stddef.h>
|
3
|
+
#include <stdbool.h>
|
4
|
+
#include <setjmp.h>
|
5
|
+
#include <cmockery.h>
|
6
|
+
#include <stdio.h>
|
7
|
+
#include <string.h>
|
8
|
+
#include <ruby.h>
|
9
|
+
#include "compile.h"
|
10
|
+
#include "Wildcard.h"
|
11
|
+
|
12
|
+
//
|
13
|
+
// Possible scenarios:
|
14
|
+
// * string empty
|
15
|
+
// string not empty
|
16
|
+
// * contains group
|
17
|
+
// contains fixed
|
18
|
+
// contains wildcard
|
19
|
+
// contains kleene
|
20
|
+
// * contains "**"
|
21
|
+
// * contains escape at EOS
|
22
|
+
// contains escape at Fixed
|
23
|
+
// contains escape at Group
|
24
|
+
//
|
25
|
+
|
26
|
+
|
27
|
+
static void generic_test(cstring, length, expected) // {{{1
|
28
|
+
const char * cstring;
|
29
|
+
const int length;
|
30
|
+
const char * expected;
|
31
|
+
{
|
32
|
+
Wildcard * wildcard;
|
33
|
+
int i;
|
34
|
+
|
35
|
+
wildcard = Wildcard_compile(cstring, strlen(cstring));
|
36
|
+
assert_int_not_equal( (int)NULL, (int)wildcard );
|
37
|
+
assert_int_not_equal( (int)NULL, (int)wildcard->first );
|
38
|
+
assert_int_not_equal( (int)NULL, (int)wildcard->last );
|
39
|
+
assert_int_equal( length, wildcard->length );
|
40
|
+
|
41
|
+
for (i = 0; i < length; i += 2) {
|
42
|
+
assert_int_equal((int)*(expected + i), (int)*(wildcard->first + i));
|
43
|
+
}
|
44
|
+
|
45
|
+
Wildcard_free(wildcard);
|
46
|
+
}
|
47
|
+
|
48
|
+
|
49
|
+
static void test_empty(state) // {{{1
|
50
|
+
void ** state;
|
51
|
+
{
|
52
|
+
Wildcard * wildcard;
|
53
|
+
int i;
|
54
|
+
|
55
|
+
wildcard = Wildcard_compile("", 0);
|
56
|
+
assert_int_not_equal( (int)NULL, (int)wildcard );
|
57
|
+
assert_int_equal( (int)NULL, (int)wildcard->first );
|
58
|
+
assert_int_equal( (int)NULL, (int)wildcard->last );
|
59
|
+
assert_int_equal( 0, wildcard->length );
|
60
|
+
|
61
|
+
Wildcard_free(wildcard);
|
62
|
+
}
|
63
|
+
|
64
|
+
|
65
|
+
static void test_fixed(state) // {{{1
|
66
|
+
void ** state;
|
67
|
+
{
|
68
|
+
const char expected[8] = {
|
69
|
+
Fixed, 'u',
|
70
|
+
Fixed, 'i',
|
71
|
+
Fixed, 'a',
|
72
|
+
Fixed, 'e',
|
73
|
+
};
|
74
|
+
|
75
|
+
generic_test("uiae", 8, expected);
|
76
|
+
}
|
77
|
+
|
78
|
+
|
79
|
+
static void test_group(state) // {{{1
|
80
|
+
void ** state;
|
81
|
+
{
|
82
|
+
const char expected[4] = {
|
83
|
+
Group, 'i',
|
84
|
+
Group, 'a',
|
85
|
+
};
|
86
|
+
|
87
|
+
generic_test("[ia]", 4, expected);
|
88
|
+
}
|
89
|
+
|
90
|
+
|
91
|
+
static void test_wild(state) // {{{1
|
92
|
+
void ** state;
|
93
|
+
{
|
94
|
+
const char expected[2] = {
|
95
|
+
Wild, '?',
|
96
|
+
};
|
97
|
+
|
98
|
+
generic_test("?", 2, expected);
|
99
|
+
}
|
100
|
+
|
101
|
+
|
102
|
+
static void test_kleene(state) // {{{1
|
103
|
+
void ** state;
|
104
|
+
{
|
105
|
+
const char expected[2] = {
|
106
|
+
Kleene, '*',
|
107
|
+
};
|
108
|
+
|
109
|
+
generic_test("*", 2, expected);
|
110
|
+
generic_test("**", 2, expected);
|
111
|
+
generic_test("****", 2, expected);
|
112
|
+
}
|
113
|
+
|
114
|
+
|
115
|
+
static void test_mixed(state) // {{{1
|
116
|
+
void ** state;
|
117
|
+
{
|
118
|
+
const char expected[10] = {
|
119
|
+
Fixed, 'u',
|
120
|
+
Wild, '?',
|
121
|
+
Kleene, '*',
|
122
|
+
Group, 'e',
|
123
|
+
Group, '?',
|
124
|
+
};
|
125
|
+
|
126
|
+
generic_test("u?*[e?]", 10, expected);
|
127
|
+
}
|
128
|
+
|
129
|
+
|
130
|
+
static void test_escaping(state) // {{{1
|
131
|
+
void ** state;
|
132
|
+
{
|
133
|
+
ruby_init(); // since we will be calling rb_warning
|
134
|
+
|
135
|
+
// escaping in fixed parts
|
136
|
+
{
|
137
|
+
char expected[4] = {
|
138
|
+
Fixed, '?',
|
139
|
+
Fixed, 'a',
|
140
|
+
};
|
141
|
+
|
142
|
+
// escaping of escapable characters
|
143
|
+
generic_test("\\?", 2, expected);
|
144
|
+
|
145
|
+
expected[1] = '*';
|
146
|
+
generic_test("\\*", 2, expected);
|
147
|
+
|
148
|
+
expected[1] = '[';
|
149
|
+
generic_test("\\[", 2, expected);
|
150
|
+
|
151
|
+
expected[1] = ']';
|
152
|
+
generic_test("\\]", 2, expected);
|
153
|
+
|
154
|
+
expected[1] = '\\';
|
155
|
+
generic_test("\\\\", 2, expected);
|
156
|
+
|
157
|
+
// special warning-generating escaping mechanism
|
158
|
+
expected[1] = ']';
|
159
|
+
generic_test("]", 2, expected);
|
160
|
+
|
161
|
+
// escaping of non-escapable characters
|
162
|
+
expected[1] = '\\';
|
163
|
+
generic_test("\\a", 4, expected);
|
164
|
+
}
|
165
|
+
// escaping in group parts
|
166
|
+
{
|
167
|
+
char expected[4] = {
|
168
|
+
Group, '[',
|
169
|
+
Group, 'a',
|
170
|
+
};
|
171
|
+
|
172
|
+
// escaping of escapable characters
|
173
|
+
generic_test("[\\[]", 2, expected);
|
174
|
+
|
175
|
+
expected[1] = ']';
|
176
|
+
generic_test("[\\]]", 2, expected);
|
177
|
+
|
178
|
+
expected[1] = '\\';
|
179
|
+
generic_test("[\\\\]", 2, expected);
|
180
|
+
|
181
|
+
// special warning-generating escaping mechanism
|
182
|
+
expected[1] = '[';
|
183
|
+
generic_test("[[]", 2, expected);
|
184
|
+
|
185
|
+
// escaping of non-escapable characters
|
186
|
+
expected[1] = '\\';
|
187
|
+
generic_test("[\\a]", 4, expected);
|
188
|
+
|
189
|
+
expected[3] = '?';
|
190
|
+
generic_test("[\\?]", 4, expected);
|
191
|
+
}
|
192
|
+
// escaping at EOS
|
193
|
+
{
|
194
|
+
char expected[4] = {
|
195
|
+
Fixed, '\\',
|
196
|
+
};
|
197
|
+
|
198
|
+
generic_test("\\", 2, expected);
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
|
203
|
+
int main() { // {{{1
|
204
|
+
const UnitTest tests[] = {
|
205
|
+
unit_test(test_empty),
|
206
|
+
unit_test(test_fixed),
|
207
|
+
unit_test(test_group),
|
208
|
+
unit_test(test_wild),
|
209
|
+
unit_test(test_kleene),
|
210
|
+
unit_test(test_mixed),
|
211
|
+
unit_test(test_escaping),
|
212
|
+
};
|
213
|
+
return run_tests(tests);
|
214
|
+
}
|
215
|
+
|