model_formatting 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +3 -0
- data/Rakefile +34 -0
- data/lib/model_formatting.rb +205 -0
- data/lib/model_formatting/config.rb +25 -0
- data/lib/model_formatting/init.rb +56 -0
- data/lib/model_formatting/instance_methods.rb +24 -0
- data/rails/init.rb +1 -0
- data/tender.patch +95 -0
- data/test/formatting_test.rb +161 -0
- data/test/model_formatting_test.rb +117 -0
- data/test/test_helper.rb +17 -0
- metadata +188 -0
data/README
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rake/gempackagetask'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
spec = Gem::Specification.new do |s|
|
5
|
+
s.name = "model_formatting"
|
6
|
+
s.version = "0.2.0"
|
7
|
+
s.author = "ENTP"
|
8
|
+
s.email = "company@entp.com"
|
9
|
+
s.homepage = "http://github.com/entp"
|
10
|
+
s.platform = Gem::Platform::RUBY
|
11
|
+
s.summary = "Automatically format model attributes using rdiscount and Tender/Lighthouse extensions."
|
12
|
+
s.files = FileList['[a-zA-Z]*', 'lib/**/*', 'rails/**/*', 'test/**/*']
|
13
|
+
s.require_path = "lib"
|
14
|
+
s.has_rdoc = false
|
15
|
+
s.extra_rdoc_files = ["README"]
|
16
|
+
s.add_dependency("rdiscount", "~>1.5.5")
|
17
|
+
s.add_dependency("actionpack", "~>2.2.3")
|
18
|
+
s.add_dependency("activerecord", "~>2.2.3")
|
19
|
+
s.add_dependency("activesupport", "~>2.2.3")
|
20
|
+
s.add_dependency("tidy", "~>1.1.2")
|
21
|
+
s.add_development_dependency("jeremymcanally-context", "~>0.5.5")
|
22
|
+
s.add_development_dependency("jeremymcanally-matchy", "~>0.1.0")
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
desc 'Build the gem.'
|
27
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
28
|
+
pkg.gem_spec = spec
|
29
|
+
end
|
30
|
+
|
31
|
+
Rake::TestTask.new do |t|
|
32
|
+
t.test_files = FileList['test/*_test.rb']
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
module ModelFormatting
|
3
|
+
class Part < Array
|
4
|
+
attr_reader :format
|
5
|
+
def initialize(format, *args)
|
6
|
+
@format = format
|
7
|
+
@simple_string = @formatted_string = nil
|
8
|
+
super(*args)
|
9
|
+
end
|
10
|
+
|
11
|
+
def <<(s)
|
12
|
+
@simple_string = @formatted_string = nil
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def compact!
|
17
|
+
@simple_string = @formatted_string = nil
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
def simple_string
|
22
|
+
@simple_string ||= join("\n")
|
23
|
+
end
|
24
|
+
|
25
|
+
def formatted_string
|
26
|
+
@formatted_string ||= begin
|
27
|
+
if @format == :html
|
28
|
+
%(<pre><code#{%( class="#{@class_name}") unless @class_name.blank?}>#{simple_string}</code></pre>)
|
29
|
+
else
|
30
|
+
simple_string
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def ==(other)
|
36
|
+
simple_string == other
|
37
|
+
end
|
38
|
+
|
39
|
+
def inspect
|
40
|
+
"#{self.class.name}: #{simple_string.inspect}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class CodePart < Part
|
45
|
+
attr_reader :class_name
|
46
|
+
|
47
|
+
def initialize(format, class_name, *args)
|
48
|
+
@class_name = class_name
|
49
|
+
super(format, *args)
|
50
|
+
end
|
51
|
+
|
52
|
+
def <<(s)
|
53
|
+
super CGI.escapeHTML(s)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class FormattedPart < Part
|
58
|
+
attr_reader :options
|
59
|
+
|
60
|
+
def initialize(format, options = {}, *args)
|
61
|
+
@options = options || {}
|
62
|
+
super(format, *args)
|
63
|
+
end
|
64
|
+
|
65
|
+
def formatted_string
|
66
|
+
@formatted_string ||= begin
|
67
|
+
str = simple_string
|
68
|
+
str = @options[:before].call(@format, str, @options) if @options[:before]
|
69
|
+
str
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.process(format, text, options = {})
|
75
|
+
parts = parse_text_parts(format, options, text)
|
76
|
+
string = parts.map { |p| p.formatted_string } * "\n"
|
77
|
+
string.gsub! /\r/, ''
|
78
|
+
if format == :html
|
79
|
+
string.gsub! /code><\/pre>/, "code>\n</pre>" # do this so markdown registers ending </pre>'s as a linebreak
|
80
|
+
string = gfm(string)
|
81
|
+
string = process_markdown(string)
|
82
|
+
string.gsub! /\s+<\/code>/, '</code>' # clear linebreak preceding closing <code>
|
83
|
+
end
|
84
|
+
if options[:after]
|
85
|
+
extract_tag(string, :pre, :code) do |str|
|
86
|
+
str.replace options[:after].call(format, str, options)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
if format == :html
|
90
|
+
if options[:white_list]
|
91
|
+
string = options[:white_list].sanitize(string)
|
92
|
+
end
|
93
|
+
string = process_tidy(string)
|
94
|
+
end
|
95
|
+
string.strip!
|
96
|
+
format == :html ? "<div>#{string}</div>" : string
|
97
|
+
end
|
98
|
+
|
99
|
+
CODEBLOCK_RE = /^@@@( ([a-z]+)\s*)?$/
|
100
|
+
|
101
|
+
# Parse a string into a given array of [CodeBlcok, FormattedBlock, CodeBlock, FormattedBlock]
|
102
|
+
#
|
103
|
+
# @@@ my code block
|
104
|
+
# @@@ end code block
|
105
|
+
#
|
106
|
+
# Blah blah formatted block
|
107
|
+
#
|
108
|
+
# @@@ my code block
|
109
|
+
# @@@ end code block
|
110
|
+
#
|
111
|
+
# Blah blah formatted block
|
112
|
+
def self.parse_text_parts(format, options, text)
|
113
|
+
parts = []
|
114
|
+
current_part = nil
|
115
|
+
in_code_block = false
|
116
|
+
text.split("\n").each do |line|
|
117
|
+
if line.rstrip =~ CODEBLOCK_RE
|
118
|
+
line.rstrip!
|
119
|
+
if in_code_block
|
120
|
+
parts << current_part
|
121
|
+
current_part = nil
|
122
|
+
else
|
123
|
+
if current_part then parts << current_part end
|
124
|
+
current_part = CodePart.new(format, $2)
|
125
|
+
end
|
126
|
+
in_code_block = !in_code_block
|
127
|
+
else
|
128
|
+
if !in_code_block && current_part.is_a?(CodePart)
|
129
|
+
parts << current_part
|
130
|
+
current_part = nil
|
131
|
+
end
|
132
|
+
current_part ||= FormattedPart.new(format, options)
|
133
|
+
current_part << line
|
134
|
+
end
|
135
|
+
end
|
136
|
+
parts << current_part if current_part
|
137
|
+
parts.compact!
|
138
|
+
parts.each { |p| p.compact! }
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.extract_tag(text, *tag_names)
|
142
|
+
# Extract pre blocks
|
143
|
+
extractions = {}
|
144
|
+
tag_names.each do |tag_name|
|
145
|
+
text.gsub!(%r{<#{tag_name}[^>]*>.*?</#{tag_name}>}m) do |match|
|
146
|
+
md5 = Digest::MD5.hexdigest(match)
|
147
|
+
extractions[md5] = match
|
148
|
+
"{mkd-extraction-#{md5}}"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
yield text
|
152
|
+
# Insert pre block extractions
|
153
|
+
text.gsub!(/\{mkd-extraction-([0-9a-f]{32})\}/) do
|
154
|
+
extractions[$1]
|
155
|
+
end
|
156
|
+
text
|
157
|
+
end
|
158
|
+
|
159
|
+
def self.gfm(text)
|
160
|
+
extract_tag(text, :pre) do |txt|
|
161
|
+
# prevent foo_bar_baz from ending up with an italic word in the middle
|
162
|
+
text.gsub!(/(^(?! {4}|\t)\w+_\w+_\w[\w_]*)/) do |x|
|
163
|
+
x.gsub('_', '\_') if x.split('').sort.to_s[0..1] == '__'
|
164
|
+
end
|
165
|
+
|
166
|
+
# in very clear cases, let newlines become <br /> tags
|
167
|
+
#text.gsub!(/(\A|^$\n)(^\w[^\n]*\n)(^\w[^\n]*$)+/m) do |x|
|
168
|
+
# x.gsub(/^(.+)$/, "\\1 ")
|
169
|
+
text.gsub!(/^[\w\<][^\n]*\n+/) do |x|
|
170
|
+
x =~ /\n{2}/ ? x : (x.strip!; x << " \n")
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
begin
|
176
|
+
gem 'rdiscount', '>= 1.2.7.1'
|
177
|
+
require 'rdiscount'
|
178
|
+
def self.process_markdown(text)
|
179
|
+
RDiscount.new(text).to_html
|
180
|
+
end
|
181
|
+
rescue Gem::LoadError
|
182
|
+
puts "No RDiscount gem found. `gem install rdiscount`."
|
183
|
+
def self.process_markdown(text)
|
184
|
+
text
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
begin
|
189
|
+
gem 'tidy'
|
190
|
+
require 'tidy'
|
191
|
+
Tidy.path = ENV['TIDY_PATH'] unless ENV['TIDY_PATH'].blank?
|
192
|
+
def self.process_tidy(text)
|
193
|
+
return text unless Tidy.path
|
194
|
+
Tidy.open(:show_body_only => true, :input_encoding => :utf8) do |tidy|
|
195
|
+
tidy.options.new_inline_tags = "video"
|
196
|
+
tidy.clean(text)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
rescue Gem::LoadError
|
200
|
+
puts "No Tidy gem found. `gem install tidy`. Don't forget to set Tidy.path."
|
201
|
+
def self.process_tidy(text)
|
202
|
+
text
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Used to configure model formatting for a specific class. See ModelFormatting::Init
|
2
|
+
class ModelFormatting::Config
|
3
|
+
attr_reader :white_list, :attributes, :context, :before_callback, :after_callback
|
4
|
+
|
5
|
+
def initialize(white_list, attributes, context)
|
6
|
+
@before_callback = nil
|
7
|
+
@after_callback = nil
|
8
|
+
@white_list = white_list
|
9
|
+
@attributes = attributes
|
10
|
+
@context = context
|
11
|
+
end
|
12
|
+
|
13
|
+
def before(&block)
|
14
|
+
@before_callback = block
|
15
|
+
end
|
16
|
+
|
17
|
+
def after(&block)
|
18
|
+
@after_callback = block
|
19
|
+
end
|
20
|
+
|
21
|
+
# replace vars in the string with a symbolized key of the same name in the context.
|
22
|
+
def replace_vars(string, context)
|
23
|
+
string.gsub!(/:([a-z_]+)/) { |m| $1 && $1.size > 0 && context[$1.to_sym] }; string
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module ModelFormatting::Init
|
2
|
+
def self.setup_on(base)
|
3
|
+
base.extend self
|
4
|
+
end
|
5
|
+
|
6
|
+
# class Foo < ActiveRecord::Base
|
7
|
+
# formats :body => :formatted_body do
|
8
|
+
# # add more attributes
|
9
|
+
# attributes[:title] = :full_title
|
10
|
+
#
|
11
|
+
# # add model methods to add to the processing context
|
12
|
+
# context << :project_id
|
13
|
+
#
|
14
|
+
# # modify the sanitizer
|
15
|
+
# white_list.allowed_tags << 'form'
|
16
|
+
# white_list.allowed_attributes << 'class'
|
17
|
+
#
|
18
|
+
# # add a callback for before html/markdown is processed
|
19
|
+
# before do |format, text, options|
|
20
|
+
#
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # add a callback for after html/markdown is processed
|
24
|
+
# after do |format, text, options|
|
25
|
+
#
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
def formats(*args, &block)
|
31
|
+
unless respond_to?(:model_formatting_attributes)
|
32
|
+
# use all these attributes instead of a single ModelFormatting::Config because
|
33
|
+
# it's easier to support subclassing.
|
34
|
+
class_inheritable_accessor :model_formatting_attributes,
|
35
|
+
:model_formatting_white_list, :model_formatting_context,
|
36
|
+
:model_formatting_before_callback, :model_formatting_after_callback
|
37
|
+
send :include, ModelFormatting::InstanceMethods
|
38
|
+
self.model_formatting_context = []
|
39
|
+
self.model_formatting_attributes = {}
|
40
|
+
self.model_formatting_white_list = HTML::WhiteListSanitizer.new
|
41
|
+
before_save :format_content_with_model_formatting
|
42
|
+
end
|
43
|
+
|
44
|
+
model_formatting_attributes.update args.extract_options!
|
45
|
+
args.each do |field|
|
46
|
+
model_formatting_attributes[field] = "formatted_#{field}"
|
47
|
+
end
|
48
|
+
|
49
|
+
if block
|
50
|
+
config = ModelFormatting::Config.new(model_formatting_white_list, model_formatting_attributes, model_formatting_context)
|
51
|
+
config.instance_eval &block
|
52
|
+
self.model_formatting_before_callback = config.before_callback if config.before_callback
|
53
|
+
self.model_formatting_after_callback = config.after_callback if config.after_callback
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ModelFormatting
|
2
|
+
module InstanceMethods
|
3
|
+
protected
|
4
|
+
def format_content_with_model_formatting
|
5
|
+
self.class.model_formatting_attributes.each do |original, formatted|
|
6
|
+
text = send(original)
|
7
|
+
data = \
|
8
|
+
if text.blank?
|
9
|
+
''
|
10
|
+
else
|
11
|
+
options = {:white_list => model_formatting_white_list,
|
12
|
+
:before => model_formatting_before_callback,
|
13
|
+
:after => model_formatting_after_callback}
|
14
|
+
model_formatting_context.inject(options) do |o, attribute|
|
15
|
+
o.update attribute => send(attribute)
|
16
|
+
end
|
17
|
+
ModelFormatting.process(:html, text, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
send("#{formatted}=", data)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ModelFormatting::Init.setup_on ActiveRecord::Base
|
data/tender.patch
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
Only in .: .git
|
2
|
+
diff -ur ./lib/model_formatting/instance_methods.rb ../tender/vendor/plugins/model_formatting/lib/model_formatting/instance_methods.rb
|
3
|
+
--- ./lib/model_formatting/instance_methods.rb 2010-07-09 10:29:14.000000000 -0700
|
4
|
+
+++ ../tender/vendor/plugins/model_formatting/lib/model_formatting/instance_methods.rb 2010-06-14 13:45:10.000000000 -0700
|
5
|
+
@@ -2,13 +2,12 @@
|
6
|
+
module InstanceMethods
|
7
|
+
protected
|
8
|
+
def format_content_with_model_formatting
|
9
|
+
- model_formatting_attributes.each do |original, formatted|
|
10
|
+
+ self.class.model_formatting_attributes.each do |original, formatted|
|
11
|
+
text = send(original)
|
12
|
+
data = \
|
13
|
+
if text.blank?
|
14
|
+
''
|
15
|
+
else
|
16
|
+
- text.strip!
|
17
|
+
options = {:white_list => model_formatting_white_list,
|
18
|
+
:before => model_formatting_before_callback,
|
19
|
+
:after => model_formatting_after_callback}
|
20
|
+
diff -ur ./lib/model_formatting.rb ../tender/vendor/plugins/model_formatting/lib/model_formatting.rb
|
21
|
+
--- ./lib/model_formatting.rb 2010-07-09 11:29:32.000000000 -0700
|
22
|
+
+++ ../tender/vendor/plugins/model_formatting/lib/model_formatting.rb 2010-02-19 14:49:00.000000000 -0800
|
23
|
+
@@ -138,11 +138,11 @@
|
24
|
+
parts.each { |p| p.compact! }
|
25
|
+
end
|
26
|
+
|
27
|
+
- def self.extract_regex(text, *regexes)
|
28
|
+
+ def self.extract_tag(text, *tag_names)
|
29
|
+
# Extract pre blocks
|
30
|
+
extractions = {}
|
31
|
+
- regexes.each do |regex|
|
32
|
+
- text.gsub!(regex) do |match|
|
33
|
+
+ tag_names.each do |tag_name|
|
34
|
+
+ text.gsub!(%r{<#{tag_name}[^>]*>.*?</#{tag_name}>}m) do |match|
|
35
|
+
md5 = Digest::MD5.hexdigest(match)
|
36
|
+
extractions[md5] = match
|
37
|
+
"{mkd-extraction-#{md5}}"
|
38
|
+
@@ -156,16 +156,6 @@
|
39
|
+
text
|
40
|
+
end
|
41
|
+
|
42
|
+
- def self.tag_name_to_regex(name)
|
43
|
+
- %r{<#{name}[^>]*>.*?</#{name}>}m
|
44
|
+
- end
|
45
|
+
-
|
46
|
+
- def self.extract_tag(text, *tag_names)
|
47
|
+
- extract_regex text, *tag_names.map { |n| tag_name_to_regex(n) } do |text|
|
48
|
+
- yield text
|
49
|
+
- end
|
50
|
+
- end
|
51
|
+
-
|
52
|
+
def self.gfm(text)
|
53
|
+
extract_tag(text, :pre) do |txt|
|
54
|
+
# prevent foo_bar_baz from ending up with an italic word in the middle
|
55
|
+
@@ -183,13 +173,13 @@
|
56
|
+
end
|
57
|
+
|
58
|
+
begin
|
59
|
+
- gem 'rdiscount', '~> 1.2.7.1'
|
60
|
+
+ gem 'rdiscount', '>= 1.2.7.1'
|
61
|
+
require 'rdiscount'
|
62
|
+
def self.process_markdown(text)
|
63
|
+
RDiscount.new(text).to_html
|
64
|
+
end
|
65
|
+
rescue Gem::LoadError
|
66
|
+
- puts "No RDiscount gem found. `gem install rdiscount -v '~>1.2.7.1'`."
|
67
|
+
+ puts "No RDiscount gem found. `gem install rdiscount`."
|
68
|
+
def self.process_markdown(text)
|
69
|
+
text
|
70
|
+
end
|
71
|
+
@@ -212,4 +202,4 @@
|
72
|
+
text
|
73
|
+
end
|
74
|
+
end
|
75
|
+
-end
|
76
|
+
+end
|
77
|
+
|
78
|
+
Only in .: tender.patch
|
79
|
+
diff -ur ./test/formatting_test.rb ../tender/vendor/plugins/model_formatting/test/formatting_test.rb
|
80
|
+
--- ./test/formatting_test.rb 2010-07-09 10:29:14.000000000 -0700
|
81
|
+
+++ ../tender/vendor/plugins/model_formatting/test/formatting_test.rb 2010-02-19 14:49:00.000000000 -0800
|
82
|
+
@@ -38,6 +38,13 @@
|
83
|
+
record.save
|
84
|
+
record.formatted_body.should == %(<div><p>booya</p></div>)
|
85
|
+
end
|
86
|
+
+
|
87
|
+
+ it "preserves leading spaces in code blocks" do
|
88
|
+
+ record = Simple.new
|
89
|
+
+ record.body = " code\n more code\n\nnot code\n\n"
|
90
|
+
+ record.save
|
91
|
+
+ record.formatted_body.should == %(<div><pre><code>code\nmore code</code></pre>\n\n<p>not code</p></div>)
|
92
|
+
+ end
|
93
|
+
end
|
94
|
+
|
95
|
+
class Post < Base
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
module ModelFormatting
|
4
|
+
class Test < Test::Unit::TestCase
|
5
|
+
class Base < Struct.new(:body, :formatted_body, :title, :title_html, :bio, :full_bio)
|
6
|
+
ModelFormatting::Init.setup_on self
|
7
|
+
class_inheritable_accessor :before_save_callback
|
8
|
+
|
9
|
+
def self.before_save(method = nil)
|
10
|
+
if method
|
11
|
+
self.before_save_callback = method
|
12
|
+
else
|
13
|
+
before_save_callback
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def save
|
18
|
+
send self.class.before_save
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Simple < Base
|
23
|
+
formats :body
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "Simple with formatting" do
|
27
|
+
it "has attribute from #formats arguments" do
|
28
|
+
Simple.model_formatting_attributes[:body].should == "formatted_body"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "sets before_save callback" do
|
32
|
+
Simple.before_save.should == :format_content_with_model_formatting
|
33
|
+
end
|
34
|
+
|
35
|
+
it "formats all fields" do
|
36
|
+
record = Simple.new
|
37
|
+
record.body = 'booya'
|
38
|
+
record.save
|
39
|
+
record.formatted_body.should == %(<div><p>booya</p></div>)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "preserves leading spaces in code blocks" do
|
43
|
+
record = Simple.new
|
44
|
+
record.body = " code\n more code\n\nnot code\n\n"
|
45
|
+
record.save
|
46
|
+
record.formatted_body.should == %(<div><pre><code>code\nmore code</code></pre>\n\n<p>not code</p></div>)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Post < Base
|
51
|
+
formats :body, :title => :title_html do
|
52
|
+
attributes[:bio] = :full_bio
|
53
|
+
|
54
|
+
white_list.allowed_tags << 'table'
|
55
|
+
|
56
|
+
before do |format, text, options|
|
57
|
+
text.reverse!
|
58
|
+
text
|
59
|
+
end
|
60
|
+
|
61
|
+
after do |format, text, options|
|
62
|
+
"(#{text.strip})"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "Post with formatting" do
|
68
|
+
it "has attribute from #formats arguments" do
|
69
|
+
Post.model_formatting_attributes[:body].should == "formatted_body"
|
70
|
+
end
|
71
|
+
|
72
|
+
it "has attribute added from #formats options hash" do
|
73
|
+
Post.model_formatting_attributes[:title].should == :title_html
|
74
|
+
end
|
75
|
+
|
76
|
+
it "has attribute added from #attributes options hash" do
|
77
|
+
Post.model_formatting_attributes[:bio].should == :full_bio
|
78
|
+
end
|
79
|
+
|
80
|
+
it "has white list sanitizer that allows table tags" do
|
81
|
+
Post.model_formatting_white_list.allowed_tags.should include('table')
|
82
|
+
end
|
83
|
+
|
84
|
+
it "sets before_save callback" do
|
85
|
+
Post.before_save.should == :format_content_with_model_formatting
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "being saved" do
|
89
|
+
before :all do
|
90
|
+
@record = Post.new
|
91
|
+
@record.body = 'booya'
|
92
|
+
@record.title = 'wtf'
|
93
|
+
@record.save
|
94
|
+
end
|
95
|
+
|
96
|
+
it "formats #body" do
|
97
|
+
@record.formatted_body.should == %(<div>(<p>ayoob</p>)</div>)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "formats #title" do
|
101
|
+
@record.title_html.should == %(<div>(<p>ftw</p>)</div>)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "formats #bio" do
|
105
|
+
@record.full_bio.should == ''
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class ChildPost < Post
|
111
|
+
formats do
|
112
|
+
attributes.delete :bio
|
113
|
+
|
114
|
+
before do |format, text, options|
|
115
|
+
text.upcase!
|
116
|
+
text
|
117
|
+
end
|
118
|
+
|
119
|
+
after do |format, text, options|
|
120
|
+
text
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "ChildPost with formatting" do
|
126
|
+
it "has attribute from #formats arguments" do
|
127
|
+
ChildPost.model_formatting_attributes[:body].should == "formatted_body"
|
128
|
+
end
|
129
|
+
|
130
|
+
it "has attribute added from #formats options hash" do
|
131
|
+
ChildPost.model_formatting_attributes[:title].should == :title_html
|
132
|
+
end
|
133
|
+
|
134
|
+
it "removes attribute added from superclass#attributes options hash" do
|
135
|
+
ChildPost.model_formatting_attributes.keys.should_not include(:bio)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "sets before_save callback" do
|
139
|
+
ChildPost.before_save.should == :format_content_with_model_formatting
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "being saved" do
|
143
|
+
before :all do
|
144
|
+
@record = ChildPost.new
|
145
|
+
@record.body = 'booya'
|
146
|
+
@record.bio = 'wtf'
|
147
|
+
@record.save
|
148
|
+
end
|
149
|
+
|
150
|
+
it "formats #body" do
|
151
|
+
@record.formatted_body.should == %(<div><p>BOOYA</p></div>)
|
152
|
+
end
|
153
|
+
|
154
|
+
it "skips #bio" do
|
155
|
+
@record.full_bio.should == nil
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class ModelFormattingTest < Test::Unit::TestCase
|
4
|
+
it "parses simple string into array of single FormattedBlock" do
|
5
|
+
parts = ModelFormatting.parse_text_parts(nil, nil, 'foo')
|
6
|
+
parts.size.should == 1
|
7
|
+
parts.first.class.should == ModelFormatting::FormattedPart
|
8
|
+
parts.first.should == 'foo'
|
9
|
+
end
|
10
|
+
|
11
|
+
it "parses empty code block into array of single CodeBlock" do
|
12
|
+
parts = ModelFormatting.parse_text_parts(nil, nil, "@@@ foo\n@@@")
|
13
|
+
parts.size.should == 1
|
14
|
+
parts.first.class.should == ModelFormatting::CodePart
|
15
|
+
parts.first.should == ''
|
16
|
+
end
|
17
|
+
|
18
|
+
it "parses simple code block into array of single CodeBlock" do
|
19
|
+
parts = ModelFormatting.parse_text_parts(nil, nil, "@@@ foo\nbar\n@@@")
|
20
|
+
parts.size.should == 1
|
21
|
+
parts.first.class.should == ModelFormatting::CodePart
|
22
|
+
parts.first.should == 'bar'
|
23
|
+
end
|
24
|
+
|
25
|
+
it "parses formatted block followed by code block into array of parts" do
|
26
|
+
parts = ModelFormatting.parse_text_parts(nil, nil, "foo \n@@@ foo\nbar\n@@@")
|
27
|
+
parts.size.should == 2
|
28
|
+
parts.map { |p| p.class }.should == [ModelFormatting::FormattedPart, ModelFormatting::CodePart]
|
29
|
+
parts.map { |p| p.simple_string }.should == ["foo ", "bar"]
|
30
|
+
end
|
31
|
+
|
32
|
+
it "parses code block followed by formatted block into array of parts" do
|
33
|
+
parts = ModelFormatting.parse_text_parts(nil, nil, "@@@ foo\nbar\n@@@\n foo \n")
|
34
|
+
parts.size.should == 2
|
35
|
+
parts.map { |p| p.class }.should == [ModelFormatting::CodePart, ModelFormatting::FormattedPart]
|
36
|
+
parts.map { |p| p.simple_string }.should == ["bar", " foo "]
|
37
|
+
end
|
38
|
+
|
39
|
+
it "parses formatted block followed by unfinished code block" do
|
40
|
+
parts = ModelFormatting.parse_text_parts(nil, nil, "foo \n@@@ foo\nbar\nbaz")
|
41
|
+
parts.size.should == 2
|
42
|
+
parts.map { |p| p.class }.should == [ModelFormatting::FormattedPart, ModelFormatting::CodePart]
|
43
|
+
parts.map { |p| p.simple_string }.should == ["foo ", "bar\nbaz"]
|
44
|
+
end
|
45
|
+
|
46
|
+
it "parses mixed blocks" do
|
47
|
+
parts = ModelFormatting.parse_text_parts(nil, nil, "foo \n@@@ foo\nbar\nbaz\n@@@\n\nblah blah")
|
48
|
+
parts.size.should == 3
|
49
|
+
parts.map { |p| p.class }.should == [ModelFormatting::FormattedPart, ModelFormatting::CodePart, ModelFormatting::FormattedPart]
|
50
|
+
parts.map { |p| p.simple_string }.should == ["foo ", "bar\nbaz", "\nblah blah"]
|
51
|
+
end
|
52
|
+
|
53
|
+
it "#replace_vars replaces variables in a string with a given context" do
|
54
|
+
ModelFormatting::Config.new(nil, nil, nil).replace_vars("a:abc/:d_e_f/:foo!", :abc => 'bc', :d_e_f => '-').should == "abc/-/!"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "links and encodes urls correctly" do
|
58
|
+
ModelFormatting.process(:html, "a *b* \n[Whoo](http://entp.com?a=1&b=2)").should == %(<div><p>a <em>b</em><br/>\n<a href="http://entp.com?a=1&b=2">Whoo</a></p></div>)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "converts @@@ to code blocks" do
|
62
|
+
ModelFormatting.process(:html, "<a>foo</a>\n\n@@@\n<a>bar</a>\n@@@\n\n@@@\nbaz\n@@@\n\n@@@ wah wah \n \n").should == %(<div><p><a>foo</a></p>\n\n<pre><code><a>bar</a></code>\n</pre>\n\n\n\n\n<pre><code>baz</code>\n</pre>\n\n\n<p>@@@ wah wah</p></div>)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "converts @@@ with params to code blocks" do
|
66
|
+
ModelFormatting.process(:html, "foo\n@@@ ninja\nbar\n@@@\n@@@\nbaz\n@@@\n@@@ wah wah \n \n").should == %(<div><p>foo<br/>\n</p>\n\n<pre><code class=\"ninja\">bar</code>\n</pre>\n\n\n<pre><code>baz</code>\n</pre>\n\n\n<p>@@@ wah wah</p></div>)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "fixes irregular number of @@@'s" do
|
70
|
+
ModelFormatting.process(:html, "foo\n@@@\nbar\n@@@\n@@@\nbaz\n@@@\n@@@ wah wah \n \n@@@").should == %(<div><p>foo<br/>\n</p>\n\n<pre><code>bar</code>\n</pre>\n\n\n<pre><code>baz</code>\n</pre>\n\n\n<p>@@@ wah wah</p>\n\n<pre><code></code>\n</pre></div>)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "converts @@@ with params to code blocks with text format" do
|
74
|
+
ModelFormatting.process(:text, "foo\n@@@ ninja\nbar\n@@@\n@@@\nbaz\n@@@\n@@@ wah wah \n \n").should == %(foo\nbar\nbaz\n@@@ wah wah)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "fixes irregular number of @@@'s with text format" do
|
78
|
+
ModelFormatting.process(:text, "foo\n@@@\nbar\n@@@\n@@@\nbaz\n@@@\n@@@ wah wah \n \n@@@").should == %(foo\nbar\nbaz\n@@@ wah wah)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "treats linebreaks correctly" do
|
82
|
+
ModelFormatting.process(:html, "Line breaks should not be treated as\nnew paragraphs. They are not paragraphs.\n\nHowever, when a line is skipped, that is a paragraph.\nGMail, and basically every comment or submission form on the \nweb work this way.").should == \
|
83
|
+
"<div><p>Line breaks should not be treated as<br/>\nnew paragraphs. They are not paragraphs.</p>\n\n<p>However, when a line is skipped, that is a paragraph.<br/>\nGMail, and basically every comment or submission form on the<br/>\nweb work this way.</p></div>"
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "GFM" do
|
87
|
+
it "does not touch single underscores inside words" do
|
88
|
+
assert_equal "foo_bar", ModelFormatting.gfm("foo_bar")
|
89
|
+
end
|
90
|
+
|
91
|
+
it "does not touch underscores in code blocks" do
|
92
|
+
assert_equal " foo_bar_baz", ModelFormatting.gfm(" foo_bar_baz")
|
93
|
+
end
|
94
|
+
|
95
|
+
it "does not touch underscores in pre blocks" do
|
96
|
+
assert_equal "<pre>\nfoo_bar_baz\n</pre>", ModelFormatting.gfm("<pre>\nfoo_bar_baz\n</pre>")
|
97
|
+
end
|
98
|
+
|
99
|
+
it "escapes two or more underscores inside words" do
|
100
|
+
assert_equal "foo\\_bar\\_baz", ModelFormatting.gfm("foo_bar_baz")
|
101
|
+
end
|
102
|
+
|
103
|
+
it "turns newlines into br tags in simple cases" do
|
104
|
+
assert_equal "foo \nbar", ModelFormatting.gfm("foo\nbar")
|
105
|
+
end
|
106
|
+
|
107
|
+
it "converts newlines in all groups" do
|
108
|
+
assert_equal "apple \npear \norange \nbanana\n\nruby \npython \nerlang \njavascript",
|
109
|
+
ModelFormatting.gfm("apple\npear\norange\nbanana\n\nruby\npython\nerlang\njavascript")
|
110
|
+
end
|
111
|
+
|
112
|
+
it "does not not convert newlines in lists" do
|
113
|
+
assert_equal "# foo\n# bar", ModelFormatting.gfm("# foo\n# bar")
|
114
|
+
assert_equal "* foo\n* bar", ModelFormatting.gfm("* foo\n* bar")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'action_controller'
|
5
|
+
require 'action_view'
|
6
|
+
require 'model_formatting'
|
7
|
+
require 'model_formatting/init'
|
8
|
+
require 'model_formatting/config'
|
9
|
+
require 'model_formatting/instance_methods'
|
10
|
+
require 'context'
|
11
|
+
require 'matchy'
|
12
|
+
|
13
|
+
begin
|
14
|
+
require 'ruby-debug'
|
15
|
+
Debugger.start
|
16
|
+
rescue LoadError
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: model_formatting
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- ENTP
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-07-09 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rdiscount
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 9
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 5
|
33
|
+
- 5
|
34
|
+
version: 1.5.5
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: actionpack
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 1
|
46
|
+
segments:
|
47
|
+
- 2
|
48
|
+
- 2
|
49
|
+
- 3
|
50
|
+
version: 2.2.3
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: activerecord
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 1
|
62
|
+
segments:
|
63
|
+
- 2
|
64
|
+
- 2
|
65
|
+
- 3
|
66
|
+
version: 2.2.3
|
67
|
+
type: :runtime
|
68
|
+
version_requirements: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: activesupport
|
71
|
+
prerelease: false
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 1
|
78
|
+
segments:
|
79
|
+
- 2
|
80
|
+
- 2
|
81
|
+
- 3
|
82
|
+
version: 2.2.3
|
83
|
+
type: :runtime
|
84
|
+
version_requirements: *id004
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: tidy
|
87
|
+
prerelease: false
|
88
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 23
|
94
|
+
segments:
|
95
|
+
- 1
|
96
|
+
- 1
|
97
|
+
- 2
|
98
|
+
version: 1.1.2
|
99
|
+
type: :runtime
|
100
|
+
version_requirements: *id005
|
101
|
+
- !ruby/object:Gem::Dependency
|
102
|
+
name: jeremymcanally-context
|
103
|
+
prerelease: false
|
104
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
hash: 1
|
110
|
+
segments:
|
111
|
+
- 0
|
112
|
+
- 5
|
113
|
+
- 5
|
114
|
+
version: 0.5.5
|
115
|
+
type: :development
|
116
|
+
version_requirements: *id006
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: jeremymcanally-matchy
|
119
|
+
prerelease: false
|
120
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ~>
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
hash: 27
|
126
|
+
segments:
|
127
|
+
- 0
|
128
|
+
- 1
|
129
|
+
- 0
|
130
|
+
version: 0.1.0
|
131
|
+
type: :development
|
132
|
+
version_requirements: *id007
|
133
|
+
description:
|
134
|
+
email: company@entp.com
|
135
|
+
executables: []
|
136
|
+
|
137
|
+
extensions: []
|
138
|
+
|
139
|
+
extra_rdoc_files:
|
140
|
+
- README
|
141
|
+
files:
|
142
|
+
- Rakefile
|
143
|
+
- README
|
144
|
+
- tender.patch
|
145
|
+
- lib/model_formatting/config.rb
|
146
|
+
- lib/model_formatting/init.rb
|
147
|
+
- lib/model_formatting/instance_methods.rb
|
148
|
+
- lib/model_formatting.rb
|
149
|
+
- rails/init.rb
|
150
|
+
- test/formatting_test.rb
|
151
|
+
- test/model_formatting_test.rb
|
152
|
+
- test/test_helper.rb
|
153
|
+
has_rdoc: true
|
154
|
+
homepage: http://github.com/entp
|
155
|
+
licenses: []
|
156
|
+
|
157
|
+
post_install_message:
|
158
|
+
rdoc_options: []
|
159
|
+
|
160
|
+
require_paths:
|
161
|
+
- lib
|
162
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
163
|
+
none: false
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
hash: 3
|
168
|
+
segments:
|
169
|
+
- 0
|
170
|
+
version: "0"
|
171
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
172
|
+
none: false
|
173
|
+
requirements:
|
174
|
+
- - ">="
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
hash: 3
|
177
|
+
segments:
|
178
|
+
- 0
|
179
|
+
version: "0"
|
180
|
+
requirements: []
|
181
|
+
|
182
|
+
rubyforge_project:
|
183
|
+
rubygems_version: 1.3.7
|
184
|
+
signing_key:
|
185
|
+
specification_version: 3
|
186
|
+
summary: Automatically format model attributes using rdiscount and Tender/Lighthouse extensions.
|
187
|
+
test_files: []
|
188
|
+
|