aerial 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/.gitignore +1 -0
  2. data/MIT-LICENSE +22 -0
  3. data/README.md +74 -0
  4. data/Rakefile +96 -0
  5. data/VERSION +1 -0
  6. data/config/config.yml +19 -0
  7. data/config/deploy.rb +50 -0
  8. data/lib/aerial.rb +100 -0
  9. data/lib/aerial/article.rb +241 -0
  10. data/lib/aerial/base.rb +172 -0
  11. data/lib/aerial/comment.rb +160 -0
  12. data/lib/aerial/config.rb +41 -0
  13. data/lib/aerial/content.rb +74 -0
  14. data/lib/aerial/vendor/akismetor.rb +52 -0
  15. data/lib/aerial/vendor/cache.rb +139 -0
  16. data/lib/features/article.feature +10 -0
  17. data/lib/features/home.feature +16 -0
  18. data/lib/features/step_definitions/article_steps.rb +4 -0
  19. data/lib/features/step_definitions/home_steps.rb +8 -0
  20. data/lib/features/support/env.rb +38 -0
  21. data/lib/features/support/pages/article.rb +9 -0
  22. data/lib/features/support/pages/homepage.rb +9 -0
  23. data/lib/spec/aerial_spec.rb +203 -0
  24. data/lib/spec/article_spec.rb +338 -0
  25. data/lib/spec/base_spec.rb +65 -0
  26. data/lib/spec/comment_spec.rb +216 -0
  27. data/lib/spec/config_spec.rb +25 -0
  28. data/lib/spec/fixtures/articles/sample-article/sample-article.article +6 -0
  29. data/lib/spec/fixtures/articles/test-article-one/test-article.article +7 -0
  30. data/lib/spec/fixtures/articles/test-article-three/test-article.article +7 -0
  31. data/lib/spec/fixtures/articles/test-article-two/comment-missing-fields.comment +8 -0
  32. data/lib/spec/fixtures/articles/test-article-two/test-article.article +7 -0
  33. data/lib/spec/fixtures/articles/test-article-two/test-comment.comment +10 -0
  34. data/lib/spec/fixtures/config.yml +35 -0
  35. data/lib/spec/fixtures/public/javascripts/application.js +109 -0
  36. data/lib/spec/fixtures/public/javascripts/jquery-1.3.1.min.js +19 -0
  37. data/lib/spec/fixtures/public/javascripts/jquery.template.js +255 -0
  38. data/lib/spec/fixtures/views/article.haml +19 -0
  39. data/lib/spec/fixtures/views/articles.haml +2 -0
  40. data/lib/spec/fixtures/views/comment.haml +8 -0
  41. data/lib/spec/fixtures/views/home.haml +2 -0
  42. data/lib/spec/fixtures/views/layout.haml +22 -0
  43. data/lib/spec/fixtures/views/post.haml +27 -0
  44. data/lib/spec/fixtures/views/rss.haml +15 -0
  45. data/lib/spec/fixtures/views/sidebar.haml +21 -0
  46. data/lib/spec/fixtures/views/style.sass +163 -0
  47. data/lib/spec/spec_helper.rb +117 -0
  48. metadata +101 -0
