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,64 @@
1
+ module Tumblargh
2
+ module Renderer
3
+
4
+ class Base
5
+
6
+ class << self
7
+
8
+ # Define a simple tag on the block.
9
+ # Name being tag name, and optionally the attibute/method to call
10
+ # on the context. If the second argument is left off, it'll just use the tag name.
11
+ def contextual_tag(name, attribute=nil)
12
+ class_eval do
13
+ define_method name do
14
+ context.send(attribute || name)
15
+ end
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+
22
+ attr_reader :node
23
+ attr_accessor :context
24
+
25
+ def initialize(node, context)
26
+ @node = node
27
+ @context = context
28
+ end
29
+
30
+ def context_post
31
+ real_post = context
32
+ while not real_post.is_a?(::Tumblargh::Resource::Post)
33
+ real_post = real_post.context
34
+ end
35
+
36
+ real_post
37
+ end
38
+
39
+ def escape_html(str)
40
+ CGI.escapeHTML(str)
41
+ end
42
+
43
+ def escape_url(url)
44
+ CGI.escape(url)
45
+ end
46
+
47
+ def strip_html(str)
48
+ str.gsub(/<\/?[^>]*>/, '')
49
+ end
50
+
51
+ def render
52
+ end
53
+
54
+ alias_method :to_s, :render
55
+
56
+ def method_missing(method, *arguments)
57
+ # puts "mm #{method} - #{self.class.name} -- context is: #{context.class.name}"
58
+ raise "Can't find anything to do with '#{method}'" if context.nil?
59
+ context.send(method, *arguments)
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,22 @@
1
+ module Tumblargh
2
+ module Renderer
3
+ module Blocks
4
+ class Answer < Post
5
+ contextual_tag :question
6
+ contextual_tag :answer
7
+
8
+ def asker
9
+ if asking_name == 'anonymous'
10
+ 'Anonymous'
11
+ else
12
+ "<a href='#{asking_url}'>#{asking_name}</a>"
13
+ end
14
+ end
15
+
16
+ def asker_portrait_url(size)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,70 @@
1
+ module Tumblargh
2
+ module Renderer
3
+ module Blocks
4
+ class Audio < Post
5
+
6
+ def audio_player(color=:default)
7
+ html = context.player
8
+
9
+ return html if color == :default
10
+
11
+ map = { :white => 'FFFFFF', :grey => 'CCCCCC', :black => '000000' }
12
+ color = map[color] unless map[color].nil?
13
+
14
+ html.gsub(/color=[A-Z]{6}/i, "color=#{color}")
15
+ end
16
+
17
+ def audio_player_white
18
+ audio_player(:white)
19
+ end
20
+
21
+ def audio_player_grey
22
+ audio_player(:grey)
23
+ end
24
+
25
+ def audio_player_black
26
+ audio_player_black(:black)
27
+ end
28
+
29
+ # def raw_audio_url
30
+ # context.player.match(/audio_file=([^&]+)/)[1] rescue nil
31
+ # end
32
+
33
+ contextual_tag :raw_audio_url, :audio_url
34
+ contextual_tag :play_count, :plays
35
+
36
+ # TODO
37
+ def formatted_play_count
38
+ play_count.to_s.reverse.scan(/.{1,3}/).join(',').reverse
39
+ end
40
+
41
+ def play_count_with_label
42
+ num = formatted_play_count
43
+ "#{num} play#{num == 1 ? '' : 's'}"
44
+ end
45
+
46
+ end
47
+
48
+ class AlbumArt < Base
49
+ should_render_unless_blank :album_art_url
50
+ contextual_tag :album_art_url, :album_art
51
+ end
52
+
53
+ class Artist < Base
54
+ should_render_unless_blank :artist
55
+ contextual_tag :artist
56
+ end
57
+
58
+ class Album < Base
59
+ should_render_unless_blank :album
60
+ contextual_tag :album
61
+ end
62
+
63
+ class TrackName < Base
64
+ should_render_unless_blank :track_name
65
+ contextual_tag :track_name
66
+ end
67
+
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,35 @@
1
+ module Tumblargh
2
+ module Renderer
3
+ module Blocks
4
+ class Base < Renderer::Base
5
+
6
+ class << self
7
+ attr_accessor :should_render_if
8
+ alias_method :should_render_unless_blank, :should_render_if=
9
+ end
10
+
11
+
12
+ def should_render?
13
+ if defined?(@should_render_if)
14
+ val = send(@should_render_if)
15
+ return !(val || val.nil? || val.blank?)
16
+ end
17
+
18
+ true
19
+ end
20
+
21
+ def render
22
+ return '' unless should_render?
23
+
24
+ sig, type, *nodes = node
25
+
26
+ res = nodes.map do |n|
27
+ Renderer.factory(n, self).render
28
+ end
29
+
30
+ " #{res.join('')} "
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,62 @@
1
+ # require 'action_view/helpers/date_helper'
2
+
3
+ module Tumblargh
4
+ module Renderer
5
+ module Blocks
6
+
7
+ # Rendered for all posts.
8
+ # Always wrap dates in this block so they will be properly hidden on non-post pages.
9
+ class Date < Base
10
+
11
+ MAP = {
12
+ # Tumblr tag => strftime symbol
13
+ :day_of_month => '-d',
14
+ :day_of_month_with_zero => :d,
15
+ :day_of_week => :A,
16
+ :short_day_of_week => :a,
17
+ :day_of_week_number => :u,
18
+ :day_of_year => '-j',
19
+ :week_of_year => '-V',
20
+ :month => :B,
21
+ :short_month => :b,
22
+ :month_number => '-m',
23
+ :month_number_with_zero => :m
24
+ }
25
+
26
+ MAP.each_pair do |tag, sym|
27
+ define_method tag do
28
+ context.date.strftime("%#{sym}")
29
+ end
30
+ end
31
+
32
+ def day_of_month_suffix
33
+ day_of_month.ordinalize
34
+ end
35
+
36
+ def year
37
+ date.year
38
+ end
39
+
40
+ def short_year
41
+ year.to_s[2..4]
42
+ end
43
+
44
+ def time_ago
45
+ # ActionView::Helpers::DateHelper::time_ago_in_words(date)
46
+ "1 day ago"
47
+ end
48
+
49
+ end
50
+
51
+ # Rendered for posts that are the first to be listed for a given day.
52
+ class NewDayDate < Base
53
+ end
54
+
55
+ # Rendered for subsequent posts listed for a given day.
56
+ class SameDayDate < Base
57
+ end
58
+
59
+ end
60
+ end
61
+ end
62
+
@@ -0,0 +1,65 @@
1
+ module Tumblargh
2
+ module Renderer
3
+ module Blocks
4
+ # TODO: Impl.
5
+ # Rendered if there is a "previous" page (newer posts) to navigate to.
6
+ class PreviousPage < Base
7
+ def previous_page
8
+ end
9
+ end
10
+
11
+ # Rendered if there is a "next" page (older posts) to navigate to.
12
+ class NextPage < Base
13
+ def next_page
14
+ end
15
+ end
16
+
17
+ # Rendered if there is a "previous" or "next" page.
18
+ class Pagination < Base
19
+ end
20
+
21
+ # undocumented??
22
+ class PermalinkPagination < Base
23
+ end
24
+
25
+ class PreviousPost < Base
26
+ def previous_post
27
+ end
28
+ end
29
+
30
+ class NextPost < Base
31
+ def next_post
32
+ end
33
+ end
34
+
35
+ # {block:JumpPagination length="5"} {/block:JumpPagination}
36
+ # Rendered for each page greater than the current page minus one-half length up to current page plus one-half length.
37
+ class JumpPagination < Base
38
+ end
39
+
40
+ # Rendered when jump page is not the current page.
41
+ class JumpPage < Base
42
+ def url
43
+ end
44
+
45
+ def page_number
46
+ end
47
+ end
48
+
49
+ # Rendered when jump page is the current page.
50
+ class CurrentPage < JumpPage
51
+ end
52
+
53
+ # Rendered if Submissions are enabled.
54
+ class SubmissionsEnabled < Base
55
+ end
56
+
57
+ # Rendered if Submissions are enabled.
58
+ class AskEnabled < Base
59
+ def should_render?
60
+ true
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,68 @@
1
+ module Tumblargh
2
+ module Renderer
3
+ module Blocks
4
+
5
+ # Rendered on permalink pages if this post has notes.
6
+ class PostNotes < Base
7
+ def should_render?
8
+ context.permalink? && note_count > 0
9
+ end
10
+
11
+ def note_count
12
+ context_post.note_count || 0
13
+ end
14
+
15
+ def post_notes
16
+ buf = ['<ol class="notes">']
17
+
18
+ # TODO: Support notes with_commentary
19
+ buf << context_post.notes.map do |note|
20
+ classes = "note without_commentary #{note.type}"
21
+ action = case note.type
22
+ when 'like'
23
+ 'liked this'
24
+ when 'reblog'
25
+ 'reblogged this from somewhere?'
26
+ end
27
+
28
+ <<-eos
29
+ <li class='#{classes}'>
30
+ <a href="#{note.blog_url}" title="" rel="nofollow">
31
+ <img src="http://assets.tumblr.com/images/default_avatar_16.gif" class="avatar" alt="">
32
+ </a>
33
+
34
+ <span class="action">
35
+ <a href="#{note.blog_url}" title="" rel="nofollow">#{note.blog_name}</a>
36
+ #{action}
37
+ </span>
38
+
39
+ <div class="clear"></div>
40
+ </li>
41
+ eos
42
+ end.join("\n")
43
+
44
+ buf << '</ol>'
45
+
46
+ buf.join ''
47
+ end
48
+ end
49
+
50
+ # Rendered if this post has notes.
51
+ # Always wrap note counts in this block so they will be properly hidden on non-post pages.
52
+ class NoteCount < Base
53
+ def should_render?
54
+ note_count > 0
55
+ end
56
+
57
+ def note_count
58
+ context_post.note_count || 0
59
+ end
60
+
61
+ def note_count_with_label
62
+ count = note_count
63
+ "#{ count } note#{ count == 1 ? '' : 's' }"
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,50 @@
1
+ module Tumblargh
2
+ module Renderer
3
+ module Blocks
4
+ # Posts Loop
5
+ #
6
+ # {block:Posts} is executed once for each post. Some post related tags can
7
+ # exist outside of a `type` block, such as {Title} or {Permalink}, so
8
+ # they should be defined here
9
+ class Posts < Base
10
+
11
+ contextual_tag :post_id, :id
12
+ contextual_tag :post_type, :type
13
+ contextual_tag :title
14
+ contextual_tag :caption
15
+
16
+ def permalink
17
+ url = context.post_url
18
+ url.gsub(/^http:\/\/[^\/]+/, '')
19
+ end
20
+
21
+ def permalink?
22
+ context.permalink?
23
+ end
24
+
25
+ def post_notes_url
26
+ # http://bikiniatoll.tumblr.com/notes/1377511430/vqS0xw8sm
27
+ "/notes/#{context.id}/"
28
+ end
29
+
30
+ def reblog_url
31
+ "/reblog/#{context.reblog_key}"
32
+ end
33
+
34
+ def render
35
+ if context.is_a? Resource::Post
36
+ super
37
+ else
38
+ posts = permalink? ? [context.posts.first] : context.posts
39
+
40
+ posts.map do |post|
41
+ post.context = self
42
+ self.class.new(node, post).render
43
+ end.flatten.join('')
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+ end