jekyll-recker 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.org +119 -0
- data/_includes/archive.html +23 -0
- data/_includes/nav.html +15 -0
- data/_includes/pager.html +10 -0
- data/_includes/stats.html +36 -0
- data/_layouts/default.html +37 -0
- data/_layouts/home.html +21 -0
- data/_layouts/page.html +10 -0
- data/_layouts/post.html +12 -0
- data/_sass/jekyll-recker.sass +15 -0
- data/assets/images/example-stats.png +0 -0
- data/assets/images/example-tweet.png +0 -0
- data/assets/images/me.jpg +0 -0
- data/assets/images/words.png +0 -0
- data/assets/jekyll-recker.scss +3 -0
- data/lib/blog/cli.rb +102 -0
- data/lib/blog/config.rb +67 -0
- data/lib/blog/entry.rb +68 -0
- data/lib/blog/git.rb +28 -0
- data/lib/blog/jekyll.rb +17 -0
- data/lib/blog/journal.rb +46 -0
- data/lib/blog/log.rb +30 -0
- data/lib/blog/slack.rb +20 -0
- data/lib/blog/words.rb +80 -0
- data/lib/blog.rb +14 -0
- data/lib/jekyll-recker/commands.rb +26 -0
- data/lib/jekyll-recker/configuration.rb +25 -0
- data/lib/jekyll-recker/generators.rb +14 -0
- data/lib/jekyll-recker/stats.rb +108 -0
- data/lib/jekyll-recker/tags.rb +16 -0
- data/lib/jekyll-recker/twitter.rb +73 -0
- data/lib/jekyll-recker/version.rb +7 -0
- data/lib/jekyll-recker/words.rb +82 -0
- data/lib/jekyll-recker.rb +15 -0
- metadata +134 -0
data/README.org
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
#+TITLE: jekyll-recker
|
2
|
+
#+SLUG: jekyll-recker.html
|
3
|
+
#+PERMALINK: jekyll-recker.html
|
4
|
+
#+STARTUP: showall
|
5
|
+
#+DESCRIPTION: my website's custom jekyll plugin
|
6
|
+
|
7
|
+
This is the jekyll plugin for my personal website.
|
8
|
+
|
9
|
+
* Installation
|
10
|
+
|
11
|
+
Add =jekyll-recker= to the =jekyll_plugins= group of your =Gemfile=.
|
12
|
+
|
13
|
+
#+BEGIN_SRC ruby
|
14
|
+
group :jekyll_plugins do
|
15
|
+
gem 'jekyll-recker', :git => 'https://github.com/arecker/jekyll-recker.git'
|
16
|
+
end
|
17
|
+
#+END_SRC
|
18
|
+
|
19
|
+
Add =jekyll-recker= to the list of plugins in jekyll's =_config.yml=.
|
20
|
+
|
21
|
+
#+BEGIN_SRC yaml
|
22
|
+
# _config.yaml
|
23
|
+
plugins:
|
24
|
+
- jekyll-recker
|
25
|
+
#+END_SRC
|
26
|
+
|
27
|
+
Set the theme.
|
28
|
+
|
29
|
+
#+BEGIN_SRC yaml
|
30
|
+
theme: jekyll-recker
|
31
|
+
#+END_SRC
|
32
|
+
|
33
|
+
Install and enjoy.
|
34
|
+
|
35
|
+
#+BEGIN_SRC sh
|
36
|
+
bundle install
|
37
|
+
bundle exec jekyll serve
|
38
|
+
#+END_SRC
|
39
|
+
|
40
|
+
* Usage
|
41
|
+
|
42
|
+
** Commands
|
43
|
+
|
44
|
+
*** =tweet=
|
45
|
+
|
46
|
+
The =tweet= command tweets a link to the latest published jekyll blog
|
47
|
+
post.
|
48
|
+
|
49
|
+
Ensure the following environment variables are set,.
|
50
|
+
|
51
|
+
#+BEGIN_SRC sh
|
52
|
+
export ACCESS_TOKEN_SECRET="..."
|
53
|
+
export ACCESS_TOKEN="..."
|
54
|
+
export CONSUMER_API_KEY="..."
|
55
|
+
export CONSUMER_API_SECRET="..."
|
56
|
+
#+END_SRC
|
57
|
+
|
58
|
+
Alternatively, configure which commands to run to fetch the secrets.
|
59
|
+
|
60
|
+
#+BEGIN_SRC yaml
|
61
|
+
# _config.yml
|
62
|
+
recker:
|
63
|
+
twitter:
|
64
|
+
access_token_secret_cmd: cat secrets/access-token-secret
|
65
|
+
access_token_cmd: cat secrets/access-token
|
66
|
+
consumer_api_key_cmd: cat secrets/consumer-api-key
|
67
|
+
consumer_api_secret_cmd: cat secrets/consumer-api-secret-key
|
68
|
+
#+END_SRC
|
69
|
+
|
70
|
+
Run =bundle exec jekyll tweet= to let it rip!
|
71
|
+
|
72
|
+
[[assets/images/example-tweet.png]]
|
73
|
+
|
74
|
+
** Generators
|
75
|
+
|
76
|
+
*** =stats=
|
77
|
+
|
78
|
+
On build time, =jekyll-recker= calculates and stores the following
|
79
|
+
stats in the =site.data.stats= object, which are by default rendered in a
|
80
|
+
widget on the home page layout.
|
81
|
+
|
82
|
+
[[assets/images/example-stats.png]]
|
83
|
+
|
84
|
+
If you'd like, you can override the template with your own stats
|
85
|
+
widget by providing your own =_includes/stats.html=.
|
86
|
+
|
87
|
+
| Field Name | Field Description |
|
88
|
+
|-----------------+------------------------------------------------------|
|
89
|
+
| =posts= | The total number of published posts. |
|
90
|
+
| =words.total= | The total number of words from all published post. |
|
91
|
+
| =words.average= | The average number of words for each published post. |
|
92
|
+
| =days.days= | Current streak of daily, consecutive posts. |
|
93
|
+
| =days.start= | First day of current streak. |
|
94
|
+
| =days.end= | Last day of current streak. |
|
95
|
+
|
96
|
+
Example:
|
97
|
+
|
98
|
+
#+BEGIN_SRC html
|
99
|
+
<!-- _includes/stats.html -->
|
100
|
+
|
101
|
+
<table>
|
102
|
+
<tr>
|
103
|
+
<th>Total Posts</th>
|
104
|
+
<th>Total Words</th>
|
105
|
+
<th>Average Words per Post</th>
|
106
|
+
<th>Current Streak</th>
|
107
|
+
<th>First day of current streak</th>
|
108
|
+
<th>Last day of current streak</th>
|
109
|
+
</tr>
|
110
|
+
<tr>
|
111
|
+
<td>{{ site.data.stats.posts }}</td>
|
112
|
+
<td>{{ site.data.stats.words.total }}</td>
|
113
|
+
<td>{{ site.data.stats.words.average }}</td>
|
114
|
+
<td>{{ site.data.stats.days.days }}</td>
|
115
|
+
<td>{{ site.data.stats.days.start }}</td>
|
116
|
+
<td>{{ site.data.stats.days.end }}</td>
|
117
|
+
</tr>
|
118
|
+
</table>
|
119
|
+
#+END_SRC
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<div class="ui two column stackable grid">
|
2
|
+
{%- for post in site.posts -%}
|
3
|
+
{%- capture this_month -%}{{ post.date | date: '%B %Y' }}{%- endcapture -%}
|
4
|
+
{%- capture next_month -%}{{ post.previous.date | date: '%B %Y' }}{%- endcapture -%}
|
5
|
+
{%- if forloop.first -%}
|
6
|
+
<div class="center aligned column">
|
7
|
+
<h3 class="ui large header">{{ this_month }}</h3>
|
8
|
+
<div class="ui middle aligned large link list">
|
9
|
+
{%- endif -%}
|
10
|
+
<a class="item" href="{{ post.url }}">{{ post.title }}</a>
|
11
|
+
{%- if this_month != next_month -%}
|
12
|
+
</div>
|
13
|
+
</div>
|
14
|
+
<div class="center aligned column">
|
15
|
+
<h3 class="ui large header">{{ next_month }}</h3>
|
16
|
+
<div class="ui middle aligned large link list">
|
17
|
+
{%- endif -%}
|
18
|
+
{%- if forloop.last -%}
|
19
|
+
</div>
|
20
|
+
</div>
|
21
|
+
{%- endif -%}
|
22
|
+
{%- endfor -%}
|
23
|
+
</div>
|
data/_includes/nav.html
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
<div class="ui container">
|
2
|
+
<h1 class="ui massive dividing header">
|
3
|
+
{{ include.title }}
|
4
|
+
<div class="sub header">
|
5
|
+
{{ include.subtitle }}
|
6
|
+
</div>
|
7
|
+
</h1>
|
8
|
+
<div class="ui huge breadcrumb">
|
9
|
+
<a href="{{ site.baseurl}}/" class="section">
|
10
|
+
<i class="home icon"></i>
|
11
|
+
</a>
|
12
|
+
<i class="right angle icon divider"></i>
|
13
|
+
<div class="active section">{{ include.slug }}</div>
|
14
|
+
</div>
|
15
|
+
</div>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
{%- if page.next -%}
|
2
|
+
<a href="{{ page.next.url }}" class="ui basic left floated button">
|
3
|
+
<i class="angle left icon"></i> {{ page.next.slug }}
|
4
|
+
</a>
|
5
|
+
{%- endif -%}
|
6
|
+
{%- if page.previous -%}
|
7
|
+
<a href="{{ page.previous.url }}" class="ui basic right floated button">
|
8
|
+
{{ page.previous.slug }} <i class="angle right icon"></i>
|
9
|
+
</a>
|
10
|
+
{%- endif -%}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<div class="ui center aligned four small statistics">
|
2
|
+
<div class="ui small statistic">
|
3
|
+
<div class="value">
|
4
|
+
{{ site.data.stats.words.total }}
|
5
|
+
</div>
|
6
|
+
<div class="label">
|
7
|
+
Total Words
|
8
|
+
</div>
|
9
|
+
</div>
|
10
|
+
<div class="ui small statistic">
|
11
|
+
<div class="value">
|
12
|
+
{{ site.data.stats.words.average }}
|
13
|
+
</div>
|
14
|
+
<div class="label">
|
15
|
+
Ave. Words per post
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
<div class="ui small statistic">
|
19
|
+
<div class="value">
|
20
|
+
{{ site.data.stats.posts }}
|
21
|
+
</div>
|
22
|
+
<div class="label">
|
23
|
+
Posts
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
<div class="ui small statistic">
|
27
|
+
<div class="value">
|
28
|
+
{{ site.data.stats.days.days }}
|
29
|
+
</div>
|
30
|
+
<div class="label">
|
31
|
+
Consecutive Days
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
|
36
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
6
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css" integrity="sha256-9mbkOfVho3ZPXfM7W8sV2SndrGDuh7wuyLjtsWeTI1Q=" crossorigin="anonymous" />
|
7
|
+
<link href="/assets/jekyll-recker.css" rel="stylesheet"/>
|
8
|
+
<title>
|
9
|
+
{{ site.title }} | {% if page.title != nil %}{{ page.title }}{% else %}{{ site.description }}{% endif %}
|
10
|
+
</title>
|
11
|
+
</head>
|
12
|
+
<body>
|
13
|
+
<div class="ui container">
|
14
|
+
{{ content }}
|
15
|
+
</div>
|
16
|
+
<div class="ui vertical footer segment">
|
17
|
+
<div class="ui center aligned container">
|
18
|
+
<div class="ui horizontal large divided link list">
|
19
|
+
<div class="ui buttons">
|
20
|
+
<a class="ui circular rss basic button" href="/feed.xml"><i class="rss icon"></i></a>
|
21
|
+
<a class="ui circular email basic button" href="mailto:{{ site.email }}"><i class="envelope icon"></i></a>
|
22
|
+
<a class="ui circular twitter basic button" href="https://www.twitter.com/{{ site.twitter_username }}"><i class="twitter icon"></i></a>
|
23
|
+
<a class="ui circular github basic button" href="https://www.github.com/{{ site.github_username }}"><i class="github icon"></i></a>
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
</div>
|
27
|
+
<div class="ui center aligned container">
|
28
|
+
<div class="ui centered mini">
|
29
|
+
<p>
|
30
|
+
powered by <a title="Jekyll is a simple, blog-aware, static site generator." href="http://jekyllrb.com/">jekyll</a> &
|
31
|
+
<a title="Alex Recker's custom jekyll plugin" href="{{ site.baseurl }}{% link README.org %}">jekyll-recker</a> v{% recker_version %}
|
32
|
+
</p>
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
</body>
|
37
|
+
</html>
|
data/_layouts/home.html
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
---
|
4
|
+
<div class="ui container">
|
5
|
+
{%- include nav.html slug="index.html" title=site.title subtitle=site.description %}
|
6
|
+
<div class="ui text container center aligned">
|
7
|
+
{{ content }}
|
8
|
+
</div>
|
9
|
+
<br/>
|
10
|
+
<div class="ui container center aligned segment">
|
11
|
+
{% include stats.html %}
|
12
|
+
</div>
|
13
|
+
<br/>
|
14
|
+
<div class="ui container">
|
15
|
+
<img class="ui fluid image" src="{{ site.baseurl }}assets/images/words.png">
|
16
|
+
</div>
|
17
|
+
<br/>
|
18
|
+
<div class="ui container">
|
19
|
+
{% include archive.html %}
|
20
|
+
</div>
|
21
|
+
</div>
|
data/_layouts/page.html
ADDED
data/_layouts/post.html
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
---
|
4
|
+
<div class="ui container">
|
5
|
+
{% capture datestring %}{{ page.date | date: '%A, %B %d %Y' }}{% endcapture %}
|
6
|
+
{%- include nav.html slug=page.slug title=datestring subtitle=page.title %}
|
7
|
+
<div class="ui container">
|
8
|
+
{{ content }}
|
9
|
+
{%- include pager.html %}
|
10
|
+
</div>
|
11
|
+
</div>
|
12
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
.ui
|
2
|
+
.container
|
3
|
+
margin: 2em
|
4
|
+
|
5
|
+
.footer
|
6
|
+
margin-top: 50px !important
|
7
|
+
|
8
|
+
p
|
9
|
+
font-size: 20px
|
10
|
+
line-height: 160%
|
11
|
+
-moz-osx-font-smoothing: grayscale
|
12
|
+
-webkit-font-smoothing: antialiased !important
|
13
|
+
-moz-font-smoothing: antialiased !important
|
14
|
+
text-rendering: optimizelegibility !important
|
15
|
+
letter-spacing: .03em
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/lib/blog/cli.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'commander'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
module Blog
|
7
|
+
# CLI
|
8
|
+
class CLI
|
9
|
+
include Commander::Methods
|
10
|
+
|
11
|
+
def logger
|
12
|
+
Blog::Log.logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def config_path
|
16
|
+
@config_path ||= File.expand_path '~/.blog.yml'
|
17
|
+
end
|
18
|
+
|
19
|
+
def config
|
20
|
+
Blog::Config.load_from_file(config_path)
|
21
|
+
end
|
22
|
+
|
23
|
+
def journal
|
24
|
+
@journal ||= Blog::Journal.from_file(config.journal_path)
|
25
|
+
end
|
26
|
+
|
27
|
+
def latest
|
28
|
+
@latest ||= journal.public_entries.first
|
29
|
+
end
|
30
|
+
|
31
|
+
def build
|
32
|
+
logger.info "deleting #{config.site_dir.pretty_path}"
|
33
|
+
FileUtils.rm_rf(config.site_dir)
|
34
|
+
logger.info "parsing #{config.journal_path.pretty_path}"
|
35
|
+
journal = Blog::Journal.from_file(config.journal_path)
|
36
|
+
logger.info "writing #{journal.public_entries.count.pretty} public entries"
|
37
|
+
journal.write_public_entries! config.posts_dir
|
38
|
+
logger.info "building jekyll"
|
39
|
+
Blog::Jekyll.build(config)
|
40
|
+
end
|
41
|
+
|
42
|
+
def commit
|
43
|
+
git = Blog::Git.new(config.blog_repo)
|
44
|
+
git.run!
|
45
|
+
end
|
46
|
+
|
47
|
+
def slack
|
48
|
+
logger.info "fetched latest entry: #{latest.excerpt}"
|
49
|
+
config.slacks.each do |info|
|
50
|
+
Blog::Slacky.post(latest, `#{info['webhook_cmd']}`, info)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def run
|
55
|
+
program :name, 'blog'
|
56
|
+
program :version, 'v0.0.0'
|
57
|
+
program :description, 'script to generate and publish my blog'
|
58
|
+
|
59
|
+
default_command :all
|
60
|
+
|
61
|
+
global_option '--config FILE', String, 'path to blog.yml' do |file|
|
62
|
+
@config_path = file
|
63
|
+
end
|
64
|
+
|
65
|
+
command :build do |c|
|
66
|
+
c.syntax = 'build'
|
67
|
+
c.description = 'build jekyll site'
|
68
|
+
c.action do |_args, _options|
|
69
|
+
build
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
command :commit do |c|
|
74
|
+
c.syntax = 'commit'
|
75
|
+
c.description = 'commit and push new post'
|
76
|
+
c.action do |_args, _options|
|
77
|
+
commit
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
command :slack do |c|
|
82
|
+
c.syntax = 'slack'
|
83
|
+
c.description = 'send slack notifications'
|
84
|
+
c.action do |_args, _options|
|
85
|
+
slack
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
command :all do |c|
|
90
|
+
c.syntax = 'all'
|
91
|
+
c.description = 'build, commit, and slack'
|
92
|
+
c.action do |_args, _options|
|
93
|
+
build
|
94
|
+
commit
|
95
|
+
slack
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
run!
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/blog/config.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
module Blog
|
7
|
+
# Config
|
8
|
+
class Config
|
9
|
+
attr_reader :data
|
10
|
+
|
11
|
+
def self.load_from_file(config_path = File.expand_path('~/.blog.yml'))
|
12
|
+
new(YAML.load_file(config_path) || {})
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(data)
|
16
|
+
@data = data
|
17
|
+
end
|
18
|
+
|
19
|
+
def journal_path
|
20
|
+
File.join blog_repo, 'journal.org'
|
21
|
+
end
|
22
|
+
|
23
|
+
def posts_dir
|
24
|
+
File.join blog_repo, '_posts'
|
25
|
+
end
|
26
|
+
|
27
|
+
def site_dir
|
28
|
+
File.join(File.expand_path(blog_repo), '_site')
|
29
|
+
end
|
30
|
+
|
31
|
+
def blog_repo
|
32
|
+
Bundler.root.to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
def stats_path
|
36
|
+
File.join blog_repo, '_data/stats.json'
|
37
|
+
end
|
38
|
+
|
39
|
+
def log_level
|
40
|
+
@data.fetch('log_level', 'INFO').upcase
|
41
|
+
end
|
42
|
+
|
43
|
+
def twitter_creds
|
44
|
+
twitter = @data.fetch('twitter')
|
45
|
+
creds = {}
|
46
|
+
[
|
47
|
+
'access_token_secret',
|
48
|
+
'access_token',
|
49
|
+
'consumer_api_key',
|
50
|
+
'consumer_api_secret'
|
51
|
+
].each do |key|
|
52
|
+
creds[key] = `#{twitter.fetch(key + '_cmd')}`.strip
|
53
|
+
end
|
54
|
+
creds
|
55
|
+
end
|
56
|
+
|
57
|
+
def slacks
|
58
|
+
@data.fetch('slacks', [])
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def missing_fields
|
64
|
+
required_keys.reject { |k| @data.key? k }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/blog/entry.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
require 'org-ruby'
|
5
|
+
|
6
|
+
module Blog
|
7
|
+
# Entry
|
8
|
+
class Entry
|
9
|
+
def initialize(headline)
|
10
|
+
@headline = headline
|
11
|
+
end
|
12
|
+
|
13
|
+
def title
|
14
|
+
date.strftime('%A, %B %-e %Y')
|
15
|
+
end
|
16
|
+
|
17
|
+
def subtitle
|
18
|
+
@subtitle ||= @headline.headline_text.split(' ').drop(2).join(' ')
|
19
|
+
end
|
20
|
+
|
21
|
+
alias excerpt subtitle
|
22
|
+
|
23
|
+
def tags
|
24
|
+
@tags ||= @headline.tags
|
25
|
+
end
|
26
|
+
|
27
|
+
def date
|
28
|
+
@date ||= Date.strptime(
|
29
|
+
@headline.headline_text.split(' ').take(2).join(' '),
|
30
|
+
'%Y-%m-%d %A'
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def date_slug
|
35
|
+
date.strftime('%Y-%m-%d')
|
36
|
+
end
|
37
|
+
|
38
|
+
def public?
|
39
|
+
!tags.include? 'private'
|
40
|
+
end
|
41
|
+
|
42
|
+
def filename
|
43
|
+
"#{date_slug}-#{date_slug}.html.html"
|
44
|
+
end
|
45
|
+
|
46
|
+
def permalink
|
47
|
+
"https://www.alexrecker.com/#{date_slug}.html"
|
48
|
+
end
|
49
|
+
|
50
|
+
def body_text
|
51
|
+
@headline.body_lines.drop(1).collect(&:output_text).join(' ')
|
52
|
+
end
|
53
|
+
|
54
|
+
def body_html
|
55
|
+
Orgmode::Parser.new(body_text).to_html
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_html
|
59
|
+
<<~HTML
|
60
|
+
---
|
61
|
+
title: #{title}
|
62
|
+
excerpt: #{excerpt}
|
63
|
+
---
|
64
|
+
#{body_html}
|
65
|
+
HTML
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/blog/git.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'git'
|
4
|
+
|
5
|
+
module Blog
|
6
|
+
# Git
|
7
|
+
class Git
|
8
|
+
attr_reader :client
|
9
|
+
|
10
|
+
def initialize(path)
|
11
|
+
@client = ::Git.open(path)
|
12
|
+
@logger = Blog::Log.logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def run!
|
16
|
+
commit!
|
17
|
+
end
|
18
|
+
|
19
|
+
def commit!
|
20
|
+
commit = '[auto] Automatic Publish'
|
21
|
+
@logger.info "writing commit: #{commit}"
|
22
|
+
@client.add
|
23
|
+
@client.commit(commit)
|
24
|
+
@logger.info 'pushing commit'
|
25
|
+
@client.push
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/blog/jekyll.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jekyll'
|
4
|
+
|
5
|
+
module Blog
|
6
|
+
module Jekyll
|
7
|
+
def self.build(config)
|
8
|
+
conf = ::Jekyll.configuration(
|
9
|
+
{
|
10
|
+
'source' => config.blog_repo,
|
11
|
+
'destination' => config.site_dir
|
12
|
+
}
|
13
|
+
)
|
14
|
+
::Jekyll::Site.new(conf).process
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/blog/journal.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'org-ruby'
|
4
|
+
|
5
|
+
module Blog
|
6
|
+
# Journal
|
7
|
+
class Journal
|
8
|
+
def self.from_file(path)
|
9
|
+
new(Orgmode::Parser.load(path))
|
10
|
+
end
|
11
|
+
|
12
|
+
def logger
|
13
|
+
Blog::Log.logger
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(parser)
|
17
|
+
@parser = parser
|
18
|
+
end
|
19
|
+
|
20
|
+
def public_entries
|
21
|
+
@public_entries ||= all_entries.select(&:public?).sort_by(&:date).reverse
|
22
|
+
end
|
23
|
+
|
24
|
+
def private_entries
|
25
|
+
@private_entries ||= all_entries.reject(&:public?).sort_by(&:date).reverse
|
26
|
+
end
|
27
|
+
|
28
|
+
def write_public_entries!(dir)
|
29
|
+
public_entries.each do |entry|
|
30
|
+
target = File.join(dir, entry.filename)
|
31
|
+
logger.debug "writing #{entry.title} to #{target.pretty_path}"
|
32
|
+
File.open(target, 'w+') { |f| f.write(entry.to_html) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def all_entries
|
37
|
+
entry_headlines.map { |h| Entry.new(h) }
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def entry_headlines
|
43
|
+
@parser.headlines.select { |h| h.level == 3 }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/blog/log.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module Blog
|
6
|
+
# Log
|
7
|
+
module Log
|
8
|
+
def self.logger
|
9
|
+
@logger ||= default_logger
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.default_logger
|
13
|
+
logger = Logger.new(STDOUT)
|
14
|
+
logger.level = Logger::INFO
|
15
|
+
logger.formatter = proc do |severity, _datetime, _progname, msg|
|
16
|
+
"blog: #{msg}\n"
|
17
|
+
end
|
18
|
+
logger
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.level=(setting)
|
22
|
+
@logger.level = case setting.upcase
|
23
|
+
when 'DEBUG'
|
24
|
+
Logger::DEBUG
|
25
|
+
else
|
26
|
+
Logger::INFO
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|