simple_posts 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/javascripts/simple_posts/application.js +13 -0
  6. data/app/assets/stylesheets/simple_posts/application.css +15 -0
  7. data/app/controllers/simple_posts/application_controller.rb +4 -0
  8. data/app/controllers/simple_posts/posts_controller.rb +46 -0
  9. data/app/helpers/simple_posts/application_helper.rb +4 -0
  10. data/app/views/layouts/simple_posts/application.html.erb +14 -0
  11. data/app/views/simple_posts/posts/index.html.erb +9 -0
  12. data/app/views/simple_posts/posts/show.html.erb +5 -0
  13. data/config/routes.rb +7 -0
  14. data/lib/simple_posts.rb +23 -0
  15. data/lib/simple_posts/engine.rb +5 -0
  16. data/lib/simple_posts/posts.rb +307 -0
  17. data/lib/simple_posts/version.rb +3 -0
  18. data/lib/tasks/simple_posts_tasks.rake +4 -0
  19. data/test/dummy/README.rdoc +28 -0
  20. data/test/dummy/Rakefile +6 -0
  21. data/test/dummy/app/assets/javascripts/application.js +13 -0
  22. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  23. data/test/dummy/app/controllers/application_controller.rb +5 -0
  24. data/test/dummy/app/helpers/application_helper.rb +2 -0
  25. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  26. data/test/dummy/bin/bundle +3 -0
  27. data/test/dummy/bin/rails +4 -0
  28. data/test/dummy/bin/rake +4 -0
  29. data/test/dummy/bin/setup +29 -0
  30. data/test/dummy/config.ru +4 -0
  31. data/test/dummy/config/application.rb +27 -0
  32. data/test/dummy/config/boot.rb +5 -0
  33. data/test/dummy/config/database.yml +25 -0
  34. data/test/dummy/config/environment.rb +5 -0
  35. data/test/dummy/config/environments/development.rb +41 -0
  36. data/test/dummy/config/environments/production.rb +79 -0
  37. data/test/dummy/config/environments/test.rb +42 -0
  38. data/test/dummy/config/initializers/assets.rb +11 -0
  39. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  40. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  41. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  42. data/test/dummy/config/initializers/inflections.rb +16 -0
  43. data/test/dummy/config/initializers/mime_types.rb +4 -0
  44. data/test/dummy/config/initializers/session_store.rb +3 -0
  45. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  46. data/test/dummy/config/locales/en.yml +23 -0
  47. data/test/dummy/config/routes.rb +4 -0
  48. data/test/dummy/config/secrets.yml +22 -0
  49. data/test/dummy/log/development.log +0 -0
  50. data/test/dummy/public/404.html +67 -0
  51. data/test/dummy/public/422.html +67 -0
  52. data/test/dummy/public/500.html +66 -0
  53. data/test/dummy/public/favicon.ico +0 -0
  54. data/test/integration/navigation_test.rb +10 -0
  55. data/test/simple_posts_test.rb +7 -0
  56. data/test/test_helper.rb +19 -0
  57. metadata +179 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d9fe02d4f12de24a74de21f2f270edccc090b48b
