jekyll-recker 0.1.0
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 +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
|