dunkstewart-blogbits 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +3 -0
- data/.rvmrc +10 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +33 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +29 -0
- data/VERSION +1 -0
- data/lib/friendconnect_helper.rb +28 -0
- data/lib/google_analytics_helper.rb +17 -0
- data/lib/paged.rb +58 -0
- data/lib/partials_helper.rb +17 -0
- data/lib/post.rb +95 -0
- data/lib/simple_blog.rb +7 -0
- data/lib/string.rb +21 -0
- data/lib/textile_tags.rb +21 -0
- data/spec/2011-04-09-bom-removal-test.textile +3 -0
- data/spec/2011-04-09-without-bom-test.textile +3 -0
- data/spec/helper.rb +18 -0
- data/spec/paged_spec.rb +75 -0
- data/spec/post_spec.rb +168 -0
- data/spec/string_spec.rb +35 -0
- metadata +232 -0
data/.document
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
rvm_install_on_use_flag=1
|
2
|
+
rvm --create use ruby-1.8.7-p299@blogbits
|
3
|
+
|
4
|
+
# make sure bundler and bundled gems are installed
|
5
|
+
if ! gem list | grep -q bundler; then gem install bundler; fi
|
6
|
+
if ! bundle check; then bundle install; fi
|
7
|
+
|
8
|
+
# boxout project commands
|
9
|
+
alias server='bundle exec thin --port 3000 start'
|
10
|
+
alias autospec='bundle exec infinity_test --rspec'
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
RedCloth (4.2.7)
|
5
|
+
ZenTest (4.5.0)
|
6
|
+
autotest (4.4.6)
|
7
|
+
ZenTest (>= 4.4.1)
|
8
|
+
diff-lcs (1.1.2)
|
9
|
+
git (1.2.5)
|
10
|
+
jeweler (1.5.2)
|
11
|
+
bundler (~> 1.0.0)
|
12
|
+
git (>= 1.2.5)
|
13
|
+
rake
|
14
|
+
nokogiri (1.4.4)
|
15
|
+
rake (0.8.7)
|
16
|
+
rspec (2.5.0)
|
17
|
+
rspec-core (~> 2.5.0)
|
18
|
+
rspec-expectations (~> 2.5.0)
|
19
|
+
rspec-mocks (~> 2.5.0)
|
20
|
+
rspec-core (2.5.1)
|
21
|
+
rspec-expectations (2.5.0)
|
22
|
+
diff-lcs (~> 1.1.2)
|
23
|
+
rspec-mocks (2.5.0)
|
24
|
+
|
25
|
+
PLATFORMS
|
26
|
+
ruby
|
27
|
+
|
28
|
+
DEPENDENCIES
|
29
|
+
RedCloth
|
30
|
+
autotest
|
31
|
+
jeweler
|
32
|
+
nokogiri
|
33
|
+
rspec
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Duncan Stewart
|
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.rdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
= blogbits
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Contributing to blogbits
|
6
|
+
|
7
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
8
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
9
|
+
* Fork the project
|
10
|
+
* Start a feature/bugfix branch
|
11
|
+
* Commit and push until you are happy with your contribution
|
12
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2011 Duncan Stewart. See LICENSE.txt for
|
18
|
+
further details.
|
19
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "dunkstewart-blogbits"
|
16
|
+
gem.homepage = "http://github.com/dunkstewart/blogbits"
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.summary = "library for creating a simple filesystem based blog from textile/yaml documents"
|
19
|
+
gem.description = "no really! that's it!"
|
20
|
+
gem.email = "dunkstewart@gmail.com"
|
21
|
+
gem.authors = ["Duncan Stewart"]
|
22
|
+
# Include your dependencies below.
|
23
|
+
gem.add_runtime_dependency 'RedCloth'
|
24
|
+
gem.add_runtime_dependency 'nokogiri'
|
25
|
+
gem.add_development_dependency 'jeweler'
|
26
|
+
gem.add_development_dependency 'rspec'
|
27
|
+
gem.add_development_dependency 'autotest'
|
28
|
+
end
|
29
|
+
Jeweler::RubygemsDotOrgTasks.new
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module FriendConnectHelper
|
2
|
+
def friendconnect_script
|
3
|
+
%{
|
4
|
+
<script type='text/javascript'>
|
5
|
+
$(document).ready(function() {
|
6
|
+
var skin = {};
|
7
|
+
skin['FONT_FAMILY'] = 'trebuchet ms,sans-serif';
|
8
|
+
skin['BORDER_COLOR'] = 'transparent';
|
9
|
+
skin['ENDCAP_BG_COLOR'] = 'transparent';
|
10
|
+
skin['ENDCAP_TEXT_COLOR'] = '#';
|
11
|
+
skin['ENDCAP_LINK_COLOR'] = '#';
|
12
|
+
skin['ALTERNATE_BG_COLOR'] = 'transparent';
|
13
|
+
skin['CONTENT_BG_COLOR'] = 'transparent';
|
14
|
+
skin['CONTENT_LINK_COLOR'] = '#';
|
15
|
+
skin['CONTENT_TEXT_COLOR'] = '#';
|
16
|
+
skin['CONTENT_SECONDARY_LINK_COLOR'] = '#';
|
17
|
+
skin['CONTENT_SECONDARY_TEXT_COLOR'] = '#';
|
18
|
+
skin['CONTENT_HEADLINE_COLOR'] = '#038AB0';
|
19
|
+
skin['NUMBER_ROWS'] = '4';
|
20
|
+
google.friendconnect.container.setParentUrl('/');
|
21
|
+
google.friendconnect.container.renderMembersGadget({
|
22
|
+
id: 'members',
|
23
|
+
site: '#{settings.friendconnect_id}'}, skin);
|
24
|
+
});
|
25
|
+
</script>
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module GoogleAnalyticsHelper
|
2
|
+
def google_analytics_script
|
3
|
+
%{
|
4
|
+
<script type='text/javascript'>
|
5
|
+
var _gaq = _gaq || [];
|
6
|
+
_gaq.push(['_setAccount', '#{settings.google_analytics_id}']);
|
7
|
+
_gaq.push(['_trackPageview']);
|
8
|
+
|
9
|
+
(function() {
|
10
|
+
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
11
|
+
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
12
|
+
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
13
|
+
})();
|
14
|
+
</script>
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
data/lib/paged.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
class CleanSlate
|
2
|
+
instance_methods.each { |m| undef_method m unless m.to_s =~ /^__/ }
|
3
|
+
end
|
4
|
+
|
5
|
+
class Paged < CleanSlate
|
6
|
+
def initialize(items, opts)
|
7
|
+
raise ArgumentError.new ':page_size must be a positive number' unless opts[:page_size] and opts[:page_size] > 0
|
8
|
+
@items = items
|
9
|
+
@page_size = opts[:page_size]
|
10
|
+
@page = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def page=(page)
|
14
|
+
raise ArgumentError.new 'page must be a number' if not page.kind_of? Numeric
|
15
|
+
@page = page - 1
|
16
|
+
end
|
17
|
+
|
18
|
+
def page
|
19
|
+
@page + 1
|
20
|
+
end
|
21
|
+
|
22
|
+
def previous_page
|
23
|
+
return if outside_bounds
|
24
|
+
page - 1 if page > 1
|
25
|
+
end
|
26
|
+
|
27
|
+
def next_page
|
28
|
+
return if outside_bounds
|
29
|
+
page + 1 if page < total_pages
|
30
|
+
end
|
31
|
+
|
32
|
+
def respond_to?(symbol, include_private=false)
|
33
|
+
@items.respond_to? symbol, include_private
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def method_missing(symbol, *args, &block)
|
39
|
+
page_of_items.send(symbol, *args, &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
def page_of_items
|
43
|
+
return [] if outside_bounds
|
44
|
+
@items[start_index, @page_size]
|
45
|
+
end
|
46
|
+
|
47
|
+
def start_index
|
48
|
+
@page * @page_size
|
49
|
+
end
|
50
|
+
|
51
|
+
def total_pages
|
52
|
+
(@items.size.quo @page_size).ceil
|
53
|
+
end
|
54
|
+
|
55
|
+
def outside_bounds
|
56
|
+
@page < 0 or @page > total_pages
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# https://gist.github.com/119874
|
2
|
+
module PartialsHelper
|
3
|
+
def partial(template, *args)
|
4
|
+
template_array = template.to_s.split('/')
|
5
|
+
template = template_array[0..-2].join('/') + "/_#{template_array[-1]}"
|
6
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
7
|
+
options.merge!(:layout => false)
|
8
|
+
if collection = options.delete(:collection) then
|
9
|
+
collection.inject([]) do |buffer, member|
|
10
|
+
buffer << haml(:"#{template}", options.merge(:layout =>
|
11
|
+
false, :locals => {template_array[-1].to_sym => member}))
|
12
|
+
end.join("\n")
|
13
|
+
else
|
14
|
+
haml(:"#{template}", options)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/post.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'redcloth'
|
2
|
+
require 'textile_tags'
|
3
|
+
|
4
|
+
class Post
|
5
|
+
DIV = /\r?\n---/
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :base_dir, :glob
|
9
|
+
|
10
|
+
def filenames
|
11
|
+
Dir.glob File.join(base_dir, Post.glob)
|
12
|
+
end
|
13
|
+
|
14
|
+
def find_latest
|
15
|
+
find.first
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :find_single, :find_latest
|
19
|
+
|
20
|
+
def categories
|
21
|
+
find.reduce({}) do |tag_score, post|
|
22
|
+
(post[:tags] || []).each do |tag|
|
23
|
+
tag_score[tag] ||= 0
|
24
|
+
tag_score[tag] += 1
|
25
|
+
end
|
26
|
+
tag_score
|
27
|
+
end.sort
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_by_tag(tag)
|
31
|
+
find.select { |post| (post[:tags] || []).map { |t| t.downcase }.include? tag.downcase }
|
32
|
+
end
|
33
|
+
|
34
|
+
def find
|
35
|
+
filenames \
|
36
|
+
.map { |path| from_file(path) } \
|
37
|
+
.sort { |p1,p2| p2[:date] <=> p1[:date] }
|
38
|
+
end
|
39
|
+
|
40
|
+
def from_file(path)
|
41
|
+
date = Date.parse(File.basename(path)[0..9])
|
42
|
+
match = path.match(/posts\/(.+)\//)
|
43
|
+
template = (match[1] + '_post').to_sym if match
|
44
|
+
meta, textile = split_and_parse read_file_strip_bom(path)
|
45
|
+
new meta, textile, date, path, template
|
46
|
+
end
|
47
|
+
|
48
|
+
def from_s(raw, date = nil)
|
49
|
+
date ||= Date.today
|
50
|
+
meta, textile = split_and_parse raw
|
51
|
+
new meta, textile, date
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def read_file_strip_bom(path)
|
57
|
+
IO.read(path, 3) == "\357\273\277" ? IO.read(path, nil, 3) : IO.read(path)
|
58
|
+
end
|
59
|
+
|
60
|
+
def split_and_parse(raw)
|
61
|
+
meta, body = {}, ''
|
62
|
+
header_body = raw.split(DIV)
|
63
|
+
meta = YAML.load(header_body.first) if raw =~ DIV
|
64
|
+
body = header_body.last if (header_body.size == 1 and raw !~ DIV) or header_body.size == 2
|
65
|
+
[meta, body]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize(meta, textile, date, path = nil, template = nil)
|
70
|
+
@meta = meta
|
71
|
+
@textile = textile
|
72
|
+
@path = path
|
73
|
+
@meta['date'] = date
|
74
|
+
@meta['template'] = template
|
75
|
+
end
|
76
|
+
|
77
|
+
def [](property)
|
78
|
+
@meta[property.to_s]
|
79
|
+
end
|
80
|
+
|
81
|
+
def html
|
82
|
+
r = RedCloth.new(@textile)
|
83
|
+
r.extend VideoTag
|
84
|
+
r.to_html
|
85
|
+
end
|
86
|
+
|
87
|
+
def path
|
88
|
+
@path
|
89
|
+
end
|
90
|
+
|
91
|
+
def slug
|
92
|
+
return '#' unless @path
|
93
|
+
File.basename(@path, '.textile').sub('-','/').sub('-','/').sub('-','/')
|
94
|
+
end
|
95
|
+
end
|
data/lib/simple_blog.rb
ADDED
data/lib/string.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
class String
|
5
|
+
def to_absolute(base_url)
|
6
|
+
base = URI.parse base_url
|
7
|
+
doc = Nokogiri::HTML self
|
8
|
+
[['img','src'],['a','href']].each do |selector,attribute|
|
9
|
+
doc.css(selector).each do |tag|
|
10
|
+
path = tag[attribute]
|
11
|
+
path.slice! 0 if path.start_with? '/'
|
12
|
+
tag[attribute] = base.merge(path).to_s
|
13
|
+
end
|
14
|
+
end
|
15
|
+
doc.to_html
|
16
|
+
end
|
17
|
+
|
18
|
+
def dehumanize
|
19
|
+
self.downcase.gsub(/\s/,'-').gsub(/[^a-z0-9-]/,'')
|
20
|
+
end
|
21
|
+
end
|
data/lib/textile_tags.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module VideoTag
|
2
|
+
def vid(opts)
|
3
|
+
dimensions, video_url, image_url = opts[:text].split("\n").map { |x| x.strip.chomp("<br />") }
|
4
|
+
width, height = dimensions.split(',').map { |x| x.strip.chomp("<br />") }
|
5
|
+
%{
|
6
|
+
<div
|
7
|
+
id="#{opts[:id]}"
|
8
|
+
class="player center"
|
9
|
+
href="#{video_url}"
|
10
|
+
style="width:#{width};height:#{height};background-image:url(#{image_url})">
|
11
|
+
<span></span>
|
12
|
+
<img class="play" src="/images/play.png" />
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<script type="text/javascript" src="/js/flowplayer-3.2.4.min.js"></script>
|
16
|
+
<script type="text/javascript">
|
17
|
+
flowplayer("#{opts[:id]}", "http://releases.flowplayer.org/swf/flowplayer-3.2.5.swf");
|
18
|
+
</script>
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
require 'shoulda'
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
|
+
require 'blogbits'
|
16
|
+
|
17
|
+
class Test::Unit::TestCase
|
18
|
+
end
|
data/spec/paged_spec.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'paged'
|
2
|
+
|
3
|
+
describe Paged do
|
4
|
+
context 'a list of items' do
|
5
|
+
before :each do
|
6
|
+
@list = ['apple', 'banana', 'cat', 'dog', 'elephant', 'frog', 'goat', 'house', 'ice', 'john']
|
7
|
+
@paged_list = Paged.new @list, :page_size => 3
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should show first page of items' do
|
11
|
+
@paged_list.should =~ ['apple', 'banana', 'cat']
|
12
|
+
@paged_list.next_page.should == 2
|
13
|
+
@paged_list.previous_page.should_not be
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should show second page of items' do
|
17
|
+
@paged_list.page = 2
|
18
|
+
@paged_list.should =~ ['dog', 'elephant', 'frog']
|
19
|
+
@paged_list.next_page.should == 3
|
20
|
+
@paged_list.previous_page.should == 1
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should show incomplete last page' do
|
24
|
+
@paged_list.page = 4
|
25
|
+
@paged_list.should =~ ['john']
|
26
|
+
@paged_list.next_page.should_not be
|
27
|
+
@paged_list.previous_page.should == 3
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should show nothing after last page' do
|
31
|
+
@paged_list.page = 99
|
32
|
+
@paged_list.should have(:no).items
|
33
|
+
@paged_list.next_page.should_not be
|
34
|
+
@paged_list.previous_page.should_not be
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should show nothing before first page' do
|
38
|
+
@paged_list.page = -1
|
39
|
+
@paged_list.should have(:no).items
|
40
|
+
@paged_list.next_page.should_not be
|
41
|
+
@paged_list.previous_page.should_not be
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should raise error for nil or non positive page size' do
|
45
|
+
expect { Paged.new @list, :page_size => nil }.to raise_error ArgumentError
|
46
|
+
expect { Paged.new @list, :page_size => 0 }.to raise_error ArgumentError
|
47
|
+
expect { Paged.new @list, :page_size => -1 }.to raise_error ArgumentError
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should raise error for nil or non number page' do
|
51
|
+
expect { @paged_list.page = nil }.to raise_error ArgumentError
|
52
|
+
expect { @paged_list.page = "2" }.to raise_error ArgumentError
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should show one page with no previous or next when page size matches list size' do
|
56
|
+
paged_list = Paged.new ['apple', 'banana'], :page_size => 2
|
57
|
+
paged_list.should =~ ['apple', 'banana']
|
58
|
+
paged_list.next_page.should_not be
|
59
|
+
paged_list.previous_page.should_not be
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should show two pages when list size is one greater then page size' do
|
63
|
+
paged_list = Paged.new ['apple', 'banana', 'cake'], :page_size => 2
|
64
|
+
paged_list.should =~ ['apple', 'banana']
|
65
|
+
paged_list.next_page.should == 2
|
66
|
+
paged_list.previous_page.should_not be
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should iterate page from list' do
|
70
|
+
x = 0
|
71
|
+
@paged_list.each { |item| x += 1 }
|
72
|
+
x.should == 3
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/spec/post_spec.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'post'
|
2
|
+
|
3
|
+
class String
|
4
|
+
def |
|
5
|
+
gsub(/^\s*\|/,'').strip
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe Post do
|
10
|
+
|
11
|
+
let(:path) { '/posts/2010-12-26-test.textile' }
|
12
|
+
|
13
|
+
describe 'filesystem reading' do
|
14
|
+
it 'should remove bom from start of file so title can be read' do
|
15
|
+
post = Post.from_file 'spec/2011-04-09-bom-removal-test.textile'
|
16
|
+
post[:title].should == 'Test title'
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should read title correctly in bom-less file' do
|
20
|
+
post = Post.from_file 'spec/2011-04-09-without-bom-test.textile'
|
21
|
+
post[:title].should == 'Test title'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'parsing' do
|
26
|
+
it 'should not require any header section' do
|
27
|
+
raw = %{
|
28
|
+
|just some body text
|
29
|
+
}.|
|
30
|
+
post = Post.from_s raw
|
31
|
+
post[:title].should_not be
|
32
|
+
post[:date].should == Date.today
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should load post properties from header' do
|
36
|
+
raw = %{
|
37
|
+
|title: test title
|
38
|
+
|tags: [post, gift, fun]
|
39
|
+
|---
|
40
|
+
|test body
|
41
|
+
}.|
|
42
|
+
post = Post.from_s raw
|
43
|
+
post[:title].should == 'test title'
|
44
|
+
post[:tags].should == ['post', 'gift', 'fun']
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should render the textile under the header as html' do
|
48
|
+
raw = %{
|
49
|
+
|title: test
|
50
|
+
|---
|
51
|
+
|body
|
52
|
+
}.|
|
53
|
+
post = Post.from_s raw
|
54
|
+
post.html.should == '<p>body</p>'
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should render all textile when no header' do
|
58
|
+
raw = %{
|
59
|
+
|body
|
60
|
+
}.|
|
61
|
+
post = Post.from_s raw
|
62
|
+
post.html.should == '<p>body</p>'
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should load meta data when no body' do
|
66
|
+
raw = %{
|
67
|
+
|title: test title
|
68
|
+
|tags: [post, gift, fun]
|
69
|
+
|---
|
70
|
+
}.|
|
71
|
+
post = Post.from_s raw
|
72
|
+
post[:title].should == 'test title'
|
73
|
+
post[:tags].should == ['post', 'gift', 'fun']
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'finding' do
|
78
|
+
it 'should glob for files under the base dir' do
|
79
|
+
Dir.should_receive(:glob).with 'posts/**/*.textile'
|
80
|
+
Post.base_dir = 'posts'
|
81
|
+
Post.glob = '**/*.textile'
|
82
|
+
Post.filenames
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should load posts sorted most recent first' do
|
86
|
+
paths = [
|
87
|
+
'/posts/2010-12-07-test-post2.textile',
|
88
|
+
'/posts/2010-12-09-test-post1.textile',
|
89
|
+
'/posts/2010-05-18-test-post3.textile'
|
90
|
+
]
|
91
|
+
Post.stub!(:filenames).and_return(paths)
|
92
|
+
IO.stub!(:read).and_return("title: test\n\nbody")
|
93
|
+
posts = Post.find
|
94
|
+
posts.map { |post| post[:date] }.should == [
|
95
|
+
Date.parse('2010-12-09'),
|
96
|
+
Date.parse('2010-12-07'),
|
97
|
+
Date.parse('2010-05-18')
|
98
|
+
]
|
99
|
+
end
|
100
|
+
|
101
|
+
[:find_latest, :find_single].each do |method|
|
102
|
+
it "should load the latest post through #{method}" do
|
103
|
+
paths = [
|
104
|
+
'/posts/2010-12-07-test-post2.textile',
|
105
|
+
'/posts/2010-12-09-test-post1.textile',
|
106
|
+
'/posts/2010-05-18-test-post3.textile'
|
107
|
+
]
|
108
|
+
Post.stub!(:filenames).and_return(paths)
|
109
|
+
IO.stub!(:read).and_return("title: test\n\nbody")
|
110
|
+
latest = Post.send(method)
|
111
|
+
latest[:date].should == Date.parse('2010-12-09')
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe 'templates' do
|
117
|
+
it 'should determine template based on post directory' do
|
118
|
+
IO.stub!(:read).and_return 'body'
|
119
|
+
Post.from_file('/posts/2010-01-01-blog-post.textile')[:template].should_not be
|
120
|
+
Post.from_file('/posts/blog/2010-01-01-blog-post.textile')[:template].should == :blog_post
|
121
|
+
Post.from_file('/posts/daily_gift_ideas/2010-01-01-blog-post.textile')[:template].should == :daily_gift_ideas_post
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe 'slugs' do
|
126
|
+
it 'should convert path to slug' do
|
127
|
+
IO.stub!(:read).and_return 'body'
|
128
|
+
post = Post.from_file '/posts/2010-12-07-test-post2.textile'
|
129
|
+
post.slug.should == '2010/12/07/test-post2'
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'should have # slug if created from string instead of path' do
|
133
|
+
post = Post.from_s ''
|
134
|
+
post.slug.should == '#'
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe 'tags' do
|
139
|
+
before :each do
|
140
|
+
posts = [['sharing'], ['social'], ['fun', 'technology'], ['technology'], ['technology']].map do |tags|
|
141
|
+
Post.new({ 'tags' => tags }, 'body', Date.parse('2010-12-09'))
|
142
|
+
end
|
143
|
+
Post.stub!(:find).and_return(posts)
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'should return hash of tags with count' do
|
147
|
+
Post.categories.should =~ [['sharing', 1], ['fun', 1], ['social', 1], ['technology', 3]]
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'should handle no tags' do
|
151
|
+
Post.stub!(:find).and_return([Post.new({}, 'body', Date.parse('2010-12-09'))])
|
152
|
+
Post.categories.should be_empty
|
153
|
+
Post.find_by_tag('fun').should be_empty
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'should sort categories alphabetically' do
|
157
|
+
Post.categories.should =~ [['fun', 1], ['sharing', 1], ['social', 1], ['technology', 3]]
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'should filter posts by tag' do
|
161
|
+
Post.find_by_tag('technology').should have(3).items
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'should filter case insensitively' do
|
165
|
+
Post.find_by_tag('Technology').should have(3).items
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
data/spec/string_spec.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'string'
|
3
|
+
|
4
|
+
describe String do
|
5
|
+
let :html do
|
6
|
+
%{
|
7
|
+
<html>
|
8
|
+
<body>
|
9
|
+
<img src='/images/lisa.jpg' />
|
10
|
+
<a href='/daily-gift-ideas?page=2' />
|
11
|
+
</body>
|
12
|
+
</html>
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should convert relative images to absolute' do
|
17
|
+
absolute_html = html.to_absolute('http://www.outsideofthebox.com.au')
|
18
|
+
Nokogiri::HTML(absolute_html).css('img').first['src'].should == 'http://www.outsideofthebox.com.au/images/lisa.jpg'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should convert relative links to absolute' do
|
22
|
+
absolute_html = html.to_absolute('http://www.outsideofthebox.com.au')
|
23
|
+
Nokogiri::HTML(absolute_html).css('a').first['href'].should == 'http://www.outsideofthebox.com.au/daily-gift-ideas?page=2'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should add path to base path when file scheme' do
|
27
|
+
absolute_html = html.to_absolute('file:/a/base/path/')
|
28
|
+
Nokogiri::HTML(absolute_html).css('img').first['src'].should == 'file:/a/base/path/images/lisa.jpg'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should dehumanize strings' do
|
32
|
+
'My new blog post!'.dehumanize.should == 'my-new-blog-post'
|
33
|
+
'My 1rst blog post...'.dehumanize.should == 'my-1rst-blog-post'
|
34
|
+
end
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dunkstewart-blogbits
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Duncan Stewart
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-04-15 00:00:00 +10:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
type: :runtime
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
name: RedCloth
|
33
|
+
version_requirements: *id001
|
34
|
+
prerelease: false
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
type: :runtime
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
name: nokogiri
|
47
|
+
version_requirements: *id002
|
48
|
+
prerelease: false
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
type: :development
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
name: jeweler
|
61
|
+
version_requirements: *id003
|
62
|
+
prerelease: false
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
type: :development
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
name: rspec
|
75
|
+
version_requirements: *id004
|
76
|
+
prerelease: false
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
type: :development
|
79
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
hash: 3
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
name: autotest
|
89
|
+
version_requirements: *id005
|
90
|
+
prerelease: false
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
type: :runtime
|
93
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
hash: 3
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
version: "0"
|
102
|
+
name: RedCloth
|
103
|
+
version_requirements: *id006
|
104
|
+
prerelease: false
|
105
|
+
- !ruby/object:Gem::Dependency
|
106
|
+
type: :runtime
|
107
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
hash: 3
|
113
|
+
segments:
|
114
|
+
- 0
|
115
|
+
version: "0"
|
116
|
+
name: nokogiri
|
117
|
+
version_requirements: *id007
|
118
|
+
prerelease: false
|
119
|
+
- !ruby/object:Gem::Dependency
|
120
|
+
type: :development
|
121
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
hash: 3
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
version: "0"
|
130
|
+
name: jeweler
|
131
|
+
version_requirements: *id008
|
132
|
+
prerelease: false
|
133
|
+
- !ruby/object:Gem::Dependency
|
134
|
+
type: :development
|
135
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
136
|
+
none: false
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
hash: 3
|
141
|
+
segments:
|
142
|
+
- 0
|
143
|
+
version: "0"
|
144
|
+
name: rspec
|
145
|
+
version_requirements: *id009
|
146
|
+
prerelease: false
|
147
|
+
- !ruby/object:Gem::Dependency
|
148
|
+
type: :development
|
149
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
150
|
+
none: false
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
hash: 3
|
155
|
+
segments:
|
156
|
+
- 0
|
157
|
+
version: "0"
|
158
|
+
name: autotest
|
159
|
+
version_requirements: *id010
|
160
|
+
prerelease: false
|
161
|
+
description: no really! that's it!
|
162
|
+
email: dunkstewart@gmail.com
|
163
|
+
executables: []
|
164
|
+
|
165
|
+
extensions: []
|
166
|
+
|
167
|
+
extra_rdoc_files:
|
168
|
+
- LICENSE.txt
|
169
|
+
- README.rdoc
|
170
|
+
files:
|
171
|
+
- .document
|
172
|
+
- .rspec
|
173
|
+
- .rvmrc
|
174
|
+
- Gemfile
|
175
|
+
- Gemfile.lock
|
176
|
+
- LICENSE.txt
|
177
|
+
- README.rdoc
|
178
|
+
- Rakefile
|
179
|
+
- VERSION
|
180
|
+
- lib/friendconnect_helper.rb
|
181
|
+
- lib/google_analytics_helper.rb
|
182
|
+
- lib/paged.rb
|
183
|
+
- lib/partials_helper.rb
|
184
|
+
- lib/post.rb
|
185
|
+
- lib/simple_blog.rb
|
186
|
+
- lib/string.rb
|
187
|
+
- lib/textile_tags.rb
|
188
|
+
- spec/2011-04-09-bom-removal-test.textile
|
189
|
+
- spec/2011-04-09-without-bom-test.textile
|
190
|
+
- spec/helper.rb
|
191
|
+
- spec/paged_spec.rb
|
192
|
+
- spec/post_spec.rb
|
193
|
+
- spec/string_spec.rb
|
194
|
+
has_rdoc: true
|
195
|
+
homepage: http://github.com/dunkstewart/blogbits
|
196
|
+
licenses:
|
197
|
+
- MIT
|
198
|
+
post_install_message:
|
199
|
+
rdoc_options: []
|
200
|
+
|
201
|
+
require_paths:
|
202
|
+
- lib
|
203
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
204
|
+
none: false
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
hash: 3
|
209
|
+
segments:
|
210
|
+
- 0
|
211
|
+
version: "0"
|
212
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
213
|
+
none: false
|
214
|
+
requirements:
|
215
|
+
- - ">="
|
216
|
+
- !ruby/object:Gem::Version
|
217
|
+
hash: 3
|
218
|
+
segments:
|
219
|
+
- 0
|
220
|
+
version: "0"
|
221
|
+
requirements: []
|
222
|
+
|
223
|
+
rubyforge_project:
|
224
|
+
rubygems_version: 1.5.2
|
225
|
+
signing_key:
|
226
|
+
specification_version: 3
|
227
|
+
summary: library for creating a simple filesystem based blog from textile/yaml documents
|
228
|
+
test_files:
|
229
|
+
- spec/helper.rb
|
230
|
+
- spec/paged_spec.rb
|
231
|
+
- spec/post_spec.rb
|
232
|
+
- spec/string_spec.rb
|