blog-generator 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +56 -0
- data/bin/blog-generator.rb +60 -0
- data/lib/blog-generator.rb +37 -0
- data/lib/blog-generator/post.rb +76 -0
- data/lib/blog-generator/post_list.rb +34 -0
- metadata +14 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f1d0ff16391680e7a1feb4dd62f7249ab4030e0
|
4
|
+
data.tar.gz: 38bae8eb5d26ea606aeea6e0e82766da615b3159
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 754eafed7232e1c80bb8924e1e33dc3c7c9567231268adca87f501efc05d61b9008ee7fc8eca29d45a583c251c63f1dfbf0cf4fe2dec54ec52380b8586897c49
|
7
|
+
data.tar.gz: 82700d910962e9d63d877d70531788f7d0af2eda225dd043de1dd081e64a0040bae7c778d8ee41a0ead96983769c8ef0ab99c9be9fb61a715e2a3905ecc31128
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# About
|
2
|
+
|
3
|
+
Few years back we used to use all the fancy static site generators for building a blog, so the blog could have layouts, tags, pagination and other features. With the arrival of frontend frameworks such as AngularJS, this is no longer necessary.
|
4
|
+
|
5
|
+
We can generate a static JSON API and let AngularJS handle the rest.
|
6
|
+
|
7
|
+
# Usage
|
8
|
+
|
9
|
+
gem install blog-generator
|
10
|
+
gem install redcarpet # If you are going to use markdown.
|
11
|
+
mkdir botanicus.me
|
12
|
+
cd botanicus.me
|
13
|
+
mkdir posts
|
14
|
+
blog-generator.rb posts api
|
15
|
+
|
16
|
+
# Post structure
|
17
|
+
|
18
|
+
```html
|
19
|
+
title: 'Hello world!'
|
20
|
+
tags: ['Hello world', 'Test']
|
21
|
+
---
|
22
|
+
|
23
|
+
<div id="excerpt">
|
24
|
+
Excerpt
|
25
|
+
</div>
|
26
|
+
|
27
|
+
|
28
|
+
<h1>Hello world!</h1>
|
29
|
+
<p>
|
30
|
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Soluta quibusdam necessitatibus tempore ullam incidunt amet omnis, veritatis dicta quisquam accusamus at provident vel facere corporis sed fugiat cumque. Consequuntur, necessitatibus!
|
31
|
+
</p>
|
32
|
+
```
|
33
|
+
|
34
|
+
## Assumptions
|
35
|
+
|
36
|
+
- Metadata `title` and optionally `tags`.
|
37
|
+
- Metadata `draft: true` will exclude the post.
|
38
|
+
- Any other metadata can be added and will be accessible in the resulting JSON.
|
39
|
+
- Body has `#excerpt`.
|
40
|
+
- Format `posts/:published_on-:slug.:format`.
|
41
|
+
|
42
|
+
# Routes generated
|
43
|
+
|
44
|
+
- `slug` and `published_on` added to metadata.
|
45
|
+
- `/posts.json`
|
46
|
+
- `/posts/:slug.json`
|
47
|
+
- `/tags.json`
|
48
|
+
- `/tags/:slug.json`
|
49
|
+
|
50
|
+
# Status
|
51
|
+
|
52
|
+
It works, but it needs polishing. Integration specs are OK, unit tests are broken.
|
53
|
+
|
54
|
+
# TODO
|
55
|
+
|
56
|
+
- GH markdown including source code support.
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# [Usage]
|
4
|
+
#
|
5
|
+
# blog-generator.rb [posts dir] [output base path]
|
6
|
+
|
7
|
+
require 'blog-generator'
|
8
|
+
|
9
|
+
POSTS_DIR, OUTPUT_BASE_PATH = ARGV
|
10
|
+
|
11
|
+
unless ARGV.length == 2
|
12
|
+
abort "Usage: #{$0} [posts dir] [output base path]"
|
13
|
+
end
|
14
|
+
|
15
|
+
unless File.directory?(POSTS_DIR)
|
16
|
+
abort "Posts directory #{POSTS_DIR} doesn't exist."
|
17
|
+
end
|
18
|
+
|
19
|
+
# Parse the posts.
|
20
|
+
generator = BlogGenerator::Generator.parse(POSTS_DIR)
|
21
|
+
|
22
|
+
unless File.directory?(OUTPUT_BASE_PATH)
|
23
|
+
puts "~ #{OUTPUT_BASE_PATH} doesn't exist, creating."
|
24
|
+
Dir.mkdir(OUTPUT_BASE_PATH)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Generate.
|
28
|
+
|
29
|
+
Dir.chdir(OUTPUT_BASE_PATH) do
|
30
|
+
# GET /api/posts.json
|
31
|
+
File.open('posts.json', 'w') do |file|
|
32
|
+
# This calls PostList#to_json
|
33
|
+
file.puts(JSON.pretty_generate(generator.posts))
|
34
|
+
end
|
35
|
+
|
36
|
+
# GET /api/posts/hello-world.json
|
37
|
+
Dir.mkdir('posts') unless Dir.exist?('posts')
|
38
|
+
generator.posts.each do |post|
|
39
|
+
File.open("posts/#{post.slug}.json", 'w') do |file|
|
40
|
+
file.puts(JSON.pretty_generate(post))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# GET /api/tags.json
|
45
|
+
File.open('tags.json', 'w') do |file|
|
46
|
+
# [{title: x, slug: y}]
|
47
|
+
tags = generator.tags.map do |tag, _|
|
48
|
+
tag
|
49
|
+
end
|
50
|
+
file.puts(JSON.pretty_generate(tags))
|
51
|
+
end
|
52
|
+
|
53
|
+
# GET /api/tags/doxxu.json
|
54
|
+
Dir.mkdir('tags') unless Dir.exist?('tags')
|
55
|
+
generator.tags.each do |tag, posts|
|
56
|
+
File.open("tags/#{tag[:slug]}.json", 'w') do |file|
|
57
|
+
file.puts(JSON.pretty_generate(posts))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
require 'blog-generator/post'
|
4
|
+
require 'blog-generator/post_list'
|
5
|
+
|
6
|
+
module BlogGenerator
|
7
|
+
class Generator
|
8
|
+
def self.parse(posts_dir)
|
9
|
+
posts = Dir.glob("#{posts_dir}/*.{html,md}").reduce(PostList.new) do |posts, path|
|
10
|
+
post = Post.new(path)
|
11
|
+
puts "~ Parsing #{post.inspect}"
|
12
|
+
posts.push(post)
|
13
|
+
end
|
14
|
+
|
15
|
+
self.new(posts)
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :posts
|
19
|
+
def initialize(posts)
|
20
|
+
@posts = posts
|
21
|
+
end
|
22
|
+
|
23
|
+
def tags
|
24
|
+
@posts.reduce(Hash.new) do |buffer, post|
|
25
|
+
puts; puts
|
26
|
+
p post
|
27
|
+
puts; puts
|
28
|
+
post.tags.each do |tag|
|
29
|
+
buffer[tag] ||= PostList.new
|
30
|
+
buffer[tag] << post
|
31
|
+
end
|
32
|
+
|
33
|
+
buffer
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'date'
|
3
|
+
require 'yaml'
|
4
|
+
require 'nokogiri'
|
5
|
+
|
6
|
+
module BlogGenerator
|
7
|
+
class Post
|
8
|
+
REGEXP = /^(\d{4}-\d{2}-\d{2})-(.+)\.(html|md)$/
|
9
|
+
|
10
|
+
attr_reader :metadata
|
11
|
+
def initialize(path)
|
12
|
+
@path = path
|
13
|
+
|
14
|
+
@metadata = YAML.load_file(path).reduce(Hash.new) do |buffer, (key, value)|
|
15
|
+
buffer.merge(key.to_sym => value)
|
16
|
+
end
|
17
|
+
|
18
|
+
published_on, slug, format = parse_path(path)
|
19
|
+
|
20
|
+
@body = convert_markdown(self.body) if format == :md
|
21
|
+
self.body # cache if it wasn't called yet
|
22
|
+
|
23
|
+
@metadata.merge!(slug: slug, published_on: published_on)
|
24
|
+
@metadata.merge!(excerpt: excerpt)
|
25
|
+
|
26
|
+
@metadata[:tags].map! do |tag|
|
27
|
+
{title: tag, slug: generate_slug(tag)}
|
28
|
+
end
|
29
|
+
|
30
|
+
document = Nokogiri::HTML(self.body)
|
31
|
+
document.css('#excerpt').remove
|
32
|
+
@body = document.css('body').inner_html.strip
|
33
|
+
end
|
34
|
+
|
35
|
+
def generate_slug(name)
|
36
|
+
name.downcase.tr(' /', '-').delete('!?')
|
37
|
+
end
|
38
|
+
|
39
|
+
# Maybe rename body -> raw_body and to_html -> body.
|
40
|
+
def body
|
41
|
+
@body ||= File.read(@path).match(/\n---\n(.+)$/m)[1].strip
|
42
|
+
end
|
43
|
+
|
44
|
+
# We're converting it to MD, apparently it's necessary even though we converted the whole text initially, but it seems like MD ignores whatever is in <div id="excerpt">...</div>.
|
45
|
+
def excerpt
|
46
|
+
@excerpt ||= Nokogiri::HTML(convert_markdown(Nokogiri::HTML(self.body).css('#excerpt').inner_html.strip)).css('p').inner_html
|
47
|
+
end
|
48
|
+
|
49
|
+
def as_json
|
50
|
+
@metadata.merge(body: body)
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_json(*args)
|
54
|
+
self.as_json.to_json(*args)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def method_missing(method, *args, &block)
|
59
|
+
return super if (! args.empty?) || block
|
60
|
+
@metadata[method]
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_path(path)
|
64
|
+
match = File.basename(path).match(REGEXP)
|
65
|
+
[Date.parse(match[1]), match[2], match[3].to_sym]
|
66
|
+
end
|
67
|
+
|
68
|
+
def convert_markdown(markup)
|
69
|
+
require 'redcarpet'
|
70
|
+
|
71
|
+
renderer = Redcarpet::Render::HTML.new(no_links: true, hard_wrap: true)
|
72
|
+
markdown = Redcarpet::Markdown.new(renderer, extensions = {})
|
73
|
+
markdown.render(markup)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module BlogGenerator
|
5
|
+
class PostList
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
attr_reader :posts
|
9
|
+
def initialize
|
10
|
+
@posts = Array.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def_delegators :@posts, :reduce, :each
|
14
|
+
|
15
|
+
def as_json
|
16
|
+
self.posts.map do |post|
|
17
|
+
post.as_json.tap do |metadata|
|
18
|
+
metadata.delete(:body)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_json(*args)
|
24
|
+
self.as_json.to_json(*args)
|
25
|
+
end
|
26
|
+
|
27
|
+
[:push, :<<].each do |method|
|
28
|
+
define_method(method) do |*args, &block|
|
29
|
+
@posts.send(method, *args, &block)
|
30
|
+
self # Important!
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: blog-generator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James C Russell
|
@@ -24,14 +24,21 @@ dependencies:
|
|
24
24
|
- - ~>
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.6'
|
27
|
-
description:
|
28
|
-
email:
|
29
|
-
executables:
|
27
|
+
description: '...'
|
28
|
+
email: james@101ideas.cz
|
29
|
+
executables:
|
30
|
+
- blog-generator.rb
|
30
31
|
extensions: []
|
31
32
|
extra_rdoc_files: []
|
32
|
-
files:
|
33
|
-
|
34
|
-
|
33
|
+
files:
|
34
|
+
- README.md
|
35
|
+
- bin/blog-generator.rb
|
36
|
+
- lib/blog-generator.rb
|
37
|
+
- lib/blog-generator/post.rb
|
38
|
+
- lib/blog-generator/post_list.rb
|
39
|
+
homepage: http://github.com/botanicus/blog-generator
|
40
|
+
licenses:
|
41
|
+
- MIT
|
35
42
|
metadata: {}
|
36
43
|
post_install_message:
|
37
44
|
rdoc_options: []
|