filtri 0.0.1

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/.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