tumblargh 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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