filtri 0.0.1 → 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/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