spree_simple_blog 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +6 -0
- data/app/controllers/articles_controller.rb +30 -0
- data/app/helpers/articles_helper.rb +37 -0
- data/app/models/article.rb +53 -0
- data/app/models/file_model.rb +98 -0
- data/app/views/articles/_archives.html.erb +8 -0
- data/app/views/articles/_article.html.erb +31 -0
- data/app/views/articles/_comment_count_js.html.erb +18 -0
- data/app/views/articles/_recent.html.erb +8 -0
- data/app/views/articles/_sidebar.html.erb +11 -0
- data/app/views/articles/_subscribe.html.erb +6 -0
- data/app/views/articles/index.html.erb +30 -0
- data/app/views/articles/index.xml.builder +20 -0
- data/app/views/articles/show.html.erb +38 -0
- data/lib/spree_simple_blog.rb +22 -0
- data/lib/tasks/blog.rake +30 -0
- data/lib/tasks/simple_blog_extension_tasks.rake +17 -0
- metadata +117 -0
data/README.markdown
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
= Spree Simple Blog
|
2
|
+
|
3
|
+
Adds a simple blog to Spree which works much like Toto (http://github.com/cloudhead/toto/), reading text files in /content/articles. Content files consist of
|
4
|
+
yaml metadata followed by Markdown content.
|
5
|
+
|
6
|
+
By extending FileModel, other types of content can be managed in the same way way.
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class ArticlesController < Spree::BaseController
|
2
|
+
rescue_from Errno::ENOENT, :with => :render_404
|
3
|
+
|
4
|
+
def index
|
5
|
+
@per_page = 10
|
6
|
+
@current_page = (params[:page] || 1).to_i
|
7
|
+
@total_pages = (Article.count / @per_page.to_f).ceil
|
8
|
+
@articles = Article.find_articles(:page => @current_page)
|
9
|
+
respond_to do |wants|
|
10
|
+
wants.html {}
|
11
|
+
wants.xml {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def archive
|
16
|
+
@articles = Article.find_articles(:year => params[:year], :month => params[:month])
|
17
|
+
render :action => 'index'
|
18
|
+
end
|
19
|
+
|
20
|
+
def tag
|
21
|
+
@articles = Article.tagged(params[:tag])
|
22
|
+
render :action => 'index'
|
23
|
+
end
|
24
|
+
|
25
|
+
def show
|
26
|
+
key = "#{params[:year]}-#{params[:month].rjust(2,'0')}-#{params[:day].rjust(2,'0')}-#{params[:permalink]}"
|
27
|
+
@article = Article.find(key)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rdiscount'
|
2
|
+
|
3
|
+
module ArticlesHelper
|
4
|
+
|
5
|
+
def html_transform(text)
|
6
|
+
RDiscount.new(text.to_s).to_html.html_safe
|
7
|
+
end
|
8
|
+
|
9
|
+
def format_date(time)
|
10
|
+
time.strftime("%B %d %Y")
|
11
|
+
end
|
12
|
+
|
13
|
+
def format_time(time)
|
14
|
+
time.strftime("%I:%m%p")
|
15
|
+
end
|
16
|
+
|
17
|
+
def link_to_archive(archive_month)
|
18
|
+
text = "#{Date::MONTHNAMES[archive_month.last.to_i]} #{archive_month.first.to_i}"
|
19
|
+
link_to text, blog_articles_archive_url(:year => archive_month.first, :month => archive_month.last.to_s.rjust(2, '0'))
|
20
|
+
end
|
21
|
+
|
22
|
+
def link_to_article(article, options = {})
|
23
|
+
link_to h(article.title), article_url(article), options
|
24
|
+
end
|
25
|
+
|
26
|
+
def article_url(article)
|
27
|
+
blog_article_url :permalink => article.slug, :year => article.date.year, :month => article.date.month, :day => article.date.day
|
28
|
+
end
|
29
|
+
|
30
|
+
def url_escape(string)
|
31
|
+
string.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
|
32
|
+
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
33
|
+
end.tr(' ', '+')
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class Article < FileModel
|
2
|
+
|
3
|
+
def self.find_articles(options = {})
|
4
|
+
options = {
|
5
|
+
:per_page => 10,
|
6
|
+
:page => 1
|
7
|
+
}.merge(options)
|
8
|
+
|
9
|
+
options[:offset] = (options[:page]-1) * options[:per_page]
|
10
|
+
options[:limit] = options[:per_page]
|
11
|
+
|
12
|
+
if options[:year] and options[:month]
|
13
|
+
options[:match] = %r(\/#{options[:year]}-#{options[:month].to_s.rjust(2,'0')})
|
14
|
+
end
|
15
|
+
|
16
|
+
all(options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.recent(number = 5)
|
20
|
+
@@recent ||= all(:limit => number)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.tagged(tag)
|
24
|
+
tag_index[tag.strip].to_a.map do |key|
|
25
|
+
Article.find(key)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.archive_months
|
30
|
+
@@archive_months ||= files.map{|f| File.basename(f).split('-')[0..1] }.uniq
|
31
|
+
end
|
32
|
+
|
33
|
+
# Store list of articles for each tag in a hash
|
34
|
+
# Fast enough for the small number of articles we're dealing with
|
35
|
+
@@tag_index = {}
|
36
|
+
def self.tag_index
|
37
|
+
return @@tag_index if @@tag_index.present?
|
38
|
+
all.each do |a|
|
39
|
+
a.tags.split(',').map(&:strip).each do |t|
|
40
|
+
@@tag_index[t] ||= []
|
41
|
+
@@tag_index[t] = (@@tag_index[t] << a.key).uniq
|
42
|
+
end
|
43
|
+
end
|
44
|
+
@@tag_index
|
45
|
+
end
|
46
|
+
|
47
|
+
%w(title_tag meta_keywords meta_description).each do |attribute|
|
48
|
+
define_method(attribute) do
|
49
|
+
meta[attribute]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# Base class for simple models that load their content from text files formatted like Toto's articles
|
2
|
+
class FileModel
|
3
|
+
attr_accessor :file, :key, :meta, :body
|
4
|
+
|
5
|
+
def initialize(file, key, meta, body)
|
6
|
+
@file = file
|
7
|
+
@key = key
|
8
|
+
@meta = meta
|
9
|
+
@body = body
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(method, *args)
|
13
|
+
if meta.has_key?(method.to_s)
|
14
|
+
meta[method.to_s]
|
15
|
+
else
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def self.all(options = {})
|
22
|
+
options = {
|
23
|
+
:offset => 0
|
24
|
+
}.merge(options)
|
25
|
+
|
26
|
+
files = get_filtered_files(options)
|
27
|
+
|
28
|
+
if options[:limit]
|
29
|
+
files = files[options[:offset] .. (options[:offset].to_i + options[:limit].to_i-1)]
|
30
|
+
end
|
31
|
+
|
32
|
+
records = files.to_a.map do |f|
|
33
|
+
from_file(f)
|
34
|
+
end
|
35
|
+
|
36
|
+
records
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.find(key)
|
40
|
+
from_file("#{content_path}/#{key}.txt")
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.count(options = {})
|
44
|
+
get_filtered_files(options).length
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.random(options = {})
|
48
|
+
from_file(files[rand(all.size)])
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.files
|
52
|
+
Dir["#{content_path}/*.txt"].sort_by {|f| File.basename(f) }.reverse
|
53
|
+
end
|
54
|
+
|
55
|
+
def index
|
56
|
+
@index ||= self.class.files.index(file)
|
57
|
+
end
|
58
|
+
|
59
|
+
def next
|
60
|
+
return @next if @next.present?
|
61
|
+
if next_file = self.class.files[index + 1]
|
62
|
+
@next = self.class.from_file(next_file)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def previous
|
67
|
+
return @previous if @previous.present?
|
68
|
+
if index > 0 and previous_file = self.class.files[index - 1]
|
69
|
+
@previous = self.class.from_file(previous_file)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.from_file(file)
|
74
|
+
meta, body = File.read(file).split(/\n\n/, 2)
|
75
|
+
meta = YAML.load(meta)
|
76
|
+
key = File.basename(file).gsub('.txt','')
|
77
|
+
new(file, key, meta, body)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.content_path
|
81
|
+
File.expand_path(File.join(Rails.root, "content", relative_path))
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.relative_path
|
85
|
+
self.name.underscore
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def self.get_filtered_files(options)
|
91
|
+
if options[:match]
|
92
|
+
files.select{|f| f =~ options[:match]}
|
93
|
+
else
|
94
|
+
files
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<div class="post">
|
2
|
+
|
3
|
+
<div class="heading">
|
4
|
+
<h2><%= link_to_article article %></h2>
|
5
|
+
<p>
|
6
|
+
<span class="author">by <strong><%= article.author %></strong></span>
|
7
|
+
•
|
8
|
+
<span class="date"><%= format_date article.date %></span>
|
9
|
+
</p>
|
10
|
+
</div>
|
11
|
+
|
12
|
+
<div class="entry">
|
13
|
+
|
14
|
+
<%= html_transform article.body %>
|
15
|
+
|
16
|
+
</div>
|
17
|
+
|
18
|
+
<% if @articles %>
|
19
|
+
<div class="meta">
|
20
|
+
<p class="comments"><a href="<%= article_url article %>#respond" title="Comment on <%= article.title %>">Comments »</a></p>
|
21
|
+
<p class="time">Posted @ <%= format_time article.date %></p>
|
22
|
+
<br class="clear" />
|
23
|
+
</div>
|
24
|
+
<% else %>
|
25
|
+
<% if @article.tags.present? %>
|
26
|
+
<p>Tags:
|
27
|
+
<%= @article.tags.split(',').map { |tag| link_to(tag, blog_tag_path(tag), :rel => 'tag')}.join(', ') %>
|
28
|
+
<% end %>
|
29
|
+
<% end %>
|
30
|
+
|
31
|
+
</div>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<% if Spree::Config[:disqus_id] %>
|
2
|
+
<% content_for :extra_javascripts do %>
|
3
|
+
<script type="text/javascript">
|
4
|
+
//<![CDATA[
|
5
|
+
(function() {
|
6
|
+
var links = document.getElementsByTagName('a');
|
7
|
+
var query = '?';
|
8
|
+
for(var i = 0; i < links.length; i++) {
|
9
|
+
if(links[i].href.indexOf('#disqus_thread') >= 0) {
|
10
|
+
query += 'url' + i + '=' + encodeURIComponent(links[i].href) + '&';
|
11
|
+
}
|
12
|
+
}
|
13
|
+
document.write('<script charset="utf-8" type="text/javascript" src="http://disqus.com/forums/<%= Spree::Config[:disqus_id] %>/get_num_replies.js' + query + '"></' + 'script>');
|
14
|
+
})();
|
15
|
+
//]]>
|
16
|
+
</script>
|
17
|
+
<% end %>
|
18
|
+
<% end %>
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<% self.title = "Blog" %>
|
2
|
+
<% @body_class = 'blog' %>
|
3
|
+
|
4
|
+
<% if params[:tag] %>
|
5
|
+
<h2 class="pagetitle">Posts Tagged ‘<%= params[:tag] %>’</h2>
|
6
|
+
<% elsif params[:year] && params[:month] %>
|
7
|
+
<h2 class="pagetitle">Archive for <%= Date::MONTHNAMES[params[:month].to_i] %>, <%= params[:year].to_i %></h2>
|
8
|
+
<% end %>
|
9
|
+
|
10
|
+
<%= render :partial => 'article', :collection => @articles %>
|
11
|
+
|
12
|
+
<% if @total_pages %>
|
13
|
+
<div class="navigation">
|
14
|
+
<% if @total_pages > 1 && @current_page < @total_pages %>
|
15
|
+
<div class="alignleft"><a href="<%= url_for(:overwrite_params => {:page => @current_page + 1}) %>" >« Older Entries</a></div>
|
16
|
+
<% end %>
|
17
|
+
<% if @current_page > 1 %>
|
18
|
+
<div class="alignright"><a href="<%= url_for(:overwrite_params => {:page => @current_page - 1}) %>" >Newer Entries »</a></div>
|
19
|
+
<% end %>
|
20
|
+
<br class="clear" />
|
21
|
+
</div>
|
22
|
+
<% end %>
|
23
|
+
|
24
|
+
<% content_for :sidebar do %>
|
25
|
+
|
26
|
+
<%= render 'sidebar' %>
|
27
|
+
|
28
|
+
<% end %>
|
29
|
+
|
30
|
+
<br class="clear" />
|
@@ -0,0 +1,20 @@
|
|
1
|
+
xml.instruct!
|
2
|
+
xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
|
3
|
+
xml.title Spree::Config[:blog_feed_title]
|
4
|
+
xml.id Spree::Config[:blog_feed_url]
|
5
|
+
xml.updated @articles.first.date.xmlschema unless @articles.empty?
|
6
|
+
xml.author { xml.name Spree::Config[:blog_feed_author] }
|
7
|
+
|
8
|
+
@articles.each do |article|
|
9
|
+
xml.entry do
|
10
|
+
xml.title article.title
|
11
|
+
xml.link "rel" => "alternate", "href" => article_url(article)
|
12
|
+
xml.id article_url(article)
|
13
|
+
xml.published article.date.xmlschema
|
14
|
+
xml.updated article.date.xmlschema
|
15
|
+
xml.author { xml.name article.author }
|
16
|
+
xml.content("type" => "html") { xml.cdata!(html_transform(article.body)) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<% self.title = "Blog - " + @article.title %>
|
2
|
+
<% @body_class = 'blog' %>
|
3
|
+
|
4
|
+
<div class="navigation">
|
5
|
+
<% if @article.previous %>
|
6
|
+
<div class="alignleft">« <%= link_to_article @article.previous %></div>
|
7
|
+
<% end %>
|
8
|
+
<% if @article.next %>
|
9
|
+
<div class="alignright"><%= link_to_article @article.next %> »</div>
|
10
|
+
<% end %>
|
11
|
+
<br class="clear" />
|
12
|
+
</div>
|
13
|
+
|
14
|
+
<%= render 'article', :article => @article %>
|
15
|
+
|
16
|
+
<% if Spree::Config[:disqus_id] %>
|
17
|
+
<div id="comments">
|
18
|
+
<a name="respond"></a>
|
19
|
+
<div id="disqus_thread"></div>
|
20
|
+
<script type="text/javascript">
|
21
|
+
var disqus_developer = <%= RAILS_ENV == 'development' ? '1' : '0' %>;
|
22
|
+
</script>
|
23
|
+
<noscript><a href="http://spreecommerce.disqus.com/?url=ref">View the discussion thread.</a></noscript>
|
24
|
+
<a href="http://disqus.com" class="dsq-brlink">blog comments powered by <span class="logo-disqus">Disqus</span></a>
|
25
|
+
</div>
|
26
|
+
<% end %>
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
<% content_for :sidebar do %>
|
31
|
+
<%= render 'sidebar' %>
|
32
|
+
<% end %>
|
33
|
+
|
34
|
+
<% if Spree::Config[:disqus_id] %>
|
35
|
+
<% content_for :extra_javascripts do %>
|
36
|
+
<script type="text/javascript" src="http://disqus.com/forums/<%= Spree::Config[:disqus_id] %>/embed.js"></script>
|
37
|
+
<% end %>
|
38
|
+
<% end %>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module SimpleBlogExtension
|
2
|
+
class Engine < Rails::Engine
|
3
|
+
|
4
|
+
def self.activate
|
5
|
+
|
6
|
+
Spree::BaseController.class_eval do
|
7
|
+
helper ArticlesHelper
|
8
|
+
end
|
9
|
+
|
10
|
+
AppConfiguration.class_eval do
|
11
|
+
preference :disqus_id, :default => nil
|
12
|
+
preference :blog_feed_url, :default => '/blog.xml'
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
config.autoload_paths += %W(#{config.root}/lib)
|
19
|
+
config.to_prepare &method(:activate).to_proc
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
data/lib/tasks/blog.rake
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
namespace :blog do
|
2
|
+
|
3
|
+
task :new => :environment do
|
4
|
+
|
5
|
+
title = ask('Title: ')
|
6
|
+
slug = title.empty?? nil : title.strip.parameterize.to_s
|
7
|
+
|
8
|
+
article = {'slug' => slug, 'title' => title, 'author' => 'Author', 'tags' => '', 'date' => Date.today}.to_yaml
|
9
|
+
article << "\n"
|
10
|
+
article << "Lorem ipsum dolor sit amet...\n\n"
|
11
|
+
|
12
|
+
path = "#{RAILS_ROOT}/content/articles/#{Time.now.strftime("%Y-%m-%d")}#{'-' + slug if slug}.txt"
|
13
|
+
|
14
|
+
unless File.exist? path
|
15
|
+
File.open(path, "w") do |file|
|
16
|
+
file.write article
|
17
|
+
end
|
18
|
+
puts "an article was created for you at #{path}."
|
19
|
+
else
|
20
|
+
puts "I can't create the article, #{path} already exists."
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
def ask message
|
28
|
+
print message
|
29
|
+
STDIN.gets.chomp
|
30
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
namespace :spree do
|
2
|
+
namespace :extensions do
|
3
|
+
namespace :simple_blog do
|
4
|
+
desc "Copies public assets of the Simple Blog to the instance public/ directory."
|
5
|
+
task :update => :environment do
|
6
|
+
is_svn_git_or_dir = proc {|path| path =~ /\.svn/ || path =~ /\.git/ || File.directory?(path) }
|
7
|
+
Dir[SimpleBlogExtension.root + "/public/**/*"].reject(&is_svn_git_or_dir).each do |file|
|
8
|
+
path = file.sub(SimpleBlogExtension.root, '')
|
9
|
+
directory = File.dirname(path)
|
10
|
+
puts "Copying #{path}..."
|
11
|
+
mkdir_p RAILS_ROOT + directory
|
12
|
+
cp file, RAILS_ROOT + path
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: spree_simple_blog
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 5
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 3
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 3.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors: []
|
13
|
+
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-09-03 00:00:00 -05:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: spree_core
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - "="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: -1848229955
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 30
|
33
|
+
- 0
|
34
|
+
- beta1
|
35
|
+
version: 0.30.0.beta1
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id001
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: rdiscount
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - "="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 9
|
47
|
+
segments:
|
48
|
+
- 1
|
49
|
+
- 5
|
50
|
+
- 5
|
51
|
+
version: 1.5.5
|
52
|
+
type: :runtime
|
53
|
+
version_requirements: *id002
|
54
|
+
description:
|
55
|
+
email:
|
56
|
+
executables: []
|
57
|
+
|
58
|
+
extensions: []
|
59
|
+
|
60
|
+
extra_rdoc_files: []
|
61
|
+
|
62
|
+
files:
|
63
|
+
- README.markdown
|
64
|
+
- lib/spree_simple_blog.rb
|
65
|
+
- lib/tasks/blog.rake
|
66
|
+
- lib/tasks/simple_blog_extension_tasks.rake
|
67
|
+
- app/controllers/articles_controller.rb
|
68
|
+
- app/helpers/articles_helper.rb
|
69
|
+
- app/models/article.rb
|
70
|
+
- app/models/file_model.rb
|
71
|
+
- app/views/articles/_archives.html.erb
|
72
|
+
- app/views/articles/_article.html.erb
|
73
|
+
- app/views/articles/_comment_count_js.html.erb
|
74
|
+
- app/views/articles/_recent.html.erb
|
75
|
+
- app/views/articles/_sidebar.html.erb
|
76
|
+
- app/views/articles/_subscribe.html.erb
|
77
|
+
- app/views/articles/index.html.erb
|
78
|
+
- app/views/articles/index.xml.builder
|
79
|
+
- app/views/articles/show.html.erb
|
80
|
+
has_rdoc: true
|
81
|
+
homepage:
|
82
|
+
licenses: []
|
83
|
+
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options: []
|
86
|
+
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
hash: 57
|
95
|
+
segments:
|
96
|
+
- 1
|
97
|
+
- 8
|
98
|
+
- 7
|
99
|
+
version: 1.8.7
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
hash: 3
|
106
|
+
segments:
|
107
|
+
- 0
|
108
|
+
version: "0"
|
109
|
+
requirements:
|
110
|
+
- none
|
111
|
+
rubyforge_project:
|
112
|
+
rubygems_version: 1.3.7
|
113
|
+
signing_key:
|
114
|
+
specification_version: 3
|
115
|
+
summary: Toto style blogging for Spree
|
116
|
+
test_files: []
|
117
|
+
|