4
+ data.tar.gz: 92b606bf2cfe6c4be37e134635af4cc5364ff22a
5
+ SHA512:
6
+ metadata.gz: abb6b6be268e3552a76bcdfe597235bc41d141b75cdefa3cbb1dd3b8700e9e7cb20a13449af4a3f269b3b016c2c0f5f1a0dc6ce16e10760485864585942efd9e
7
+ data.tar.gz: 030058d5ddc8a88d8043808c264101db02a7037d6af50da7e5aa1985bfae75d4a6bfc35f64352b7da30dfddb881f0f040f83856239690b05d7376ad8f3ae1c70
@@ -0,0 +1,20 @@
1
+ Copyright 2015 Pete Keen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,3 @@
1
+ = SimplePosts
2
+
3
+ This project rocks and uses MIT-LICENSE.
@@ -0,0 +1,37 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'SimplePosts'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task default: :test
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ module SimplePosts
2
+ class ApplicationController < ::ApplicationController
3
+ end
4
+ end
@@ -0,0 +1,46 @@
1
+ module SimplePosts
2
+ class PostsController < ::ApplicationController
3
+ before_filter :load_posts
4
+
5
+ def index
6
+ @blog_posts = @posts.blog_posts.reverse
7
+ render layout: SimplePosts.layout
8
+ end
9
+
10
+ def show
11
+ @post = @posts.find(params[:id])
12
+ raise ActionController::RoutingError.new('Not Found') unless @post
13
+ render layout: SimplePosts.layout
14
+ end
15
+
16
+ def atom
17
+ posts = @posts.blog_posts.reverse
18
+
19
+ feed = RSS::Maker.make("atom") do |f|
20
+ f.channel.title = SimplePosts.site_title
21
+ f.channel.author = SimplePosts.site_author
22
+ f.channel.about = SimplePosts::Engine.routes.url_helpers.posts_url
23
+ f.channel.updated = posts[0].date.to_time
24
+
25
+ posts.each do |post|
26
+ f.items.new_item do |e|
27
+ e.title = post.title
28
+ e.link = SimplePosts::Engine.routes.url_helpers.post_url(id: post.name)
29
+ e.id = SimplePosts::Engine.routes.url_helpers.post_url(id: post.id)
30
+ e.updated = post.date.to_time
31
+ e.pubDate = post.date.to_time
32
+ e.content.content = post.render
33
+ end
34
+ end
35
+ end
36
+
37
+ render text: feed, content_type: 'application/atom+xml'
38
+ end
39
+
40
+ private
41
+
42
+ def load_posts
43
+ @posts = Posts.new
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,4 @@
1
+ module SimplePosts
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>SimplePosts</title>
5
+ <%= stylesheet_link_tag "simple_posts/application", media: "all" %>
6
+ <%= javascript_include_tag "simple_posts/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
@@ -0,0 +1,9 @@
1
+ <% @blog_posts.each do |post| %>
2
+ <div class="blogpost">
3
+ <h2 class="blogpost-header"><%= link_to post.title, post_path(id: post.name) %></h2>
4
+ <p class="blogpost-date"><%= post.short_date %></p>
5
+ <%= post.render_before_fold.html_safe %>
6
+ </div>
7
+ <hr>
8
+ <% end %>
9
+
@@ -0,0 +1,5 @@
1
+ <h1 class="blogpost-title"><%= @post.title %></h1>
2
+ <div class="blogpost">
3
+ <p class="text-center"><small><%= @post.natural_date %></small></p>
4
+ <%= @post.render.html_safe %>
5
+ </div>
@@ -0,0 +1,7 @@
1
+ SimplePosts::Engine.routes.draw do
2
+ get '/' => 'posts#index', as: :posts
3
+ get '/index.xml' => 'posts#atom', as: :atom
4
+ get '/:id' => 'posts#show', as: :post
5
+
6
+ default_url_options Rails.application.routes.default_url_options
7
+ end
@@ -0,0 +1,23 @@
1
+ require "simple_posts/engine"
2
+ require 'simple_posts/posts'
3
+
4
+ module SimplePosts
5
+ class << self
6
+ attr_accessor :site_title,
7
+ :site_author,
8
+ :layout
9
+
10
+ def reset!
11
+ self.site_title = 'Exampleville'
12
+ self.site_author = 'Example Author'
13
+ self.layout = 'application'
14
+ end
15
+
16
+ def configure(&block)
17
+ raise ArgumentError, "must provide a block" unless block_given?
18
+ block.arity.zero? ? instance_eval(&block) : yield(self)
19
+ end
20
+ end
21
+
22
+ self.reset!
23
+ end
@@ -0,0 +1,5 @@
1
+ module SimplePosts
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace SimplePosts
4
+ end
5
+ end
@@ -0,0 +1,307 @@
1
+ Encoding.default_internal, Encoding.default_external = ['utf-8'] * 2
2
+
3
+ require 'redcarpet'
4
+ require 'date'
5
+ require 'redcarpet'
6
+ require 'rouge'
7
+ require 'rouge/plugins/redcarpet'
8
+
9
+ module SimplePosts
10
+
11
+ class HTMLwithHighlights < Redcarpet::Render::HTML
12
+ include Rouge::Plugins::Redcarpet
13
+
14
+ def postprocess(document)
15
+ document.gsub('&#39;', "'")
16
+ end
17
+ end
18
+
19
+ class Posts
20
+ attr_reader :posts, :non_blog_posts, :renderer
21
+
22
+ def initialize
23
+ @posts_by_post_name = {}
24
+ @posts_by_post_id = {}
25
+ @posts_by_tag = {}
26
+ @posts_by_topic = {}
27
+ @blog_posts = []
28
+ @non_blog_posts = []
29
+
30
+ setup_renderer
31
+ parse_all
32
+ end
33
+
34
+ def setup_renderer
35
+ @renderer = Redcarpet::Markdown.new(
36
+ HTMLwithHighlights, :fenced_code_blocks => true)
37
+ end
38
+
39
+ def parse_all
40
+ @posts = find_all_files.map do |post|
41
+ next if File.basename(post).start_with?('_')
42
+ Post.new(post, @renderer)
43
+ end.compact
44
+
45
+ @posts.each do |post|
46
+ @posts_by_post_name[post.name] = post
47
+ @posts_by_post_id[post.post_id] = post
48
+ if post.topic
49
+ topic = post.topic.downcase
50
+ @posts_by_topic[topic] ||= []
51
+ @posts_by_topic[topic] << post
52
+ end
53
+
54
+ post.alternate_links.each do |link|
55
+ @posts_by_post_id[link] = post
56
+ end
57
+
58
+ post.tags.each do |tag|
59
+ @posts_by_tag[tag.downcase] ||= []
60
+ @posts_by_tag[tag.downcase] << post
61
+ end
62
+
63
+ if post.is_blog_post?
64
+ @blog_posts << post
65
+ else
66
+ @non_blog_posts << post
67
+ end
68
+ end
69
+ end
70
+
71
+ def find(thing)
72
+ @posts_by_post_id[thing] || @posts_by_post_name[thing]
73
+ end
74
+
75
+ def tagged(tag)
76
+ (@posts_by_tag[tag.downcase] || [])
77
+ end
78
+
79
+ def find_all_files
80
+ basepath = Rails.root.join('app', 'posts').to_s
81
+ Dir.glob(File.join(basepath, "**/*")).map do |fullpath|
82
+ next if File.directory?(fullpath)
83
+ fullpath.gsub(basepath + "/", '')
84
+ end.compact
85
+ end
86
+
87
+ def each
88
+ @posts.each do |post|
89
+ yield post
90
+ end
91
+ end
92
+
93
+ def topics
94
+ @posts_by_topic.keys.sort
95
+ end
96
+
97
+ def for_topic(topic)
98
+ @posts_by_topic[topic].sort { |a,b| b.date <=> a.date }
99
+ end
100
+
101
+ def tag_frequencies
102
+ tags = Hash.new(0)
103
+ @posts.each do |post|
104
+ post.tags.each do |tag|
105
+ tags[tag] += 1
106
+ end
107
+ end
108
+ tags
109
+ end
110
+
111
+ def related_posts(target)
112
+ freqs = tag_frequencies
113
+
114
+ highest_freq = freqs.values.max
115
+ related_scores = Hash.new(0)
116
+
117
+ blog_posts.each do |post|
118
+ post.tags.each do |tag|
119
+ if target.tags.include?(tag) && target != post
120
+ tag_freq = freqs[tag]
121
+ related_scores[post] += (1 + highest_freq - tag_freq)
122
+ end
123
+ end
124
+ end
125
+
126
+ related_scores.sort do |a,b|
127
+ if a[1] < b[1]
128
+ 1
129
+ elsif a[1] > b[1]
130
+ -1
131
+ else
132
+ b[0].date <=> a[0].date
133
+ end
134
+ end.select{|post,freq| freq > 1}.collect {|post,freq| post}
135
+ end
136
+
137
+ def blog_posts
138
+ @blog_posts.sort { |a, b| a.date <=> b.date }
139
+ end
140
+ end
141
+
142
+ class Post
143
+
144
+ DATE_REGEX = /\d{4}-\d{2}-\d{2}/
145
+ SHORT_DATE_FORMAT = "%Y-%m-%d"
146
+ DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
147
+
148
+ attr_accessor :docid
149
+ attr_reader :name, :body, :original_filename, :original_body, :headers
150
+
151
+ def initialize(filename, renderer)
152
+ @file = filename
153
+ @original_filename = filename
154
+ @name = self.class.normalize_name(filename)
155
+ @renderer = renderer
156
+ parse_post
157
+ end
158
+
159
+ def parse_post
160
+ if contents =~ /\A(---\s*\n.*?\n?)^(---\s*$\n?)(.*)/m
161
+ @headers = YAML.load($1)
162
+ parse_body($3)
163
+ end
164
+ end
165
+
166
+ def parse_body(body_text)
167
+ @original_body = body_text
168
+ @before_fold, after_fold = body_text.split("--fold--")
169
+ @body = body_text.sub("--fold--", '')
170
+ end
171
+
172
+ def self.normalize_name(post)
173
+ return post.downcase.strip.sub(/\.(html|md|pdf)(\.erb)?$/,'').sub(/\d{4}-\d{2}-\d{2}-/, '')
174
+ end
175
+
176
+ def is_blog_post?
177
+ return filename =~ DATE_REGEX
178
+ end
179
+
180
+ def render(renderer=nil, app=nil)
181
+ content = is_erb? ? render_erb(@body, app) : @body
182
+ if is_html?
183
+ content
184
+ else
185
+ (renderer || @renderer).render(content)
186
+ end
187
+ end
188
+
189
+ def render_erb(content, app)
190
+ template = ERB.new(content)
191
+ @app = app
192
+ template.result(binding)
193
+ end
194
+
195
+ def is_erb?
196
+ original_filename.end_with?('.erb')
197
+ end
198
+
199
+ def is_html?
200
+ original_filename =~ /\.html(\.erb)?$/
201
+ end
202
+
203
+ def id_matches?(id)
204
+ links = alternate_links + [self.post_id]
205
+ links.include? id
206
+ end
207
+
208
+ def render_before_fold
209
+ @renderer.render(@before_fold)
210
+ end
211
+
212
+ def contents
213
+ @contents ||= File.open(filename, 'r:utf-8') do |file|
214
+ file.read
215
+ end
216
+ end
217
+
218
+ def filename
219
+ Rails.root.join("app", "posts", @file).to_s
220
+ end
221
+
222
+ def matches_path(path)
223
+ normalized = self.class.normalize_name(path)
224
+ return @name == normalized || @headers['id'] == normalized
225
+ end
226
+
227
+ def [](key)
228
+ return @headers[key]
229
+ end
230
+
231
+ def tags
232
+ if @headers.has_key?('tags')
233
+ return @headers['tags'].split(/,\s+/)
234
+ else
235
+ return []
236
+ end
237
+ end
238
+
239
+ def alternate_links
240
+ if @headers.has_key?('alternate_links')
241
+ return @headers['alternate_links'].split(/\s+/)
242
+ else
243
+ return []
244
+ end
245
+ end
246
+
247
+ def has_tag(tag)
248
+ tags.detect { |t| t == tag }
249
+ end
250
+
251
+ def title
252
+ @headers['title']
253
+ end
254
+
255
+ def post_id
256
+ @headers['id']
257
+ end
258
+
259
+ def id
260
+ post_id
261
+ end
262
+
263
+ def date
264
+ if is_blog_post?
265
+ if @headers['date']
266
+ Time.strptime(@headers['date'], DATE_FORMAT)
267
+ else
268
+ Time.strptime(@file, SHORT_DATE_FORMAT)
269
+ end
270
+ elsif @headers['date']
271
+ Time.strptime(@headers['date'], SHORT_DATE_FORMAT)
272
+ end
273
+ end
274
+
275
+ def reading_time
276
+ ([body.split(/\s+/).length / 180.0, 1].max).to_i
277
+ end
278
+
279
+ def natural_date
280
+ date ? date.strftime("%e %B %Y") : ''
281
+ end
282
+
283
+ def short_date
284
+ date ? date.strftime("%e %b %Y") : ''
285
+ end
286
+
287
+ def topic
288
+ headers['topic']
289
+ end
290
+
291
+ def html_path
292
+ "/blog/#{@name}"
293
+ end
294
+
295
+ def view
296
+ @headers.has_key?('view') ? @headers['view'].to_sym : nil
297
+ end
298
+
299
+ def layout
300
+ @headers.has_key?('layout') ? @headers['layout'].to_sym : nil
301
+ end
302
+
303
+ def show_byline?
304
+ is_blog_post?
305
+ end
306
+ end
307
+ end