preprocessor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+
3
+ gem 'rspec', '~> 2.6.0'
4
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2009 Brandan Lennox
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,6 @@
1
+ Text Preprocessor
2
+ =================
3
+
4
+ Ruby module that I use on a couple of blogs as a shortcut to reference images, audio, footnotes, etc. It's very specific to my needs, but you might find something useful in it.
5
+
6
+ I'm maintaining [the docs](http://bclennox.com/projects/preprocessor) on my site now.
@@ -0,0 +1,88 @@
1
+ require 'preprocessor/utilities'
2
+ require 'preprocessor/image'
3
+ require 'preprocessor/audio'
4
+ require 'preprocessor/video'
5
+ require 'preprocessor/footnote'
6
+
7
+ module Preprocessor
8
+ def self.included(base)
9
+ base.class_eval do
10
+ def preprocessed
11
+ @preprocessed ||= BasicPreprocessor.new(self)
12
+ end
13
+ end
14
+ end
15
+
16
+ class BasicPreprocessor
17
+ attr_accessor :delegate
18
+
19
+ include ::Preprocessor::Utilities
20
+ include ::Preprocessor::Image
21
+ include ::Preprocessor::Audio
22
+ include ::Preprocessor::Video
23
+ include ::Preprocessor::Footnote
24
+
25
+ TAG_REGEX = /(.)?\{\{(\S+?)(?:\s+(.*?))?\}\}/
26
+ REPLACEMENT_REGEX = /\{-\{-(.*?)-\}-\}/
27
+
28
+ def initialize(delegate)
29
+ self.delegate = delegate
30
+ end
31
+
32
+ def method_missing(sym, *args)
33
+ text = delegate.send(sym, *args)
34
+
35
+ while text.match(TAG_REGEX)
36
+ text.sub!(TAG_REGEX, replacement_for($2, $3, $1 || ''))
37
+ end
38
+
39
+ text.gsub!(REPLACEMENT_REGEX, '{{\1}}')
40
+ text
41
+ end
42
+
43
+ private
44
+
45
+ def replacement_for(message, options_string, antecedant)
46
+ if escape?(antecedant)
47
+ antecedant = ''
48
+ replacement = replacement_placeholder(message, options_string)
49
+ else
50
+ replacement = if respond_to?(message)
51
+ send(message, options_from_string(options_string))
52
+ elsif delegate.respond_to?(message)
53
+ delegate.send(message)
54
+ else
55
+ replacement_placeholder(message, options_string)
56
+ end
57
+ end
58
+
59
+ antecedant + replacement
60
+ end
61
+
62
+ def replacement_placeholder(text, options)
63
+ "{-{-#{[text, options].compact.join(' ')}-}-}"
64
+ end
65
+
66
+ def escape?(char)
67
+ char == '\\'
68
+ end
69
+
70
+ def options_from_string(options_string)
71
+ return {} if options_string.nil?
72
+
73
+ # replace strings first
74
+ re = /:(["'|])(.*?)(\1)/
75
+ string_refs = []
76
+ while options_string.match(re)
77
+ options_string.sub!(re, ":ppstringref#{string_refs.length}")
78
+ string_refs.push($2)
79
+ end
80
+
81
+ options_string.split(/\s+/).inject({}) do |memo, o|
82
+ q = o.split(/:/, 2)
83
+ memo[q.first.to_sym] = q.last.match(/ppstringref(\d+)/) ? string_refs[$1.to_i] : q.last
84
+ memo
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,14 @@
1
+ module Preprocessor
2
+ module Audio
3
+ class << self
4
+ attr_accessor :options
5
+ end
6
+
7
+ @options = { :default_path => '/audio' }
8
+
9
+ def audio(options)
10
+ src = absolutize(options[:src], ::Preprocessor::Audio.options[:default_path])
11
+ %{<audio controls><source src="#{src}.oga" type="audio/ogg" /><source src="#{src}.mp3" type="audio/mpeg" /></audio>}
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,37 @@
1
+ module Preprocessor
2
+ module Footnote
3
+ def self.included(base)
4
+ base.class_eval do
5
+ attr_accessor :footnote_refs
6
+ end
7
+ end
8
+
9
+ def footnote(options)
10
+ ref = options[:ref]
11
+ add_footnote(ref, options[:text])
12
+
13
+ id = "#{guid}-#{options[:ref]}"
14
+ %{<a href="#fn-#{id}" class="footnote" id="ref-#{id}">#{ref}</a>}
15
+ end
16
+
17
+ def footnotes(options)
18
+ footnote_refs.nil? ? "" : footnote_refs.map { |fn| footnote_content(fn) }.join
19
+ end
20
+
21
+ private
22
+
23
+ def guid
24
+ delegate.slug
25
+ end
26
+
27
+ def add_footnote(ref, text)
28
+ @footnote_refs ||= {}
29
+ @footnote_refs[ref] = text
30
+ end
31
+
32
+ def footnote_content(fn)
33
+ id = "#{guid}-#{fn[0]}"
34
+ %:#(#fn-#{id}) #{fn[1]} <a href="#ref-#{id}" class="returner" title="Return to where you left off">&#8617;</a>\n:
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ module Preprocessor
2
+ module Image
3
+ class << self
4
+ attr_accessor :options
5
+ end
6
+
7
+ @options = { :default_path => '/images' }
8
+
9
+ def image(options)
10
+ attrs = {
11
+ :src => absolutize(options[:src], ::Preprocessor::Image.options[:default_path]),
12
+ :alt => options[:alt] || "",
13
+ :title => options[:title] == "alt" ? options[:alt] : options[:title]
14
+ }
15
+
16
+ caption_text = case options[:caption]
17
+ when "alt"
18
+ attrs[:alt]
19
+ when "title"
20
+ attrs[:title]
21
+ else
22
+ options[:caption]
23
+ end
24
+
25
+ caption = %{<span class="caption">#{caption_text}</span>}
26
+ attr_str = attrs.map { |k, v| %{#{k}="#{v}"} }.join(' ')
27
+
28
+ %{<p class="image"><img #{attr_str} />#{caption}</p>}
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ module Preprocessor
2
+ module Utilities
3
+ def absolutize(src, path)
4
+ absolute?(src) ? src : "#{path}/#{src}"
5
+ end
6
+
7
+ def absolute?(src)
8
+ src =~ /^\/|http:\/\//
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,56 @@
1
+ module Preprocessor
2
+ module Video
3
+ class << self
4
+ attr_accessor :options
5
+ end
6
+
7
+ @options = { :default_path => '/video' }
8
+
9
+ class YouTube
10
+ attr_accessor :src
11
+ URL_REGEXP = /youtube\.com\/(?:watch\?v=|v\/)([^\/?&]+)/i
12
+
13
+ def initialize(options)
14
+ self.src = options[:src]
15
+ end
16
+
17
+ def tag
18
+ if src =~ URL_REGEXP
19
+ url = "http://www.youtube.com/v/#{$1}"
20
+ %{<object width="425" height="344"><param name="movie" value="#{url}"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="#{url}" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"></embed></object>}
21
+ end
22
+ end
23
+
24
+ def self.recognizes?(src)
25
+ src =~ URL_REGEXP
26
+ end
27
+ end
28
+
29
+ class Default
30
+ attr_accessor :src, :poster
31
+
32
+ # lame
33
+ include ::Preprocessor::Utilities
34
+
35
+ def initialize(options)
36
+ self.src = options[:src]
37
+ self.poster = options[:poster]
38
+ end
39
+
40
+ def tag
41
+ src = absolutize(self.src, ::Preprocessor::Video.options[:default_path])
42
+ poster = self.poster && %{poster="#{self.poster}"}
43
+
44
+ # eventually should use http://camendesign.com/code/video_for_everybody
45
+ %{<video controls #{poster}><source src="#{src}.ogv" type="video/ogg" /><source src="#{src}.mp4" type="video/mp4" /></video>}
46
+ end
47
+ end
48
+
49
+ CUSTOM_EMBEDDERS = [YouTube]
50
+
51
+ def video(options)
52
+ embedder = CUSTOM_EMBEDDERS.detect { |e| e.recognizes?(options[:src]) } || Default
53
+ embedder.new(options).tag
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,39 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "preprocessor/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "preprocessor"
7
+ s.version = Preprocessor::VERSION
8
+ s.authors = ["Brandan Lennox"]
9
+ s.email = ["brandan@bclennox.com"]
10
+ s.homepage = "https://github.com/bclennox/preprocessor"
11
+ s.date = "2012-01-28"
12
+ s.summary = %q{Ruby text munger}
13
+ s.description = %q{Ruby module that I use on a couple of blogs as a shortcut to reference images, audio, footnotes, etc. It's very specific to my needs, but you might find something useful in it.}
14
+
15
+ s.rubyforge_project = "preprocessor"
16
+
17
+ s.files = %w{
18
+ preprocessor.gemspec
19
+ Gemfile
20
+ MIT-LICENSE
21
+ README.markdown
22
+
23
+ lib/preprocessor.rb
24
+ lib/preprocessor/audio.rb
25
+ lib/preprocessor/footnote.rb
26
+ lib/preprocessor/image.rb
27
+ lib/preprocessor/utilities.rb
28
+ lib/preprocessor/video.rb
29
+ }
30
+
31
+ s.test_files = %w{
32
+ spec/preprocessor_spec.rb
33
+ spec/spec_helper.rb
34
+ }
35
+
36
+ s.require_paths = ["lib"]
37
+
38
+ s.add_development_dependency "rspec"
39
+ end
@@ -0,0 +1,181 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ class PreprocessorTester
4
+ def initialize(options = {})
5
+ options.each do |message, value|
6
+ self.class.class_eval { attr_accessor message }
7
+ send("#{message}=", value)
8
+ end
9
+ end
10
+
11
+ include ::Preprocessor
12
+ end
13
+
14
+ describe Preprocessor do
15
+ it 'should ignore escaped curlies' do
16
+ tester = PreprocessorTester.new(:name => 'Munge', :title => 'Remember my \{{name with:"a mnemonic"}}')
17
+ tester.preprocessed.title.should == 'Remember my {{name with:"a mnemonic"}}'
18
+ end
19
+
20
+ it 'should substitute method return values if the delegate responds_to?' do
21
+ tester = PreprocessorTester.new(:name => 'Fooject', :title => 'This is the {{name}}')
22
+ tester.preprocessed.title.should == 'This is the Fooject'
23
+ end
24
+
25
+ it 'should prioritize sub-module methods over delegate methods' do
26
+ tester = PreprocessorTester.new(:image => 'paperclip', :text => 'Look! My new Zune: {{image src:shit-brown.png}}')
27
+ tester.preprocessed.text.should_not =~ /paperclip/
28
+ end
29
+
30
+ it 'should return the unchanged string if neither it nor the delegate responds_to?' do
31
+ tester = PreprocessorTester.new(:text => "I don't understand {{anything}}")
32
+ tester.preprocessed.text.should == "I don't understand {{anything}}"
33
+ end
34
+
35
+ describe "parsing option strings" do
36
+ before(:each) do
37
+ @tester = PreprocessorTester.new
38
+ end
39
+
40
+ def options_from_string(string)
41
+ @tester.preprocessed.send(:options_from_string, string)
42
+ end
43
+
44
+ it 'should parse unquoted option values' do
45
+ options_from_string('arg:simple').should == { :arg => 'simple' }
46
+ end
47
+
48
+ it 'should parse single-quoted option values' do
49
+ options_from_string(%{arg:'Not so much "fat" as "dumb".'}).should == { :arg => 'Not so much "fat" as "dumb".' }
50
+ end
51
+
52
+ it 'should parse double-quoted option values' do
53
+ options_from_string(%{arg:"D'oh!"}).should == { :arg => "D'oh!" }
54
+ end
55
+
56
+ it 'should parse piped option values' do
57
+ options_from_string(%{arg:|And I said, "Frankly, my dear, I don't give a damn!"|}).should == { :arg => %{And I said, "Frankly, my dear, I don't give a damn!"} }
58
+ end
59
+
60
+ it 'should parse option values with special characters' do
61
+ options_from_string(%{arg:http://www.example.com/}).should == { :arg => 'http://www.example.com/' }
62
+ end
63
+ end
64
+
65
+ describe "utilities" do
66
+ class UtilitiesTester
67
+ include ::Preprocessor::Utilities
68
+ end
69
+
70
+ it 'should ignore absolute paths' do
71
+ UtilitiesTester.new.absolutize('/my/stuff/me.png', '/where/ever').should == '/my/stuff/me.png'
72
+ end
73
+
74
+ it 'should not prepend the default path to URLs' do
75
+ UtilitiesTester.new.absolutize('http://example.com/illicit.png', '/lalala').should == 'http://example.com/illicit.png'
76
+ end
77
+ end
78
+
79
+ describe "#image" do
80
+ it 'should have src, alt, title attributes and a caption' do
81
+ tester = PreprocessorTester.new(:text => 'This chick is mad hot: {{image src:chick.png alt:"Chicken in the oven" title:"No title necessary" caption:"Look how hot yah"}}')
82
+ tester.preprocessed.text.should =~ /src="\/images\/chick.png"/
83
+ tester.preprocessed.text.should =~ /alt="Chicken in the oven"/
84
+ tester.preprocessed.text.should =~ /title="No title necessary"/
85
+ tester.preprocessed.text.should =~ /Look how hot yah/
86
+ end
87
+
88
+ it 'should allow shortcutting title and caption' do
89
+ tester = PreprocessorTester.new(:text => 'Lazy {{image src:foo.png alt:"Required" title:alt caption:title}}')
90
+ tester.preprocessed.text.should =~ /title="Required"/
91
+ tester.preprocessed.text.should =~ /<span class="caption">Required<\/span>/
92
+ end
93
+
94
+ it 'should set the default path' do
95
+ ::Preprocessor::Image.options[:default_path] = '/system/assets'
96
+ tester = PreprocessorTester.new(:text => 'Hope it works: {{image src:boo.png alt:"Whatevs"}}')
97
+ tester.preprocessed.text.should =~ /src="\/system\/assets\/boo.png"/
98
+ end
99
+ end
100
+
101
+ describe "#audio" do
102
+ before(:each) do
103
+ @tester = PreprocessorTester.new(:text => 'Peep this sweet cut: {{audio src:piano-rock}}')
104
+ end
105
+
106
+ it 'should contain a source for the MP3 file' do
107
+ @tester.preprocessed.text.should =~ /piano-rock.mp3/
108
+ end
109
+
110
+ it 'should contain a source for the Ogg file' do
111
+ @tester.preprocessed.text.should =~ /piano-rock.oga/
112
+ end
113
+
114
+ it 'should set the default path' do
115
+ ::Preprocessor::Audio.options[:default_path] = '/somewhere/else'
116
+ @tester.preprocessed.text.should =~ /\/somewhere\/else\/piano-rock/
117
+ end
118
+
119
+ it 'should not prepend the default path to absolute paths' do
120
+ tester = PreprocessorTester.new(:text => 'Baddies: {{audio src:/i/love/bob}}')
121
+ tester.preprocessed.text.should =~ /\/i\/love\/bob/
122
+ end
123
+ end
124
+
125
+ describe "#video" do
126
+ describe 'YouTube videos' do
127
+ it 'should recognize a "watch" URL with a query string' do
128
+ ::Preprocessor::Video::YouTube.should recognize('http://www.youtube.com/watch?v=jHyC0ggI3Ow&feature=player_embedded')
129
+ end
130
+
131
+ it 'should recognize a "/v/" URL with a query string' do
132
+ ::Preprocessor::Video::YouTube.should recognize('http://www.youtube.com/v/jHyC0ggI3Ow&hl=en_US&fs=1&rel=0')
133
+ end
134
+
135
+ it 'should include the object and embed shit' do
136
+ tester = PreprocessorTester.new(:text => 'LOLOL {{video src:"http://www.youtube.com/watch?v=jHyC0ggI3Ow"}}')
137
+ tester.preprocessed.text.should =~ /param name="movie" value="http:\/\/www.youtube.com\/v\/jHyC0ggI3Ow"/
138
+ tester.preprocessed.text.should =~ /embed src="http:\/\/www.youtube.com\/v\/jHyC0ggI3Ow"/
139
+ end
140
+ end
141
+
142
+ describe "HTML 5 videos" do
143
+ it 'should include the MP4 source' do
144
+ tester = PreprocessorTester.new(:text => '{{video src:/videos/dancedancedance}}')
145
+ tester.preprocessed.text.should =~ /dancedancedance.mp4/
146
+ end
147
+
148
+ it 'should include the Ogg source' do
149
+ tester = PreprocessorTester.new(:text => '{{video src:/videos/ooouuuuttttt}}')
150
+ tester.preprocessed.text.should =~ /ooouuuuttttt.ogv/
151
+ end
152
+
153
+ it 'should include an optional poster' do
154
+ tester = PreprocessorTester.new(:text => '{{video src:/videos/help poster:/images/posse.png}}')
155
+ tester.preprocessed.text.should =~ /poster="\/images\/posse.png"/
156
+ end
157
+ end
158
+ end
159
+
160
+ describe "#footnote" do
161
+ before(:each) do
162
+ @tester = PreprocessorTester.new(:text => 'Surprised kitten is surprised.{{footnote ref:1 text:"Not for resale."}} Some more content{{footnote ref:2 text:"Is there more?"}} haha. Content sucks. {{footnotes}}', :slug => 'slug-boat')
163
+ end
164
+
165
+ it 'should contain reference links' do
166
+ [1, 2].each do |i|
167
+ @tester.preprocessed.text.should =~ /<a href="#fn-slug-boat-#{i}".*id="ref-slug-boat-#{i}".*?>#{i}/
168
+ end
169
+ end
170
+
171
+ it 'should contain the footnotes in a Textile list' do
172
+ @tester.preprocessed.text.should =~ /#\(#fn-slug-boat-1\)\s+Not for resale./
173
+ @tester.preprocessed.text.should =~ /#\(#fn-slug-boat-2\)\s+Is there more?/
174
+ end
175
+
176
+ it 'should not explode if {{footnotes}} is called without any footnotes' do
177
+ tester = PreprocessorTester.new(:text => 'There are no {{footnotes}}.')
178
+ tester.preprocessed.text.should == 'There are no .'
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,20 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'preprocessor')
2
+
3
+ # for video embedders
4
+ RSpec::Matchers.define :recognize do |src|
5
+ match do |embedder|
6
+ embedder.recognizes?(src)
7
+ end
8
+
9
+ failure_message_for_should do |embedder|
10
+ "#{embedder} should recognize #{src} and doesn't"
11
+ end
12
+
13
+ failure_message_for_should_not do |embedder|
14
+ "#{embedder} should not recognize #{src} and does"
15
+ end
16
+
17
+ description do
18
+ "whatever"
19
+ end
20
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: preprocessor
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Brandan Lennox
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-01-28 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :development
33
+ version_requirements: *id001
34
+ description: Ruby module that I use on a couple of blogs as a shortcut to reference images, audio, footnotes, etc. It's very specific to my needs, but you might find something useful in it.
35
+ email:
36
+ - brandan@bclennox.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - preprocessor.gemspec
45
+ - Gemfile
46
+ - MIT-LICENSE
47
+ - README.markdown
48
+ - lib/preprocessor.rb
49
+ - lib/preprocessor/audio.rb
50
+ - lib/preprocessor/footnote.rb
51
+ - lib/preprocessor/image.rb
52
+ - lib/preprocessor/utilities.rb
53
+ - lib/preprocessor/video.rb
54
+ - spec/preprocessor_spec.rb
55
+ - spec/spec_helper.rb
56
+ homepage: https://github.com/bclennox/preprocessor
57
+ licenses: []
58
+
59
+ post_install_message:
60
+ rdoc_options: []
61
+
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ hash: 3
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ hash: 3
79
+ segments:
80
+ - 0
81
+ version: "0"
82
+ requirements: []
83
+
84
+ rubyforge_project: preprocessor
85
+ rubygems_version: 1.8.15
86
+ signing_key:
87
+ specification_version: 3
88
+ summary: Ruby text munger
89
+ test_files:
90
+ - spec/preprocessor_spec.rb
91
+ - spec/spec_helper.rb