tumblr-themer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.gitignore +17 -0
  2. data/.irbrc +2 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +9 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +79 -0
  8. data/Rakefile +1 -0
  9. data/bin/tumblr-themer +7 -0
  10. data/lib/tumblr-themer/api.rb +71 -0
  11. data/lib/tumblr-themer/chat_line.rb +19 -0
  12. data/lib/tumblr-themer/cli.rb +56 -0
  13. data/lib/tumblr-themer/html_snippet.rb +25 -0
  14. data/lib/tumblr-themer/photoset_photo.rb +20 -0
  15. data/lib/tumblr-themer/post/audio.rb +35 -0
  16. data/lib/tumblr-themer/post/chat.rb +6 -0
  17. data/lib/tumblr-themer/post/link.rb +7 -0
  18. data/lib/tumblr-themer/post/photo.rb +50 -0
  19. data/lib/tumblr-themer/post/photoset.rb +7 -0
  20. data/lib/tumblr-themer/post/quote.rb +10 -0
  21. data/lib/tumblr-themer/post/text.rb +4 -0
  22. data/lib/tumblr-themer/post/video.rb +26 -0
  23. data/lib/tumblr-themer/post.rb +157 -0
  24. data/lib/tumblr-themer/server.rb +16 -0
  25. data/lib/tumblr-themer/tag.rb +22 -0
  26. data/lib/tumblr-themer/tag_helper.rb +79 -0
  27. data/lib/tumblr-themer/theme.rb +78 -0
  28. data/lib/tumblr-themer/version.rb +3 -0
  29. data/lib/tumblr-themer.rb +28 -0
  30. data/theme_template/config.ru.tt +10 -0
  31. data/theme_template/index.html.tt +26 -0
  32. data/theme_template/posts/chat.html.cc +14 -0
  33. data/theme_template/posts/link.html.tt +8 -0
  34. data/theme_template/posts/photo.html.tt +6 -0
  35. data/theme_template/posts/quote.html.tt +8 -0
  36. data/theme_template/posts/text.html.tt +6 -0
  37. data/theme_template/posts/video.html.tt +8 -0
  38. data/theme_template/tumblr-theme.yml.tt +10 -0
  39. data/tumblr-themer.gemspec +28 -0
  40. metadata +188 -0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.irbrc ADDED
