filtri 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -11,8 +11,6 @@ Filtri is a tiny DSL for text substitution.
11
11
 
12
12
  Define a set of substitution rules using `rule` and apply to an input string:
13
13
 
14
- in_str = "foobazbarfoo"
15
- expected = "barbugbarbar"
16
14
 
17
15
  f = filtri do
18
16
  rule /fo+/ => "bar"
@@ -36,8 +34,69 @@ Load with `Filtri::load`:
36
34
  Filtri.load("foo.rules").apply("foobarfoobar")
37
35
  => "**b**!!a!!z**b**!!a!!g**b**!!a!!z**b**!!a!!g"
38
36
 
37
+ ## Command line tool
38
+
39
+ A command line tool is included in this gem, `filtri`
40
+
41
+ ### Usage
42
+
43
+ Usage: filtri [options] [input]
44
+
45
+ Options:
46
+ -r, --rule STRING Single rule
47
+ -f, --file RULE_FILE File containing rules
48
+ -v, --version Version information
49
+ -h, --help Help
50
+
51
+
52
+ ### Examples
53
+
54
+ An example applying rules from the files `f1.rule` and `f2.rule` to the input files `input1` and `input2`
55
+
56
+ File `f1.rules`:
57
+
58
+ rule /(foo+)/ => 'THIS WAS \1'
59
+ rule /(ba+r)/ => 'THIS WAS \1'
60
+
61
+ File `f1.rules`:
62
+
63
+ rule /(ba+z)/ => 'THIS WAS \1'
64
+ rule /bag/ => 'THIS WAS BAG'
65
+
66
+ File `input1`:
67
+
68
+ foooo
69
+ baaaaaaaaaaaaaaaaz
70
+ bag
71
+
72
+ File `input2`:
73
+
74
+ and baaaaaaaaaaar
75
+
76
+ Running `filtri` as
77
+
78
+ $ filtri -f f1.rules -f f2.rules input1 input2 > result.txt
79
+
80
+ ..gives `result.txt`:
81
+
82
+ THIS WAS foooo
83
+ THIS WAS baaaaaaaaaaaaaaaaz
84
+ THIS WAS BAG
85
+ and THIS WAS baaaaaaaaaaar
86
+
87
+ Single rules can be provided using the option `-r`:
88
+
89
+ $ echo "foo" | filtri -r '"foo" => "bar"'
90
+ bar
91
+
92
+
93
+ ## Syntax highlighting
94
+
95
+ Definitions for syntax highlighting of `.rule` files in Sublime Text 2 and TextMate is available (`resource/filtri_rules.tmLanguage`)
96
+
39
97
  ## Version information
40
98
 
99
+ * 0.1.0 - Added filtri tool and syntax highlighting
41
100
  * 0.0.1 - Initial version
42
101
 
43
102
  ## License
data/bin/filtri ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pp'
4
+ require_relative '../lib/filtri/command'
5
+
6
+ begin
7
+
8
+ debug = true
9
+
10
+ opts = FiltriCmd.parse_opts
11
+
12
+ unless FiltriCmd.validate_opts(opts)
13
+ exit 1
14
+ end
15
+ result = FiltriCmd.run(opts, opts[:input])
16
+ puts result
17
+ rescue SystemExit => sys_exit
18
+ exit sys_exit.status
19
+ rescue Exception => ex
20
+
21
+ puts "[Error] #{ex.message}"
22
+ puts " -----> #{ex.backtrace.join("\n ")}" if debug
23
+
24
+ end
data/filtri.gemspec CHANGED
@@ -9,12 +9,14 @@ Gem::Specification.new do |spec|
9
9
  spec.version = Filtri::VERSION
10
10
  spec.authors = ["karl l"]
11
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."
12
+ spec.date = '2013-04-07'
13
+ spec.summary = "A tiny tool for text substitution"
14
+ spec.description = "Filtri is a tool that simplifies the work of applying multiple substitution rules to a string."
15
15
  spec.homepage = "https://github.com/karlll/filtri/"
16
16
  spec.license = "MIT"
17
-
17
+
18
+ spec.required_ruby_version = '>= 1.9.3'
19
+
18
20
  spec.files = `git ls-files`.split($/)
19
21
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
22
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
@@ -22,9 +24,9 @@ Gem::Specification.new do |spec|
22
24
 
23
25
  spec.add_development_dependency "bundler", "~> 1.3"
24
26
  spec.add_development_dependency "rake"
25
- spec.add_development_dependency "rspec"
27
+ spec.add_development_dependency "rspec", ">= 2.11"
26
28
  spec.add_runtime_dependency "docile"
