blog-generator 0.0.1 → 0.0.2
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.
- 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: []
|