tumblargh 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. data/.gitignore +2 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +15 -0
  4. data/Gemfile.lock +52 -0
  5. data/LICENSE +20 -0
  6. data/README.md +84 -0
  7. data/Rakefile +43 -0
  8. data/VERSION +1 -0
  9. data/examples/confg.ru +18 -0
  10. data/examples/middleman_config.rb +7 -0
  11. data/lib/middleman/features/tumblargh.rb +41 -0
  12. data/lib/rack/tumblargh.rb +51 -0
  13. data/lib/tumblargh.rb +60 -0
  14. data/lib/tumblargh/api.rb +60 -0
  15. data/lib/tumblargh/grammar.rb +560 -0
  16. data/lib/tumblargh/grammar.treetop +42 -0
  17. data/lib/tumblargh/node.rb +14 -0
  18. data/lib/tumblargh/node/base.rb +21 -0
  19. data/lib/tumblargh/node/block.rb +31 -0
  20. data/lib/tumblargh/node/block_end.rb +9 -0
  21. data/lib/tumblargh/node/block_start.rb +22 -0
  22. data/lib/tumblargh/node/literal.rb +9 -0
  23. data/lib/tumblargh/node/root.rb +17 -0
  24. data/lib/tumblargh/node/tag.rb +33 -0
  25. data/lib/tumblargh/parser.rb +96 -0
  26. data/lib/tumblargh/renderer.rb +316 -0
  27. data/lib/tumblargh/renderer/base.rb +64 -0
  28. data/lib/tumblargh/renderer/blocks/answer.rb +22 -0
  29. data/lib/tumblargh/renderer/blocks/audio.rb +70 -0
  30. data/lib/tumblargh/renderer/blocks/base.rb +35 -0
  31. data/lib/tumblargh/renderer/blocks/dates.rb +62 -0
  32. data/lib/tumblargh/renderer/blocks/navigation.rb +65 -0
  33. data/lib/tumblargh/renderer/blocks/notes.rb +68 -0
  34. data/lib/tumblargh/renderer/blocks/posts.rb +50 -0
  35. data/lib/tumblargh/renderer/blocks/reblogs.rb +50 -0
  36. data/lib/tumblargh/renderer/blocks/tags.rb +37 -0
  37. data/lib/tumblargh/renderer/document.rb +70 -0
  38. data/lib/tumblargh/renderer/literal.rb +9 -0
  39. data/lib/tumblargh/renderer/tag.rb +37 -0
  40. data/lib/tumblargh/resource.rb +12 -0
  41. data/lib/tumblargh/resource/base.rb +39 -0
  42. data/lib/tumblargh/resource/blog.rb +49 -0
  43. data/lib/tumblargh/resource/note.rb +8 -0
  44. data/lib/tumblargh/resource/post.rb +63 -0
  45. data/lib/tumblargh/resource/tag.rb +8 -0
  46. data/lib/tumblargh/resource/user.rb +8 -0
  47. data/spec/api_spec.rb +1 -0
  48. data/spec/fixtures/data/staff.tumblr.com-2012-05-06/posts.json +1203 -0
  49. data/spec/fixtures/themes/fluid.html +1138 -0
  50. data/spec/fixtures/themes/solstice.html +392 -0
  51. data/spec/parser_spec.rb +159 -0
  52. data/spec/renderer/blocks/posts_spec.rb +17 -0
  53. data/spec/renderer/document_spec.rb +57 -0
  54. data/spec/resource/post_spec.rb +38 -0
  55. data/spec/resource_spec.rb +23 -0
  56. data/spec/spec_helper.rb +24 -0
  57. data/spec/tumblargh_spec.rb +50 -0
  58. data/tumblargh.gemspec +120 -0
  59. metadata +237 -0
