baron 1.0.9 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|