@@ -0,0 +1,2 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__),'lib'))
2
+ require 'tumblr-themer'
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ tumblr-themer
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-1.9.3-p392
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # A sample Gemfile
2
+ source "https://rubygems.org"
3
+
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'wirb'
8
+ gem 'looksee'
9
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Tal Atlas
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # Tumblr Themer
2
+
3
+ A utility for making tumblr themes easier. Generates a framework for the theme and runs a server to allow you to see your theme with real data while you test.
4
+
5
+ ## Installation
6
+
7
+ You must load this gem manually for now, until it's released to rubygems.
8
+
9
+ ## Usage
10
+
11
+ ### Generation
12
+
13
+ Once installed you can generate a base theme in a new folder with
14
+
15
+ $ tumblr-themer new theme_name
16
+
17
+ or have it place the files in the current directory with:
18
+
19
+ $ tumblr-themer new .
20
+
21
+ The folder structure generated is as such:
22
+
23
+ index.html #=> base layout file
24
+ config.ru #=> Rack file to allow for use with pow or any other rack app
25
+ posts/text.html #=> html for text post
26
+ posts/….html #=> html for each post type
27
+ tumblr-themer.yml #=> config file
28
+
29
+ The utility can inject all the post types into the custom tag `{PostsCode}`
30
+ so that you can keep your theme more organized. I'm looking to add more custom partial support in the future.
31
+
32
+ ### Customization
33
+
34
+ The `tumblr-themer.yml` file contains configuration information.
35
+
36
+ The first thing you need is an api key, found at [tumblr's developer site](http://www.tumblr.com/oauth/apps). It's called the OAuth Consumer Key.
37
+
38
+ The second key can be the blog. It's your tumblr base name to get the posts from.
39
+
40
+ Optionally you can provide an array of posts to render.
41
+
42
+ ### Server
43
+
44
+ You can run a server by the command
45
+
46
+ $ tumblr-themer server
47
+
48
+ This starts a web server on localhost:4567.
49
+
50
+ You can use the [Pow](http://pow.cx) utility to run the server as well. In fact this method is the recommended way to use this utility.
51
+
52
+ ### Loading
53
+
54
+ To take the html and put it into your tumblr account you can run
55
+
56
+ $ tumblr-themer copy
57
+
58
+ and it will place the entire theme in your clipboard.
59
+
60
+ Alternately you can have it print out the whole theme with:
61
+
62
+ $ tumblr-themer stdout
63
+
64
+ ## Todo
65
+
66
+ So many things, starting with:
67
+
68
+ * Distinguishig between permalink/listing pages
69
+ * Testing
70
+ * More tag support for various post types
71
+ * Too much to list, please help out with what you want/can
72
+
73
+ ## Contributing
74
+
75
+ 1. Fork it
76
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
77
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
78
+ 4. Push to the branch (`git push origin my-new-feature`)
79
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/tumblr-themer ADDED
@@ -0,0 +1,7 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'tumblr-themer'
5
+ require 'tumblr-themer/cli'
6
+
7
+ TumblrThemer::CLI.start(ARGV)
@@ -0,0 +1,71 @@
1
+ module TumblrThemer::API
2
+ extend self
3
+ attr_writer :base_hostname, :api_key
4
+
5
+ def base_hostname
6
+ name = @base_hostname || settings['blog']
7
+ if name =~ /tumblr\.com/
8
+ name
9
+ else
10
+ "#{name}.tumblr.com"
11
+ end
12
+ end
13
+
14
+ def settings dirname='.'
15
+ file = File.expand_path("#{dirname}/tumblr-theme.yml")
16
+
17
+ unless File.exist?(file)
18
+ raise ConfigFileError, "#{file} not found"
19
+ end
20
+
21
+ data = YAML.load_file(file)
22
+ raise ConfigFileError, "no api_key found in config file" unless data['api_key']
23
+ data
24
+ end
25
+
26
+ def api_key
27
+ @api_key ||= settings['api_key']
28
+ end
29
+
30
+ def posts
31
+ if settings['posts']
32
+ selected_posts
33
+ else
34
+ all_posts['response']
35
+ end
36
+ end
37
+
38
+ def selected_posts
39
+ blog = nil
40
+ posts = []
41
+ settings['posts'].each do |id|
42
+ data = all_posts(:id => id)
43
+ posts << data['response']['posts'].first
44
+ blog ||= data['response']['blog']
45
+ end
46
+
47
+ {
48
+ 'posts' => posts,
49
+ 'blog' => blog
50
+ }
51
+ end
52
+
53
+ def all_posts(params={})
54
+ url = URL.new("http://api.tumblr.com/v2/blog/#{base_hostname}/posts")
55
+ url[:api_key] = api_key
56
+ url[:reblog_info] = true
57
+ url[:id] = params[:id] if params[:id]
58
+ json = url.get.json
59
+
60
+ status = json['meta']['status']
61
+ if status == 401
62
+ raise ConfigFileError, "invalid api_key found in config file"
63
+ elsif status != 200
64
+ raise RequestError, json['meta'].inspect
65
+ end
66
+ json
67
+ end
68
+
69
+ class ConfigFileError < StandardError; end
70
+ class RequestError < StandardError; end
71
+ end
@@ -0,0 +1,19 @@
1
+ class TumblrThemer::ChatLine
2
+ include TumblrThemer::TagHelper
3
+ attr_reader :html, :data, :line_index
4
+
5
+ def initialize html, data, index=0
6
+ @html = TumblrThemer::HtmlSnippet.new(html.dup) if html
7
+ @data = data.dup
8
+ @line_index = index
9
+ end
10
+
11
+ tag('Alt') { line_index%2==0 ? 'odd' : 'even' }
12
+
13
+ block('Label') { boolify(data['label']) }
14
+ tag('Label') { data['label'] }
15
+
16
+ tag('UserNumber') { line_index }
17
+ tag('Name') { data['name'] }
18
+ tag('Line') { data['phrase'] }
19
+ end
@@ -0,0 +1,56 @@
1
+ require 'thor'
2
+ require 'tumblr-themer/server'
3
+
4
+ module TumblrThemer
5
+ class CLI < Thor
6
+ include Thor::Actions
7
+
8
+ def self.source_root
9
+ File.expand_path(File.join(File.dirname(__FILE__),'..','..','theme_template'))
10
+ end
11
+
12
+ class_option :'theme-dir', :type => :string, :default => '.', :aliases => %w{-t}
13
+
14
+ desc 'new', 'make a new theme in a folder of the name'
15
+ def new name='.'
16
+ @name = name
17
+
18
+ files = Dir[File.join(self.class.source_root,'**/*.tt')].collect do |f|
19
+ f.sub(self.class.source_root+'/','')
20
+ end
21
+
22
+ files.each do |source|
23
+ if name == '.' || name == File.basename(Dir.pwd)
24
+ dest = ""
25
+ else
26
+ dest = "#{name}/"
27
+ end
28
+ dest << source.sub(/\.tt$/, '')
29
+
30
+ template source, dest
31
+ end
32
+ end
33
+
34
+ desc 'copy', 'copy the theme into the clipboard'
35
+ def copy
36
+ theme = TumblrThemer::Theme.new(options[:'theme-dir'])
37
+
38
+ IO.popen('pbcopy', 'w') { |f| f << theme.body.to_s }
39
+ end
40
+
41
+ desc 'stdout', 'print theme to stdout'
42
+ def stdout
43
+ theme = TumblrThemer::Theme.new(options[:'theme-dir'])
44
+
45
+ puts theme.body
46
+ end
47
+
48
+ desc 'server', 'run a server that allows you to play with your theme in the browser'
49
+ def server
50
+ TumblrThemer::Server.run!
51
+ end
52
+ end
53
+
54
+ class TemplateBuilder < Thor
55
+ end
56
+ end
@@ -0,0 +1,25 @@
1
+ class TumblrThemer::HtmlSnippet
2
+ attr_reader :str
3
+
4
+ def initialize(str)
5
+ @str = str.dup
6
+ end
7
+
8
+ def tag key, val
9
+ str.gsub! "{#{key}}", val.to_s
10
+ end
11
+
12
+ def block _type, tf=true
13
+ regex = Regexp.new("{block:#{_type}}(.*?){/block:#{_type}}",Regexp::MULTILINE)
14
+
15
+ if tf
16
+ if block_given?
17
+ str.gsub!(regex) {yield($1)}
18
+ else
19
+ str.gsub!(regex, '\1')
20
+ end
21
+ else
22
+ str.gsub!(regex, '')
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,20 @@
1
+ class TumblrThemer::PhotosetPhoto
2
+ include TumblrThemer::TagHelper
3
+ attr_reader :html, :data
4
+
5
+ def initialize html, data, i=0
6
+ @html = TumblrThemer::HtmlSnippet.new(html.dup) if html
7
+ @data = data.dup
8
+ end
9
+
10
+ def photo_size size
11
+ data.find {|p| p['width'] == size}||data.first
12
+ end
13
+
14
+ [500,400,250,100].each do |size|
15
+ tag("PhotoURL-#{size}") { photo_size(size)['url'] }
16
+ tag("PhotoWidth-#{size}") { photo_size(size)['width'] }
17
+ tag("PhotoHeight-#{size}") { photo_size(size)['height'] }
18
+ end
19
+ tag("PhotoURL-75sq") { photo_size(75)['url'] }
20
+ end
@@ -0,0 +1,35 @@
1
+ class TumblrThemer::Post::Audio < TumblrThemer::Post
2
+
3
+ def embed_size size
4
+ data['embed'].gsub(/width=['"](\d+)['"]/,%Q{width="#{size}"})
5
+ end
6
+
7
+ block_tag('Caption')
8
+
9
+ block('AudioEmbed', 'embed')
10
+ [250,400,500,640].each do |size|
11
+ tag("AudioEmbed-#{size}") { embed_size(size) }
12
+ end
13
+ tag("AudioEmbed") { embed_size(500) }
14
+
15
+ block('AudioPlayer','player')
16
+ %w{AudioPlayer AudioPlayerWhite AudioPlayerGrey AudioPlayerBlack}.each do |t|
17
+ tag(t,'player')
18
+ end
19
+
20
+ block_tag('PlayCount','plays')
21
+ tag('FormattedPlayCount') { data['plays'].to_s.reverse.gsub(/...(?=.)/,'\&,').reverse }
22
+ tag('PlayCountWithLabel') do
23
+ num = data['plays']
24
+ str = num.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse
25
+ str << (num == 1 ? 'play' : 'plays')
26
+ end
27
+
28
+ block('AlbumArt', 'album_art')
29
+ tag('AlbumArtURL', 'album_art')
30
+
31
+ block_tag('Artist')
32
+ block_tag('Album')
33
+ block_tag('TrackName')
34
+
35
+ end
@@ -0,0 +1,6 @@
1
+ class TumblrThemer::Post::Chat < TumblrThemer::Post
2
+ block('Title') { boolify(data['title']) }
3
+ tag('Title') { data['title'] }
4
+
5
+ for_each('Lines',TumblrThemer::ChatLine) { data['dialogue'] }
6
+ end
@@ -0,0 +1,7 @@
1
+ class TumblrThemer::Post::Link < TumblrThemer::Post
2
+ tag('URL') { data['url'] }
3
+ tag('Name'){ data['title'] }
4
+ tag('target') { 'target="_blank"' }
5
+ block('Description') { boolify(data['description']) }
6
+ tag('Description') { data['description'] }
7
+ end
@@ -0,0 +1,50 @@
1
+ class TumblrThemer::Post::Photo < TumblrThemer::Post
2
+ def photo
3
+ data['photos'].first['alt_sizes']
4
+ end
5
+
6
+ def photo_size size
7
+ photo.find {|p| p['width'] == size}||photo.first
8
+ end
9
+
10
+ def exif
11
+ data['photos'].first['exif'] || {}
12
+ end
13
+
14
+ def exif?
15
+ boolify(exif)
16
+ end
17
+
18
+ tag('PhotoAlt') { 'alt photo' }
19
+
20
+ block('Caption') { boolify(data['caption']) }
21
+ tag('Caption') { data['caption'] }
22
+
23
+ block('LinkURL') { boolify(data['link_url']) }
24
+ tag('LinkURL') { data['link_url'] }
25
+ tag('LinkOpenTag') { %Q[<a href="#{data['link_url']}">]}
26
+ tag('LinkCloseTag') { '</a>' }
27
+
28
+ [500,400,250,100].each do |size|
29
+ tag("PhotoURL-#{size}") do
30
+ photo_size(size)['url']
31
+ end
32
+ tag("PhotoWidth-#{size}") { photo_size(size)['width'] }
33
+ tag("PhotoHeight-#{size}") { photo_size(size)['height'] }
34
+ end
35
+ tag("PhotoURL-75sq") { photo_size(75)['url'] }
36
+
37
+ block('HighRes') { false }
38
+
39
+ block("Exif") { exif? }
40
+ block("Camera") { exif? && boolify(exif['Camera'])}
41
+ tag("Camera") { exif['Camera'] }
42
+ block("Aperture") { exif? && boolify(exif['Aperture'])}
43
+ tag("Aperture") { exif['Aperture'] }
44
+ block("Exposure") { exif? && boolify(exif['Exposure'])}
45
+ tag("Exposure") { exif['Exposure'] }
46
+ block("FocalLength") { exif? && boolify(exif['FocalLength'])}
47
+ tag("FocalLength") { exif['FocalLength'] }
48
+ block("ISO") { exif? && boolify(exif['ISO'])}
49
+ tag("ISO") { exif['ISO'] }
50
+ end
@@ -0,0 +1,7 @@
1
+ class TumblrThemer::Post::Photoset < TumblrThemer::Post::Photo
2
+ [700,500,400,250].each do |size|
3
+ tag("Photoset-#{size}") { %Q[<img src="http://placekitten.com/#{size}/#{(size*(rand+1)).to_i}" width="#{size}"/>] }
4
+ end
5
+ tag("PhotoCount") { data['photos'].size }
6
+ tag("PostType") { 'photoset' }
7
+ end
@@ -0,0 +1,10 @@
1
+ class TumblrThemer::Post::Quote < TumblrThemer::Post
2
+ tag('Quote') { data['text'] }
3
+
4
+ block('Source') { boolify(data['source']) }
5
+ tag('Source') { data['source'] }
6
+
7
+ tag 'Length' do
8
+ %w{short medium long}[rand(3)]
9
+ end
10
+ end
@@ -0,0 +1,4 @@
1
+ class TumblrThemer::Post::Text < TumblrThemer::Post
2
+ block_tag('Title')
3
+ tag('Body')
4
+ end
@@ -0,0 +1,26 @@
1
+ class TumblrThemer::Post::Video < TumblrThemer::Post
2
+ def player
3
+ data['player']||[{}]
4
+ end
5
+
6
+ def video_size size
7
+ (player.find {|p| p['width'] == size}||player.first)['embed_code']
8
+ end
9
+
10
+ block('Caption') { boolify(data['caption']) }
11
+ tag('Caption') { data['caption'] }
12
+
13
+ [700,500,400,250].each do |size|
14
+ tag("Video-#{size}") { video_size(size) }
15
+ tag("VideoEmbed-#{size}") { video_size(size) }
16
+ end
17
+
18
+ tag('PlayCount') { rand(90000) }
19
+ tag('FormattedPlayCount') { rand(90000).to_s.reverse.gsub(/...(?=.)/,'\&,').reverse }
20
+ tag('PlayCountWithLabel') do
21
+ num = rand(90000)
22
+ str = num.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse
23
+ str << (num == 1 ? 'play' : 'plays')
24
+ end
25
+
26
+ end
@@ -0,0 +1,157 @@
1
+ class TumblrThemer::Post
2
+ include TumblrThemer::TagHelper
3
+ attr_reader :data, :post_index, :html
4
+
5
+ def initialize html, data, index=0
6
+ @html = TumblrThemer::HtmlSnippet.new(html.dup) if html
7
+ @data = data.dup
8
+ @post_index = index
9
+ end
10
+
11
+ def type
12
+ self.class.to_s.sub('TumblrThemer::Post::','').downcase
13
+ end
14
+
15
+ def self.render html, data, i=0
16
+ type = data['type']
17
+ if type == 'photo'
18
+ if data['photos'].size > 1
19
+ klass = TumblrThemer::Post::Photoset
20
+ else
21
+ klass = TumblrThemer::Post::Photo
22
+ end
23
+ else
24
+ klass = TumblrThemer::Post.const_get(type.capitalize)
25
+ end
26
+
27
+ klass.new(html,data,i).render
28
+ # rescue NameError
29
+ # ''
30
+ end
31
+
32
+ def render
33
+ return '' unless html
34
+
35
+ self.class.blocks.each do |name,blk|
36
+ html.block(name,instance_exec(self,&blk))
37
+ end
38
+
39
+ self.class.tag_iterators.each do |name,opts|
40
+ vals = instance_exec(self,&opts[:blk])
41
+ html.block(name) do |str|
42
+ strs = []
43
+ vals.each_with_index { |val,i| strs << opts[:klass].new(str,val,i).render }
44
+ strs.join("\n")
45
+ end
46
+ end
47
+
48
+ puts html.str
49
+
50
+ self.class.tags.each do |name, blk|
51
+ html.tag(name,instance_exec(self,&blk))
52
+ end
53
+
54
+ html.str
55
+ end
56
+
57
+ def reblogged?
58
+ boolify(data['reblogged_from_id'])
59
+ end
60
+
61
+ def created_at
62
+ @created_at ||= Time.parse(data['date'])
63
+ end
64
+
65
+ POST_TYPES = %w{
66
+ text
67
+ photo
68
+ panorama
69
+ photoset
70
+ quote
71
+ link
72
+ chat
73
+ audio
74
+ video
75
+ answer
76
+ }.each do |_type|
77
+ block(_type.capitalize) { self.type == _type }
78
+ end
79
+
80
+ tag('PostType') { data['type'] }
81
+ tag('Permalink') { "/post/#{data['id']}" }
82
+ tag('ShortURL') { data['short_url'] }
83
+ tag('PostID') { data['id'] }
84
+ tag 'TagsAsClasses' do
85
+ classes = data['tags']||[]
86
+ classes << 'reblog' if reblogged?
87
+ classes.join(' ')
88
+ end
89
+
90
+ 15.times do |i|
91
+ block "Post#{i+1}" do
92
+ post_index == i
93
+ end
94
+ end
95
+
96
+ block('Even') { post_index%2==1 }
97
+ block('Odd') { post_index%2==0 }
98
+
99
+ block('RebloggedFrom') { reblogged? }
100
+ tag('ReblogParentName') { data['reblogged_from_name'] }
101
+ tag('ReblogParentTitle') { data['reblogged_from_title'] }
102
+ tag('ReblogParentURL') { data['reblogged_from_url'] }
103
+ tag('ReblogRootName') { data['reblogged_root_name'] }
104
+ tag('ReblogRootTitle') { data['reblogged_root_title'] }
105
+ tag('ReblogRootURL') { data['reblogged_root_url'] }
106
+ block('NotReblog') { !reblogged? }
107
+
108
+ block('HasTags') { boolify(data['tags']) }
109
+ for_each('Tags',TumblrThemer::Tag) { data['tags'] }
110
+
111
+ block('Date') { true }
112
+ block('NewDayDate') { post_index%2==1 }
113
+ block('SameDayDate') { post_index%2==0 }
114
+ tag('DayOfMonth') { created_at.strftime('%-d') }
115
+ tag('DayOfMonthWithZero') { created_at.strftime('%d') }
116
+ tag('DayOfWeek') { created_at.strftime('%A') }
117
+ tag('ShortDayOfWeek') { created_at.strftime('%a') }
118
+ tag('DayOfWeekNumber') { created_at.strftime('%u') }
119
+ # tag('DayOfMonthSuffix') { 'fixme' }
120
+ tag('DayOfYear') { created_at.yday }
121
+ tag('WeekOfYear') { created_at.strftime('%U') }
122
+ tag('Month') { created_at.strftime('%B') }
123
+ tag('ShortMonth') { created_at.strftime('%b') }
124
+ tag('MonthNumber') { created_at.strftime('%-m') }
125
+ tag('MonthNumberWithZero') { created_at.strftime('%m') }
126
+ tag('Year') { created_at.strftime('%Y') }
127
+ tag('ShortYear') { created_at.strftime('%y') }
128
+ tag('AmPm') { created_at.strftime('%P') }
129
+ tag('CapitalAmPm') { created_at.strftime('%p') }
130
+ tag('12Hour') { created_at.strftime('%l') }
131
+ tag('24Hour') { created_at.strftime('%k') }
132
+ tag('12HourWithZero') { created_at.strftime('%I') }
133
+ tag('24HourWithZero') { created_at.strftime('%H') }
134
+ tag('Minutes') { created_at.strftime('%M') }
135
+ tag('Seconds') { created_at.strftime('%S') }
136
+ tag('Beats') { created_at.strftime('%L') }
137
+ tag('Timestamp') { created_at.to_i}
138
+ # tag('TimeAgo') { 'fixme' }
139
+
140
+ block_tag('NoteCount')
141
+ tag('NoteCountWithLabel') do
142
+ num = data['note_count'].to_i
143
+ str = num.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse
144
+ str << (num == 1 ? 'note' : 'notes')
145
+ end
146
+
147
+ block('ContentSource', 'source_url')
148
+ tag('SourceURL')
149
+ tag('SourceTitle')
150
+ tag('BlackLogoURL') { 'http://assets.tumblr.com/images/source_logos/fastcompany_7f8b98.png?1092' }
151
+ tag('LogoWidth') { 74 }
152
+ tag('LogoHeight') { 11 }
153
+
154
+ def source_logo; @source_logo ||= rand(2); end
155
+ block('SourceLogo') { source_logo == 0 }
156
+ block('NoSourceLogo') { source_logo == 1 }
157
+ end
@@ -0,0 +1,16 @@
1
+ require 'sinatra/base'
2
+
3
+ class TumblrThemer::Server < Sinatra::Base
4
+ get '/' do
5
+ theme = TumblrThemer::Theme.new('.')
6
+
7
+ theme.render
8
+ end
9
+
10
+ get '/post/:id' do
11
+ theme = TumblrThemer::Theme.new('.')
12
+ end
13
+
14
+ # start the server if ruby file executed directly
15
+ run! if app_file == $0
16
+ end
@@ -0,0 +1,22 @@
1
+ class TumblrThemer::Tag
2
+ include TumblrThemer::TagHelper
3
+ attr_reader :html, :data
4
+
5
+ def initialize html, data, index=0
6
+ @html = TumblrThemer::HtmlSnippet.new(html.dup) if html
7
+ @data = data.dup
8
+ end
9
+
10
+ def render
11
+ self.class.tags.each do |name, blk|
12
+ html.tag(name,instance_exec(self,&blk))
13
+ end
14
+
15
+ html.str
16
+ end
17
+
18
+ tag('Tag') { data }
19
+ tag('URLSafeTag') { CGI.escape(data) }
20
+ tag('TagURL') { '#' }
21
+ tag('TagURLChrono') { '#' }
22
+ end
@@ -0,0 +1,79 @@
1
+ module TumblrThemer::TagHelper
2
+ module InstanceMethods
3
+ def tag name
4
+ blk = self.class.tags[name]
5
+
6
+ instance_exec(self,&blk)
7
+ end
8
+
9
+ def block name
10
+ instance_exec(self,&blk)
11
+ end
12
+
13
+ def boolify val
14
+ if !val
15
+ false
16
+ elsif val.respond_to?(:empty?) && val.empty?
17
+ false
18
+ else
19
+ true
20
+ end
21
+ end
22
+
23
+ # File activesupport/lib/active_support/inflector/methods.rb, line 77
24
+ def underscore(camel_cased_word)
25
+ word = camel_cased_word.to_s.dup
26
+ word.gsub!(/::/, '/')
27
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
28
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
29
+ word.tr!("-", "_")
30
+ word.downcase!
31
+ word
32
+ end
33
+
34
+ end
35
+
36
+ module ClassMethods
37
+ def tags
38
+ @tags ||= {}
39
+ end
40
+
41
+ def tag name, data_name=nil, &blk
42
+ blk ||= Proc.new { data[data_name||underscore(name)] }
43
+ tags[name] = blk
44
+ end
45
+
46
+ def for_each name, klass, &blk
47
+ tag_iterators[name] = {:klass => klass, :blk => blk}
48
+ end
49
+
50
+ def tag_iterators
51
+ @tag_iterators ||= {}
52
+ end
53
+
54
+ def blocks
55
+ @blocks ||= {}
56
+ end
57
+
58
+ def block name, data_name=nil, &blk
59
+ blk ||= Proc.new { boolify(data[data_name||underscore(name)]) }
60
+ blocks[name] = blk
61
+ end
62
+
63
+ def block_tag name, data_name=nil
64
+ block(name,data_name)
65
+ tag(name,data_name)
66
+ end
67
+
68
+ def inherited subklass
69
+ subklass.instance_variable_set(:@tags,@tags.dup)
70
+ subklass.instance_variable_set(:@blocks,@blocks.dup)
71
+ subklass.instance_variable_set(:@tag_iterators,@tag_iterators.dup)
72
+ end
73
+ end
74
+
75
+ def self.included subklass
76
+ subklass.send :include, InstanceMethods
77
+ subklass.extend ClassMethods
78
+ end
79
+ end
@@ -0,0 +1,78 @@
1
+ class TumblrThemer::Theme
2
+ include TumblrThemer::TagHelper
3
+ attr_reader :body
4
+
5
+ def initialize dirname
6
+ base = File.expand_path(dirname)
7
+
8
+ @body = File.read(File.join(base,'index.html'))
9
+ @posts = {}
10
+ Dir[File.join(base,'posts/*.html')].each do |f|
11
+ basename = File.basename(f).sub(/\.html$/,'').gsub(/[_-]/,' ')
12
+ html = File.read(f)
13
+ @posts[basename] = html
14
+ end
15
+
16
+ posts_html = @posts.collect do |basename, html|
17
+ str = "{block:#{basename.capitalize}}"
18
+ str << html
19
+ str << "{/block:#{basename.capitalize}}"
20
+ end
21
+
22
+ @body.gsub!("{PostsCode}",posts_html.join("\n"))
23
+ end
24
+
25
+ def get_data
26
+ return @post_data if defined?(@post_data)
27
+ data = TumblrThemer::API.posts
28
+ @blog_data = data['blog']
29
+ @post_data = data['posts']
30
+ end
31
+
32
+ def post_data
33
+ @post_data || (get_data && @post_data)
34
+ end
35
+
36
+ def blog_data
37
+ @blog_data || (get_data && @blog_data)
38
+ end
39
+
40
+ def render
41
+ html = TumblrThemer::HtmlSnippet.new(@body)
42
+
43
+ self.class.tag_iterators.each do |name, opts|
44
+ vals = instance_exec(self,&opts[:blk])
45
+ html.block(name) do |str|
46
+ vals.collect.with_index do |val,i|
47
+ opts[:klass].render(str,val,i)
48
+ end.join("\n")
49
+ end
50
+ end
51
+
52
+ self.class.blocks.each do |name,blk|
53
+ html.block(name,instance_exec(self,&blk))
54
+ end
55
+
56
+ self.class.tags.each do |name, blk|
57
+ html.tag(name,instance_exec(self,&blk))
58
+ end
59
+
60
+ html.str
61
+ end
62
+
63
+ for_each('Posts',TumblrThemer::Post) { post_data }
64
+
65
+ tag('Title') { blog_data['title'] }
66
+ tag('Description') { blog_data['description'] }
67
+ tag('CopyrightYears') {'2007-2013'}
68
+ tag('CustomCSS') {''}
69
+
70
+ # block('IndexPage') { true }
71
+ # block('PermalinkPage') { false }
72
+ # block('PostTitle') { false }
73
+ # tag('PostTitle') {''}
74
+ # block('PostSummary') { false }
75
+ # tag('PostSummary') {''}
76
+
77
+ block('Date') { false }
78
+ end
@@ -0,0 +1,3 @@
1
+ module TumblrThemer
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,28 @@
1
+ require 'time'
2
+ require 'url'
3
+ require 'json'
4
+ require "yaml"
5
+ require 'cgi'
6
+
7
+ require "tumblr-themer/version"
8
+
9
+ module TumblrThemer
10
+
11
+ end
12
+
13
+ require 'tumblr-themer/api'
14
+ require 'tumblr-themer/tag_helper'
15
+ require 'tumblr-themer/html_snippet'
16
+ require 'tumblr-themer/tag'
17
+ require 'tumblr-themer/photoset_photo'
18
+ require 'tumblr-themer/chat_line'
19
+ require 'tumblr-themer/post'
20
+ require 'tumblr-themer/theme'
21
+ require 'tumblr-themer/post/audio'
22
+ require 'tumblr-themer/post/quote'
23
+ require 'tumblr-themer/post/text'
24
+ require 'tumblr-themer/post/photo'
25
+ require 'tumblr-themer/post/photoset'
26
+ require 'tumblr-themer/post/link'
27
+ require 'tumblr-themer/post/video'
28
+ require 'tumblr-themer/post/chat'
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ <% if ENV['BUNDLE_GEMFILE'] %>
3
+ require 'bundler'
4
+ Bundler.setup
5
+ <% end %>
6
+
7
+ require 'tumblr-themer'
8
+ require 'tumblr-themer/server'
9
+
10
+ run TumblrThemer::Server
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= @name %> Tumblr Theme</title>
5
+ </head>
6
+ <body>
7
+ <h1>Look for me in index.html</h1>
8
+ <div class="posts">
9
+ {block:Posts}
10
+ <article class="post {PostType}" id="post{PostID}">
11
+ {PostsCode}
12
+
13
+ {block:HasTags}
14
+ <section class="tags">
15
+ {block:Tags}
16
+ <a href="{TagURL}" class="tag">#{Tag}</a>
17
+ {/block:Tags}
18
+
19
+ </section>
20
+ {/block:HasTags}
21
+ </article>
22
+ {/block:Posts}
23
+ </div>
24
+ </body>
25
+ </html>
26
+
@@ -0,0 +1,14 @@
1
+ {block:Title}
2
+ <h1>{Title}</h1>
3
+ {/block:Title}
4
+ <section class="body">
5
+ {block:Lines}
6
+ <div class="line {Alt}">
7
+ {block:Label}
8
+ <label>{Label}</label>
9
+ {/block:Label}
10
+ <span class="name user-{UserNumber}">{Name}</span>
11
+ <span class="phrase">{Line}</span>
12
+ </div>
13
+ {/block:Lines}
14
+ </section>
@@ -0,0 +1,8 @@
1
+ <a href="{URL}">
2
+ <h1 class="post-title">{Name}</h1>
3
+ </a>
4
+ {block:Description}
5
+ <section class="body">
6
+ {Description}
7
+ </section>
8
+ {/block:Description}
@@ -0,0 +1,6 @@
1
+ <img src="{PhotoURL-500}" width="500" class="main-photo"/>
2
+ {block:Caption}
3
+ <section class="caption">
4
+ {Caption}
5
+ </section>
6
+ {/block:Caption}
@@ -0,0 +1,8 @@
1
+ <div class="quote-text {Length}">
2
+ {Quote}
3
+ </div>
4
+ {block:Source}
5
+ <div class="quote-source">
6
+ {Source}
7
+ </div>
8
+ {/block:Source}
@@ -0,0 +1,6 @@
1
+ {block:Title}
2
+ <h1 class="text-title">{Title}</h1>
3
+ {/block:Title}
4
+ <section class="body">
5
+ {Body}
6
+ </section>
@@ -0,0 +1,8 @@
1
+ <section class="video-embed">
2
+ {VideoEmbed-500}
3
+ </section>
4
+ {block:Caption}
5
+ <section class="body">
6
+ {Caption}
7
+ </section>
8
+ {/block:Caption}
@@ -0,0 +1,10 @@
1
+ blog: staff
2
+ # get your api key here: http://www.tumblr.com/oauth/apps
3
+ api_key: ...
4
+ # posts:
5
+ # - 47647699733
6
+ # - 48697769466
7
+ # - 48092885854
8
+ # - 48612769386
9
+ # - 2314876463
10
+ # - 30527468175
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tumblr-themer/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "tumblr-themer"
8
+ spec.version = TumblrThemer::VERSION
9
+ spec.authors = ["Tal Atlas"]
10
+ spec.email = ["me@tal.by"]
11
+ spec.description = %q{A tool for building tumblr themes}
12
+ spec.summary = %q{A tool for building tumblr themes}
13
+ spec.homepage = "https://github.com/tal/tumblr-themer"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "url"
22
+ spec.add_dependency "sinatra"
23
+ spec.add_dependency "json"
24
+ spec.add_dependency "thor"
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.3"
27
+ spec.add_development_dependency "rake"
28
+ end
metadata ADDED
@@ -0,0 +1,188 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tumblr-themer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tal Atlas
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: url
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: sinatra
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: json
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: thor
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: bundler
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '1.3'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: '1.3'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rake
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: A tool for building tumblr themes
111
+ email:
112
+ - me@tal.by
113
+ executables:
114
+ - tumblr-themer
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - .gitignore
119
+ - .irbrc
120
+ - .ruby-gemset
121
+ - .ruby-version
122
+ - Gemfile
123
+ - LICENSE.txt
124
+ - README.md
125
+ - Rakefile
126
+ - bin/tumblr-themer
127
+ - lib/tumblr-themer.rb
128
+ - lib/tumblr-themer/api.rb
129
+ - lib/tumblr-themer/chat_line.rb
130
+ - lib/tumblr-themer/cli.rb
131
+ - lib/tumblr-themer/html_snippet.rb
132
+ - lib/tumblr-themer/photoset_photo.rb
133
+ - lib/tumblr-themer/post.rb
134
+ - lib/tumblr-themer/post/audio.rb
135
+ - lib/tumblr-themer/post/chat.rb
136
+ - lib/tumblr-themer/post/link.rb
137
+ - lib/tumblr-themer/post/photo.rb
138
+ - lib/tumblr-themer/post/photoset.rb
139
+ - lib/tumblr-themer/post/quote.rb
140
+ - lib/tumblr-themer/post/text.rb
141
+ - lib/tumblr-themer/post/video.rb
142
+ - lib/tumblr-themer/server.rb
143
+ - lib/tumblr-themer/tag.rb
144
+ - lib/tumblr-themer/tag_helper.rb
145
+ - lib/tumblr-themer/theme.rb
146
+ - lib/tumblr-themer/version.rb
147
+ - theme_template/config.ru.tt
148
+ - theme_template/index.html.tt
149
+ - theme_template/posts/chat.html.cc
150
+ - theme_template/posts/link.html.tt
151
+ - theme_template/posts/photo.html.tt
152
+ - theme_template/posts/quote.html.tt
153
+ - theme_template/posts/text.html.tt
154
+ - theme_template/posts/video.html.tt
155
+ - theme_template/tumblr-theme.yml.tt
156
+ - tumblr-themer.gemspec
157
+ homepage: https://github.com/tal/tumblr-themer
158
+ licenses:
159
+ - MIT
160
+ post_install_message:
161
+ rdoc_options: []
162
+ require_paths:
163
+ - lib
164
+ required_ruby_version: !ruby/object:Gem::Requirement
165
+ none: false
166
+ requirements:
167
+ - - ! '>='
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ segments:
171
+ - 0
172
+ hash: -3075578209271866441
173
+ required_rubygems_version: !ruby/object:Gem::Requirement
174
+ none: false
175
+ requirements:
176
+ - - ! '>='
177
+ - !ruby/object:Gem::Version
178
+ version: '0'
179
+ segments:
180
+ - 0
181
+ hash: -3075578209271866441
182
+ requirements: []
183
+ rubyforge_project:
184
+ rubygems_version: 1.8.25
185
+ signing_key:
186
+ specification_version: 3
187
+ summary: A tool for building tumblr themes
188
+ test_files: []