@@ -0,0 +1,42 @@
1
+ grammar Tumblr
2
+
3
+ rule root
4
+ (block / tag / orphan / literal)* <Tumblargh::Node::Root>
5
+ end
6
+
7
+ rule block
8
+ block_start
9
+ (block / tag / orphan / literal)*
10
+ block_end
11
+ <Tumblargh::Node::Block>
12
+ end
13
+
14
+ rule block_start
15
+ '{block:' [^\s}:;]+ '}' space? <Tumblargh::Node::BlockStart>
16
+ end
17
+
18
+ rule block_end
19
+ '{/block:' [^\s}]+ '}' space? <Tumblargh::Node::BlockEnd>
20
+ end
21
+
22
+ rule tag
23
+ '{' tag_name '}' <Tumblargh::Node::Tag>
24
+ end
25
+
26
+ rule tag_name
27
+ ([a-zA-Z0-9]+ ':'? [^\n:/{};\[\]\(\)]*)
28
+ end
29
+
30
+ rule orphan
31
+ '{' !'/' <Tumblargh::Node::Literal>
32
+ end
33
+
34
+ rule literal
35
+ [^{]+ <Tumblargh::Node::Literal>
36
+ end
37
+
38
+ rule space
39
+ [\s]+
40
+ end
41
+
42
+ end
@@ -0,0 +1,14 @@
1
+ module Tumblargh
2
+ module Node
3
+
4
+ autoload :Base, 'tumblargh/node/base'
5
+ autoload :Block, 'tumblargh/node/block'
6
+ autoload :BlockEnd, 'tumblargh/node/block_end'
7
+ autoload :BlockStart, 'tumblargh/node/block_start'
8
+ autoload :HtmlStyleTag, 'tumblargh/node/html_style_tag'
9
+ autoload :Literal, 'tumblargh/node/literal'
10
+ autoload :Root, 'tumblargh/node/root'
11
+ autoload :Tag, 'tumblargh/node/tag'
12
+
13
+ end
14
+ end
@@ -0,0 +1,21 @@
1
+ module Tumblargh
2
+ module Node
3
+
4
+ class Base < Treetop::Runtime::SyntaxNode
5
+
6
+ def type
7
+ @type ||= self.class.name.split('::').last.to_sym
8
+ end
9
+
10
+ def to_tree
11
+ [type, text_value]
12
+ end
13
+
14
+ def to_s
15
+ text_value
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+ module Tumblargh
2
+ module Node
3
+
4
+ class Block < Root
5
+
6
+ def name
7
+ # First node is BlockStart
8
+ elements.first.name
9
+ end
10
+
11
+ def to_tree
12
+ ary = [type, name]
13
+
14
+ # Second node is a Treetop SyntaxNode which holds
15
+ # the rest of the block contents. Extra parse node
16
+ # due to grouping in the block grammar
17
+ elements[1].elements.each do |e|
18
+ if e.respond_to?(:to_tree)
19
+ ary << e.to_tree
20
+ else
21
+ raise ParserError, "Unknown node type '#{e.class.name}' in Block '#{name}'"
22
+ end
23
+ end
24
+
25
+ return ary
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,9 @@
1
+ module Tumblargh
2
+ module Node
3
+
4
+ class BlockEnd < Base
5
+
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,22 @@
1
+ module Tumblargh
2
+ module Node
3
+
4
+ class BlockStart < Base
5
+
6
+ def name
7
+ str = elements[1].text_value
8
+ "#{str[0].upcase}#{str[1..str.size]}"
9
+ end
10
+
11
+ def matching_end
12
+ "{/block:#{name}}"
13
+ end
14
+
15
+ def to_tree
16
+ return [type, name]
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,9 @@
1
+ module Tumblargh
2
+ module Node
3
+
4
+ class Literal < Base
5
+
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ module Tumblargh
2
+ module Node
3
+
4
+ class Root < Base
5
+
6
+ def to_tree
7
+ elements.map(&:to_tree)
8
+ end
9
+
10
+ def to_s
11
+ elements.map(&:to_s).join ''
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ module Tumblargh
2
+ module Node
3
+
4
+ class Tag < Base
5
+ def type
6
+ return @type if defined?(@type)
7
+
8
+ n = name.split(':')
9
+ if n.size == 2
10
+ @type = "#{n.first.camelize.to_sym}Tag"
11
+
12
+ if @type == 'BlockTag'
13
+ raise ParserError, "There's an unclosed block somewhere near `#{name}`"
14
+ end
15
+
16
+ @type
17
+ else
18
+ super
19
+ end
20
+ end
21
+
22
+ def name
23
+ elements[1].text_value
24
+ end
25
+
26
+ def to_tree
27
+ return [type, name]
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,96 @@
1
+ require 'treetop'
2
+ require 'open-uri'
3
+ require 'nokogiri'
4
+
5
+ module Tumblargh
6
+
7
+ class Parser
8
+ grammar_file = File.join(File.dirname(__FILE__), 'grammar')
9
+
10
+ if File.exists?("#{grammar_file}.rb")
11
+ require "#{grammar_file}.rb"
12
+ else
13
+ Treetop.load("#{grammar_file}.treetop")
14
+ end
15
+
16
+ @@parser = TumblrParser.new
17
+
18
+ def initialize(template=nil)
19
+ self.file = template
20
+ end
21
+
22
+ attr_reader :file
23
+
24
+ def file=(file)
25
+ @file = file
26
+ @html = nil
27
+ @structure = nil
28
+ @tree = nil
29
+ @config = nil
30
+ end
31
+
32
+ def html=(html)
33
+ self.file = nil
34
+ @html = html
35
+ end
36
+
37
+ def html
38
+ @html ||= open(@file).read
39
+ end
40
+
41
+ def tree
42
+ @tree ||= parse
43
+ end
44
+
45
+ def options
46
+ @options ||= extract_options
47
+ end
48
+
49
+ def to_s
50
+ parse unless @structure
51
+ @structure.to_s
52
+ end
53
+
54
+ private
55
+
56
+ def parse
57
+ @structure = @@parser.parse(html)
58
+
59
+ if(@structure.nil?)
60
+ puts @@parser.failure_reason
61
+ puts "#{@@parser.failure_line}:#{@@parser.failure_column}"
62
+ raise ParserError, "Parse error at offset: #{@@parser.index}"
63
+ end
64
+
65
+ @tree = @structure.to_tree
66
+ end
67
+
68
+ def extract_options
69
+ opts = {}.with_indifferent_access
70
+
71
+ doc = Nokogiri::HTML(html)
72
+ doc.css('meta[name*=":"]').each do |meta|
73
+ type, variable = meta['name'].downcase.split(':')
74
+ variable.gsub!(/\s/, '')
75
+
76
+ default = meta['content']
77
+
78
+ default = case type
79
+ when "if"
80
+ default == "1" ? true : false
81
+ else
82
+ default
83
+ end
84
+
85
+ opts[type] ||= {}
86
+ opts[type][variable] = default
87
+ end
88
+
89
+ opts
90
+ end
91
+ end
92
+
93
+ class ParserError < StandardError
94
+ end
95
+
96
+ end
@@ -0,0 +1,316 @@
1
+ require 'cgi'
2
+
3
+ require 'tumblargh/renderer/base'
4
+ require 'tumblargh/renderer/document'
5
+ require 'tumblargh/renderer/literal'
6
+ require 'tumblargh/renderer/tag'
7
+
8
+ module Tumblargh
9
+ module Renderer
10
+
11
+ def self.factory(node, context)
12
+ args = []
13
+ base = node.first.to_s
14
+
15
+ if base == 'Block'
16
+ block_name = node[1]
17
+
18
+ if block_name[0..1] == 'If'
19
+ if block_name[2..4] == 'Not'
20
+ args << block_name[5..block_name.size]
21
+ block_name = 'InverseBoolean'
22
+ else
23
+ args << block_name[2..block_name.size]
24
+ block_name = 'Boolean'
25
+ end
26
+ elsif n_post = block_name.match(/Post(\d+)/)
27
+ block_name = 'NumberedPost'
28
+ args << n_post[1].to_i
29
+ end
30
+
31
+ base = "Blocks::#{block_name}"
32
+ end
33
+
34
+ klass_name = "Tumblargh::Renderer::#{base}"
35
+ klass = klass_name.constantize
36
+
37
+ klass.new(node, context, *args)
38
+ end
39
+
40
+
41
+ module Blocks
42
+
43
+ require 'tumblargh/renderer/blocks/base'
44
+
45
+ class Description < Base
46
+ def should_render?
47
+ !description.blank?
48
+ end
49
+ end
50
+
51
+ class NumberedPost < Base
52
+ attr_reader :num
53
+
54
+ def initialize(node, context, *args)
55
+ @num = args[0]
56
+ super(node, context)
57
+ end
58
+
59
+ def should_render?
60
+ num == context.posts.index(context_post) + 1
61
+ end
62
+ end
63
+
64
+
65
+
66
+ # Common post blocks
67
+ class Title < Base
68
+ def should_render?
69
+ !(title.nil? || title.blank?)
70
+ end
71
+ end
72
+
73
+ class Caption < Base
74
+ def should_render?
75
+ !(caption.nil? || caption.blank?)
76
+ end
77
+ end
78
+
79
+ # Base post type
80
+ class Post < Base
81
+ def should_render?
82
+ # TODO Looks like photosets come as type = 'photo'
83
+ self.class.name.split('::').last.downcase == context.type
84
+ end
85
+ end
86
+
87
+ # Source block for Quote posts
88
+ class Source < Base
89
+ def should_render?
90
+ !source.blank?
91
+ end
92
+
93
+ def source
94
+ context.source
95
+ end
96
+ end
97
+
98
+ class Text < Post
99
+ end
100
+
101
+ class Photo < Post
102
+ def photo_url(size=500)
103
+ context.photo_url(size)
104
+ end
105
+
106
+ def photo_alt
107
+ strip_html(context.caption)
108
+ end
109
+ end
110
+
111
+ class Photoset < Photo
112
+ end
113
+
114
+ class Video < Photo
115
+ end
116
+
117
+
118
+
119
+ class Quote < Post
120
+ def quote
121
+ context.text
122
+ end
123
+ end
124
+
125
+ class Chat < Post
126
+ end
127
+
128
+ class Link < Post
129
+ end
130
+
131
+
132
+ # Meta-block for Appearance booleans, like {block:IfSomething}
133
+ class Boolean < Base
134
+ attr_reader :variable
135
+
136
+ def initialize(node, context, *args)
137
+ @variable = args[0]
138
+ super(node, context)
139
+ end
140
+
141
+ def should_render?
142
+ context.boolean(variable.downcase)
143
+ end
144
+ end
145
+
146
+ class InverseBoolean < Boolean
147
+ def should_render?
148
+ ! super
149
+ end
150
+ end
151
+
152
+
153
+ # Rendered on permalink pages. (Useful for displaying the current post's
154
+ # title in the page title)
155
+ class PostTitle < Base
156
+ def should_render?
157
+ false
158
+ end
159
+
160
+ def post_title
161
+ # TODO: Implementation
162
+ end
163
+ end
164
+
165
+ # Identical to {PostTitle}, but will automatically generate a summary
166
+ # if a title doesn't exist.
167
+ class PostSummary < PostTitle
168
+ def post_summary
169
+ # TODO: Implementation
170
+ end
171
+ end
172
+
173
+
174
+
175
+ class ContentSource < Base
176
+ def should_render?
177
+ !source_title.nil?
178
+ end
179
+
180
+ contextual_tag :source_url
181
+ contextual_tag :source_title
182
+
183
+ # TODO: Impl.
184
+ def black_logo_url
185
+ end
186
+
187
+ def logo_width
188
+ end
189
+
190
+ def logo_height
191
+ end
192
+
193
+ end
194
+
195
+ class SourceLogo < Base
196
+ # TODO: Impl.
197
+ end
198
+
199
+ class NoSourceLogo < SourceLogo
200
+ def should_render?
201
+ ! super
202
+ end
203
+ end
204
+
205
+ class HasTags < Base
206
+ def should_render?
207
+ !(tags.nil? || tags.blank?)
208
+ end
209
+ end
210
+
211
+
212
+ # Rendered on index (post) pages.
213
+ class IndexPage < Base
214
+ def should_render?
215
+ ! context.permalink?
216
+ end
217
+ end
218
+
219
+ # Rendered on post permalink pages.
220
+ class PermalinkPage < Base
221
+ def should_render?
222
+ context.permalink?
223
+ end
224
+ end
225
+
226
+ class SearchPage < Base
227
+ def should_render?
228
+ false
229
+ end
230
+ end
231
+
232
+ class NoSearchResults < Base
233
+ def should_render?
234
+ false
235
+ end
236
+ end
237
+
238
+ class TagPage < Base
239
+ def should_render?
240
+ false
241
+ end
242
+ end
243
+
244
+ # Rendered if you have defined any custom pages.
245
+ class HasPages < Base
246
+ # TODO: Implementation
247
+
248
+ def should_render?
249
+ false
250
+ end
251
+ end
252
+
253
+ # Rendered for each custom page.
254
+ class Pages < Base
255
+ # TODO: Implementation
256
+
257
+ def should_render?
258
+ false
259
+ end
260
+
261
+ # custom page url
262
+ def url
263
+ end
264
+
265
+ # custom page name/label
266
+ def label
267
+ end
268
+
269
+ end
270
+
271
+ # Rendered if you have Twitter integration enabled.
272
+ class Twitter < Base
273
+ # TODO: Implementation
274
+
275
+ def should_render?
276
+ false
277
+ end
278
+
279
+ def twitter_username
280
+ end
281
+
282
+ end
283
+
284
+
285
+ # {block:Likes} {/block:Likes} Rendered if you are sharing your likes.
286
+ # {Likes} Standard HTML output of your likes.
287
+ # {Likes limit="5"} Standard HTML output of your last 5 likes.
288
+ # Maximum: 10
289
+ # {Likes width="200"} Standard HTML output of your likes with Audio and Video players scaled to 200-pixels wide.
290
+ # Scale images with CSS max-width or similar.
291
+ # {Likes summarize="100"} Standard HTML output of your likes with text summarize to 100-characters.
292
+ # Maximum: 250
293
+ class Likes < Base
294
+ # TODO: Implementation
295
+
296
+ def should_render?
297
+ false
298
+ end
299
+
300
+ def likes
301
+ end
302
+
303
+ end
304
+
305
+ require 'tumblargh/renderer/blocks/answer'
306
+ require 'tumblargh/renderer/blocks/audio'
307
+ require 'tumblargh/renderer/blocks/dates'
308
+ require 'tumblargh/renderer/blocks/navigation'
309
+ require 'tumblargh/renderer/blocks/notes'
310
+ require 'tumblargh/renderer/blocks/posts'
311
+ require 'tumblargh/renderer/blocks/reblogs'
312
+ require 'tumblargh/renderer/blocks/tags'
313
+
314
+ end
315
+ end
316
+ end