myobie-turbine-core 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +6 -0
- data/Rakefile +13 -0
- data/VERSION.yml +4 -0
- data/lib/ext.rb +63 -0
- data/lib/importers/json_importer.rb +14 -0
- data/lib/importers/text_importer.rb +113 -0
- data/lib/post_type.rb +362 -0
- data/lib/types/article.rb +10 -0
- data/lib/types/audio.rb +10 -0
- data/lib/types/chat.rb +29 -0
- data/lib/types/link.rb +26 -0
- data/lib/types/photo.rb +9 -0
- data/lib/types/quote.rb +27 -0
- data/lib/types/review.rb +14 -0
- data/lib/types/video.rb +10 -0
- data/spec/post_type_spec.rb +174 -0
- data/spec/post_types/chat_spec.rb +25 -0
- data/spec/post_types/link_spec.rb +25 -0
- data/spec/post_types/quote_spec.rb +35 -0
- data/spec/post_types/review_spec.rb +30 -0
- data/spec/spec.opts +0 -0
- data/spec/spec_helper.rb +60 -0
- metadata +79 -0
data/README
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gemspec|
|
4
|
+
gemspec.name = "turbine-core"
|
5
|
+
gemspec.summary = "TODO"
|
6
|
+
gemspec.email = "nathan@myobie.com"
|
7
|
+
gemspec.homepage = "http://github.com/myobie/turbine-core"
|
8
|
+
gemspec.description = "TODO"
|
9
|
+
gemspec.authors = ["Nathan Herald"]
|
10
|
+
end
|
11
|
+
rescue LoadError
|
12
|
+
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
13
|
+
end
|
data/VERSION.yml
ADDED
data/lib/ext.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
class String
|
2
|
+
def slugify
|
3
|
+
self.dup.slugify!
|
4
|
+
end
|
5
|
+
|
6
|
+
def slugify!
|
7
|
+
self.gsub!(/[^\x00-\x7F]+/, '') # Remove non-ASCII (e.g. diacritics).
|
8
|
+
self.gsub!(/[^a-z0-9\-_\+]+/i, '-') # Turn non-slug chars into the separator.
|
9
|
+
self.gsub!(/-{2,}/, '-') # No more than one of the separator in a row.
|
10
|
+
self.gsub!(/^-|-$/, '') # Remove leading/trailing separator.
|
11
|
+
self.downcase!
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def make_attr
|
16
|
+
self.downcase.to_sym
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Symbol
|
21
|
+
def html
|
22
|
+
(self.to_s + '_html').to_sym
|
23
|
+
end
|
24
|
+
|
25
|
+
def make_attr
|
26
|
+
self.to_s.downcase.to_sym
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Array
|
31
|
+
def make_attrs
|
32
|
+
self.collect! { |a| a.to_s.downcase.to_sym }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
### class instance vars that inherit down to children
|
37
|
+
module ClassLevelInheritableAttributes
|
38
|
+
|
39
|
+
PRIMITIVES = [NilClass, TrueClass, FalseClass, Fixnum, Float]
|
40
|
+
|
41
|
+
def cattr_inheritable(*args)
|
42
|
+
@cattr_inheritable_attrs ||= [:cattr_inheritable_attrs]
|
43
|
+
@cattr_inheritable_attrs += args
|
44
|
+
args.each do |arg|
|
45
|
+
class_eval %(
|
46
|
+
class << self; attr_accessor :#{arg}; end
|
47
|
+
)
|
48
|
+
end
|
49
|
+
@cattr_inheritable_attrs
|
50
|
+
end
|
51
|
+
|
52
|
+
def inherited(subclass)
|
53
|
+
@all_children ||= []
|
54
|
+
@cattr_inheritable_attrs.each do |inheritable_attribute|
|
55
|
+
instance_var = "@#{inheritable_attribute}"
|
56
|
+
current_value = instance_variable_get(instance_var)
|
57
|
+
current_value = current_value.dup unless PRIMITIVES.include?(current_value.class)
|
58
|
+
subclass.instance_variable_set(instance_var, current_value)
|
59
|
+
end
|
60
|
+
@all_children << subclass
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
class TextImporter
|
2
|
+
|
3
|
+
attr_accessor :remainder, :result, :type
|
4
|
+
|
5
|
+
def initialize(type)
|
6
|
+
@type = type
|
7
|
+
@result = []
|
8
|
+
@remainder = ''
|
9
|
+
@primary = nil
|
10
|
+
@heading = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def import(text)
|
14
|
+
pull_pairs(text)
|
15
|
+
eval_primary_field
|
16
|
+
|
17
|
+
@result
|
18
|
+
end
|
19
|
+
|
20
|
+
def pull_pairs(text)
|
21
|
+
last_pair_found = false
|
22
|
+
|
23
|
+
text.each_line do |line|
|
24
|
+
|
25
|
+
unless last_pair_found
|
26
|
+
possible_pair = detect_pair(line)
|
27
|
+
if possible_pair and possible_pair.captures.length == 2
|
28
|
+
key = possible_pair.captures[0].downcase.to_sym
|
29
|
+
value = possible_pair.captures[1]
|
30
|
+
number_of_lines = value.count("\n") + 1
|
31
|
+
@result << { key => value.strip }
|
32
|
+
next # don't put this in the remainder
|
33
|
+
else
|
34
|
+
last_pair_found = true
|
35
|
+
end#of if
|
36
|
+
end
|
37
|
+
|
38
|
+
@remainder << line # keep this around, it might be useful
|
39
|
+
end#of each_line
|
40
|
+
|
41
|
+
@remainder.strip!
|
42
|
+
end#of parse_pairs
|
43
|
+
|
44
|
+
def detect_pair(line)
|
45
|
+
line.strip.match(/(^[A-Za-z0-9_]+):(.+)/)
|
46
|
+
end
|
47
|
+
|
48
|
+
def eval_primary_field
|
49
|
+
unless @remainder.blank? || @type.primary_field.blank?
|
50
|
+
@primary = @remainder
|
51
|
+
|
52
|
+
eval_heading_field unless @type.heading_field.blank?
|
53
|
+
|
54
|
+
save_primary
|
55
|
+
save_heading
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def eval_heading_field
|
60
|
+
# check for an <h1> at the beginning of the primary and if it's there pull it out for the heading
|
61
|
+
possible_hone = remainder.match(/(.+)\n=+\n*|^# (.+)\w*\n*/)
|
62
|
+
|
63
|
+
caps = []
|
64
|
+
caps = possible_hone.captures.compact unless possible_hone.blank?
|
65
|
+
|
66
|
+
# if it's there, then parse it out of the remainder
|
67
|
+
if !possible_hone.blank? and caps.length == 1
|
68
|
+
@heading = caps.first
|
69
|
+
|
70
|
+
### Remove heading from text
|
71
|
+
@primary = @primary.
|
72
|
+
strip.
|
73
|
+
gsub(/^#{possible_hone.captures.first}\n=+\n*/, '').
|
74
|
+
gsub(/^# #{possible_hone.captures.first}\w*\n*/, '').
|
75
|
+
strip
|
76
|
+
end#of if
|
77
|
+
end#of eval_heading_field
|
78
|
+
|
79
|
+
def save_primary
|
80
|
+
unless @primary.blank?
|
81
|
+
# see if a pair already exists for the primary_field
|
82
|
+
existing_primary = @result.select { |pair| pair.keys.first == @type.primary_field }
|
83
|
+
existing_primary = existing_primary.first
|
84
|
+
|
85
|
+
# get rid of the primary_field if it's already there
|
86
|
+
@result.delete(existing_primary)
|
87
|
+
|
88
|
+
# if there was any existing, prepend it to remanding
|
89
|
+
@primary = existing_primary.to_s + @primary unless existing_primary.blank?
|
90
|
+
|
91
|
+
# append a new pair for the fields
|
92
|
+
@result << { @type.primary_field => @primary } unless @primary.blank?
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def save_heading
|
97
|
+
unless @heading.blank?
|
98
|
+
# see if a pair already exists for the primary_field
|
99
|
+
existing_heading = @result.select { |pair| pair.keys.first == @type.heading_field }
|
100
|
+
existing_heading = existing_heading.first
|
101
|
+
|
102
|
+
# get rid of the primary_field if it's already there
|
103
|
+
@result.delete(existing_heading)
|
104
|
+
|
105
|
+
# if there was any existing, prepend it to remanding
|
106
|
+
@heading = existing_heading.to_s + @heading unless existing_heading.blank?
|
107
|
+
|
108
|
+
# append a new pair for the fields
|
109
|
+
@result << { @type.heading_field => @heading } unless @heading.blank?
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
data/lib/post_type.rb
ADDED
@@ -0,0 +1,362 @@
|
|
1
|
+
require 'rdiscount' # markdown
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'uuidtools'
|
4
|
+
|
5
|
+
class PostType
|
6
|
+
extend ClassLevelInheritableAttributes
|
7
|
+
include Extlib::Hook
|
8
|
+
|
9
|
+
DEFAULT_FIELDS = [:published_at, :status, :slug, :trackbacks, :type, :tags]
|
10
|
+
|
11
|
+
# vars to inherit down
|
12
|
+
cattr_inheritable :fields_list, :allowed_fields_list, :required_fields_list, :primary_field, :heading_field,
|
13
|
+
:specials_blocks, :defaults_blocks, :only_declared_fields, :always_use_uuid,
|
14
|
+
:truncate_slugs, :markdown_fields
|
15
|
+
|
16
|
+
# defaults for class instance inheritable vars
|
17
|
+
@fields_list = []
|
18
|
+
@allowed_fields_list = []
|
19
|
+
@required_fields_list = []
|
20
|
+
@primary_field = nil
|
21
|
+
@heading_field = nil
|
22
|
+
@specials_blocks = {}
|
23
|
+
@defaults_blocks = {}
|
24
|
+
@only_declared_fields = true
|
25
|
+
@always_use_uuid = false
|
26
|
+
@truncate_slugs = true
|
27
|
+
@markdown_fields = []
|
28
|
+
|
29
|
+
|
30
|
+
### cattr_accessor
|
31
|
+
@@preferred_order = []
|
32
|
+
|
33
|
+
def self.preferred_order
|
34
|
+
@@preferred_order
|
35
|
+
end
|
36
|
+
def self.preferred_order=(new_order)
|
37
|
+
@@preferred_order = new_order
|
38
|
+
end
|
39
|
+
### cattr_accessor
|
40
|
+
|
41
|
+
attr_accessor :content # where everything is stored
|
42
|
+
|
43
|
+
### basic setup methods for types of posts
|
44
|
+
def self.fields(*list) # NOTE: this is a replacing function, not addititve like the others
|
45
|
+
self.fields_list = list.make_attrs
|
46
|
+
self.allowed_fields_list = [DEFAULT_FIELDS, list.make_attrs].flatten.uniq
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.allow(*list)
|
50
|
+
self.allowed_fields_list = [self.allowed_fields_list, list.make_attrs].flatten.uniq
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.required(*list)
|
54
|
+
self.required_fields_list = [
|
55
|
+
self.required_fields_list,
|
56
|
+
list.make_attrs.reject { |l| !fields_list.include? l }
|
57
|
+
].flatten.uniq
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.primary(field)
|
61
|
+
field = field.make_attr
|
62
|
+
|
63
|
+
if fields_list.include? field
|
64
|
+
self.primary_field = field
|
65
|
+
markdown field # primary is a markdown field by default
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.heading(field)
|
70
|
+
field = field.make_attr
|
71
|
+
self.heading_field = field if fields_list.include? field
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.special(field, &block)
|
75
|
+
field = field.make_attr
|
76
|
+
self.specials_blocks[field.make_attr] = block if fields_list.include? field
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.default(field, &block)
|
80
|
+
field = field.make_attr
|
81
|
+
self.defaults_blocks[field.make_attr] = block if fields_list.include? field
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.dynamic(field, &block)
|
85
|
+
field = field.make_attr
|
86
|
+
self.dynamic_blocks[field] = block
|
87
|
+
allow(field)
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.markdown(*list)
|
91
|
+
self.markdown_fields = [
|
92
|
+
self.markdown_fields,
|
93
|
+
list.make_attrs.reject { |l| !fields_list.include? l }
|
94
|
+
].flatten.uniq
|
95
|
+
|
96
|
+
self.allowed_fields_list = [
|
97
|
+
self.allowed_fields_list,
|
98
|
+
self.markdown_fields.collect { |m| m.html }
|
99
|
+
].flatten.uniq
|
100
|
+
end
|
101
|
+
|
102
|
+
def set_attr(key, value)
|
103
|
+
key = key.make_attr
|
104
|
+
|
105
|
+
unless value.blank?
|
106
|
+
@content[key] = value
|
107
|
+
|
108
|
+
if self.class.markdown_fields.include?(key)
|
109
|
+
markdown = Markdown.new @content[key].strip
|
110
|
+
@content[key.html] = markdown.to_html.strip
|
111
|
+
else
|
112
|
+
@content.delete(key.html)
|
113
|
+
end
|
114
|
+
else
|
115
|
+
@content.delete(key)
|
116
|
+
@content.delete(key.html)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def set_default(key, value)
|
121
|
+
set_attr(key, value) if blank_attr?(key)
|
122
|
+
end
|
123
|
+
|
124
|
+
def blank_attr?(key)
|
125
|
+
get_attr(key).blank?
|
126
|
+
end
|
127
|
+
|
128
|
+
alias :get_attr? :blank_attr?
|
129
|
+
|
130
|
+
def get_attr(key, html = true)
|
131
|
+
key = key.make_attr
|
132
|
+
|
133
|
+
if html && self.class.markdown_fields.include?(key)
|
134
|
+
@content[key.html]
|
135
|
+
else
|
136
|
+
@content[key]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def delete_attr(key)
|
141
|
+
set_attr(key.make_attr, nil)
|
142
|
+
end
|
143
|
+
|
144
|
+
alias :remove_attr :delete_attr
|
145
|
+
alias :del_attr :delete_attr
|
146
|
+
|
147
|
+
# can be overriden to provide auto detection of type from a block of text
|
148
|
+
#
|
149
|
+
# Examples:
|
150
|
+
# def self.detect?(text)
|
151
|
+
# has_keys? text, :title, :body
|
152
|
+
# end
|
153
|
+
#
|
154
|
+
# def self.detect?(text)
|
155
|
+
# has_required? text
|
156
|
+
# end
|
157
|
+
#
|
158
|
+
# def self.detect?(text)
|
159
|
+
# has_one_or_more? text, :me
|
160
|
+
# end
|
161
|
+
#
|
162
|
+
def self.detect?(text)
|
163
|
+
false
|
164
|
+
end
|
165
|
+
|
166
|
+
# useful for detection
|
167
|
+
def self.has_keys?(text, *fields)
|
168
|
+
needed = fields.make_attrs
|
169
|
+
get_pairs_count(text, needed).length == needed.length
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.has_more_than_one?(text, field)
|
173
|
+
has_more_than? text, field, 1
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.has_one_or_more?(text, field)
|
177
|
+
has_more_than? text, field, 0
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.has_more_than?(text, field, amount)
|
181
|
+
get_pairs_count(text, [field]).length > amount
|
182
|
+
end
|
183
|
+
|
184
|
+
def self.get_pairs(text)
|
185
|
+
TextImporter.new(self).import(text)
|
186
|
+
end
|
187
|
+
|
188
|
+
def self.get_pairs_count(text, fields)
|
189
|
+
pairs = get_pairs(text)
|
190
|
+
pairs.reject { |pair| !fields.include?(pair.keys.first) }
|
191
|
+
end
|
192
|
+
|
193
|
+
def self.has_required?(text)
|
194
|
+
has_keys? text, *self.required_fields_list
|
195
|
+
end
|
196
|
+
|
197
|
+
# runs through the list of children looking for one that will work
|
198
|
+
def self.auto_detect(text)
|
199
|
+
list = self.preferred_order.blank? ? @all_children : self.preferred_order
|
200
|
+
|
201
|
+
list.each { |l| return l.new(text) if l.detect?(text) }
|
202
|
+
end
|
203
|
+
|
204
|
+
def content=(stuff) # !> method redefined; discarding old content=
|
205
|
+
@content = { :type => self.class.name.to_s }
|
206
|
+
import(stuff)
|
207
|
+
eval_specials
|
208
|
+
eval_defaults
|
209
|
+
parse_tags unless blank_attr?(:tags)
|
210
|
+
generate_slug if get_attr?(:slug)
|
211
|
+
@content
|
212
|
+
end#of content=
|
213
|
+
|
214
|
+
def valid? # TODO: this doesn't work if there are no required fields and the slug is not unique
|
215
|
+
v = true
|
216
|
+
|
217
|
+
if self.class.required_fields_list.blank?
|
218
|
+
v = false unless self.class.required_fields_list.reject { |item| !get_attr(item).blank? }.blank?
|
219
|
+
end
|
220
|
+
|
221
|
+
v = false unless slug_is_unique
|
222
|
+
|
223
|
+
v
|
224
|
+
end
|
225
|
+
|
226
|
+
def initialize(text = nil)
|
227
|
+
if text
|
228
|
+
self.content = text
|
229
|
+
# sanitize_content_fields
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def save
|
234
|
+
if valid?
|
235
|
+
truncate_slug if self.class.truncate_slugs
|
236
|
+
fill_default_fields
|
237
|
+
send_to_storage
|
238
|
+
else
|
239
|
+
false
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# TODO: how to determine the type of stuff? (text, json, yaml, image, video, photo, pdf, generic download file (can lookup type of file for icon if needed))
|
244
|
+
def import(stuff, type = :text)
|
245
|
+
importer = Kernel.const_get(type.to_s.camel_case+'Importer').new(self.class)
|
246
|
+
|
247
|
+
# The result sent back by an importer is either:
|
248
|
+
# Array:
|
249
|
+
# [{ :one => 'stuff' }, { :two => 'stuff' }]
|
250
|
+
# Hash:
|
251
|
+
# { :one => 'stuff', :two => 'stuff' }
|
252
|
+
result = importer.import(stuff)
|
253
|
+
|
254
|
+
case result
|
255
|
+
when Array
|
256
|
+
commit_array(result)
|
257
|
+
when Hash
|
258
|
+
commit_hash(result)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def commit_hash(pairs_hash)
|
263
|
+
pairs_hash.each do |key, value|
|
264
|
+
set_attr(key, value)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def commit_array(pairs_array)
|
269
|
+
pairs_array.each do |pairs_hash|
|
270
|
+
commit_hash(pairs_hash)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def eval_defaults
|
275
|
+
if valid?
|
276
|
+
self.class.defaults_blocks.each do |key, block|
|
277
|
+
set_default(key, self.instance_eval(&block))
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def eval_specials
|
283
|
+
self.class.specials_blocks.each do |key, block|
|
284
|
+
unless get_attr(key).blank?
|
285
|
+
set_attr(key, block.call(get_attr(key)))
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def parse_tags
|
291
|
+
if get_attr(:tags).class == String
|
292
|
+
tags_array = get_attr(:tags).split(',').collect { |t| t.strip }
|
293
|
+
set_attr(:tags, tags_array)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def sanitize_content_fields
|
298
|
+
@content.reject! { |key, value| !self.class.allowed_fields_list.include?(key) }
|
299
|
+
end
|
300
|
+
|
301
|
+
def send_to_storage # send to the db or whatever
|
302
|
+
false
|
303
|
+
end
|
304
|
+
|
305
|
+
def slug_is_unique # validate uniqueness
|
306
|
+
true
|
307
|
+
end
|
308
|
+
|
309
|
+
def fill_default_fields
|
310
|
+
set_default(:published_at, Time.now.utc)
|
311
|
+
set_default(:status, default_status)
|
312
|
+
end
|
313
|
+
|
314
|
+
def generate_slug # OPTIMIZE: this slug generation is ugly
|
315
|
+
result = ''
|
316
|
+
|
317
|
+
unless self.class.always_use_uuid
|
318
|
+
result = get_attr(self.class.heading_field, false).to_s.dup unless self.class.heading_field.blank?
|
319
|
+
|
320
|
+
if result.blank?
|
321
|
+
result = get_attr(self.class.primary_field, false).to_s.dup unless self.class.primary_field.blank?
|
322
|
+
end
|
323
|
+
|
324
|
+
if result.blank?
|
325
|
+
self.class.required_fields_list.each do |required_field|
|
326
|
+
unless get_attr(required_field).blank?
|
327
|
+
result = get_attr(required_field).to_s.dup
|
328
|
+
break
|
329
|
+
end#of unless
|
330
|
+
end#of each
|
331
|
+
end#of if
|
332
|
+
|
333
|
+
result.slugify!
|
334
|
+
end#of unless
|
335
|
+
|
336
|
+
if result.blank?
|
337
|
+
result = uuid
|
338
|
+
end
|
339
|
+
|
340
|
+
set_attr(:slug, result)
|
341
|
+
end
|
342
|
+
|
343
|
+
def truncate_slug(letter_count = 50)
|
344
|
+
unless get_attr(:slug).blank?
|
345
|
+
new_slug = get_attr(:slug).gsub(/^(.{#{letter_count}})(.*)/) { $1.slugify }
|
346
|
+
set_attr(:slug, new_slug)
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def default_status
|
351
|
+
:published
|
352
|
+
end
|
353
|
+
|
354
|
+
def post_to_trackbacks
|
355
|
+
false
|
356
|
+
end
|
357
|
+
|
358
|
+
def uuid
|
359
|
+
UUID.timestamp_create.to_s
|
360
|
+
end
|
361
|
+
|
362
|
+
end
|
data/lib/types/audio.rb
ADDED
data/lib/types/chat.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
class Chat < PostType
|
2
|
+
fields :transcript
|
3
|
+
required :transcript
|
4
|
+
|
5
|
+
# override commit_pairs to make chat transcripts form the Me: pairs stuff
|
6
|
+
def commit_array(pairs_array)
|
7
|
+
transcript = []
|
8
|
+
|
9
|
+
pairs_array.each do |pairs_hash|
|
10
|
+
|
11
|
+
pairs_hash.each do |key, value|
|
12
|
+
|
13
|
+
unless self.class.allowed_fields_list.include?(key)
|
14
|
+
transcript << { key => value }
|
15
|
+
else
|
16
|
+
set_attr(key, value)
|
17
|
+
end
|
18
|
+
end#of each
|
19
|
+
|
20
|
+
end#of each
|
21
|
+
|
22
|
+
set_attr(:transcript, transcript)
|
23
|
+
|
24
|
+
end#of commit_pairs
|
25
|
+
|
26
|
+
def self.detect?(text)
|
27
|
+
has_one_or_more? text, :me
|
28
|
+
end
|
29
|
+
end#of Chat
|
data/lib/types/link.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
class Link < PostType
|
2
|
+
fields :url, :title, :description
|
3
|
+
required :url
|
4
|
+
primary :description
|
5
|
+
heading :title
|
6
|
+
|
7
|
+
special :url do |link_content|
|
8
|
+
'http://' + link_content.gsub(/^http:\/\//, '')
|
9
|
+
end
|
10
|
+
|
11
|
+
default :title do
|
12
|
+
result = ''
|
13
|
+
|
14
|
+
begin
|
15
|
+
doc = Nokogiri::HTML(open(get_attr(:url)))
|
16
|
+
doc.css('title').each { |t| result = t.content }
|
17
|
+
rescue Exception => e
|
18
|
+
end
|
19
|
+
|
20
|
+
result
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.detect?(text)
|
24
|
+
has_required? text
|
25
|
+
end
|
26
|
+
end
|
data/lib/types/photo.rb
ADDED
data/lib/types/quote.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
class Quote < PostType
|
2
|
+
fields :quote, :source
|
3
|
+
required :quote
|
4
|
+
primary :quote
|
5
|
+
|
6
|
+
special :quote do |quote_content|
|
7
|
+
unless (quote_content =~ /^<blockquote/).nil?
|
8
|
+
doc = Nokogiri::HTML(quote_content)
|
9
|
+
doc.css('blockquote').each { |q| quote_content = q.content }
|
10
|
+
else
|
11
|
+
quote_content = ''
|
12
|
+
end
|
13
|
+
quote_content.strip
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.detect?(text)
|
17
|
+
pairs = get_pairs(text)
|
18
|
+
|
19
|
+
the_quote = pairs.select { |pair| pair.keys.first == :quote }
|
20
|
+
the_quote = the_quote.first || {}
|
21
|
+
|
22
|
+
markdown = Markdown.new(the_quote[:quote])
|
23
|
+
quote_html = markdown.to_html.strip
|
24
|
+
|
25
|
+
!(text =~ /Quote: (.*)/).nil? || !(quote_html =~ /^<blockquote(.+)<\/blockquote>$/m).nil?
|
26
|
+
end
|
27
|
+
end
|
data/lib/types/review.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
class Review < PostType
|
2
|
+
fields :rating, :item, :description
|
3
|
+
required :rating, :item
|
4
|
+
primary :description
|
5
|
+
heading :item
|
6
|
+
|
7
|
+
special :rating do |rating_content|
|
8
|
+
rating_content.to_f
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.detect?(text)
|
12
|
+
has_required? text
|
13
|
+
end
|
14
|
+
end
|
data/lib/types/video.rb
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper.rb')
|
2
|
+
|
3
|
+
describe PostType do
|
4
|
+
|
5
|
+
before do
|
6
|
+
class Yellow; end
|
7
|
+
Object.send(:remove_const, :Yellow)
|
8
|
+
end
|
9
|
+
|
10
|
+
should "have empty content when new" do
|
11
|
+
PostType.new.content.should == nil
|
12
|
+
end
|
13
|
+
|
14
|
+
should "have a preferred order array" do
|
15
|
+
PostType.preferred_order.should == [Video, Audio, Photo, Chat, Review, Link, Quote, Article]
|
16
|
+
end
|
17
|
+
|
18
|
+
should "set fields" do
|
19
|
+
class Yellow < PostType
|
20
|
+
fields :one, :two
|
21
|
+
end
|
22
|
+
|
23
|
+
Yellow.fields_list.should == [:one, :two]
|
24
|
+
end
|
25
|
+
|
26
|
+
should "set allowed fields" do
|
27
|
+
class Yellow < PostType
|
28
|
+
fields :one
|
29
|
+
allow :anyone, :noone
|
30
|
+
end
|
31
|
+
|
32
|
+
Yellow.allowed_fields_list.should == [Yellow::DEFAULT_FIELDS, :one, :anyone, :noone].flatten
|
33
|
+
end
|
34
|
+
|
35
|
+
should "set required fields" do
|
36
|
+
class Yellow < PostType
|
37
|
+
fields :one, :two, :three
|
38
|
+
required :one, :two
|
39
|
+
end
|
40
|
+
|
41
|
+
Yellow.required_fields_list.should == [:one, :two]
|
42
|
+
end
|
43
|
+
|
44
|
+
should "set primary field" do
|
45
|
+
class Yellow < PostType
|
46
|
+
fields :one, :two
|
47
|
+
primary :one
|
48
|
+
end
|
49
|
+
|
50
|
+
Yellow.primary_field.should == :one
|
51
|
+
end
|
52
|
+
|
53
|
+
# should use primary field
|
54
|
+
# should make primary field markdown automatically
|
55
|
+
|
56
|
+
should "set heading field" do
|
57
|
+
class Yellow < PostType
|
58
|
+
fields :one, :two
|
59
|
+
heading :one
|
60
|
+
end
|
61
|
+
|
62
|
+
Yellow.heading_field.should == :one
|
63
|
+
end
|
64
|
+
|
65
|
+
should "use heading field" do
|
66
|
+
class Yellow < PostType
|
67
|
+
fields :one, :two
|
68
|
+
primary :one
|
69
|
+
heading :two
|
70
|
+
end
|
71
|
+
|
72
|
+
y = Yellow.new "Hello Two
|
73
|
+
=========
|
74
|
+
|
75
|
+
This will be in One."
|
76
|
+
|
77
|
+
y.get_attr(:one).should == "<p>This will be in One.</p>"
|
78
|
+
y.get_attr(:two).should == "Hello Two"
|
79
|
+
end
|
80
|
+
|
81
|
+
should "set special values" do
|
82
|
+
class Yellow < PostType
|
83
|
+
fields :plus_one, :two
|
84
|
+
|
85
|
+
special :plus_one do |field_value|
|
86
|
+
field_value + 1
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
Yellow.specials_blocks[:plus_one].should.not.be.blank?
|
91
|
+
end
|
92
|
+
|
93
|
+
should "use special values" do
|
94
|
+
class Yellow < PostType
|
95
|
+
fields :one, :two
|
96
|
+
|
97
|
+
special :one do |field_value|
|
98
|
+
field_value.to_i + 1
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
y = Yellow.new "One: 2\n\nhello"
|
103
|
+
|
104
|
+
y.get_attr(:one).should == 3
|
105
|
+
end
|
106
|
+
|
107
|
+
should "set default values" do
|
108
|
+
class Yellow < PostType
|
109
|
+
fields :one, :two
|
110
|
+
|
111
|
+
default(:one) { 1 }
|
112
|
+
end
|
113
|
+
|
114
|
+
Yellow.defaults_blocks[:one].call.should == 1
|
115
|
+
end
|
116
|
+
|
117
|
+
should "use default values" do
|
118
|
+
class Yellow < PostType
|
119
|
+
fields :one, :two
|
120
|
+
|
121
|
+
default :two do
|
122
|
+
2
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
y = Yellow.new 'hello'
|
127
|
+
|
128
|
+
y.get_attr(:two).should == 2
|
129
|
+
end
|
130
|
+
|
131
|
+
should "use markdown fields" do
|
132
|
+
class Yellow < PostType
|
133
|
+
fields :one, :two
|
134
|
+
markdown :two
|
135
|
+
end
|
136
|
+
|
137
|
+
Yellow.markdown_fields.should == [:two]
|
138
|
+
Yellow.allowed_fields_list.should == [Yellow::DEFAULT_FIELDS, :one, :two, :two_html].flatten
|
139
|
+
end
|
140
|
+
|
141
|
+
should "use markdown fields" do
|
142
|
+
class Yellow < PostType
|
143
|
+
fields :one, :two
|
144
|
+
markdown :two
|
145
|
+
end
|
146
|
+
|
147
|
+
y = Yellow.new "Two: *hahaha*"
|
148
|
+
|
149
|
+
y.get_attr(:two).should == "<p><em>hahaha</em></p>"
|
150
|
+
end
|
151
|
+
|
152
|
+
should "allow setting and getting of an attribute of the content" do
|
153
|
+
class Yellow < PostType; end
|
154
|
+
y = Yellow.new 'stuff'
|
155
|
+
y.set_attr(:first, 'woo hoo')
|
156
|
+
y.get_attr(:first).should == 'woo hoo'
|
157
|
+
end
|
158
|
+
|
159
|
+
should "not keep empty attributes" do
|
160
|
+
class Yellow < PostType
|
161
|
+
fields :one
|
162
|
+
primary :one
|
163
|
+
end
|
164
|
+
|
165
|
+
y = Yellow.new 'stuff'
|
166
|
+
y.set_attr(:one, '')
|
167
|
+
y.get_attr(:one).should.be.nil
|
168
|
+
end
|
169
|
+
|
170
|
+
# should auto_detect ?
|
171
|
+
# should save ?
|
172
|
+
# should generate slug
|
173
|
+
|
174
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../spec_helper.rb')
|
2
|
+
|
3
|
+
describe Chat do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@good_chat = "You: Hello\nMe: Hello back at ya!\nYou: Wanna go eat\nYou: Somehwere\nMe: Yes!"
|
7
|
+
@bad_chat = "Rating: 4\nItem: Dyson\n\nThis should be the description."
|
8
|
+
|
9
|
+
class Yellow; end
|
10
|
+
Object.send(:remove_const, :Yellow)
|
11
|
+
end
|
12
|
+
|
13
|
+
should "detect a well-formed chat" do
|
14
|
+
Chat.detect?(@good_chat).should.be.true
|
15
|
+
end
|
16
|
+
|
17
|
+
should "not detect a malformed chat" do
|
18
|
+
Chat.detect?(@bad_chat).should.be.false
|
19
|
+
end
|
20
|
+
|
21
|
+
should "autodetect a well-formed chat" do
|
22
|
+
PostType.auto_detect(@good_chat).should.be.kind_of Chat
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../spec_helper.rb')
|
2
|
+
|
3
|
+
describe Link do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@good_link = "URL: google.com\n\nThis should be the description."
|
7
|
+
@bad_link = "Rating: 4\nItem: Dyson\n\nThis should be the description."
|
8
|
+
|
9
|
+
class Yellow; end
|
10
|
+
Object.send(:remove_const, :Yellow)
|
11
|
+
end
|
12
|
+
|
13
|
+
should "detect a well-formed link" do
|
14
|
+
Link.detect?(@good_link).should.be.true
|
15
|
+
end
|
16
|
+
|
17
|
+
should "not detect a malformed link" do
|
18
|
+
Link.detect?(@bad_link).should.be.false
|
19
|
+
end
|
20
|
+
|
21
|
+
should "autodetect a well-formed link" do
|
22
|
+
PostType.auto_detect(@good_link).should.be.kind_of Link
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../spec_helper.rb')
|
2
|
+
|
3
|
+
describe Quote do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@good_quote = "> This should be a quote."
|
7
|
+
@good_quote_with_source = "Source: George Washington\n\n> This is what he said."
|
8
|
+
@good_quote_all_pairs = "Quote: Hello\nSource: Me"
|
9
|
+
@bad_quote = "Rating: 4\nItem: Dyson\n\nThis should be the description."
|
10
|
+
|
11
|
+
class Yellow; end
|
12
|
+
Object.send(:remove_const, :Yellow)
|
13
|
+
end
|
14
|
+
|
15
|
+
should "detect a well-formed quote through markdown" do
|
16
|
+
Quote.detect?(@good_quote).should.be.true
|
17
|
+
end
|
18
|
+
|
19
|
+
should "detect a well-formed quote with source" do
|
20
|
+
Quote.detect?(@good_quote_with_source).should.be.true
|
21
|
+
end
|
22
|
+
|
23
|
+
should "detect a well-formed quote if it's all pairs" do
|
24
|
+
Quote.detect?(@good_quote_all_pairs).should.be.true
|
25
|
+
end
|
26
|
+
|
27
|
+
should "not detect a malformed quote" do
|
28
|
+
Quote.detect?(@bad_quote).should.be.false
|
29
|
+
end
|
30
|
+
|
31
|
+
should "autodetect a well-formed quote" do
|
32
|
+
PostType.auto_detect(@good_quote).should.be.kind_of Quote
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../spec_helper.rb')
|
2
|
+
|
3
|
+
describe Review do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@good_review = "Rating: 4\nItem: Dyson\n\nThis should be the description."
|
7
|
+
@bad_review = "You: Hello\nMe: Hello back at ya!\nYou: Wanna go eat\nYou: Somehwere\nMe: Yes!"
|
8
|
+
@almost_good_review = "Rating: 4\n\nThis should be the description."
|
9
|
+
|
10
|
+
class Yellow; end
|
11
|
+
Object.send(:remove_const, :Yellow)
|
12
|
+
end
|
13
|
+
|
14
|
+
should "detect a well-formed review" do
|
15
|
+
Review.detect?(@good_review).should.be.true
|
16
|
+
end
|
17
|
+
|
18
|
+
should "not detect a malformed review" do
|
19
|
+
Review.detect?(@bad_review).should.be.false
|
20
|
+
end
|
21
|
+
|
22
|
+
should "not detect an almost good review" do
|
23
|
+
Review.detect?(@almost_good_review).should.be.false
|
24
|
+
end
|
25
|
+
|
26
|
+
should "autodetect a well-formed review" do
|
27
|
+
PostType.auto_detect(@good_review).should.be.kind_of Review
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/spec/spec.opts
ADDED
File without changes
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require 'bacon'
|
3
|
+
require 'mocha'
|
4
|
+
|
5
|
+
require File.expand_path(File.join(__FILE__.split('/spec').first, 'turbine-core.rb'))
|
6
|
+
|
7
|
+
PostType.preferred_order = [Video, Audio, Photo, Chat, Review, Link, Quote, Article]
|
8
|
+
|
9
|
+
### run specs with `bacon spec/**/*.rb spec/*.rb`
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
# from 1 to 10 how likely, 1 being not very likely and 10 being all the time
|
14
|
+
def do_i?(i = 5)
|
15
|
+
rand(500) < 50 * i
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
##
|
22
|
+
# Hash additions.
|
23
|
+
#
|
24
|
+
# From
|
25
|
+
# * http://wincent.com/knowledge-base/Fixtures_considered_harmful%3F
|
26
|
+
# * Neil Rahilly
|
27
|
+
|
28
|
+
class Hash
|
29
|
+
|
30
|
+
##
|
31
|
+
# Filter keys out of a Hash.
|
32
|
+
#
|
33
|
+
# { :a => 1, :b => 2, :c => 3 }.except(:a)
|
34
|
+
# => { :b => 2, :c => 3 }
|
35
|
+
|
36
|
+
def except(*keys)
|
37
|
+
self.reject { |k,v| keys.include?(k || k.to_sym) }
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Override some keys.
|
42
|
+
#
|
43
|
+
# { :a => 1, :b => 2, :c => 3 }.with(:a => 4)
|
44
|
+
# => { :a => 4, :b => 2, :c => 3 }
|
45
|
+
|
46
|
+
def with(overrides = {})
|
47
|
+
self.merge overrides
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Returns a Hash with only the pairs identified by +keys+.
|
52
|
+
#
|
53
|
+
# { :a => 1, :b => 2, :c => 3 }.only(:a)
|
54
|
+
# => { :a => 1 }
|
55
|
+
|
56
|
+
def only(*keys)
|
57
|
+
self.reject { |k,v| !keys.include?(k || k.to_sym) }
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: myobie-turbine-core
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nathan Herald
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-12 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: TODO
|
17
|
+
email: nathan@myobie.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
files:
|
25
|
+
- Rakefile
|
26
|
+
- VERSION.yml
|
27
|
+
- lib/ext.rb
|
28
|
+
- lib/importers/json_importer.rb
|
29
|
+
- lib/importers/text_importer.rb
|
30
|
+
- lib/post_type.rb
|
31
|
+
- lib/types/article.rb
|
32
|
+
- lib/types/audio.rb
|
33
|
+
- lib/types/chat.rb
|
34
|
+
- lib/types/link.rb
|
35
|
+
- lib/types/photo.rb
|
36
|
+
- lib/types/quote.rb
|
37
|
+
- lib/types/review.rb
|
38
|
+
- lib/types/video.rb
|
39
|
+
- spec/post_type_spec.rb
|
40
|
+
- spec/post_types/chat_spec.rb
|
41
|
+
- spec/post_types/link_spec.rb
|
42
|
+
- spec/post_types/quote_spec.rb
|
43
|
+
- spec/post_types/review_spec.rb
|
44
|
+
- spec/spec.opts
|
45
|
+
- spec/spec_helper.rb
|
46
|
+
- README
|
47
|
+
has_rdoc: true
|
48
|
+
homepage: http://github.com/myobie/turbine-core
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options:
|
51
|
+
- --charset=UTF-8
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.2.0
|
70
|
+
signing_key:
|
71
|
+
specification_version: 2
|
72
|
+
summary: TODO
|
73
|
+
test_files:
|
74
|
+
- spec/post_type_spec.rb
|
75
|
+
- spec/post_types/chat_spec.rb
|
76
|
+
- spec/post_types/link_spec.rb
|
77
|
+
- spec/post_types/quote_spec.rb
|
78
|
+
- spec/post_types/review_spec.rb
|
79
|
+
- spec/spec_helper.rb
|