model_formatting 0.2.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 +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
|
+
|