27
- spec.add_runtime_dependency "str2hash"
29
+ spec.add_runtime_dependency "str2hash", ">= 0.1.0"
28
30
  spec.add_runtime_dependency "to_regexp"
29
31
 
30
32
  end
@@ -0,0 +1,108 @@
1
+ require 'optparse'
2
+ require_relative 'version'
3
+ require_relative '../filtri'
4
+
5
+ class FiltriCmd
6
+ def self.parse_opts
7
+ options = {}
8
+
9
+ OptionParser.new do |o|
10
+ o.banner = "Usage: filtri [options] [input]"
11
+ o.separator ""
12
+ o.separator "Options:"
13
+
14
+ o.on("-r", "--rule STRING", "Single rule") do |rule|
15
+ if options.include?(:rules)
16
+ options[:rules] << rule
17
+ else
18
+ options[:rules] = [rule]
19
+ end
20
+ end
21
+
22
+ o.on("-f", "--file RULE_FILE", "File containing rules") do |rule_files|
23
+ if options.include?(:rule_files)
24
+ options[:rule_files] << rule_files
25
+ else
26
+ options[:rule_files] = [rule_files]
27
+ end
28
+ end
29
+
30
+ o.on( '-v', '--version', 'Version information' ) do
31
+ puts "version #{Filtri::VERSION}"
32
+ exit
33
+ end
34
+
35
+ o.on( '-h', '--help', 'Help' ) do
36
+ puts o
37
+ exit
38
+ end
39
+
40
+
41
+
42
+ end.parse!
43
+
44
+ options[:input] = ARGV unless ARGV.length <= 0
45
+ options
46
+
47
+ end
48
+
49
+ def self.validate_opts(opts)
50
+
51
+
52
+ unless opts.include?(:rules) || opts.include?(:rule_files)
53
+ puts "Error: provide a rule or a rule file."
54
+ return false
55
+ end
56
+
57
+ [:rule_files, :input].each do |f|
58
+ if opts.include?(f)
59
+ opts[f].each do |i|
60
+ unless File.exist?(i)
61
+ puts "Error: file #{i} does not exist."
62
+ return false
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ true
69
+
70
+ end
71
+
72
+ # Apply rules to the provided input
73
+ #
74
+ # @param [Hash{Symbol => Array<String>}] rules Where :rules contain single rule strings and :rule_files contain filename(s) of the rule file(s)
75
+ # @param [Array<String>] input Where :input contain the input file(s)
76
+ # @return [String] The rules applied to the input
77
+ # @raise [FiltriInitError, IOError] if an error occurs when initialising the rules from the provided strings or files
78
+ def self.run(rules, input)
79
+ rule_strs = rules[:rules] || []
80
+ rule_files = rules[:rule_files] || []
81
+ result = []
82
+
83
+
84
+ # Allow for shorthand format of rules
85
+
86
+ upd_rule_strs = rule_strs.reduce([]) do |r,v|
87
+ if v =~ /^(#{Filtri.valid_rules.join("|")})/
88
+ r << v
89
+ else
90
+ r << "rule #{v}"
91
+ end
92
+ end
93
+ f = Filtri.new
94
+ if upd_rule_strs.length > 0
95
+ f.add_rule_str(upd_rule_strs.join("\n"))
96
+ end
97
+
98
+ rule_files.each do |rf|
99
+ f.load rf
100
+ end
101
+
102
+ ARGV.replace(input || [])
103
+ result << f.apply(ARGF.read)
104
+
105
+ result.join("")
106
+ end
107
+
108
+ end
@@ -1,6 +1,6 @@
1
1
  class Filtri
2
2
 
3
3
  # Filtri version (semantic versioning)
4
- VERSION = "0.0.1"
4
+ VERSION = "0.1.0"
5
5
 
6
6
  end
data/lib/filtri.rb CHANGED
@@ -21,6 +21,7 @@ class Filtri
21
21
  @rules = []
22
22
  @meta_rules = []
23
23
  @passes = 1
24
+ @meta_applied = false
24
25
  end
25
26
 
26
27
  # Add a filtering rule
@@ -92,7 +93,10 @@ class Filtri
92
93
  @passes.times do
93
94
 
94
95
  unless @meta_rules.empty?
95
- @rules = rewrite(@rules, @meta_rules)
96
+ unless @meta_applied
97
+ @rules = rewrite(@rules, @meta_rules)
98
+ @meta_applied = true
99
+ end
96
100
  end
97
101
 
98
102
  @rules.each do |rule|
@@ -103,8 +107,6 @@ class Filtri
103
107
  in_str
104
108
  end
105
109
 
106
- # Factory, init with rule-set from a string
107
- #
108
110
  # The input string is expected to contain rules and comments, one per line,
109
111
  # separated by a line break.
110
112
  # The expected format of a line is "{operation} <space> {argument} <eol>".
@@ -112,11 +114,8 @@ class Filtri
112
114
  # is trimmed.
113
115
  #
114
116
  # @param [String] rule_str
115
- # @return [Filtri] A new Filtri object with the rules parsed from the provided string(s).
116
117
  # @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
118
+ def add_rule_str(rule_str)
120
119
 
121
120
  rule_str.strip.lines do |l|
122
121
 
@@ -133,7 +132,7 @@ class Filtri
133
132
  raise FiltriInitError, "Invalid rule format: '#{op_arg}' (#{err.message})"
134
133
  end
135
134
  # add rule
136
- inst.send(op.to_sym,arg_hash)
135
+ self.send(op.to_sym,arg_hash)
137
136
  else
138
137
  raise FiltriInitError, "Unknown rule: #{op}" unless op == "#"
139
138
  end
@@ -141,12 +140,42 @@ class Filtri
141
140
  end
142
141
  end
143
142
 
143
+
144
+ end
145
+
146
+
147
+ # Factory, init with rule-set from a string
148
+ #
149
+ # The input string is expected to contain rules and comments, one per line,
150
+ # separated by a line break.
151
+ # The expected format of a line is '{operation} <space> {argument} <eol>'.
152
+ # Empty lines and lines starting with a '#' are ignored. Whitespace at the beginning of a line
153
+ # is trimmed.
154
+ #
155
+ # @param [String] rule_str
156
+ # @return [Filtri] A new Filtri object with the rules parsed from the provided string(s).
157
+ # @raise [FiltriInitError] if an error occurs when initialising the rules from the provided strings
158
+ def self.from_str(rule_str)
159
+
160
+ inst = Filtri.new
161
+ inst.add_rule_str rule_str
144
162
  inst
145
163
 
146
164
  end
147
165
 
148
166
  # Load rules from a file
149
167
  # @param [String] file_name
168
+ # @raise [IOError,SystemCallError] If an error occurs when opening the file
169
+ # @raise [FiltriInitError] If an error occurs when parsing the rules in the file
170
+ def load(file_name)
171
+
172
+ data = IO.read(file_name)
173
+ add_rule_str(data)
174
+
175
+ end
176
+
177
+ # Factory, Init by loading rules from a file
178
+ # @param [String] file_name
150
179
  # @return [Filtri] A new Filtri object with the rules contained in the file
151
180
  # @raise [IOError,SystemCallError] If an error occurs when opening the file
152
181
  # @raise [FiltriInitError] If an error occurs when parsing the rules in the file
@@ -158,6 +187,10 @@ class Filtri
158
187
  end
159
188
 
160
189
 
190
+ def self.valid_rules
191
+ Filtri::RULES
192
+ end
193
+
161
194
  end
162
195
 
163
196
  class FiltriInitError < StandardError
@@ -180,4 +213,5 @@ end
180
213
  # @return [Filtri] A new Filtri object containing the provided rules
181
214
  def filtri(&block)
182
215
  Docile.dsl_eval(Filtri.new, &block)
183
- end
216
+ end
217
+
@@ -0,0 +1,75 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>fileTypes</key>
6
+ <array>
7
+ <string>rules</string>
8
+ </array>
9
+ <key>name</key>
10
+ <string>Filtri rules</string>
11
+ <key>patterns</key>
12
+ <array>
13
+ <dict>
14
+ <key>comment</key>
15
+ <string>A comment</string>
16
+ <key>match</key>
17
+ <string>^\s*(#.*)$</string>
18
+ <key>name</key>
19
+ <string>comment.line.numer-sign.rules</string>
20
+ </dict>
21
+ <dict>
22
+ <key>comment</key>
23
+ <string>A rule</string>
24
+ <key>match</key>
25
+ <string>^\s*rule</string>
26
+ <key>name</key>
27
+ <string>support.function.rules</string>
28
+ </dict>
29
+ <dict>
30
+ <key>comment</key>
31
+ <string>A meta rule</string>
32
+ <key>match</key>
33
+ <string>^\s*meta</string>
34
+ <key>name</key>
35
+ <string>support.function.rules</string>
36
+ </dict>
37
+ <dict>
38
+ <key>comment</key>
39
+ <string>A string</string>
40
+ <key>match</key>
41
+ <string>"([^"]*)"</string>
42
+ <key>name</key>
43
+ <string>string.quoted.double.rules</string>
44
+ </dict>
45
+ <dict>
46
+ <key>comment</key>
47
+ <string>A string</string>
48
+ <key>match</key>
49
+ <string>'([^']*)'</string>
50
+ <key>name</key>
51
+ <string>string.quoted.single.rules</string>
52
+ </dict>
53
+ <dict>
54
+ <key>comment</key>
55
+ <string>A regular expression</string>
56
+ <key>match</key>
57
+ <string>/([^']*)/</string>
58
+ <key>name</key>
59
+ <string>string.regexp.rules</string>
60
+ </dict>
61
+ <dict>
62
+ <key>comment</key>
63
+ <string>A mapping</string>
64
+ <key>match</key>
65
+ <string>=&gt;</string>
66
+ <key>name</key>
67
+ <string>keyword.operator.rules</string>
68
+ </dict>
69
+ </array>
70
+ <key>scopeName</key>
71
+ <string>text.rules</string>
72
+ <key>uuid</key>
73
+ <string>7d1b615b-cfb6-41e1-83e1-6ff8508e0133</string>
74
+ </dict>
75
+ </plist>
@@ -0,0 +1,127 @@
1
+ require 'rspec'
2
+ require 'filtri/command'
3
+
4
+ describe FiltriCmd do
5
+
6
+ describe "#parse_opts" do
7
+
8
+ it 'parses multiple rules' do
9
+
10
+ stub_const("ARGV",["-r", "{:foo => 'bar'}", "--rule", "{:foo2 => 'bar2'}"])
11
+ out_opts = FiltriCmd.parse_opts
12
+ out_opts.should eq({:rules=>["{:foo => 'bar'}", "{:foo2 => 'bar2'}"]})
13
+
14
+ end
15
+
16
+ it 'parses multiple rule files' do
17
+
18
+ stub_const("ARGV",["-f", "file1", "--file", "file2"])
19
+ out_opts = FiltriCmd.parse_opts
20
+ out_opts.should eq({:rule_files=>["file1", "file2"]})
21
+
22
+ end
23
+
24
+ it 'parses multiple input files' do
25
+
26
+ stub_const("ARGV",["-f", "file1", "input1", "input2"])
27
+ out_opts = FiltriCmd.parse_opts
28
+ out_opts.should eq({:rule_files=>["file1"], :input=> ["input1", "input2"]})
29
+
30
+
31
+ end
32
+
33
+ end
34
+
35
+ describe "#validate_opts" do
36
+
37
+ it 'requires a rule or a rule file' do
38
+
39
+ stub_const("ARGV",["-f", "file1", "input1", "input2"])
40
+ out_opts = FiltriCmd.parse_opts
41
+ File.stub!(:exist?).and_return true
42
+ FiltriCmd.validate_opts(out_opts).should be_true
43
+
44
+ stub_const("ARGV",["--rule", "{:foo => 'bar'}", "input2"])
45
+ out_opts = FiltriCmd.parse_opts
46
+ FiltriCmd.validate_opts(out_opts).should be_true
47
+
48
+ stub_const("ARGV",["input1", "input2"])
49
+ out_opts = FiltriCmd.parse_opts
50
+ $stdout.should_receive(:puts).with(/Error/i)
51
+ FiltriCmd.validate_opts(out_opts).should be_false
52
+
53
+ end
54
+
55
+ it 'checks if files exist' do
56
+
57
+ stub_const("ARGV",["-f", "existing_file", "input_file1", "input_file2"])
58
+ out_opts = FiltriCmd.parse_opts
59
+ File.stub!(:exist?).with("existing_file").and_return true
60
+ File.stub!(:exist?).with("input_file1").and_return true
61
+ File.stub!(:exist?).with("input_file2").and_return true
62
+ FiltriCmd.validate_opts(out_opts).should be_true
63
+
64
+ stub_const("ARGV",["-f", "existing_file", "input_file1", "bad_input_file2"])
65
+ out_opts = FiltriCmd.parse_opts
66
+ File.stub!(:exist?).with("existing_file").and_return true
67
+ File.stub!(:exist?).with("input_file1").and_return true
68
+ File.stub!(:exist?).with("bad_input_file2").and_return false
69
+ $stdout.should_receive(:puts).with(/Error/i)
70
+ FiltriCmd.validate_opts(out_opts).should be_false
71
+
72
+ end
73
+
74
+
75
+ end
76
+
77
+ describe "#run" do
78
+
79
+ let(:rules) { %q(
80
+
81
+ # this is a comment
82
+ rule /fo+bar/ => "bazbag"
83
+ rule /go+blin/ => "NILBOG"
84
+
85
+ ) }
86
+
87
+ let(:input1) { %q(
88
+
89
+ fooooooooooooooobar
90
+ cookie
91
+ goooooblin
92
+
93
+ ) }
94
+
95
+ let(:result1) { %q(
96
+
97
+ bazbag
98
+ cookie
99
+ NILBOG
100
+
101
+ ) }
102
+
103
+
104
+
105
+ it 'reads rules from a file and applies to input file' do
106
+ opts = {:rule_files=>["rule_file"], :input=> ["input_file"]}
107
+
108
+ IO.stub(:read).with("rule_file").and_return rules
109
+ ARGF.stub!(:read).and_return input1
110
+
111
+ result = FiltriCmd.run(opts, opts[:input])
112
+ result.should eq(result1)
113
+ end
114
+
115
+ it 'reads single rules applies to input file' do
116
+ opts = {:rules=>['rule /fo+bar/ => "bazbag"','rule /go+blin/ => "NILBOG"'], :input=> ["input_file"]}
117
+
118
+ ARGF.stub!(:read).and_return input1
119
+
120
+ result = FiltriCmd.run(opts, opts[:input])
121
+ result.should eq(result1)
122
+ end
123
+
124
+ end
125
+
126
+
127
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: filtri
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-01 00:00:00.000000000 Z
12
+ date: 2013-04-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -50,7 +50,7 @@ dependencies:
50
50
  requirements:
51
51
  - - ! '>='
52
52
  - !ruby/object:Gem::Version
53
- version: '0'
53
+ version: '2.11'
54
54
  type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
@@ -58,7 +58,7 @@ dependencies:
58
58
  requirements:
59
59
  - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: '2.11'
62
62
  - !ruby/object:Gem::Dependency
63
63
  name: docile
64
64
  requirement: !ruby/object:Gem::Requirement
@@ -82,7 +82,7 @@ dependencies:
82
82
  requirements:
83
83
  - - ! '>='
84
84
  - !ruby/object:Gem::Version
85
- version: '0'
85
+ version: 0.1.0
86
86
  type: :runtime
87
87
  prerelease: false
88
88
  version_requirements: !ruby/object:Gem::Requirement
@@ -90,7 +90,7 @@ dependencies:
90
90
  requirements:
91
91
  - - ! '>='
92
92
  - !ruby/object:Gem::Version
93
- version: '0'
93
+ version: 0.1.0
94
94
  - !ruby/object:Gem::Dependency
95
95
  name: to_regexp
96
96
  requirement: !ruby/object:Gem::Requirement
@@ -107,11 +107,12 @@ dependencies:
107
107
  - - ! '>='
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
- description: Filtri is a little DSL that simplifies the work of applying multiple
111
- substitution rules to a string.
110
+ description: Filtri is a tool that simplifies the work of applying multiple substitution
111
+ rules to a string.
112
112
  email:
113
113
  - karl@ninjacontrol.com
114
- executables: []
114
+ executables:
115
+ - filtri
115
116
  extensions: []
116
117
  extra_rdoc_files: []
117
118
  files:
@@ -120,9 +121,13 @@ files:
120
121
  - LICENSE.txt
121
122
  - README.md
122
123
  - Rakefile
124
+ - bin/filtri
123
125
  - filtri.gemspec
124
126
  - lib/filtri.rb
127
+ - lib/filtri/command.rb
125
128
  - lib/filtri/version.rb
129
+ - resource/filtri_rules.tmLanguage
130
+ - spec/command_spec.rb
126
131
  - spec/filtri_spec.rb
127
132
  homepage: https://github.com/karlll/filtri/
128
133
  licenses:
@@ -136,10 +141,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
136
141
  requirements:
137
142
  - - ! '>='
138
143
  - !ruby/object:Gem::Version
139
- version: '0'
140
- segments:
141
- - 0
142
- hash: 1607576313604211744
144
+ version: 1.9.3
143
145
  required_rubygems_version: !ruby/object:Gem::Requirement
144
146
  none: false
145
147
  requirements:
@@ -148,12 +150,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
148
150
  version: '0'
149
151
  segments:
150
152
  - 0
151
- hash: 1607576313604211744
153
+ hash: 107123931516266263
152
154
  requirements: []
153
155
  rubyforge_project:
154
156
  rubygems_version: 1.8.24
155
157
  signing_key:
156
158
  specification_version: 3
157
- summary: A tiny DSL for filtering strings
159
+ summary: A tiny tool for text substitution
158
160
  test_files:
161
+ - spec/command_spec.rb
159
162
  - spec/filtri_spec.rb