baron 1.0.9 → 1.0.11
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +9 -5
- data/Rakefile +1 -3
- data/Readme.md +1 -1
- data/VERSION +1 -1
- data/baron.gemspec +9 -3
- data/lib/baron.rb +12 -347
- data/lib/baron/blog_engine.rb +185 -0
- data/lib/baron/config.rb +84 -0
- data/lib/baron/models/article.rb +61 -0
- data/lib/baron/models/theme.rb +25 -0
- data/lib/baron/page_controller.rb +25 -0
- data/lib/baron/utils.rb +39 -0
- data/spec/sample_data/supplemental-files/theme_config.yml +1 -1
- metadata +9 -3
data/LICENSE
CHANGED
@@ -20,19 +20,23 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
21
|
SOFTWARE.
|
22
22
|
|
23
|
-
|
23
|
+
---
|
24
24
|
|
25
25
|
While writing this blog engine, I barrowed some code and design approches
|
26
|
-
from
|
27
|
-
|
28
|
-
|
29
|
-
each of them just in case (and thank you both for your awesomeness!)
|
26
|
+
from many existing blog engines. I'm not sure how much one must borrow before
|
27
|
+
they are obligated to include the original license, so I'm including all of
|
28
|
+
them below just in case.
|
30
29
|
|
31
30
|
Toto
|
32
31
|
- URL: https://github.com/cloudhead/toto
|
33
32
|
- Author: http://cloudhead.io/ (Alexis Sellier)
|
34
33
|
- License: https://github.com/cloudhead/toto/blob/master/LICENSE
|
35
34
|
|
35
|
+
Jekyll
|
36
|
+
- URL: https://github.com/mojombo/jekyll
|
37
|
+
- Author: http://tom.preston-werner.com/ (Tom Preston-Werner)
|
38
|
+
- License: https://github.com/mojombo/jekyll/blob/master/LICENSE
|
39
|
+
|
36
40
|
Scanty
|
37
41
|
- URL: https://github.com/adamwiggins/scanty
|
38
42
|
- Author: http://about.adamwiggins.com/ (Adam Wiggins)
|
data/Rakefile
CHANGED
@@ -8,7 +8,7 @@ begin
|
|
8
8
|
require 'jeweler'
|
9
9
|
Jeweler::Tasks.new do |gem|
|
10
10
|
gem.name = "baron"
|
11
|
-
gem.summary = %Q{Minimalist, yet full-featured, blog engine
|
11
|
+
gem.summary = %Q{Minimalist, yet full-featured, blog engine.}
|
12
12
|
gem.description = %Q{What's in the box: (1) Publish to Heroku using Git (2) Author articles in markdown (3) Article categories (4) Multiple permalink formats supported (5) Redirects (6) Advanced SEO optimizations and Google Analytics/ Webmaster Tools support. Uses Rack, RSpec, Bootstrap, JQuery, Disqus, Thin}
|
13
13
|
gem.email = "nbuggia@gmail.com"
|
14
14
|
gem.homepage = "https://github.com/nbuggia/baron-blog-engine-gem"
|
@@ -25,9 +25,7 @@ end
|
|
25
25
|
# Run RSpec tests
|
26
26
|
|
27
27
|
require 'rspec/core/rake_task'
|
28
|
-
'clear'
|
29
28
|
RSpec::Core::RakeTask.new(:spec)
|
30
29
|
|
31
|
-
|
32
30
|
task :default => :test
|
33
31
|
task :test => [:check_dependencies, :spec]
|
data/Readme.md
CHANGED
@@ -5,7 +5,7 @@ A minimalist, yet fully-featured, blog engine for developers.
|
|
5
5
|
This project is only the blog engine gem, if you want to play with your own
|
6
6
|
blog, you should use this project, which includes the client:
|
7
7
|
|
8
|
-
https://github.com/nbuggia/baron-blog
|
8
|
+
**https://github.com/nbuggia/baron-blog**
|
9
9
|
|
10
10
|
##How Does it Work?
|
11
11
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.
|
1
|
+
1.0.11
|
data/baron.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "baron"
|
8
|
-
s.version = "1.0.
|
8
|
+
s.version = "1.0.11"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Nathan Buggia"]
|
12
|
-
s.date = "2013-03-
|
12
|
+
s.date = "2013-03-25"
|
13
13
|
s.description = "What's in the box: (1) Publish to Heroku using Git (2) Author articles in markdown (3) Article categories (4) Multiple permalink formats supported (5) Redirects (6) Advanced SEO optimizations and Google Analytics/ Webmaster Tools support. Uses Rack, RSpec, Bootstrap, JQuery, Disqus, Thin"
|
14
14
|
s.email = "nbuggia@gmail.com"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -22,6 +22,12 @@ Gem::Specification.new do |s|
|
|
22
22
|
"VERSION",
|
23
23
|
"baron.gemspec",
|
24
24
|
"lib/baron.rb",
|
25
|
+
"lib/baron/blog_engine.rb",
|
26
|
+
"lib/baron/config.rb",
|
27
|
+
"lib/baron/models/article.rb",
|
28
|
+
"lib/baron/models/theme.rb",
|
29
|
+
"lib/baron/page_controller.rb",
|
30
|
+
"lib/baron/utils.rb",
|
25
31
|
"spec/baron_article_spec.rb",
|
26
32
|
"spec/baron_blog_engine_spec.rb",
|
27
33
|
"spec/baron_spec.rb",
|
@@ -89,7 +95,7 @@ Gem::Specification.new do |s|
|
|
89
95
|
s.homepage = "https://github.com/nbuggia/baron-blog-engine-gem"
|
90
96
|
s.require_paths = ["lib"]
|
91
97
|
s.rubygems_version = "1.8.25"
|
92
|
-
s.summary = "Minimalist, yet full-featured, blog engine
|
98
|
+
s.summary = "Minimalist, yet full-featured, blog engine."
|
93
99
|
|
94
100
|
if s.respond_to? :specification_version then
|
95
101
|
s.specification_version = 3
|
data/lib/baron.rb
CHANGED
@@ -1,51 +1,22 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
# std libs
|
1
4
|
require 'yaml'
|
2
5
|
require 'date'
|
3
6
|
require 'erb'
|
7
|
+
|
8
|
+
# 3rd party
|
4
9
|
require 'rack'
|
5
10
|
require 'digest'
|
6
11
|
require 'rdiscount'
|
7
12
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
else
|
16
|
-
case self % 10
|
17
|
-
when 1; "#{self}st"
|
18
|
-
when 2; "#{self}nd"
|
19
|
-
when 3; "#{self}rd"
|
20
|
-
else "#{self}th"
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# Avoid a collision with ActiveSupport
|
27
|
-
class Date
|
28
|
-
unless respond_to? :iso8601
|
29
|
-
# Return the date as a String formatted according to ISO 8601.
|
30
|
-
def iso8601
|
31
|
-
::Time.utc(year, month, day, 0, 0, 0, 0).iso8601
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
class String
|
37
|
-
# Support String::bytesize in old versions of Ruby
|
38
|
-
if RUBY_VERSION < "1.9"
|
39
|
-
def bytesize
|
40
|
-
size
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
# Capitalize the first letter of each word in a string
|
45
|
-
def titleize
|
46
|
-
self.split(/(\W)/).map(&:capitalize).join
|
47
|
-
end
|
48
|
-
end
|
13
|
+
# baron specific
|
14
|
+
require 'baron/utils'
|
15
|
+
require 'baron/page_controller'
|
16
|
+
require 'baron/blog_engine'
|
17
|
+
require 'baron/config'
|
18
|
+
require 'baron/models/article'
|
19
|
+
require 'baron/models/theme'
|
49
20
|
|
50
21
|
module Baron
|
51
22
|
def self.env
|
@@ -56,312 +27,6 @@ module Baron
|
|
56
27
|
ENV['RACK_ENV'] = env
|
57
28
|
end
|
58
29
|
|
59
|
-
class PageController
|
60
|
-
def initialize articles_parts, categories, max_articles, params, theme, config
|
61
|
-
@categories, @params, @theme, @config = categories, params, theme, config
|
62
|
-
stop_at = (:all == max_articles) ? articles_parts.count : max_articles
|
63
|
-
@articles = articles_parts.take(stop_at).map { |file_parts| Article.new(file_parts, @config) }
|
64
|
-
@article = @articles.first
|
65
|
-
end
|
66
|
-
|
67
|
-
def render_html partial_template, layout_template
|
68
|
-
@content = ERB.new(File.read(partial_template)).result(binding)
|
69
|
-
if @content[0..99].include? '<html'
|
70
|
-
return @content
|
71
|
-
else
|
72
|
-
ERB.new(File.read(layout_template)).result(binding)
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
def render_rss template
|
77
|
-
ERB.new(File.read(template)).result(binding)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
class BlogEngine
|
82
|
-
def initialize config
|
83
|
-
@config = config
|
84
|
-
end
|
85
|
-
|
86
|
-
def process_redirects request_path
|
87
|
-
File.open(get_system_resource('redirects.txt'), 'r') do |file|
|
88
|
-
file.each_line do |line|
|
89
|
-
if line[0] != '#'
|
90
|
-
command, status, source_path, destination_path = line.split(' ')
|
91
|
-
return destination_path, status if request_path == source_path
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def process_request path, env = {}, mime_type = :html
|
98
|
-
route = (path || '/').split('/').reject { |i| i.empty? }
|
99
|
-
route << @config[:root] if route.empty?
|
100
|
-
mime_type = (mime_type =~ /txt|rss|json/) ? mime_type.to_sym : :html
|
101
|
-
categories = get_all_categories
|
102
|
-
params = {:page_name => route.first, :rss_feed => get_feed_path}
|
103
|
-
params[:page_title] = (route.first == @config[:root] ? '' : "#{route.first.capitalize} #{@config[:title_delimiter]} ") + "#{@config[:title]}"
|
104
|
-
theme = Theme.new(@config)
|
105
|
-
theme.load_config
|
106
|
-
|
107
|
-
begin
|
108
|
-
|
109
|
-
# RSS feed... /feed.rss
|
110
|
-
body = if mime_type == :rss
|
111
|
-
PageController.new(get_all_articles, categories, @config[:article_max], params, theme, @config) .
|
112
|
-
render_rss(get_system_resource('feed.rss'))
|
113
|
-
|
114
|
-
# Robots... /robots.txt
|
115
|
-
elsif route.first == 'robots'
|
116
|
-
PageController.new(get_all_articles, categories, @config[:article_max], params, theme, @config) .
|
117
|
-
render_rss(get_system_resource('robots.txt'))
|
118
|
-
|
119
|
-
# Home page... /
|
120
|
-
elsif route.first == @config[:root]
|
121
|
-
all_articles = get_all_articles
|
122
|
-
params[:page_forward] = '/page/2/' if @config[:article_max] < all_articles.count
|
123
|
-
PageController.new(all_articles, categories, @config[:article_max], params, theme, @config) .
|
124
|
-
render_html(theme.get_template(route.first), theme.get_template('layout'))
|
125
|
-
|
126
|
-
# Pagination... /page/2, /page/2/
|
127
|
-
elsif route.first == 'page' && route.count == 2
|
128
|
-
page_num = route.last.to_i rescue page_num = -1
|
129
|
-
all_articles = get_all_articles
|
130
|
-
max_pages = (all_articles.count.to_f / @config[:article_max].to_f).ceil
|
131
|
-
raise(Errno::ENOENT, 'Page not found') if page_num < 1 or page_num > max_pages
|
132
|
-
|
133
|
-
starting_article = ((page_num - 1) * @config[:article_max])
|
134
|
-
articles_on_this_page = all_articles.slice(starting_article, @config[:article_max])
|
135
|
-
|
136
|
-
show_next = (page_num * @config[:article_max]) < all_articles.count
|
137
|
-
params[:page_back] = "/page/#{(page_num-1).to_s}/" if page_num > 1
|
138
|
-
params[:page_forward] = "/page/#{(page_num+1).to_s}/" if show_next
|
139
|
-
params[:page_title] = "Page #{page_num.to_s} #{@config[:title_delimiter]} #{@config[:title]}"
|
140
|
-
|
141
|
-
PageController.new(articles_on_this_page, categories, @config[:article_max], params, theme, @config) .
|
142
|
-
render_html(theme.get_template('home'), theme.get_template('layout'))
|
143
|
-
|
144
|
-
# System routes... /robots.txt, /archives
|
145
|
-
elsif route.first == 'archives' or route.first == 'robots'
|
146
|
-
max_articles = ('archives' == route.first) ? :all : @config[:article_max]
|
147
|
-
PageController.new(get_all_articles, categories, max_articles, params, theme, @config) .
|
148
|
-
render_html(theme.get_template(route.first), theme.get_template('layout'))
|
149
|
-
|
150
|
-
# Custom pages... /about, /contact-us
|
151
|
-
elsif is_route_custom_page? route.first
|
152
|
-
PageController.new(get_all_articles, categories, @config[:article_max], params, theme, @config) .
|
153
|
-
render_html(get_page_template(route.first), theme.get_template('layout'))
|
154
|
-
|
155
|
-
# Category home pages... /projects/, /photography/, /poems/, etc
|
156
|
-
elsif is_route_category_home? route.last
|
157
|
-
filtered_articles = get_all_articles.select { |h| h[:category] == route.last }
|
158
|
-
params[:page_name] = route.last.gsub('-', ' ').titleize
|
159
|
-
PageController.new(filtered_articles, categories, :all, params, theme, @config) .
|
160
|
-
render_html(theme.get_template('category'), theme.get_template('layout'))
|
161
|
-
|
162
|
-
# Articles... /posts/2013/01/18/my-article-title, /posts/category/2013/my-article-title, etc
|
163
|
-
else
|
164
|
-
article = [ find_single_article(route.last) ]
|
165
|
-
params[:page_title] = "#{article.first[:filename].gsub('-',' ').titleize} #{@config[:title_delimiter]} #{@config[:title]}"
|
166
|
-
PageController.new(article, categories, 1, params, theme, @config) .
|
167
|
-
render_html(theme.get_template('article'), theme.get_template('layout'))
|
168
|
-
end
|
169
|
-
|
170
|
-
return :body => body, :type => mime_type, :status => 200
|
171
|
-
|
172
|
-
rescue Errno::ENOENT => e
|
173
|
-
|
174
|
-
# 404 Page Not Found
|
175
|
-
params[:error_message] = 'Page not found'
|
176
|
-
params[:error_code] = '404'
|
177
|
-
body = PageController.new([], categories, 0, params, theme, @config) .
|
178
|
-
render_html(theme.get_template('error'), theme.get_template('layout'))
|
179
|
-
|
180
|
-
return :body => body, :type => :html, :status => 404
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
def get_all_category_folder_paths
|
185
|
-
category_paths = Dir["#{get_articles_path}/*/"].map { |a| "#{get_articles_path}/#{File.basename(a)}" }
|
186
|
-
# includes the default articles directory as an unnamed (e.g. empty) path
|
187
|
-
category_paths << "#{get_articles_path}"
|
188
|
-
end
|
189
|
-
|
190
|
-
def get_all_articles
|
191
|
-
get_all_category_folder_paths.map do |folder_name|
|
192
|
-
Dir["#{folder_name}/*"].map do |e|
|
193
|
-
if e.end_with? @config[:ext]
|
194
|
-
parts = e.split('/')
|
195
|
-
{
|
196
|
-
:filename_and_path => e,
|
197
|
-
:date => parts.last[0..9],
|
198
|
-
:filename => parts.last[11..(-1 * (@config[:ext].length + 2))].downcase, # trims date and extention
|
199
|
-
:category => parts[parts.count-2] == 'articles' ? '' : parts[parts.count-2]
|
200
|
-
}
|
201
|
-
end
|
202
|
-
end
|
203
|
-
end .
|
204
|
-
flatten .
|
205
|
-
delete_if { |a| a == nil } .
|
206
|
-
sort_by { |hash| hash[:date] } .
|
207
|
-
reverse # sorts by decending date
|
208
|
-
end
|
209
|
-
|
210
|
-
def get_all_categories
|
211
|
-
Dir["#{get_articles_path}/*/"].map do |a|
|
212
|
-
folder_name = File.basename(a)
|
213
|
-
{
|
214
|
-
:name => folder_name.titleize,
|
215
|
-
:node_name => folder_name.gsub(' ', '-'),
|
216
|
-
:path => "/#{@config[:permalink_prefix]}/#{folder_name.gsub(' ', '-')}/".squeeze('/'),
|
217
|
-
:count => Dir["#{get_articles_path}/#{folder_name}/*"].count
|
218
|
-
}
|
219
|
-
end .
|
220
|
-
sort_by { |hash| hash[:name] }
|
221
|
-
end
|
222
|
-
|
223
|
-
def find_single_article article_slug
|
224
|
-
get_all_articles.each { |fileparts| return fileparts if fileparts[:filename] == article_slug }
|
225
|
-
raise Errno::ENOENT, 'Article not found'
|
226
|
-
end
|
227
|
-
|
228
|
-
def is_route_custom_page? path_node
|
229
|
-
(Dir["#{get_pages_path}/*"]).include?("#{get_pages_path}/#{path_node}.rhtml")
|
230
|
-
end
|
231
|
-
|
232
|
-
def is_route_category_home? path_node
|
233
|
-
get_all_categories.each { |h| return true if h[:node_name] == path_node }
|
234
|
-
return false
|
235
|
-
end
|
236
|
-
|
237
|
-
def get_pages_path() "#{@config[:sample_data_path]}pages/" end
|
238
|
-
def get_articles_path() "#{@config[:sample_data_path]}articles" end
|
239
|
-
def get_page_template(name) "#{@config[:sample_data_path]}pages/#{name}.rhtml" end
|
240
|
-
def get_system_resource(name) "#{@config[:sample_data_path]}resources/#{name}" end
|
241
|
-
def get_feed_path() "#{@config[:url]}/feed.rss" end
|
242
|
-
end
|
243
|
-
|
244
|
-
class Theme < Hash
|
245
|
-
def initialize config
|
246
|
-
@config = config
|
247
|
-
self[:root] = "/themes/#{config[:theme]}"
|
248
|
-
self[:name] = config[:theme]
|
249
|
-
self[:file_root] = "#{@config[:sample_data_path]}themes/#{@config[:theme]}".squeeze('/')
|
250
|
-
self[:theme_config] = "#{self[:file_root]}/theme_config.yml".squeeze('/')
|
251
|
-
end
|
252
|
-
|
253
|
-
def load_config filename_and_path = ''
|
254
|
-
filename_and_path = filename_and_path.empty? ? self[:theme_config] : filename_and_path
|
255
|
-
params = YAML.load(File.read(filename_and_path))
|
256
|
-
params.each_pair { |key, value| self[key.downcase.to_sym] = value } unless !params
|
257
|
-
rescue Errno::ENOENT => e
|
258
|
-
puts "Warning: unable to load config file : " + filename_and_path
|
259
|
-
end
|
260
|
-
|
261
|
-
def root() self[:root] end
|
262
|
-
|
263
|
-
def get_template name
|
264
|
-
"#{self[:file_root]}/templates/#{name}.rhtml".squeeze('/')
|
265
|
-
end
|
266
|
-
end
|
267
|
-
|
268
|
-
class Article < Hash
|
269
|
-
def initialize file_parts, config = {}
|
270
|
-
@config = config
|
271
|
-
self[:filename_and_path] = file_parts[:filename_and_path]
|
272
|
-
self[:slug] = file_parts[:filename]
|
273
|
-
self[:category] = file_parts[:category].empty? ? '' : file_parts[:category]
|
274
|
-
self[:date] = Date.parse(file_parts[:date].gsub('/', '-')) rescue Date.today
|
275
|
-
load_article(file_parts[:filename_and_path])
|
276
|
-
end
|
277
|
-
|
278
|
-
def summary length = nil
|
279
|
-
config = @config[:summary]
|
280
|
-
sum = if self[:body] =~ config[:delim]
|
281
|
-
self[:body].split(config[:delim]).first
|
282
|
-
else
|
283
|
-
self[:body].match(/(.{1,#{length || config[:length] || config[:max]}}.*?)(\n|\Z)/m).to_s
|
284
|
-
end
|
285
|
-
markdown(sum.length == self[:body].length ? sum : sum.strip.sub(/\.\Z/, @config[:truncation_marker]))
|
286
|
-
end
|
287
|
-
|
288
|
-
def body
|
289
|
-
markdown self[:body].sub(@config[:summary][:delim], '') rescue markdown self[:body]
|
290
|
-
end
|
291
|
-
|
292
|
-
def path prefix = '', date_format = ''
|
293
|
-
permalink_prefix = prefix.empty? ? @config[:permalink_prefix] : prefix
|
294
|
-
permalink_date_format = date_format.empty? ? @config[:permalink_date_format] : date_format
|
295
|
-
date_path = case permalink_date_format
|
296
|
-
when :year_date; self[:date].strftime("/%Y")
|
297
|
-
when :year_month_date; self[:date].strftime("/%Y/%m")
|
298
|
-
when :year_month_day_date; self[:date].strftime("/%Y/%m/%d")
|
299
|
-
else ''
|
300
|
-
end
|
301
|
-
|
302
|
-
"/#{permalink_prefix}/#{self[:category]}#{date_path}/#{slug}/".squeeze('/')
|
303
|
-
end
|
304
|
-
|
305
|
-
def title() self[:title] || 'Untitled' end
|
306
|
-
def date() @config[:date].call(self[:date]) end
|
307
|
-
def author() self[:author] || @config[:author] end
|
308
|
-
def category() self[:category] end
|
309
|
-
def permalink() "http://#{(@config[:url].sub("http://", '') + self.path).squeeze('/')}" end
|
310
|
-
def slug() self[:slug] end
|
311
|
-
|
312
|
-
protected
|
313
|
-
|
314
|
-
def load_article filename_and_path
|
315
|
-
metadata, self[:body] = File.read(filename_and_path).split(/\n\n/, 2)
|
316
|
-
YAML.load(metadata).each_pair { |key, value| self[key.downcase.to_sym] = value }
|
317
|
-
end
|
318
|
-
|
319
|
-
def markdown text
|
320
|
-
if (options = @config[:markdown])
|
321
|
-
Markdown.new(text.to_s.strip, *(options.eql?(true) ? [] : options)).to_html
|
322
|
-
else
|
323
|
-
text.strip
|
324
|
-
end
|
325
|
-
end
|
326
|
-
end
|
327
|
-
|
328
|
-
class Config < Hash
|
329
|
-
Defaults = {
|
330
|
-
:cache => 28800, # cache duration (seconds)
|
331
|
-
:root => 'home', # site home page
|
332
|
-
:sample_data_path => '', # used by the RSpec tests to show where the sample data is stored
|
333
|
-
:author => ENV['USER'], # blog author
|
334
|
-
:title => Dir.pwd.split('/').last, # site title
|
335
|
-
:title_delimiter => "›", # used to divide the different elements of the page title
|
336
|
-
:truncation_marker => '…', # symbol used to represent trucated text (article summary)
|
337
|
-
:url => 'http://localhost/', # root URL of the site
|
338
|
-
:date => lambda {|now| now.strftime("%d/%m/%Y") }, # date function
|
339
|
-
:markdown => :smart, # use markdown
|
340
|
-
:summary => {:max => 150, :delim => /~\n/}, # length of summary and delimiter
|
341
|
-
:ext => 'txt', # extension for articles
|
342
|
-
:permalink_prefix => '', # common path prefix for article permalinks
|
343
|
-
:permalink_date_format => :year_month_day_date, # :year_date, :year_month_date, :year_month_day_date, :no_date
|
344
|
-
:article_max => 5, # number of most recent articles to return to custom pages
|
345
|
-
:theme => 'default', # name of the theme to use
|
346
|
-
:google_analytics => '', # account id for google analytics account
|
347
|
-
:google_webmaster => '', # HTML Meta Tag verification code for google webmaster account
|
348
|
-
:disqus_shortname => false # account name for your disqus account www.disqus.com
|
349
|
-
}
|
350
|
-
|
351
|
-
def initialize obj
|
352
|
-
self.update Defaults
|
353
|
-
self.update obj
|
354
|
-
end
|
355
|
-
|
356
|
-
def set key, val = nil, &block
|
357
|
-
if val.is_a? Hash
|
358
|
-
self[key].update val
|
359
|
-
else
|
360
|
-
self[key] = block_given?? block : val
|
361
|
-
end
|
362
|
-
end
|
363
|
-
end
|
364
|
-
|
365
30
|
class Server
|
366
31
|
attr_reader :config, :site
|
367
32
|
|
@@ -0,0 +1,185 @@
|
|
1
|
+
module Baron
|
2
|
+
|
3
|
+
class BlogEngine
|
4
|
+
def initialize config
|
5
|
+
@config = config
|
6
|
+
end
|
7
|
+
|
8
|
+
def process_redirects request_path
|
9
|
+
File.open(get_system_resource('redirects.txt'), 'r') do |file|
|
10
|
+
file.each_line do |line|
|
11
|
+
if line[0] != '#'
|
12
|
+
command, status, source_path, destination_path = line.split(' ')
|
13
|
+
return destination_path, status if request_path == source_path
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def process_request path, env = {}, mime_type = :html
|
20
|
+
route = (path || '/').split('/').reject { |i| i.empty? }
|
21
|
+
route << @config[:root] if route.empty?
|
22
|
+
mime_type = (mime_type =~ /txt|rss|json/) ? mime_type.to_sym : :html
|
23
|
+
categories = get_all_categories
|
24
|
+
params = {:page_name => route.first, :rss_feed => get_feed_path}
|
25
|
+
params[:page_title] = (route.first == @config[:root] ? '' : "#{route.first.capitalize} #{@config[:title_delimiter]} ") + "#{@config[:title]}"
|
26
|
+
theme = Theme.new(@config)
|
27
|
+
theme.load_config
|
28
|
+
|
29
|
+
begin
|
30
|
+
|
31
|
+
# RSS feed... /feed.rss
|
32
|
+
body = if mime_type == :rss
|
33
|
+
PageController.new(get_all_articles, categories, @config[:article_max], params, theme, @config) .
|
34
|
+
render_rss(get_system_resource('feed.rss'))
|
35
|
+
|
36
|
+
# Robots... /robots.txt
|
37
|
+
elsif route.first == 'robots'
|
38
|
+
PageController.new(get_all_articles, categories, @config[:article_max], params, theme, @config) .
|
39
|
+
render_rss(get_system_resource('robots.txt'))
|
40
|
+
|
41
|
+
# Home page... /
|
42
|
+
elsif route.first == @config[:root]
|
43
|
+
all_articles = get_all_articles
|
44
|
+
params[:page_forward] = '/page/2/' if @config[:article_max] < all_articles.count
|
45
|
+
PageController.new(all_articles, categories, @config[:article_max], params, theme, @config) .
|
46
|
+
render_html(theme.get_template(route.first), theme.get_template('layout'))
|
47
|
+
|
48
|
+
# Pagination... /page/2, /page/2/
|
49
|
+
elsif route.first == 'page' && route.count == 2
|
50
|
+
page_num = route.last.to_i rescue page_num = -1
|
51
|
+
all_articles = get_all_articles
|
52
|
+
max_pages = (all_articles.count.to_f / @config[:article_max].to_f).ceil
|
53
|
+
raise(Errno::ENOENT, 'Page not found') if page_num < 1 or page_num > max_pages
|
54
|
+
|
55
|
+
starting_article = ((page_num - 1) * @config[:article_max])
|
56
|
+
articles_on_this_page = all_articles.slice(starting_article, @config[:article_max])
|
57
|
+
|
58
|
+
show_next = (page_num * @config[:article_max]) < all_articles.count
|
59
|
+
params[:page_back] = "/page/#{(page_num-1).to_s}/" if page_num > 1
|
60
|
+
params[:page_forward] = "/page/#{(page_num+1).to_s}/" if show_next
|
61
|
+
params[:page_title] = "Page #{page_num.to_s} #{@config[:title_delimiter]} #{@config[:title]}"
|
62
|
+
|
63
|
+
PageController.new(articles_on_this_page, categories, @config[:article_max], params, theme, @config) .
|
64
|
+
render_html(theme.get_template('home'), theme.get_template('layout'))
|
65
|
+
|
66
|
+
# System routes... /robots.txt, /archives
|
67
|
+
elsif route.first == 'archives' or route.first == 'robots'
|
68
|
+
max_articles = ('archives' == route.first) ? :all : @config[:article_max]
|
69
|
+
PageController.new(get_all_articles, categories, max_articles, params, theme, @config) .
|
70
|
+
render_html(theme.get_template(route.first), theme.get_template('layout'))
|
71
|
+
|
72
|
+
# Custom pages... /about, /contact-us
|
73
|
+
elsif is_route_custom_page? route.first
|
74
|
+
PageController.new(get_all_articles, categories, @config[:article_max], params, theme, @config) .
|
75
|
+
render_html(get_page_template(route.first), theme.get_template('layout'))
|
76
|
+
|
77
|
+
# Category home pages... /projects/, /photography/, /poems/, etc
|
78
|
+
elsif is_route_category_home? route.last
|
79
|
+
filtered_articles = get_all_articles.select { |h| h[:category] == route.last }
|
80
|
+
params[:page_name] = route.last.gsub('-', ' ').titleize
|
81
|
+
PageController.new(filtered_articles, categories, :all, params, theme, @config) .
|
82
|
+
render_html(theme.get_template('category'), theme.get_template('layout'))
|
83
|
+
|
84
|
+
# Articles... /posts/2013/01/18/my-article-title, /posts/category/2013/my-article-title, etc
|
85
|
+
else
|
86
|
+
article = [ find_single_article(route.last) ]
|
87
|
+
params[:page_title] = "#{article.first[:filename].gsub('-',' ').titleize} #{@config[:title_delimiter]} #{@config[:title]}"
|
88
|
+
PageController.new(article, categories, 1, params, theme, @config) .
|
89
|
+
render_html(theme.get_template('article'), theme.get_template('layout'))
|
90
|
+
end
|
91
|
+
|
92
|
+
return :body => body, :type => mime_type, :status => 200
|
93
|
+
|
94
|
+
rescue Errno::ENOENT => e
|
95
|
+
|
96
|
+
# 404 Page Not Found
|
97
|
+
params[:error_message] = 'Page not found'
|
98
|
+
params[:error_code] = '404'
|
99
|
+
body = PageController.new([], categories, 0, params, theme, @config) .
|
100
|
+
render_html(theme.get_template('error'), theme.get_template('layout'))
|
101
|
+
|
102
|
+
return :body => body, :type => :html, :status => 404
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def get_all_category_folder_paths
|
107
|
+
category_paths = Dir["#{get_articles_path}/*/"].map do |a|
|
108
|
+
"#{get_articles_path}/#{File.basename(a)}"
|
109
|
+
end
|
110
|
+
# includes the default articles directory as an unnamed (e.g. empty) path
|
111
|
+
category_paths << "#{get_articles_path}"
|
112
|
+
end
|
113
|
+
|
114
|
+
def get_all_articles
|
115
|
+
get_all_category_folder_paths.map do |folder_name|
|
116
|
+
Dir["#{folder_name}/*"].map do |e|
|
117
|
+
if e.end_with? @config[:ext]
|
118
|
+
parts = e.split('/')
|
119
|
+
{
|
120
|
+
:filename_and_path => e,
|
121
|
+
:date => parts.last[0..9],
|
122
|
+
# trims date and extention
|
123
|
+
:filename => parts.last[11..(-1 * (@config[:ext].length + 2))].downcase,
|
124
|
+
:category => parts[parts.count-2] == 'articles' ? '' : parts[parts.count-2]
|
125
|
+
}
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end .
|
129
|
+
flatten .
|
130
|
+
delete_if { |a| a == nil } .
|
131
|
+
sort_by { |hash| hash[:date] } .
|
132
|
+
reverse # sorts by decending date
|
133
|
+
end
|
134
|
+
|
135
|
+
def get_all_categories
|
136
|
+
Dir["#{get_articles_path}/*/"].map do |a|
|
137
|
+
folder_name = File.basename(a)
|
138
|
+
{
|
139
|
+
:name => folder_name.titleize,
|
140
|
+
:node_name => folder_name.gsub(' ', '-'),
|
141
|
+
:path => "/#{@config[:permalink_prefix]}/#{folder_name.gsub(' ', '-')}/".squeeze('/'),
|
142
|
+
:count => Dir["#{get_articles_path}/#{folder_name}/*"].count
|
143
|
+
}
|
144
|
+
end .
|
145
|
+
sort_by { |hash| hash[:name] }
|
146
|
+
end
|
147
|
+
|
148
|
+
def find_single_article article_slug
|
149
|
+
get_all_articles.each do |fileparts|
|
150
|
+
return fileparts if fileparts[:filename] == article_slug
|
151
|
+
end
|
152
|
+
raise Errno::ENOENT, 'Article not found'
|
153
|
+
end
|
154
|
+
|
155
|
+
def is_route_custom_page? path_node
|
156
|
+
(Dir["#{get_pages_path}/*"]).include?("#{get_pages_path}/#{path_node}.rhtml")
|
157
|
+
end
|
158
|
+
|
159
|
+
def is_route_category_home? path_node
|
160
|
+
get_all_categories.each { |h| return true if h[:node_name] == path_node }
|
161
|
+
return false
|
162
|
+
end
|
163
|
+
|
164
|
+
def get_pages_path
|
165
|
+
"#{@config[:sample_data_path]}pages/"
|
166
|
+
end
|
167
|
+
|
168
|
+
def get_articles_path
|
169
|
+
"#{@config[:sample_data_path]}articles".squeeze('/')
|
170
|
+
end
|
171
|
+
|
172
|
+
def get_page_template name
|
173
|
+
"#{@config[:sample_data_path]}pages/#{name}.rhtml".squeeze('/')
|
174
|
+
end
|
175
|
+
|
176
|
+
def get_system_resource name
|
177
|
+
"#{@config[:sample_data_path]}resources/#{name}".squeeze('/')
|
178
|
+
end
|
179
|
+
|
180
|
+
def get_feed_path
|
181
|
+
"#{@config[:url]}/feed.rss"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
data/lib/baron/config.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
module Baron
|
2
|
+
|
3
|
+
class Config < Hash
|
4
|
+
|
5
|
+
Defaults = {
|
6
|
+
|
7
|
+
# cache duration (seconds)
|
8
|
+
:cache => 28800,
|
9
|
+
|
10
|
+
# token to represent root in the app
|
11
|
+
:root => 'home',
|
12
|
+
|
13
|
+
# used by the RSpec tests to show where the sample data is stored
|
14
|
+
:sample_data_path => '',
|
15
|
+
|
16
|
+
# default name to use for the blog's author
|
17
|
+
:author => ENV['USER'],
|
18
|
+
|
19
|
+
# default title for the site
|
20
|
+
:title => Dir.pwd.split('/').last,
|
21
|
+
|
22
|
+
# used to divide the different elements of the page title
|
23
|
+
:title_delimiter => "›",
|
24
|
+
|
25
|
+
# symbol used to represent trucated text (article summary)
|
26
|
+
:truncation_marker => '…',
|
27
|
+
|
28
|
+
# root URL of the site
|
29
|
+
:url => 'http://localhost:3000/',
|
30
|
+
|
31
|
+
# date function block
|
32
|
+
:date => lambda {|now| now.strftime("%d/%m/%Y") },
|
33
|
+
|
34
|
+
# use markdown
|
35
|
+
:markdown => :smart,
|
36
|
+
|
37
|
+
# length of summary and delimiter
|
38
|
+
:summary => {:max => 150, :delim => /~\n/},
|
39
|
+
|
40
|
+
# extension for article files
|
41
|
+
:ext => 'txt',
|
42
|
+
|
43
|
+
# common path prefix for article permalinks
|
44
|
+
:permalink_prefix => '',
|
45
|
+
|
46
|
+
# :year_date, :year_month_date, :year_month_day_date, :no_date
|
47
|
+
:permalink_date_format => :year_month_day_date,
|
48
|
+
|
49
|
+
# number of most recent articles to return to custom pages
|
50
|
+
:article_max => 5,
|
51
|
+
|
52
|
+
# name of the theme to use
|
53
|
+
:theme => 'default',
|
54
|
+
|
55
|
+
# account id for google analytics account
|
56
|
+
:google_analytics => '',
|
57
|
+
|
58
|
+
# HTML Meta Tag verification code for google webmaster account
|
59
|
+
:google_webmaster => '',
|
60
|
+
|
61
|
+
# account name for your disqus account www.disqus.com
|
62
|
+
:disqus_shortname => false
|
63
|
+
}
|
64
|
+
|
65
|
+
def get_feed_permalink
|
66
|
+
"#{self[:url]}feed.rss"
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize obj
|
70
|
+
self.update Defaults
|
71
|
+
self.update obj
|
72
|
+
end
|
73
|
+
|
74
|
+
def set key, val = nil, &block
|
75
|
+
if val.is_a? Hash
|
76
|
+
self[key].update val
|
77
|
+
else
|
78
|
+
self[key] = block_given?? block : val
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end # Config
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Baron
|
2
|
+
class Article < Hash
|
3
|
+
def initialize file_parts, config = {}
|
4
|
+
@config = config
|
5
|
+
self[:filename_and_path] = file_parts[:filename_and_path]
|
6
|
+
self[:slug] = file_parts[:filename]
|
7
|
+
self[:category] = file_parts[:category].empty? ? '' : file_parts[:category]
|
8
|
+
self[:date] = Date.parse(file_parts[:date].gsub('/', '-')) rescue Date.today
|
9
|
+
load_article(file_parts[:filename_and_path])
|
10
|
+
end
|
11
|
+
|
12
|
+
def summary length = nil
|
13
|
+
config = @config[:summary]
|
14
|
+
sum = if self[:body] =~ config[:delim]
|
15
|
+
self[:body].split(config[:delim]).first
|
16
|
+
else
|
17
|
+
self[:body].match(/(.{1,#{length || config[:length] || config[:max]}}.*?)(\n|\Z)/m).to_s
|
18
|
+
end
|
19
|
+
markdown(sum.length == self[:body].length ? sum : sum.strip.sub(/\.\Z/, @config[:truncation_marker]))
|
20
|
+
end
|
21
|
+
|
22
|
+
def body
|
23
|
+
markdown self[:body].sub(@config[:summary][:delim], '') rescue markdown self[:body]
|
24
|
+
end
|
25
|
+
|
26
|
+
def path prefix = '', date_format = ''
|
27
|
+
permalink_prefix = prefix.empty? ? @config[:permalink_prefix] : prefix
|
28
|
+
permalink_date_format = date_format.empty? ? @config[:permalink_date_format] : date_format
|
29
|
+
date_path = case permalink_date_format
|
30
|
+
when :year_date; self[:date].strftime("/%Y")
|
31
|
+
when :year_month_date; self[:date].strftime("/%Y/%m")
|
32
|
+
when :year_month_day_date; self[:date].strftime("/%Y/%m/%d")
|
33
|
+
else ''
|
34
|
+
end
|
35
|
+
|
36
|
+
"/#{permalink_prefix}/#{self[:category]}#{date_path}/#{slug}/".squeeze('/')
|
37
|
+
end
|
38
|
+
|
39
|
+
def title() self[:title] || 'Untitled' end
|
40
|
+
def date() @config[:date].call(self[:date]) end
|
41
|
+
def author() self[:author] || @config[:author] end
|
42
|
+
def category() self[:category] end
|
43
|
+
def permalink() "http://#{(@config[:url].sub("http://", '') + self.path).squeeze('/')}" end
|
44
|
+
def slug() self[:slug] end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def load_article filename_and_path
|
49
|
+
metadata, self[:body] = File.read(filename_and_path).split(/\n\n/, 2)
|
50
|
+
YAML.load(metadata).each_pair { |key, value| self[key.downcase.to_sym] = value }
|
51
|
+
end
|
52
|
+
|
53
|
+
def markdown text
|
54
|
+
if (options = @config[:markdown])
|
55
|
+
Markdown.new(text.to_s.strip, *(options.eql?(true) ? [] : options)).to_html
|
56
|
+
else
|
57
|
+
text.strip
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Baron
|
2
|
+
class Theme < Hash
|
3
|
+
def initialize config
|
4
|
+
@config = config
|
5
|
+
self[:root] = "/themes/#{config[:theme]}"
|
6
|
+
self[:name] = config[:theme]
|
7
|
+
self[:file_root] = "#{@config[:sample_data_path]}themes/#{@config[:theme]}".squeeze('/')
|
8
|
+
self[:theme_config] = "#{self[:file_root]}/theme_config.yml".squeeze('/')
|
9
|
+
end
|
10
|
+
|
11
|
+
def load_config filename_and_path = ''
|
12
|
+
filename_and_path = filename_and_path.empty? ? self[:theme_config] : filename_and_path
|
13
|
+
params = YAML.load(File.read(filename_and_path))
|
14
|
+
params.each_pair { |key, value| self[key.downcase.to_sym] = value } unless !params
|
15
|
+
rescue Errno::ENOENT => e
|
16
|
+
puts "Warning: unable to load config file : " + filename_and_path
|
17
|
+
end
|
18
|
+
|
19
|
+
def root() self[:root] end
|
20
|
+
|
21
|
+
def get_template name
|
22
|
+
"#{self[:file_root]}/templates/#{name}.rhtml".squeeze('/')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Baron
|
2
|
+
|
3
|
+
class PageController
|
4
|
+
def initialize articles_parts, categories, max_articles, params, theme, config
|
5
|
+
@categories, @params, @theme, @config = categories, params, theme, config
|
6
|
+
stop_at = (:all == max_articles) ? articles_parts.count : max_articles
|
7
|
+
@articles = articles_parts.take(stop_at).map { |file_parts| Article.new(file_parts, @config) }
|
8
|
+
@article = @articles.first
|
9
|
+
end
|
10
|
+
|
11
|
+
def render_html partial_template, layout_template
|
12
|
+
@content = ERB.new(File.read(partial_template)).result(binding)
|
13
|
+
if @content[0..99].include? '<html'
|
14
|
+
return @content
|
15
|
+
else
|
16
|
+
ERB.new(File.read(layout_template)).result(binding)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def render_rss template
|
21
|
+
ERB.new(File.read(template)).result(binding)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/lib/baron/utils.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Converts a number into an ordinal, 1=>1st, 2=>2nd, 3=>3rd, etc
|
2
|
+
class Fixnum
|
3
|
+
def ordinal
|
4
|
+
case self % 100
|
5
|
+
when 11..13; "#{self}th"
|
6
|
+
else
|
7
|
+
case self % 10
|
8
|
+
when 1; "#{self}st"
|
9
|
+
when 2; "#{self}nd"
|
10
|
+
when 3; "#{self}rd"
|
11
|
+
else "#{self}th"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Avoid a collision with ActiveSupport
|
18
|
+
class Date
|
19
|
+
unless respond_to? :iso8601
|
20
|
+
# Return the date as a String formatted according to ISO 8601.
|
21
|
+
def iso8601
|
22
|
+
::Time.utc(year, month, day, 0, 0, 0, 0).iso8601
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class String
|
28
|
+
# Support String::bytesize in old versions of Ruby
|
29
|
+
if RUBY_VERSION < "1.9"
|
30
|
+
def bytesize
|
31
|
+
size
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Capitalize the first letter of each word in a string
|
36
|
+
def titleize
|
37
|
+
self.split(/(\W)/).map(&:capitalize).join
|
38
|
+
end
|
39
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: baron
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.11
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -59,6 +59,12 @@ files:
|
|
59
59
|
- VERSION
|
60
60
|
- baron.gemspec
|
61
61
|
- lib/baron.rb
|
62
|
+
- lib/baron/blog_engine.rb
|
63
|
+
- lib/baron/config.rb
|
64
|
+
- lib/baron/models/article.rb
|
65
|
+
- lib/baron/models/theme.rb
|
66
|
+
- lib/baron/page_controller.rb
|
67
|
+
- lib/baron/utils.rb
|
62
68
|
- spec/baron_article_spec.rb
|
63
69
|
- spec/baron_blog_engine_spec.rb
|
64
70
|
- spec/baron_spec.rb
|
@@ -145,5 +151,5 @@ rubyforge_project:
|
|
145
151
|
rubygems_version: 1.8.25
|
146
152
|
signing_key:
|
147
153
|
specification_version: 3
|
148
|
-
summary: Minimalist, yet full-featured, blog engine
|
154
|
+
summary: Minimalist, yet full-featured, blog engine.
|
149
155
|
test_files: []
|