@@ -0,0 +1,52 @@
1
+ require 'net/http'
2
+
3
+ # http://railscasts.com/episodes/65-stopping-spam-with-akismet
4
+ # http://github.com/hornbeck/blerb-core/tree/master/lib/akismetor.rb
5
+
6
+ class Akismetor
7
+ attr_accessor :attributes
8
+
9
+ # Does a comment-check on Akismet with the submitted hash.
10
+ # Returns true or false depending on response.
11
+ def self.spam?(attributes)
12
+ self.new(attributes).execute('comment-check') == "true"
13
+ end
14
+
15
+ # Does a submit-spam on Akismet with the submitted hash.
16
+ # Use this when Akismet incorrectly approves a spam comment.
17
+ def self.submit_spam(attributes)
18
+ self.new(attributes).execute('submit-spam')
19
+ end
20
+
21
+ # Does a submit-ham on Akismet with the submitted hash.
22
+ # Use this for a false positive, when Akismet incorrectly rejects a normal comment
23
+ def self.submit_ham(attributes)
24
+ self.new(attributes).execute('submit-ham')
25
+ end
26
+
27
+
28
+ def initialize(attributes)
29
+ @attributes = attributes
30
+ end
31
+
32
+ def execute(command)
33
+ http = Net::HTTP.new("#{attributes[:key]}.rest.akismet.com", 80)
34
+ response, content = http.post("/1.1/#{command}", attributes_for_post, http_headers)
35
+ content
36
+ end
37
+
38
+ private
39
+
40
+ def http_headers
41
+ {
42
+ 'User-Agent' => 'Akismetor Ruby Library/1.0',
43
+ 'Content-Type' => 'application/x-www-form-urlencoded'
44
+ }
45
+ end
46
+
47
+ def attributes_for_post
48
+ result = attributes.map { |k, v| "#{k}=#{v}" }.join('&')
49
+ URI.escape(result)
50
+ end
51
+
52
+ end
@@ -0,0 +1,139 @@
1
+ require 'fileutils'
2
+ # require 'sinatra/base'
3
+
4
+ module Sinatra
5
+
6
+ # Sinatra Caching module
7
+ #
8
+ # TODO:: Need to write documentation here
9
+ #
10
+ module Cache
11
+
12
+ VERSION = 'Sinatra::Cache v0.2.2'
13
+ def self.version; VERSION; end
14
+
15
+
16
+ module Helpers
17
+
18
+ # Caches the given URI to a html file in /public
19
+ #
20
+ # <b>Usage:</b>
21
+ # >> cache( erb(:contact, :layout => :layout))
22
+ # => returns the HTML output written to /public/<CACHE_DIR_PATH>/contact.html
23
+ #
24
+ # Also accepts an Options Hash, with the following options:
25
+ # * :extension => in case you need to change the file extension
26
+ #
27
+ # TODO:: implement the opts={} hash functionality. What other options are needed?
28
+ #
29
+ def cache(content, opts={})
30
+ return content unless options.respond_to?("cache_enabled") && options.cache_enabled
31
+
32
+ unless content.nil?
33
+ content = "#{content}\n#{page_cached_timestamp}"
34
+ path = cache_page_path(request.path_info,opts)
35
+ FileUtils.makedirs(File.dirname(path))
36
+ open(path, 'wb+') { |f| f << content }
37
+ log("Cached Page: [#{path}]",:info)
38
+ content
39
+ end
40
+ end
41
+
42
+ # Expires the cached URI (as .html file) in /public
43
+ #
44
+ # <b>Usage:</b>
45
+ # >> cache_expire('/contact')
46
+ # => deletes the /public/<CACHE_DIR_PATH>contact.html page
47
+ #
48
+ # get '/contact' do
49
+ # cache_expire # deletes the /public/<CACHE_DIR_PATH>contact.html page as well
50
+ # end
51
+ #
52
+ # TODO:: implement the options={} hash functionality. What options are really needed ?
53
+ def cache_expire(path = nil, opts={})
54
+ return unless options.respond_to?("cache_enabled") && options.cache_enabled
55
+
56
+ path = (path.nil?) ? cache_page_path(request.path_info) : cache_page_path(path)
57
+ if File.exist?(path)
58
+ File.delete(path)
59
+ log("Expired Page deleted at: [#{path}]",:info)
60
+ else
61
+ log("No Expired Page was found at the path: [#{path}]",:info)
62
+ end
63
+ end
64
+
65
+ # Prints a basic HTML comment with a timestamp in it, so that you can see when a file was cached last.
66
+ #
67
+ # *NB!* IE6 does NOT like this to be the first line of a HTML document, so output
68
+ # inside the <head> tag. Many hours wasted on that lesson ;-)
69
+ #
70
+ # <b>Usage:</b>
71
+ # >> <%= page_cached_timestamp %>
72
+ # => <!-- page cached: 2009-02-24 12:00:00 -->
73
+ #
74
+ def page_cached_timestamp
75
+ "<!-- page cached: #{Time.now.strftime("%Y-%d-%m %H:%M:%S")} -->\n" if options.cache_enabled
76
+ end
77
+
78
+
79
+ private
80
+
81
+ # Establishes the file name of the cached file from the path given
82
+ #
83
+ # TODO:: implement the opts={} functionality, and support for custom extensions on a per request basis.
84
+ #
85
+ def cache_file_name(path,opts={})
86
+ name = (path.empty? || path == "/") ? "index" : Rack::Utils.unescape(path.sub(/^(\/)/,'').chomp('/'))
87
+ name << options.cache_page_extension unless (name.split('/').last || name).include? '.'
88
+ return name
89
+ end
90
+
91
+ # Sets the full path to the cached page/file
92
+ # Dependent upon Sinatra.options .public and .cache_dir variables being present and set.
93
+ #
94
+ def cache_page_path(path,opts={})
95
+ # test if given a full path rather than relative path, otherwise join the public path to cache_dir
96
+ # and ensure it is a full path
97
+ cache_dir = (options.cache_output_dir == File.expand_path(options.cache_output_dir)) ?
98
+ options.cache_output_dir : File.expand_path("#{options.public}/#{options.cache_output_dir}")
99
+ cache_dir = cache_output_dir[0..-2] if cache_dir[-1,1] == '/'
100
+ "#{cache_dir}/#{cache_file_name(path,opts)}"
101
+ end
102
+
103
+ # TODO:: this implementation really stinks, how do I incorporate Sinatra's logger??
104
+ def log(msg,scope=:debug)
105
+ if options.cache_logging
106
+ "Log: msg=[#{msg}]" if scope == options.cache_logging_level
107
+ else
108
+ # just ignore the stuff...
109
+ # puts "just ignoring msg=[#{msg}] since cache_logging => [#{options.cache_logging.to_s}]"
110
+ end
111
+ end
112
+
113
+ end #/module Helpers
114
+
115
+
116
+ # Sets the default options:
117
+ #
118
+ # * +:cache_enabled+ => toggle for the cache functionality. Default is: +true+
119
+ # * +:cache_page_extension+ => sets the default extension for cached files. Default is: +.html+
120
+ # * +:cache_dir+ => sets cache directory where cached files are stored. Default is: ''(empty) == root of /public.<br>
121
+ # set to empty, since the ideal 'system/cache/' does not work with Passenger & mod_rewrite :(
122
+ # * +cache_logging+ => toggle for logging the cache calls. Default is: +true+
123
+ # * +cache_logging_level+ => sets the level of the cache logger. Default is: <tt>:info</tt>.<br>
124
+ # Options:(unused atm) [:info, :warn, :debug]
125
+ #
126
+ def self.registered(app)
127
+ app.helpers(Cache::Helpers)
128
+ app.set :cache_enabled, true
129
+ app.set :cache_page_extension, '.html'
130
+ app.set :cache_output_dir, ''
131
+ app.set :cache_logging, true
132
+ app.set :cache_logging_level, :info
133
+ end
134
+
135
+ end #/module Cache
136
+
137
+ #register(Sinatra::Cache)
138
+
139
+ end #/module Sinatra
@@ -0,0 +1,10 @@
1
+ Feature: Article
2
+ In order to an Article
3
+ As anonymous
4
+ wants to view the the first article
5
+
6
+ Scenario: View a single article
7
+ Given there is one article
8
+ When I view the Article
9
+ Then the title should be "Aerial | Congratulations! Aerial is configured correctly"
10
+ Then I should see "Comments"
@@ -0,0 +1,16 @@
1
+ Feature: Homepage
2
+ In order to view recent Articles
3
+ As anonymous
4
+ wants to view the most recent articles
5
+
6
+ Scenario: Opening the home page
7
+ Given there are articles
8
+ When I view the Homepage
9
+ Then the title should be "Aerial | A Microblog by Matt Sears"
10
+ Then I should see "Congratulations! Aerial is configured correctly"
11
+ Then I should see a link to "Home":http://localhost:4567/home
12
+ Then I should see a link to "About":http://localhost:4567/about
13
+ Then I should see a link to "ruby":http://localhost:4567/tags/ruby
14
+ Then I should see a link to "sinatra":http://localhost:4567/tags/sinatra
15
+ Then I should see a link to "git":http://localhost:4567/tags/git
16
+ Then I should see a link to "aerial":http://localhost:4567/tags/aerial
@@ -0,0 +1,4 @@
1
+ Given /^there is one article$/ do
2
+ @articles = Aerial::Article.all.first
3
+ end
4
+
@@ -0,0 +1,8 @@
1
+
2
+ Given /^there are articles$/ do
3
+ @articles = Aerial::Article.all
4
+ end
5
+
6
+ Then /^I should have 2 articles$/ do
7
+ @articles.size.should == 2
8
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec'
2
+ require 'webrat'
3
+ require 'spec/expectations'
4
+ require 'webrat/sinatra'
5
+ require 'safariwatir'
6
+
7
+ $0 = File.expand_path(File.dirname(__FILE__) + "/../../aerial.rb")
8
+ require File.expand_path(File.dirname(__FILE__) + "/../../aerial")
9
+
10
+ browser = Watir::Safari.new
11
+ pages = {
12
+ "Homepage" => "http://localhost:4567",
13
+ "Article" => "http://localhost:4567/2009/3/31/congratulations-aerial-is-configured-correctly"
14
+ }
15
+
16
+ Before do
17
+ @browser = browser
18
+ @pages = pages
19
+ end
20
+
21
+ # Common steps - should these go somewhere else?
22
+
23
+ When /^I view the (.*)$/ do |page|
24
+ @page = eval("#{page}.new(@browser)")
25
+ @page.goto
26
+ end
27
+
28
+ Then /^the title should be "(.*)"$/ do |text|
29
+ @browser.title.should == text
30
+ end
31
+
32
+ Then /^I should see "(.*)"$/ do |text|
33
+ @browser.text.should include(text)
34
+ end
35
+
36
+ Then /I should see a link to "(.*)":(.*)/ do |text, url|
37
+ @browser.link(:url, url).text.should == text
38
+ end
@@ -0,0 +1,9 @@
1
+ class Article
2
+ def initialize(browser)
3
+ @browser = browser
4
+ end
5
+
6
+ def goto
7
+ @browser.goto 'http://localhost:4567/2009/3/31/congratulations-aerial-is-configured-correctly'
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class Homepage
2
+ def initialize(browser)
3
+ @browser = browser
4
+ end
5
+
6
+ def goto
7
+ @browser.goto 'http://localhost:4567/'
8
+ end
9
+ end
@@ -0,0 +1,203 @@
1
+ require "#{File.dirname(__FILE__)}/spec_helper"
2
+
3
+ describe 'main aerial application' do
4
+
5
+ before do
6
+ @articles = Article.all
7
+ setup_repo
8
+ end
9
+
10
+ it "should define the root directory" do
11
+ AERIAL_ROOT.should_not be_nil
12
+ end
13
+
14
+ it "should define the configuration file" do
15
+ CONFIG.should_not be_nil
16
+ end
17
+
18
+ it 'should show the default index page' do
19
+ get '/'
20
+ @response.should be_ok
21
+ end
22
+
23
+ it 'should send 404 properly' do
24
+ get '/not-found.html'
25
+ @response.status.should == 404
26
+ end
27
+
28
+ it "should render a single article page" do
29
+ get '/2009/1/31/test-article'
30
+ @response.status.should == 200
31
+ end
32
+
33
+ it "should render the home page" do
34
+ get '/home'
35
+ @response.status.should == 200
36
+ end
37
+
38
+ describe "calling /style.css" do
39
+
40
+ before do
41
+ get '/style.css'
42
+ end
43
+
44
+ it "should return an okay status code" do
45
+ @response.status.should == 200
46
+ end
47
+
48
+ it "should return a css stylesheet" do
49
+ @response.headers["Content-Type"].should == "text/css;charset=utf-8"
50
+ end
51
+
52
+ end
53
+
54
+ describe "calling /tags" do
55
+
56
+ before do
57
+ @article = Article.new(:title => "Test Article",
58
+ :tags => "git, sinatra",
59
+ :published_at => DateTime.new,
60
+ :comments => [],
61
+ :file_name => "test-article")
62
+ Aerial::Article.stub!(:with_tag).and_return([@article])
63
+ get '/tags/git'
64
+ end
65
+
66
+ it "should return a valid response" do
67
+ @response.status.should == 200
68
+ end
69
+
70
+ it "should return a response body" do
71
+ @response.body.should_not be_empty
72
+ end
73
+
74
+ end
75
+
76
+ describe "calling /feed" do
77
+
78
+ before do
79
+ @articles = Article.find_all
80
+ Aerial::Article.stub!(:all).and_return(@articles)
81
+ get '/feed'
82
+ end
83
+
84
+ it "should produce an rss tag" do
85
+ @response.body.should have_tag('//rss')
86
+ end
87
+
88
+ it "should contain a title tag" do
89
+ @response.body.should have_tag('//title').with_text(Aerial.config.title)
90
+ end
91
+
92
+ it "should contain a language tag" do
93
+ @response.body.should have_tag('//language').with_text("en-us")
94
+ end
95
+
96
+ it "should contain a description tag that containts the subtitle" do
97
+ @response.body.should have_tag('//description').with_text(Aerial.config.subtitle)
98
+ end
99
+
100
+ it "should contain an item tag" do
101
+ @response.body.should have_tag('//item')
102
+ end
103
+
104
+ it "should have the title tags for the articles" do
105
+ @response.body.should have_tag('//item[1]/title').with_text(@articles[0].title)
106
+ @response.body.should have_tag('//item[2]/title').with_text(@articles[1].title)
107
+ end
108
+
109
+ it "should have the link tag with the articles permalink" do
110
+ #@response.body.should have_tag('//item[1]/link').with_text("http://#{@articles[0].permalink}")
111
+ end
112
+
113
+ it "should have a pubDate tag with the article's publication date" do
114
+ @response.body.should have_tag('//item[1]/pubDate').with_text(@articles[0].published_at.to_s)
115
+ @response.body.should have_tag('//item[2]/pubDate').with_text(@articles[1].published_at.to_s)
116
+ end
117
+
118
+ it "should have a guid date that matches the articles id" do
119
+ @response.body.should have_tag('//item[1]/guid').with_text(@articles[0].id)
120
+ @response.body.should have_tag('//item[2]/guid').with_text(@articles[1].id)
121
+ end
122
+
123
+ after do
124
+ @articles = nil
125
+ end
126
+
127
+ end
128
+
129
+ describe "calling /feed" do
130
+
131
+ before do
132
+ @articles = Article.find_all
133
+ Aerial::Article.stub!(:all).and_return(@articles)
134
+ get '/articles'
135
+ end
136
+
137
+ it "should return a valid response" do
138
+ @response.status.should == 200
139
+ end
140
+
141
+ it "should return a response body" do
142
+ @response.body.should_not be_empty
143
+ end
144
+
145
+ end
146
+
147
+
148
+ describe "calling /arhives" do
149
+ before do
150
+ @article = Article.new(:title => "Test Article",
151
+ :body => "Test Content",
152
+ :id => 333,
153
+ :published_at => DateTime.new,
154
+ :comments => [],
155
+ :file_name => "test-article.article")
156
+ Aerial::Article.stub!(:with_date).and_return([@article])
157
+ get '/archives/year/month'
158
+ end
159
+
160
+ it "should return a valid response" do
161
+ @response.status.should == 200
162
+ end
163
+
164
+ end
165
+
166
+ describe "posting a new comment" do
167
+
168
+ before do
169
+ @article = Article.new(:title => "Test Article",
170
+ :body => "Test Content",
171
+ :file_name => "test-article",
172
+ :archive_name => "test-article",
173
+ :id => 333)
174
+ Aerial::Article.stub!(:find).and_return(@article)
175
+ @article.stub!(:comments).and_return(Array.new)
176
+ @article.stub!(:permalink).and_return('/permalink')
177
+ post "/article/#{@article.id}/comments"
178
+ end
179
+
180
+ it "should return a valid response" do
181
+ @response.status.should == 204
182
+ end
183
+ end
184
+
185
+ describe "calling Git operations" do
186
+
187
+ before do
188
+ @dir = "#{Aerial.repo.working_dir}/articles/new_dir"
189
+ FileUtils.mkdir(@dir)
190
+ FileUtils.cd(@dir) do
191
+ FileUtils.touch 'new.file'
192
+ end
193
+ end
194
+
195
+ it "should commit all new untracked and tracked content" do
196
+ Aerial.repo.status.untracked.should_not be_empty
197
+ get '/'
198
+ # Aerial.repo.status.untracked.should be_empty
199
+ end
200
+
201
+ end
202
+
203
+ end