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 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 the Toto project by Cloudhead and the Scanty project by Adam Wiggins.
27
- I'm not sure how much code or design awesomeness one needs to use before
28
- they are obligated to include their license, so I'm included a link to
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 in 400 lines of code.}
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.9
1
+ 1.0.11
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "baron"
8
- s.version = "1.0.9"
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-23"
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 in 400 lines of code."
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
@@ -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
- $:.unshift File.dirname(__FILE__)
9
-
10
- # Converts a number into an ordinal, 1=>1st, 2=>2nd, 3=>3rd, etc
11
- class Fixnum
12
- def ordinal
13
- case self % 100
14
- when 11..13; "#{self}th"
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 => "&rsaquo;", # used to divide the different elements of the page title
336
- :truncation_marker => '&hellip;', # 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
@@ -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 => "&rsaquo;",
24
+
25
+ # symbol used to represent trucated text (article summary)
26
+ :truncation_marker => '&hellip;',
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
@@ -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
@@ -2,4 +2,4 @@
2
2
  # Theme Config File
3
3
  #
4
4
  # sample config file with no variables. Need to make sure this doesn't crash the app
5
- #
5
+ #d
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.9
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-23 00:00:00.000000000 Z
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 in 400 lines of code.
154
+ summary: Minimalist, yet full-featured, blog engine.
149
155
  test_files: []