simple_posts 0.0.1

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.
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