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
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ coverage/
2
+ pkg/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'activesupport', '>= 3.1'
4
+ gem 'treetop'
5
+ gem 'nokogiri'
6
+ gem 'api_cache'
7
+
8
+
9
+ group :development do
10
+ gem 'autotest-standalone'
11
+ gem 'jeweler'
12
+ gem 'rake'
13
+ gem 'rspec'
14
+ gem 'simplecov'
15
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,52 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activesupport (3.2.2)
5
+ i18n (~> 0.6)
6
+ multi_json (~> 1.0)
7
+ api_cache (0.2.3)
8
+ autotest-standalone (4.5.9)
9
+ diff-lcs (1.1.3)
10
+ git (1.2.5)
11
+ i18n (0.6.0)
12
+ jeweler (1.8.3)
13
+ bundler (~> 1.0)
14
+ git (>= 1.2.5)
15
+ rake
16
+ rdoc
17
+ json (1.6.6)
18
+ multi_json (1.1.0)
19
+ nokogiri (1.5.2)
20
+ polyglot (0.3.3)
21
+ rake (0.9.2.2)
22
+ rdoc (3.12)
23
+ json (~> 1.4)
24
+ rspec (2.8.0)
25
+ rspec-core (~> 2.8.0)
26
+ rspec-expectations (~> 2.8.0)
27
+ rspec-mocks (~> 2.8.0)
28
+ rspec-core (2.8.0)
29
+ rspec-expectations (2.8.0)
30
+ diff-lcs (~> 1.1.2)
31
+ rspec-mocks (2.8.0)
32
+ simplecov (0.6.1)
33
+ multi_json (~> 1.0)
34
+ simplecov-html (~> 0.5.3)
35
+ simplecov-html (0.5.3)
36
+ treetop (1.4.10)
37
+ polyglot
38
+ polyglot (>= 0.3.1)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ activesupport (>= 3.1)
45
+ api_cache
46
+ autotest-standalone
47
+ jeweler
48
+ nokogiri
49
+ rake
50
+ rspec
51
+ simplecov
52
+ treetop
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Jason Webster, MetaLab Design Ltd.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # Tumblargh
2
+ ## Groan-less Tumblr theme development
3
+
4
+ ### What is this thing, and why should I care?
5
+
6
+ If you've ever had to build a Tumblr theme, you've probably cried out in pain
7
+ while tweaking locally, copying, pasting into the theme editor, saving, switching
8
+ tabs and finally refreshing and waiting for your tesing blog to reload.
9
+
10
+ Tumblargh aims to reduce suffering involved with building a theme by offering
11
+ a way to fully develop, lint and test Tumblr themes locally, with real posts
12
+ from any existing Tumblog.
13
+
14
+ ### Getting Started
15
+
16
+ You'll need to get an OAuth consumer key for the Tumblr v2 API to use remote data
17
+ with Tumblargh. Registration is simple enough, just go to http://www.tumblr.com/oauth/apps
18
+ and fill out the form. Any time Tumblargh asks for your API key, it'll be the
19
+ OAuth Consumer key provided there.
20
+
21
+ #### Middleman
22
+
23
+ The recommended way to use tumblargh is in conjuction with
24
+ [Middleman](http://middlemanapp.com/).
25
+
26
+ > Middleman is a static site generator based on Sinatra. Providing dozens of
27
+ templating languages (Haml, Sass, Compass, Slim, CoffeeScript, and more).
28
+
29
+ Tumblargh includes a simple Middleman extension that turns any Middleman project
30
+ into a local Tumblr theme building machine.
31
+
32
+ Tumblargh will automatically parse any html files served by Middleman, and
33
+ populate them with content from the Tumblr of your choosing. It will not
34
+ parse any HTML during Middleman's build process. The output of`middleman build`
35
+ is ready for use on your blog, or submission to the Tumblr theme store.
36
+
37
+ To get up an running with Middleman, first create a new Middleman project:
38
+
39
+ ```
40
+ $ middleman init MY_PROJECT_NAME
41
+ ```
42
+
43
+ If one does not already exist, create a Gemfile and add the following as needed:
44
+
45
+ ```ruby
46
+ source "http://rubygems.org"
47
+
48
+ gem 'middleman'
49
+ gem 'tumblargh', :git => 'git://github.com/jasonwebster/tumblargh.git'
50
+ ```
51
+
52
+ Note that there has not yet been an official release of tumblargh to RubyGems,
53
+ so currently, specifying the gem via git is necessary.
54
+
55
+ Run `bundle install`.
56
+
57
+ The bare minimum setup in your Middleman config.rb is:
58
+
59
+ ```ruby
60
+ require 'tumblargh'
61
+ require 'middleman/features/tumblargh'
62
+
63
+ activate :tumblargh
64
+
65
+ set_tumblr_api_key 'API KEY' # This is your OAuth consumer key
66
+ set_tumblr_blog 'staff.tumblr.com'
67
+ ```
68
+
69
+ It is highly recommended to run the Middleman server via `bundle exec`.
70
+
71
+ #### Rack
72
+
73
+ See `examples/config.ru` for a minimal Rack setup, ready to go with `rackup` or
74
+ your Ruby server of choice.
75
+
76
+ ### Known issues & planned features
77
+
78
+ - Source attribution `{block:ContentSource}`
79
+ - Your likes `{block:Likes}`
80
+ - Twitter integration `{block:Twitter}`
81
+ - Custom page support
82
+
83
+
84
+
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc 'Default: run specs.'
7
+ task :default => :spec
8
+
9
+ desc "Run specs"
10
+ RSpec::Core::RakeTask.new do |t|
11
+ t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
12
+ # Put spec opts in a file named .rspec in root
13
+ end
14
+
15
+ desc "Generate code coverage"
16
+ RSpec::Core::RakeTask.new(:coverage) do |t|
17
+ t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
18
+ end
19
+
20
+
21
+ desc "Open an irb session preloaded with this library"
22
+ task :console do
23
+ sh "irb -rubygems -I lib -r tumblargh.rb"
24
+ end
25
+
26
+
27
+ require 'jeweler'
28
+ Jeweler::Tasks.new do |s|
29
+ s.name = 'tumblargh'
30
+ s.summary = 'Groan-less Tumblr theme development.'
31
+ s.description = `cat README.md`
32
+ s.authors = ['Jason Webster']
33
+ s.email = 'jason@metalabdesign.com'
34
+ s.homepage = 'http://github.com/jasonwebster/tumblargh'
35
+
36
+ s.files = `git ls-files`.split("\n")
37
+ s.require_path = 'lib'
38
+ end
39
+
40
+ Jeweler::RubygemsDotOrgTasks.new
41
+
42
+
43
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/examples/confg.ru ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'bundler/setup'
4
+
5
+ require 'tumblargh'
6
+
7
+ Tumblargh::API::set_api_key 'YOUR_TUMBLR_API_KEY'
8
+
9
+ map "/" do
10
+
11
+ app = proc do |env|
12
+ html = Tumblargh::render_file('my_theme.html', 'willw.tumblr.com')
13
+
14
+ [200, { "Content-Type" => "text/html" }, html.lines]
15
+ end
16
+
17
+ run app
18
+ end
@@ -0,0 +1,7 @@
1
+ require 'tumblargh'
2
+ require 'middleman/features/tumblargh'
3
+
4
+
5
+ activate :tumblargh
6
+ set_tumblr_api_key 'API KEY'
7
+ set_tumlbr_blog 'staff.tumblr.com'
@@ -0,0 +1,41 @@
1
+ require 'rack/tumblargh'
2
+
3
+ module Middleman::Features::Tumblargh
4
+
5
+ class << self
6
+
7
+ def registered(app)
8
+ options = {}
9
+ app.set(:tumblr_options, options)
10
+ app.extend(ClassMethods)
11
+
12
+ unless app.build?
13
+ app.use(Rack::Tumblargh, options)
14
+
15
+ ['/tweets.js', %r{/api.*}].each do |route|
16
+ app.get route do
17
+ redirect "http://#{app.tumblr_options[:blog]}#{request.path}?#{request.query_string}"
18
+ end
19
+ end
20
+
21
+ app.page '/post/:id*', :proxy => '/index.html'
22
+ end
23
+ end
24
+
25
+ alias :included :registered
26
+
27
+ module ClassMethods
28
+ def tumblr_api_key=(key)
29
+ Tumblargh::API::set_api_key(key)
30
+ end
31
+
32
+ def tumblr_blog=(blog=nil)
33
+ tumblr_options[:blog] = blog
34
+ end
35
+
36
+ alias_method :set_tumblr_api_key, :tumblr_api_key=
37
+ alias_method :set_tumblr_blog, :tumblr_blog=
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,51 @@
1
+ module Rack
2
+ class Tumblargh
3
+ Tumblargh = ::Tumblargh
4
+
5
+ def initialize(app, options={})
6
+ @app = app
7
+ @options = options
8
+ @options[:blog] = 'staff.tumblr.com' if @options[:blog].nil?
9
+ end
10
+
11
+ attr_reader :options
12
+
13
+ def call(env)
14
+ status, headers, response = @app.call(env)
15
+
16
+ if should_parse?(status, headers)
17
+
18
+ content = response.respond_to?(:body) ? response.body : response
19
+ render_opts = { :permalink => permalink?(env['PATH_INFO']) }
20
+
21
+ headers.delete('Content-Length')
22
+ response = Rack::Response.new(
23
+ render(content, render_opts),
24
+ status,
25
+ headers
26
+ )
27
+ response.finish
28
+ response.to_a
29
+ else
30
+ [status, headers, response]
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def permalink?(path)
37
+ !! path.match(/^\/post\/\d+/)
38
+ end
39
+
40
+ def should_parse?(status, headers)
41
+ status == 200 &&
42
+ headers["Content-Type"] &&
43
+ headers["Content-Type"].include?("text/html")
44
+ end
45
+
46
+ def render(content, opts)
47
+ Tumblargh::render_html(content.first, options[:blog], opts)
48
+ end
49
+
50
+ end
51
+ end
data/lib/tumblargh.rb ADDED
@@ -0,0 +1,60 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext/hash/indifferent_access'
3
+ require 'active_support/core_ext/string/conversions'
4
+ require 'active_support/core_ext/time/conversions'
5
+ require 'active_support/inflector'
6
+
7
+ module Tumblargh
8
+
9
+ autoload :API, 'tumblargh/api'
10
+ autoload :Node, 'tumblargh/node'
11
+ autoload :Parser, 'tumblargh/parser'
12
+ autoload :Renderer, 'tumblargh/renderer'
13
+ autoload :Resource, 'tumblargh/resource'
14
+
15
+ class << self
16
+
17
+ attr_accessor :config
18
+
19
+ def render_file(file, blog, options={})
20
+ render(:file, file, blog, options)
21
+ end
22
+
23
+ def render_html(string, blog, options={})
24
+ render(:html, string, blog, options)
25
+ end
26
+
27
+ private
28
+
29
+ def render(setter, theme, blog, options)
30
+ parser = Parser.new
31
+ parser.send("#{setter}=", theme)
32
+
33
+ blog = create_blog blog
34
+
35
+ options = parser.options.merge(options)
36
+
37
+ Renderer::Document.new(parser.tree, blog, options).render
38
+ end
39
+
40
+ def create_blog(blog)
41
+ if blog.is_a? Resource::Blog
42
+ blog
43
+ elsif blog.is_a? Hash
44
+ create_blog_from_hash blog
45
+ elsif File.exists? blog
46
+ json = ActiveSupport::JSON.decode(open(blog).read)
47
+ create_blog_from_hash json
48
+ else
49
+ Resource::Blog.new(blog)
50
+ end
51
+ end
52
+
53
+ def create_blog_from_hash(hash)
54
+ hash = hash["response"] if hash.key? "response"
55
+ Resource::Blog.new("#{hash["blog"]["name"]}.tumblr.com", hash)
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,60 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext/hash/indifferent_access'
3
+ require 'active_support/core_ext/object/to_query'
4
+ require 'active_support/json'
5
+
6
+ require 'api_cache'
7
+ require 'open-uri'
8
+
9
+ module Tumblargh
10
+ module API
11
+
12
+ API_ROOT = 'http://api.tumblr.com/v2/blog/'
13
+
14
+ @enabled = true
15
+
16
+ class << self
17
+
18
+ attr_accessor :api_key
19
+ alias_method :set_api_key, :api_key=
20
+
21
+ def fetch(path, query={})
22
+ raise "API is disabled" unless enabled?
23
+
24
+ query = query.merge(:api_key => api_key).to_query
25
+ url = "#{API_ROOT}#{path}?#{query}"
26
+ resp = APICache.get(url) { open(url).read }
27
+ ActiveSupport::JSON.decode(resp)['response']
28
+ end
29
+
30
+ def blog(domain)
31
+ fetch("#{domain}/info")['blog']
32
+ end
33
+
34
+ def posts(domain, query={})
35
+ fetch("#{domain}/posts")['posts']
36
+ end
37
+
38
+ def notes(domain, query)
39
+ query.merge!(:notes_info => 'true')
40
+ fetch("#{domain}/posts", query)['posts'][0]['notes']
41
+ end
42
+
43
+
44
+ def enable!
45
+ @enabled = true
46
+ end
47
+
48
+ def disable!
49
+ @enabled = false
50
+ end
51
+
52
+ def enabled?
53
+ @enabled
54
+ end
55
+
56
+ end
57
+
58
+
59
+ end
60
+ end