filtri 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
19
+ Gemfile.lock
20
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,15 @@
1
+ Copyright (c) 2013 Karl L <karl@ninjacontrol.com>
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to
8
+ the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
13
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
14
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
15
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,63 @@
1
+ Filtri
2
+ ======
3
+
4
+ Filtri is a tiny DSL for text substitution.
5
+
6
+ ## Installation
7
+
8
+ $ gem install filtri
9
+
10
+ ## Usage
11
+
12
+ Define a set of substitution rules using `rule` and apply to an input string:
13
+
14
+ in_str = "foobazbarfoo"
15
+ expected = "barbugbarbar"
16
+
17
+ f = filtri do
18
+ rule /fo+/ => "bar"
19
+ rule "baz" => "bug"
20
+ end
21
+
22
+ f.apply("foobazbarfoo")
23
+ => "barbugbarbar"
24
+
25
+ Rules can also be read from a file:
26
+
27
+ File `foo.rules`:
28
+
29
+ # this is a comment
30
+ rule /fo+bar/ => "bazbag"
31
+ rule /(a)+/ => '!!\1!!'
32
+ rule /(b)+/ => '**\1**'
33
+
34
+ Load with `Filtri::load`:
35
+
36
+ Filtri.load("foo.rules").apply("foobarfoobar")
37
+ => "**b**!!a!!z**b**!!a!!g**b**!!a!!z**b**!!a!!g"
38
+
39
+ ## Version information
40
+
41
+ * 0.0.1 - Initial version
42
+
43
+ ## License
44
+
45
+ Copyright (c) 2013 Karl L <karl@ninjacontrol.com>
46
+
47
+ MIT License
48
+
49
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the
50
+ "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish,
51
+ distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to
52
+ the following conditions:
53
+
54
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
55
+
56
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
57
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
58
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
59
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
60
+
61
+ ## Author
62
+
63
+ Karl L, <karl@ninjacontrol.com>
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:test)
5
+
data/filtri.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'filtri/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+
8
+ spec.name = "filtri"
9
+ spec.version = Filtri::VERSION
10
+ spec.authors = ["karl l"]
11
+ spec.email = ["karl@ninjacontrol.com"]
12
+ spec.date = '2013-04-01'
13
+ spec.summary = "A tiny DSL for filtering strings"
14
+ spec.description = "Filtri is a little DSL that simplifies the work of applying multiple substitution rules to a string."
15
+ spec.homepage = "https://github.com/karlll/filtri/"
16
+ spec.license = "MIT"
17
+
18
+ spec.files = `git ls-files`.split($/)
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ spec.add_runtime_dependency "docile"
27
+ spec.add_runtime_dependency "str2hash"
28
+ spec.add_runtime_dependency "to_regexp"
29
+
30
+ end
31
+
data/lib/filtri.rb ADDED
@@ -0,0 +1,183 @@
1
+ require 'docile'
2
+ require 'pp'
3
+ require 'to_regexp'
4
+ require 'str2hash'
5
+
6
+ # Filtri DSL
7
+ # @author karl l <karl@ninjacontrol.com>
8
+ class Filtri
9
+
10
+ # The rules
11
+ attr_reader :rules
12
+ # The meta rules
13
+ attr_reader :meta_rules
14
+
15
+ # @private
16
+ RULES = [:rule, :meta]
17
+
18
+
19
+
20
+ def initialize
21
+ @rules = []
22
+ @meta_rules = []
23
+ @passes = 1
24
+ end
25
+
26
+ # Add a filtering rule
27
+ # @param [Hash{Regexp => String},Hash{String => String}] rule_hash
28
+ def rule(rule_hash)
29
+ add_rule(@rules, rule_hash)
30
+ end
31
+
32
+ # Add a meta rule
33
+ # @param [Hash{Regexp=>String}] rule_hash
34
+ def meta(rule_hash)
35
+ add_rule(@meta_rules, rule_hash)
36
+ end
37
+
38
+ # Add a rule to the current rule-set
39
+ # @param [Array<Hash{Regexp => String},Hash{String => String}>] rule_set
40
+ # @param [Hash{Regexp => String},Hash{String => String}] rule_hash
41
+ # @private
42
+ def add_rule(rule_set, rule_hash)
43
+
44
+ rule_hash.each_key do |k|
45
+ rule_set << { from: k, to: rule_hash[k] }
46
+ end
47
+
48
+ end
49
+
50
+ # @param [Regexp, String] val
51
+ # @param [Hash{Regexp => String},Hash{String => String}] rule
52
+ # @private
53
+ def do_rewrite(val, rule)
54
+ case val
55
+ when Regexp
56
+ val_str = PP.singleline_pp(val, "")
57
+ val_str.gsub!(rule[:from], rule[:to])
58
+ val_str.to_regexp
59
+ when String
60
+ val.gsub(rule[:from], rule[:to])
61
+ else
62
+ val
63
+ end
64
+
65
+ end
66
+
67
+ # Rewrite a hash with a set of rules
68
+ # @param [Hash{Regexp => String},Hash{String => String}] in_hash
69
+ # @param [Hash{Regexp => String},Hash{String => String}] rules
70
+ # @private
71
+ def rewrite(in_hash, rules)
72
+ out_hash = []
73
+ in_hash.each do |v|
74
+ f = v[:from]
75
+ t = v[:to]
76
+
77
+ rules.each do |r|
78
+ f = do_rewrite(f, r)
79
+ t = do_rewrite(t, r)
80
+ end
81
+ out_hash << { from: f, to: t }
82
+ end
83
+ out_hash
84
+ end
85
+
86
+
87
+ # Apply filtering rules to the provided string
88
+ # @param [String] in_str
89
+ # @return [String] the resulting string
90
+ def apply(in_str)
91
+
92
+ @passes.times do
93
+
94
+ unless @meta_rules.empty?
95
+ @rules = rewrite(@rules, @meta_rules)
96
+ end
97
+
98
+ @rules.each do |rule|
99
+ in_str = in_str.gsub(rule[:from], rule[:to])
100
+ end
101
+
102
+ end
103
+ in_str
104
+ end
105
+
106
+ # Factory, init with rule-set from a string
107
+ #
108
+ # The input string is expected to contain rules and comments, one per line,
109
+ # separated by a line break.
110
+ # The expected format of a line is "{operation} <space> {argument} <eol>".
111
+ # Empty lines and lines starting with a '#' are ignored. Whitespace at the beginning of a line
112
+ # is trimmed.
113
+ #
114
+ # @param [String] rule_str
115
+ # @return [Filtri] A new Filtri object with the rules parsed from the provided string(s).
116
+ # @raise [FiltriInitError] if an error occurs when initialising the rules from the provided strings
117
+ def self.from_str(rule_str)
118
+
119
+ inst = Filtri.new
120
+
121
+ rule_str.strip.lines do |l|
122
+
123
+ op_str = l.strip.partition " "
124
+ if op_str[0].length > 0
125
+ op = op_str[0]
126
+ op_arg = op_str[2]
127
+
128
+ if Filtri::RULES.include? op.to_sym
129
+ # parse arg string
130
+ begin
131
+ arg_hash = op_arg.to_h
132
+ rescue Parslet::ParseFailed => err
133
+ raise FiltriInitError, "Invalid rule format: '#{op_arg}' (#{err.message})"
134
+ end
135
+ # add rule
136
+ inst.send(op.to_sym,arg_hash)
137
+ else
138
+ raise FiltriInitError, "Unknown rule: #{op}" unless op == "#"
139
+ end
140
+
141
+ end
142
+ end
143
+
144
+ inst
145
+
146
+ end
147
+
148
+ # Load rules from a file
149
+ # @param [String] file_name
150
+ # @return [Filtri] A new Filtri object with the rules contained in the file
151
+ # @raise [IOError,SystemCallError] If an error occurs when opening the file
152
+ # @raise [FiltriInitError] If an error occurs when parsing the rules in the file
153
+ def self.load(file_name)
154
+
155
+ data = IO.read(file_name)
156
+ Filtri.from_str(data)
157
+
158
+ end
159
+
160
+
161
+ end
162
+
163
+ class FiltriInitError < StandardError
164
+ def initialize(msg)
165
+ super(msg)
166
+ @msg = msg
167
+ end
168
+ end
169
+
170
+
171
+ # Create a Filtri object containing a set of rules
172
+ #
173
+ # @example
174
+ #
175
+ # f = filtri do
176
+ # rule "foo" => "bar"
177
+ # rule "baz" => "bug"
178
+ # end
179
+ #
180
+ # @return [Filtri] A new Filtri object containing the provided rules
181
+ def filtri(&block)
182
+ Docile.dsl_eval(Filtri.new, &block)
183
+ end
@@ -0,0 +1,6 @@
1
+ class Filtri
2
+
3
+ # Filtri version (semantic versioning)
4
+ VERSION = "0.0.1"
5
+
6
+ end
@@ -0,0 +1,223 @@
1
+ require "rspec"
2
+ require "filtri"
3
+
4
+ describe Filtri do
5
+
6
+ it "applies a rule to translate a string" do
7
+
8
+ in_str = "foo\nbaz\nbar\nfoo"
9
+ expected = "bar\nbug\nbar\nbar"
10
+
11
+ f = filtri do
12
+ rule "foo" => "bar"
13
+ rule "baz" => "bug"
14
+ end
15
+
16
+ result = f.apply(in_str)
17
+ expect(result).to eq(expected)
18
+
19
+
20
+ end
21
+
22
+ it "applies a simple regexp rule to translate a string" do
23
+
24
+ in_str = "foo\nbaz\nbar\nfoo"
25
+ expected = "bar\nbug\nbar\nbar"
26
+
27
+ f = filtri do
28
+ rule /fo+/ => "bar"
29
+ rule "baz" => "bug"
30
+ end
31
+
32
+ result = f.apply(in_str)
33
+ expect(result).to eq(expected)
34
+
35
+
36
+ end
37
+
38
+ it "applies a regexp and include matches in the result" do
39
+
40
+ in_str = "This is a test XXXXX"
41
+ expected = "This is a passing test ! (XXXXX)"
42
+
43
+ f = filtri do
44
+ rule /(test)/ => 'passing \1'
45
+ rule /(X+)/ => '! (\1)'
46
+ end
47
+
48
+ result = f.apply(in_str)
49
+ expect(result).to eq(expected)
50
+
51
+
52
+ end
53
+
54
+
55
+ it "re-writes rules with meta-rules" do
56
+
57
+ in_str = "foo\nbaz\nbar\nfoo"
58
+ expected = "bar\nbug\nbar\nbar"
59
+
60
+
61
+ f = filtri do
62
+ meta /META/ => 'fo+'
63
+ rule /META/ => "bar"
64
+ rule "baz" => "bug"
65
+ end
66
+
67
+ result = f.apply(in_str)
68
+ expect(result).to eq(expected)
69
+
70
+
71
+ end
72
+
73
+ it "re-writes regexps and plain strings with meta-rules" do
74
+
75
+ in_str = "foobarfoobar"
76
+ expected = "bazbagbazbag"
77
+
78
+
79
+ f = filtri do
80
+ meta "FB" => "foobar"
81
+ rule /FB/ => "bazbag"
82
+ end
83
+
84
+ result = f.apply(in_str)
85
+ expect(result).to eq(expected)
86
+
87
+
88
+ end
89
+
90
+ it "applies meta-rules on both parts of the rule" do
91
+
92
+ in_str = "foobarfoobar"
93
+ expected = "bazbagbazbag"
94
+
95
+
96
+ f = filtri do
97
+ meta "FB" => "bazbag"
98
+ rule /foobar/ => "FB"
99
+ end
100
+
101
+ result = f.apply(in_str)
102
+ expect(result).to eq(expected)
103
+
104
+ end
105
+
106
+
107
+ it "parses rules from strings" do
108
+
109
+
110
+
111
+ in_str = "foobarfoobar"
112
+ expected = "bazbagbazbag"
113
+
114
+ strings = <<EOF
115
+
116
+ rule /fo+bar/ => "bazbag"
117
+
118
+ EOF
119
+
120
+
121
+ result = Filtri.from_str(strings).apply(in_str)
122
+
123
+ expect(result).to eq(expected)
124
+
125
+ end
126
+
127
+ it "parses rules with comments and empty lines" do
128
+
129
+ in_str = "foobarfoobar"
130
+ expected = "**b**!!a!!z**b**!!a!!g**b**!!a!!z**b**!!a!!g"
131
+
132
+ strings = %q(
133
+
134
+ # this is a comment
135
+ rule /fo+bar/ => "bazbag"
136
+ rule /(a)+/ => '!!\1!!'
137
+ rule /(b)+/ => '**\1**'
138
+
139
+ # empty line above
140
+ rule "text" => "some other text"
141
+
142
+
143
+
144
+ )
145
+
146
+
147
+ f = Filtri.from_str(strings)
148
+ result = f.apply(in_str)
149
+
150
+ expect(result).to eq(expected)
151
+
152
+ end
153
+
154
+ it "raises an exception when parsing an unknown rule" do
155
+
156
+
157
+ strings = %q(
158
+
159
+ # Below are valid rules
160
+
161
+ meta /XXX/ => "YYYYY"
162
+ rule /FOO/ => "BAR"
163
+
164
+ # Below is an unknown rule name
165
+ stupid_rule /BAG/ => "BAR"
166
+
167
+ )
168
+ expect { Filtri.from_str(strings) }.to raise_error(FiltriInitError)
169
+
170
+ end
171
+
172
+ it "raises an exception when parsing an invalid rule format" do
173
+
174
+
175
+ strings = %q(
176
+
177
+ # Below are valid rules
178
+
179
+ meta /XXX/ => "YYYYY"
180
+ rule /FOO/ => "BAR"
181
+
182
+ # Below is an a rule w. invalid format
183
+ rule NOT_VALID
184
+
185
+ )
186
+ expect { Filtri.from_str(strings) }.to raise_error(FiltriInitError)
187
+
188
+ end
189
+
190
+
191
+ it "loads rules from external files" do
192
+
193
+ in_str = "foobarfoobar"
194
+ expected = "bazbagbazbag"
195
+
196
+
197
+ content = %q(
198
+
199
+ # this is a comment
200
+ rule /fo+bar/ => "bazbag"
201
+
202
+ )
203
+
204
+ IO.stub(:read).with("test.filtri").and_return content
205
+
206
+ filename = "test.filtri"
207
+
208
+ result = Filtri.load(filename).apply(in_str)
209
+
210
+ expect(result).to eq(expected)
211
+
212
+
213
+ end
214
+
215
+ it "raises an exception when file is missing" do
216
+
217
+
218
+ expect { Filtri.load("stupid_invalid_file") }.to raise_error(SystemCallError)
219
+
220
+ end
221
+
222
+
223
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: filtri
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - karl l
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: docile
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: str2hash
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: to_regexp
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: Filtri is a little DSL that simplifies the work of applying multiple
111
+ substitution rules to a string.
112
+ email:
113
+ - karl@ninjacontrol.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - .gitignore
119
+ - Gemfile
120
+ - LICENSE.txt
121
+ - README.md
122
+ - Rakefile
123
+ - filtri.gemspec
124
+ - lib/filtri.rb
125
+ - lib/filtri/version.rb
126
+ - spec/filtri_spec.rb
127
+ homepage: https://github.com/karlll/filtri/
128
+ licenses:
129
+ - MIT
130
+ post_install_message:
131
+ rdoc_options: []
132
+ require_paths:
133
+ - lib
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - ! '>='
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ segments:
141
+ - 0
142
+ hash: 1607576313604211744
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ none: false
145
+ requirements:
146
+ - - ! '>='
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ segments:
150
+ - 0
151
+ hash: 1607576313604211744
152
+ requirements: []
153
+ rubyforge_project:
154
+ rubygems_version: 1.8.24
155
+ signing_key:
156
+ specification_version: 3
157
+ summary: A tiny DSL for filtering strings
158
+ test_files:
159
+ - spec/filtri_spec.rb