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.
- data/.gitignore +2 -0
- data/.rspec +1 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +52 -0
- data/LICENSE +20 -0
- data/README.md +84 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/examples/confg.ru +18 -0
- data/examples/middleman_config.rb +7 -0
- data/lib/middleman/features/tumblargh.rb +41 -0
- data/lib/rack/tumblargh.rb +51 -0
- data/lib/tumblargh.rb +60 -0
- data/lib/tumblargh/api.rb +60 -0
- data/lib/tumblargh/grammar.rb +560 -0
- data/lib/tumblargh/grammar.treetop +42 -0
- data/lib/tumblargh/node.rb +14 -0
- data/lib/tumblargh/node/base.rb +21 -0
- data/lib/tumblargh/node/block.rb +31 -0
- data/lib/tumblargh/node/block_end.rb +9 -0
- data/lib/tumblargh/node/block_start.rb +22 -0
- data/lib/tumblargh/node/literal.rb +9 -0
- data/lib/tumblargh/node/root.rb +17 -0
- data/lib/tumblargh/node/tag.rb +33 -0
- data/lib/tumblargh/parser.rb +96 -0
- data/lib/tumblargh/renderer.rb +316 -0
- data/lib/tumblargh/renderer/base.rb +64 -0
- data/lib/tumblargh/renderer/blocks/answer.rb +22 -0
- data/lib/tumblargh/renderer/blocks/audio.rb +70 -0
- data/lib/tumblargh/renderer/blocks/base.rb +35 -0
- data/lib/tumblargh/renderer/blocks/dates.rb +62 -0
- data/lib/tumblargh/renderer/blocks/navigation.rb +65 -0
- data/lib/tumblargh/renderer/blocks/notes.rb +68 -0
- data/lib/tumblargh/renderer/blocks/posts.rb +50 -0
- data/lib/tumblargh/renderer/blocks/reblogs.rb +50 -0
- data/lib/tumblargh/renderer/blocks/tags.rb +37 -0
- data/lib/tumblargh/renderer/document.rb +70 -0
- data/lib/tumblargh/renderer/literal.rb +9 -0
- data/lib/tumblargh/renderer/tag.rb +37 -0
- data/lib/tumblargh/resource.rb +12 -0
- data/lib/tumblargh/resource/base.rb +39 -0
- data/lib/tumblargh/resource/blog.rb +49 -0
- data/lib/tumblargh/resource/note.rb +8 -0
- data/lib/tumblargh/resource/post.rb +63 -0
- data/lib/tumblargh/resource/tag.rb +8 -0
- data/lib/tumblargh/resource/user.rb +8 -0
- data/spec/api_spec.rb +1 -0
- data/spec/fixtures/data/staff.tumblr.com-2012-05-06/posts.json +1203 -0
- data/spec/fixtures/themes/fluid.html +1138 -0
- data/spec/fixtures/themes/solstice.html +392 -0
- data/spec/parser_spec.rb +159 -0
- data/spec/renderer/blocks/posts_spec.rb +17 -0
- data/spec/renderer/document_spec.rb +57 -0
- data/spec/resource/post_spec.rb +38 -0
- data/spec/resource_spec.rb +23 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/tumblargh_spec.rb +50 -0
- data/tumblargh.gemspec +120 -0
- 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
|