gerablog 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 841577cfe74ad755f922db7916d3a65586ee67a7
4
+ data.tar.gz: c390d5e4f493b5fc39d10fcaf4c6cf6f9a0a588c
5
+ SHA512:
6
+ metadata.gz: d56e2d2c48854cd453ac24f2affca7a226cff52dc56f5e71f943ca43ca481c7aab9f9f9d6af497f1c0c8f0a86e2daf05ea3c0c7d2512a8033683992d0ae4989f
7
+ data.tar.gz: d545dfe321b12f1c69da0ffed74ac0c917abab45e159586dbbc91cf9d015921dc52fb506335a68fba6abc1cd649c99473b94221f50bd464da5b681eae1a6e063
@@ -0,0 +1,68 @@
1
+ body {
2
+ font-family: Verdana, Geneva, sans-serif;
3
+
4
+ background-color: white;
5
+ color: black;
6
+
7
+ width: 90%;
8
+ margin: 0 auto;
9
+ }
10
+
11
+ a:link, a:visited { color: royalblue; text-decoration: none; }
12
+ a:hover { color: blue; text-decoration: underline; font-weight: 500; }
13
+ a:active { color: white; }
14
+
15
+ h1 {
16
+ color: green;
17
+ }
18
+
19
+ /* https://css-tricks.com/snippets/css/simple-and-nice-blockquote-styling/ */
20
+ blockquote {
21
+ background: #f9f9f9;
22
+ border-left: 10px solid #ccc;
23
+ margin: 1.5em 10px;
24
+ padding: 0.5em 10px;
25
+ quotes: "\201C""\201D""\2018""\2019";
26
+ }
27
+
28
+ footer {
29
+ clear: both;
30
+ font-size: 60%;
31
+ margin-top: 3em;
32
+ }
33
+
34
+ #mainnav {
35
+ padding: 0 0;
36
+ margin-bottom: 22px;
37
+ }
38
+
39
+ #mainnav ul {
40
+ margin: 0 0 0 0;
41
+ padding: 0;
42
+ list-style-type: none;
43
+ border-left: 1px solid black;
44
+ }
45
+
46
+ #mainnav li {
47
+ display: inline;
48
+ padding: 0 10px;
49
+ border-right: 1px solid black;
50
+ }
51
+
52
+ h3.textcal {
53
+ color: green;
54
+ margin-bottom: 0;
55
+ }
56
+
57
+ p.textcal {
58
+ color: black;
59
+ margin-top: 0;
60
+ }
61
+
62
+ img {
63
+ vertical-align: top;
64
+ width: auto;
65
+ height: auto;
66
+ max-width:300px;
67
+ max-height:300px;
68
+ }
data/bin/gerablog ADDED
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'fileutils'
5
+
6
+ require 'gerablog'
7
+
8
+ options = {}
9
+
10
+ optparse = OptionParser.new do |opts|
11
+ opts.banner = 'Usage: optparse [options]'
12
+
13
+ opts.on('-h', '--help', 'Display this screen') do
14
+ puts opts
15
+ exit
16
+ end
17
+
18
+ options[:new] = nil
19
+ opts.on('-n', '--new FILENAME', 'Create a new project.') do |value|
20
+ options[:new] = value
21
+ end
22
+
23
+ options[:generate] = nil
24
+ opts.on('-g', '--generate FILENAME', 'Generate the static blog.') do |value|
25
+ options[:generate] = value
26
+ end
27
+ end
28
+
29
+ optparse.parse!
30
+
31
+ if options[:generate]
32
+ gb = GeraBlog::Blog.new
33
+ gb.load_config options[:generate] unless options[:generate].nil?
34
+ gb.save
35
+ elsif options[:new]
36
+ gb = GeraBlog::Blog.new options[:new]
37
+ gb.new_blog
38
+ gb.save_config
39
+ else
40
+ puts optparse.parse! %w(--help)
41
+ end
@@ -0,0 +1,205 @@
1
+ require 'date'
2
+ require 'parseconfig'
3
+
4
+ require_relative 'render'
5
+
6
+ # GeraBlog - a Static Blog Generator
7
+ module GeraBlog
8
+ VERSION = '0.1.0'.freeze
9
+
10
+ def self.make_dest_dir(src, dest, remove: false)
11
+ return unless Dir.exist? src
12
+ FileUtils.cp_r(src, dest, remove_destination: remove)
13
+ end
14
+
15
+ def self.create_dir(dir)
16
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
17
+ end
18
+
19
+ # Blog
20
+ class Blog
21
+ attr_reader :config
22
+
23
+ @posts = []
24
+
25
+ def ini_blog
26
+ @config.add 'blog',
27
+ 'root' => '.',
28
+ 'url' => 'http://localhost:8080',
29
+ 'title' => 'GeraBlog',
30
+ 'name' => 'GeraBlog Static Blog Generator',
31
+ 'description' => 'GeraBlog - My own static site generator',
32
+ 'language' => 'pt-br',
33
+ 'itens_in_rss' => '50'
34
+ end
35
+
36
+ def ini_dir(root)
37
+ @config.add 'dir',
38
+ 'root' => root,
39
+ 'texts' => File.join(root, 'texts'),
40
+ 'assets' => File.join(root, 'assets'),
41
+ 'output' => File.join(root, 'output'),
42
+ 'template' => File.join(root, 'templates')
43
+ end
44
+
45
+ def full_template_dir(file)
46
+ File.join @config['dir']['template'], file
47
+ end
48
+
49
+ def ini_template
50
+ @config.add 'template',
51
+ 'category' => full_template_dir('category.rbhtml'),
52
+ 'categories' => full_template_dir('categories.rbhtml'),
53
+ 'feed' => full_template_dir('feed.rbxml'),
54
+ 'post' => full_template_dir('post.rbhtml'),
55
+ 'footer' => full_template_dir('footer.rbhtml'),
56
+ 'header' => full_template_dir('header.rbhtml'),
57
+ 'index' => full_template_dir('index.rbhtml')
58
+ end
59
+
60
+ def reset_parser
61
+ @parser = Tenjin::Engine.new path: @config['dir']['templates']
62
+ end
63
+
64
+ def initialize(root = './')
65
+ @config = ParseConfig.new
66
+ ini_blog
67
+ ini_dir root
68
+ ini_template
69
+
70
+ reset_parser
71
+ end
72
+
73
+ def load_config(config_file)
74
+ @config = ParseConfig.new(config_file)
75
+ reset_parser
76
+ end
77
+
78
+ def save_config
79
+ @config.write(
80
+ File.open(File.join(@config['dir']['root'], 'gerablog.conf'), 'w')
81
+ )
82
+ end
83
+
84
+ def create_category_dir(category)
85
+ category_dir = File.join config['dir']['output'], 'texts', category
86
+
87
+ GeraBlog.create_dir category_dir
88
+
89
+ GeraBlog.make_dest_dir(
90
+ File.join(@config['dir']['texts'], category, 'images'),
91
+ category_dir,
92
+ remove: true
93
+ )
94
+ end
95
+
96
+ def new_blog
97
+ @config['dir'].values.map { |dir| GeraBlog.create_dir(dir) }
98
+ GeraBlog.make_dest_dir(
99
+ File.join(__dir__, '..', '..', 'assets'),
100
+ @config['dir']['root']
101
+ )
102
+
103
+ GeraBlog.make_dest_dir(
104
+ File.join(__dir__, '..', '..', 'templates'),
105
+ @config['dir']['root']
106
+ )
107
+ end
108
+
109
+ def create_dirs
110
+ GeraBlog.create_dir File.join(@config['dir']['output'], 'texts')
111
+
112
+ GeraBlog.make_dest_dir(
113
+ @config['dir']['assets'],
114
+ @config['dir']['output'],
115
+ remove: true
116
+ )
117
+
118
+ @posts.map { |p| p[:category] }
119
+ .uniq
120
+ .map { |category| create_category_dir category }
121
+ end
122
+
123
+ def write_category_index(file, template, category, title, posts)
124
+ context = {
125
+ title: title,
126
+ posts: posts,
127
+ config: @config,
128
+ categories: @categories
129
+ }
130
+
131
+ File.write(
132
+ File.join(@config['dir']['output'], 'texts', category, file),
133
+ @parser.render(template, context)
134
+ )
135
+ end
136
+
137
+ def write_posts
138
+ create_dirs
139
+
140
+ @posts.map { |post| File.write(post[:filename], post[:content]) }
141
+ end
142
+
143
+ def write_general_rss
144
+ File.write(
145
+ File.join(@config['dir']['output'], 'feed.xml'),
146
+ @parser.render(
147
+ @config['template']['feed'],
148
+ config: @config,
149
+ posts: @posts.take(Integer(@config['blog']['itens_in_rss']))
150
+ )
151
+ )
152
+ end
153
+
154
+ def write_general_html
155
+ context = {
156
+ config: @config,
157
+ categories: @categories,
158
+ title: {
159
+ title: @config['blog']['title'],
160
+ description: @config['blog']['description']
161
+ }
162
+ }
163
+
164
+ File.write(
165
+ File.join(@config['dir']['output'], 'index.html'),
166
+ @parser.render(@config['template']['index'], context)
167
+ )
168
+ end
169
+
170
+ def write_by_category_files
171
+ @posts.map { |p| p[:category] }.uniq.each do |category|
172
+ title = {
173
+ url: File.join(@config['blog']['url'], 'texts', category),
174
+ description: "#{@config['blog']['description']} (#{category})"
175
+ }
176
+
177
+ category_posts = @posts.select { |p| p[:category] == category }
178
+
179
+ write_category_index(
180
+ 'feed.xml',
181
+ @config['template']['feed'],
182
+ category,
183
+ title,
184
+ category_posts
185
+ )
186
+ write_category_index(
187
+ 'index.html',
188
+ @config['template']['category'],
189
+ category,
190
+ title,
191
+ category_posts
192
+ )
193
+ end
194
+ end
195
+
196
+ def save
197
+ @posts, @categories = GeraBlog::Render.new(@config).render
198
+
199
+ write_posts
200
+ write_general_rss
201
+ write_general_html
202
+ write_by_category_files
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,58 @@
1
+ require 'redcarpet'
2
+
3
+ module GeraBlog
4
+ # My subclass to perform parse with code bloques options
5
+ class RedcarpetCustom < Redcarpet::Render::HTML
6
+ attr_reader :lang
7
+
8
+ def initialize(lang:)
9
+ super(prettify: true, escape_html: true)
10
+ @lang = lang
11
+ end
12
+
13
+ def block_code(code, language = nil)
14
+ l = language.nil? ? @lang : language
15
+ %(<pre><code class="language-#{l}">\n#{html_escape(code)}\n</code></pre>)
16
+ end
17
+
18
+ def html_escape(string)
19
+ string.gsub(
20
+ %r{['&\"<>\/]},
21
+ '&' => '&amp;',
22
+ '<' => '&lt;',
23
+ '>' => '&gt;',
24
+ '"' => '&quot;',
25
+ "'" => '&#x27;',
26
+ '/' => '&#x2F;'
27
+ )
28
+ end
29
+ end
30
+
31
+ # My Render class
32
+ class RedcarpetDriver
33
+ def initialize(lang, config)
34
+ @template = config['template']
35
+ @blog = config['blog']
36
+ @config = config
37
+ @render = Redcarpet::Markdown.new(RedcarpetCustom.new(lang: lang))
38
+ end
39
+
40
+ def converted(post, content)
41
+ post[:converted] = @render.render(content)
42
+ post
43
+ end
44
+
45
+ def to_html(post, content, categories)
46
+ Tenjin::Engine.new.render(
47
+ @template['post'],
48
+ config: @config,
49
+ post: converted(post, content),
50
+ categories: categories,
51
+ title: {
52
+ title: post[:title],
53
+ description: post[:description]
54
+ }
55
+ )
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,78 @@
1
+ require 'tenjin'
2
+ require_relative 'redcarpet'
3
+
4
+ module GeraBlog
5
+ # Get all markdown files and return posts in html
6
+ class Render
7
+ attr_reader :footer
8
+
9
+ def initialize(config)
10
+ @config = config
11
+ end
12
+
13
+ def render
14
+ c_dir = Dir[File.join(@config['dir']['texts'], '*')]
15
+ c_names = c_dir.map { |d| File.basename d }
16
+ categories_dir = Hash[c_names.zip c_dir]
17
+ parser = Tenjin::Engine.new
18
+ context = { categories: c_names, config: @config }
19
+ categories = parser.render @config['template']['categories'], context
20
+
21
+ [rendered_posts(categories_dir, categories), categories]
22
+ end
23
+
24
+ def rendered_posts(categories_dir, categories)
25
+ posts = []
26
+
27
+ categories_dir.each do |category, dir|
28
+ md_render = GeraBlog::RedcarpetDriver.new(category, @config)
29
+
30
+ Dir["#{dir}/*.md"].sort.reverse.each do |file|
31
+ posts.push render_post(md_render, file, category, categories)
32
+ end
33
+ end
34
+
35
+ posts
36
+ end
37
+
38
+ def make_full_filename(filename, category)
39
+ File.join(@config['dir']['output'], 'texts', category, filename)
40
+ end
41
+
42
+ def make_full_url(filename, category)
43
+ File.join(@config['blog']['url'], 'texts', category, filename)
44
+ end
45
+
46
+ def post_element(md_content, newfile, category)
47
+ lines = md_content.split("\n")
48
+
49
+ {
50
+ title: lines[0][2..-1],
51
+ category: category,
52
+ description: lines[2][3..-1],
53
+ date: Date.parse(newfile.match('\A(....-..-..)')[1]).rfc822,
54
+ filename: make_full_filename(newfile, category),
55
+ url: make_full_url(newfile, category),
56
+ markdown: md_content
57
+ }
58
+ end
59
+
60
+ def file_to_post(filename, category)
61
+ md_content = File.read(filename)
62
+ newfile = File.basename(filename).sub(/md$/, 'html')
63
+
64
+ post_element(md_content, newfile, category)
65
+ end
66
+
67
+ def render_post(md_render, filename, category, categories)
68
+ post = file_to_post(filename, category)
69
+ post[:content] = md_render.to_html(
70
+ post,
71
+ post[:markdown],
72
+ categories
73
+ )
74
+
75
+ post
76
+ end
77
+ end
78
+ end
data/lib/gerablog.rb ADDED
@@ -0,0 +1 @@
1
+ require 'gerablog/blog'
@@ -0,0 +1,12 @@
1
+ <?rb #@ARGS categories, config ?>
2
+ <nav id="mainnav">
3
+ <ul>
4
+ <?rb for category in categories do ?>
5
+ <li>
6
+ <a href="${config['blog']['url']}/texts/${category}/">
7
+ ${category}
8
+ </a>
9
+ </li>
10
+ <?rb end ?>
11
+ </ul>
12
+ </nav>
@@ -0,0 +1,11 @@
1
+ <?rb #@ARGS blog, posts, config, title, categories ?>
2
+ <?rb import config['template']['header'] ?>
3
+
4
+ <?rb for post in posts do ?>
5
+ <a href="${post[:url]}">
6
+ <h3 class="textcal">${post[:title]}</h3>
7
+ <p class="textcal">${post[:description]}</p>
8
+ </a>
9
+ <?rb end ?>
10
+
11
+ <?rb import config['template']['footer'] ?>
@@ -0,0 +1,21 @@
1
+ <?rb #@ARGS config, posts ?>
2
+ <?xml version="1.0"?>
3
+
4
+ <rss version="2.0">
5
+ <channel>
6
+ <title>${@config['blog']['name']}</title>
7
+ <link>${@config['blog']['url']}</link>
8
+ <description>${@config['blog']['description']}</description>
9
+ <language>${@config['blog']['language']}</language>
10
+
11
+ <?rb for post in posts ?>
12
+ <item>
13
+ <title>${post[:title]}</title>
14
+ <link>${post[:url]}</link>
15
+ <description>${post[:description]}</description>
16
+ <pubDate>${post[:date]}</pubDate>
17
+ </item>
18
+ <?rb end ?>
19
+
20
+ </channel>
21
+ </rss>
@@ -0,0 +1,20 @@
1
+ <?rb #@ARGS config ?>
2
+ <hr>
3
+
4
+ <footer>
5
+ <p>Copyright &copy; 2016-2017, ${@config['blog']['name']}</p>
6
+ <p><a href="${@config['blog']['url']}/feed.xml">RSS Feed for this blog</a></p>
7
+ <ul>
8
+ <li>Static Blog Engine by
9
+ <a href="https://github.com/paulohrpinheiro/gerablog">GeraBlog</a>
10
+ </li>
11
+ <li>Code syntax highlight by
12
+ <a href="http://prismjs.com/">Prism</a>
13
+ </li>
14
+ </ul>
15
+ </footer>
16
+
17
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/prism.min.js"></script>
18
+
19
+ </body>
20
+ </html>
@@ -0,0 +1,33 @@
1
+ <?rb #@ARGS config, post, categories, title ?>
2
+ <!DOCTYPE html>
3
+ <html lang="${@config['blog']['language']}">
4
+
5
+ <head>
6
+ <title>${@title[:title]}</title>
7
+
8
+ <meta charset="utf-8"/>
9
+ <meta name="description" content="${@title[:description]}">
10
+ <meta
11
+ name="viewport"
12
+ content="width=device-width, initial-scale=1, maximum-scale=1"
13
+ >
14
+
15
+ <link
16
+ rel="alternate"
17
+ type="application/rss+xml"
18
+ title="RSS Feed for ${@config['blog']['title']}"
19
+ href="${@config['blog']['url']}/feed.xml"
20
+ >
21
+
22
+ <?rb cssdir = File.join config['blog']['url'], 'assets', 'css' ?>
23
+
24
+ <link rel="stylesheet" href="${cssdir}/gerablog.css">
25
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/themes/prism.css" />
26
+ </head>
27
+
28
+ <body>
29
+ <header>
30
+ <h1><a href="/">${@config['blog']['name']}</a></h1>
31
+ <h2>${@config['blog']['description']}</h2>
32
+ #{@categories}
33
+ </header>
@@ -0,0 +1,10 @@
1
+ <?rb #@ARGS config, categories ?>
2
+ <?rb import config['template']['header'] ?>
3
+
4
+ <article>
5
+ <p>
6
+ This is <a href="https://github.com/paulohpinheiro/gerablog">Gerablog</a>
7
+ </p>
8
+ </article>
9
+
10
+ <?rb import config['template']['footer'] ?>
@@ -0,0 +1,8 @@
1
+ <?rb #@ARGS post, config ?>
2
+ <?rb import config['template']['header'] ?>
3
+
4
+ <article>
5
+ #{@post[:converted]}
6
+ </article>
7
+
8
+ <?rb import config['template']['footer'] ?>
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gerablog
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Paulo Henrique Rodrigues Pinheiro
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-01-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redcarpet
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: tenjin
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.7'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: parseconfig
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-reporters
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: nokogiri
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.7'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.7'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.46'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.46'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Write posts in Markdown, publish a static blog in HTML
112
+ email: paulohrpinheiro@gmail.com
113
+ executables:
114
+ - gerablog
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - assets/css/gerablog.css
119
+ - bin/gerablog
120
+ - lib/gerablog.rb
121
+ - lib/gerablog/blog.rb
122
+ - lib/gerablog/redcarpet.rb
123
+ - lib/gerablog/render.rb
124
+ - templates/categories.rbhtml
125
+ - templates/category.rbhtml
126
+ - templates/feed.rbxml
127
+ - templates/footer.rbhtml
128
+ - templates/header.rbhtml
129
+ - templates/index.rbhtml
130
+ - templates/post.rbhtml
131
+ homepage: https://github.com/paulohrpinheiro/gerablog
132
+ licenses:
133
+ - MIT
134
+ metadata: {}
135
+ post_install_message:
136
+ rdoc_options: []
137
+ require_paths:
138
+ - lib
139
+ required_ruby_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '2.0'
144
+ required_rubygems_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ requirements: []
150
+ rubyforge_project:
151
+ rubygems_version: 2.5.1
152
+ signing_key:
153
+ specification_version: 4
154
+ summary: My own static site generator.
155
+ test_files: []