jekyll-recker 0.3.0 → 0.4.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 +4 -4
- data/README.org +93 -0
- data/assets/images/example-slack.png +0 -0
- data/lib/jekyll-recker/commands.rb +17 -6
- data/lib/jekyll-recker/configuration.rb +4 -0
- data/lib/jekyll-recker/error.rb +8 -0
- data/lib/jekyll-recker/log.rb +5 -0
- data/lib/jekyll-recker/shell.rb +29 -0
- data/lib/jekyll-recker/slack.rb +68 -0
- data/lib/jekyll-recker/twitter.rb +28 -15
- data/lib/jekyll-recker/version.rb +1 -1
- data/lib/jekyll-recker.rb +5 -0
- metadata +19 -11
- data/lib/blog/cli.rb +0 -102
- data/lib/blog/config.rb +0 -67
- data/lib/blog/entry.rb +0 -68
- data/lib/blog/git.rb +0 -28
- data/lib/blog/jekyll.rb +0 -17
- data/lib/blog/journal.rb +0 -46
- data/lib/blog/log.rb +0 -30
- data/lib/blog/slack.rb +0 -20
- data/lib/blog/words.rb +0 -80
- data/lib/blog.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27007112ff63186d2d55ea50ea4318817aab091824c2657401c8f6b51d0cd7a2
|
4
|
+
data.tar.gz: 7cd0e1169baa77572ff3cead226a6a7b03f0e74849bba8e3b362c1eeba39a579
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 47d59369933cd797ab422071c43f5a62c8619791511931a2c5f4a5aa16539cda1c9fe80f9bee37f57fe877d43a83a027d869b0434a99a5282f48af8f97f99dea
|
7
|
+
data.tar.gz: 70ec0f5d69e913b6810b4c5ca57568710d4854285126e51f6a4431b5e53f3f2e893a6e90e58501cba3f4fdc0b509e411901bc8ecdc6789d43dcc01e23534dcfc
|
data/README.org
CHANGED
@@ -41,6 +41,82 @@ bundle exec jekyll serve
|
|
41
41
|
|
42
42
|
** Commands
|
43
43
|
|
44
|
+
*** =slack=
|
45
|
+
|
46
|
+
The =slack= command posts a slack message advertising the latest
|
47
|
+
published jekyll blog post using a private incoming webhook.
|
48
|
+
|
49
|
+
Configure _config.yml
|
50
|
+
|
51
|
+
#+BEGIN_SRC yaml
|
52
|
+
# _config.yml
|
53
|
+
recker:
|
54
|
+
slack:
|
55
|
+
MyTeam:
|
56
|
+
channel: '#blogs' # required!
|
57
|
+
username: 'blogbot' # required!
|
58
|
+
emoji: ':robot:' # required!
|
59
|
+
#+END_SRC
|
60
|
+
|
61
|
+
Multiple teams are supported too!
|
62
|
+
|
63
|
+
#+BEGIN_SRC yaml
|
64
|
+
# _config.yml
|
65
|
+
recker:
|
66
|
+
slack:
|
67
|
+
MyTeamA:
|
68
|
+
channel: '#blogs' # required!
|
69
|
+
username: 'blogbot' # required!
|
70
|
+
emoji: ':robot:' # required!
|
71
|
+
MyTeamB:
|
72
|
+
channel: '#blogs' # required!
|
73
|
+
username: 'blogbot' # required!
|
74
|
+
emoji: ':robot:' # required!
|
75
|
+
MyTeamC:
|
76
|
+
channel: '#blogs' # required!
|
77
|
+
username: 'blogbot' # required!
|
78
|
+
emoji: ':robot:' # required!
|
79
|
+
#+END_SRC
|
80
|
+
|
81
|
+
|
82
|
+
Supply the private webhook through an environment variable.
|
83
|
+
|
84
|
+
#+BEGIN_SRC sh
|
85
|
+
export SLACK_MYTEAM_WEBHOOK="https://..." # SLACK_ + <MyTeam.upcase> + _WEBHOOK
|
86
|
+
#+END_SRC
|
87
|
+
|
88
|
+
Alternatively, add the command with which to retrieve the webhook in
|
89
|
+
_config.yml
|
90
|
+
|
91
|
+
#+BEGIN_SRC yaml
|
92
|
+
# _config.yml
|
93
|
+
recker:
|
94
|
+
slack:
|
95
|
+
MyTeam:
|
96
|
+
webhook_cmd: cat secrets/my-teams-secret-webhook.txt
|
97
|
+
#+END_SRC
|
98
|
+
|
99
|
+
Run =bundle exec jekyll slack= to let it rip!
|
100
|
+
|
101
|
+
[[assets/images/example-slack.png]]
|
102
|
+
|
103
|
+
Using the =--dry= flag, you can preview the message post without
|
104
|
+
actually posting anything.
|
105
|
+
|
106
|
+
#+BEGIN_EXAMPLE
|
107
|
+
arecker@25732-arecker:~/src/blog$ be jekyll slack --dry
|
108
|
+
Configuration file: /Users/arecker/src/blog/_config.yml
|
109
|
+
jekyll-recker: reckerfamily: discovering webhook
|
110
|
+
Configuration file: /Users/arecker/src/blog/_config.yml
|
111
|
+
jekyll-recker: reckerfamily: posting drag racing, windshield wipers, and alex's painting tips
|
112
|
+
jekyll-recker: postign in dry mode, printing message
|
113
|
+
jekyll-recker: BEGIN MESSAGE
|
114
|
+
Sunday, March 15 2020
|
115
|
+
drag racing, windshield wipers, and alex's painting tips
|
116
|
+
https://www.alexrecker.com/2020-03-15.html
|
117
|
+
END MESSAGE
|
118
|
+
#+END_EXAMPLE
|
119
|
+
|
44
120
|
*** =tweet=
|
45
121
|
|
46
122
|
The =tweet= command tweets a link to the latest published jekyll blog
|
@@ -71,6 +147,23 @@ Run =bundle exec jekyll tweet= to let it rip!
|
|
71
147
|
|
72
148
|
[[assets/images/example-tweet.png]]
|
73
149
|
|
150
|
+
Using the =--dry= flag, you can test your configuration without
|
151
|
+
actually tweeting anything.
|
152
|
+
|
153
|
+
#+BEGIN_EXAMPLE
|
154
|
+
arecker@25732-arecker:~/src/blog$ be jekyll tweet --dry
|
155
|
+
jekyll-recker: discovering credentials
|
156
|
+
Configuration file: /Users/arecker/src/blog/_config.yml
|
157
|
+
Configuration file: /Users/arecker/src/blog/_config.yml
|
158
|
+
jekyll-recker: tweeting drag racing, windshield wipers, and alex's painting tips
|
159
|
+
jekyll-recker: tweeting in dry mode, printing message
|
160
|
+
jekyll-recker: BEGIN TWEET
|
161
|
+
Sunday, March 15 2020
|
162
|
+
drag racing, windshield wipers, and alex's painting tips
|
163
|
+
https://www.alexrecker.com/2020-03-15.html
|
164
|
+
END TWEET
|
165
|
+
#+END_EXAMPLE
|
166
|
+
|
74
167
|
** Generators
|
75
168
|
|
76
169
|
*** =stats=
|
Binary file
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Jekyll
|
2
4
|
module Recker
|
3
5
|
module Commands
|
@@ -8,14 +10,15 @@ module Jekyll
|
|
8
10
|
prog.command(:tweet) do |c|
|
9
11
|
c.syntax 'tweet'
|
10
12
|
c.description 'tweet latest post'
|
11
|
-
c.
|
12
|
-
|
13
|
+
c.option 'dry', '-d', '--dry', 'print message instead of tweeting'
|
14
|
+
c.action do |_args, options|
|
15
|
+
client = Jekyll::Recker::Twitter.new(dry: options['dry'])
|
13
16
|
Recker.info 'discovering credentials'
|
14
17
|
client.discover_credentials!
|
15
18
|
Recker.info "tweeting #{client.latest.data['title']}"
|
16
19
|
client.post_latest!
|
17
|
-
rescue => e
|
18
|
-
abort_with e.message
|
20
|
+
rescue ReckerError => e
|
21
|
+
Recker.abort_with e.message
|
19
22
|
end
|
20
23
|
end
|
21
24
|
end
|
@@ -29,8 +32,16 @@ module Jekyll
|
|
29
32
|
prog.command(:slack) do |c|
|
30
33
|
c.syntax 'slack'
|
31
34
|
c.description 'slack latest post'
|
32
|
-
c.
|
33
|
-
|
35
|
+
c.option 'dry', '-d', '--dry', 'print message instead of posting'
|
36
|
+
c.action do |_args, options|
|
37
|
+
Recker::Slack.each_in_config(dry: options['dry']) do |client|
|
38
|
+
Recker.info "#{client.key}: discovering webhook"
|
39
|
+
client.discover_webhook!
|
40
|
+
Recker.info "#{client.key}: posting #{client.latest.data['title']}"
|
41
|
+
client.post_latest!
|
42
|
+
end
|
43
|
+
rescue ReckerError => e
|
44
|
+
Recker.abort_with e.message
|
34
45
|
end
|
35
46
|
end
|
36
47
|
end
|
data/lib/jekyll-recker/log.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
|
5
|
+
module Jekyll
|
6
|
+
# Recker
|
7
|
+
module Recker
|
8
|
+
# ShellCommandFailed
|
9
|
+
class ShellCommandFailed < ReckerError; end
|
10
|
+
|
11
|
+
def self.shell(cmd)
|
12
|
+
Recker.debug("running shell command \`#{cmd}\`")
|
13
|
+
out, err, status = Open3.capture3(cmd)
|
14
|
+
return out if status.success?
|
15
|
+
|
16
|
+
msg = <<~ERROR
|
17
|
+
the command \`#{cmd}\` failed!
|
18
|
+
--- exit
|
19
|
+
#{status}
|
20
|
+
--- stdout
|
21
|
+
#{out}
|
22
|
+
--- stderr
|
23
|
+
#{err}
|
24
|
+
ERROR
|
25
|
+
|
26
|
+
raise ShellCommandFailed, msg
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'slack-notifier'
|
4
|
+
|
5
|
+
module Jekyll
|
6
|
+
# Recker
|
7
|
+
module Recker
|
8
|
+
# Slack
|
9
|
+
class Slack
|
10
|
+
def self.each_in_config(dry: false)
|
11
|
+
Configuration.slack.map do |key, body|
|
12
|
+
yield new(key, body, dry: dry)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :key
|
17
|
+
|
18
|
+
def initialize(config_key, config_body, dry: false)
|
19
|
+
@key = config_key
|
20
|
+
@data = config_body
|
21
|
+
@webhook = nil
|
22
|
+
@dry = dry
|
23
|
+
end
|
24
|
+
|
25
|
+
def discover_webhook!
|
26
|
+
@webhook = ENV["SLACK_#{@key.upcase}_WEBHOOK"] || extract_from_config
|
27
|
+
raise ReckerError, 'cannot find slack credentials!' if @webhook.nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
def latest
|
31
|
+
@latest ||= Configuration.latest_post
|
32
|
+
end
|
33
|
+
|
34
|
+
def post_latest!
|
35
|
+
if @dry
|
36
|
+
Recker.info('postign in dry mode, printing message')
|
37
|
+
Recker.info("BEGIN MESSAGE\n#{message_body.strip}\nEND MESSAGE")
|
38
|
+
else
|
39
|
+
::Slack::Notifier.new(
|
40
|
+
@webhook.strip,
|
41
|
+
channel: @data.fetch('channel'),
|
42
|
+
username: @data.fetch('username'),
|
43
|
+
icon_emoji: @data.fetch('emoji')
|
44
|
+
).post(text: message_body)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def message_body
|
51
|
+
url = File.join Configuration.jekyll['url'], latest.url
|
52
|
+
body = <<~MSG
|
53
|
+
#{latest.data['date'].strftime('%A, %B %-d %Y')}
|
54
|
+
#{latest.data['title']}
|
55
|
+
#{url}
|
56
|
+
MSG
|
57
|
+
::Slack::Notifier::Util::LinkFormatter.format(body)
|
58
|
+
end
|
59
|
+
|
60
|
+
def extract_from_config
|
61
|
+
cmd = @data['webhook_cmd']
|
62
|
+
return nil if cmd.nil?
|
63
|
+
|
64
|
+
Recker.shell(cmd)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -1,28 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'twitter'
|
3
4
|
|
4
5
|
module Jekyll
|
5
6
|
module Recker
|
6
7
|
# Twitter Client
|
7
8
|
class Twitter
|
8
|
-
|
9
|
-
|
10
|
-
super
|
11
|
-
end
|
9
|
+
def initialize(dry: false)
|
10
|
+
@dry = dry
|
12
11
|
end
|
13
12
|
|
14
13
|
def discover_credentials!
|
15
14
|
@creds = extract_from_env || extract_from_config
|
16
|
-
raise
|
15
|
+
raise ReckerError, 'cannot find twitter credentials!' if @creds.nil?
|
16
|
+
|
17
17
|
set_credentials!
|
18
18
|
end
|
19
19
|
|
20
20
|
def post_latest!
|
21
|
-
@
|
21
|
+
if @dry
|
22
|
+
Recker.info('tweeting in dry mode, printing message')
|
23
|
+
Recker.info("BEGIN TWEET\n#{tweet_body.strip}\nEND TWEET")
|
24
|
+
else
|
25
|
+
@client.update(tweet_body)
|
26
|
+
end
|
22
27
|
end
|
23
28
|
|
24
29
|
def latest
|
25
|
-
|
30
|
+
Configuration.latest_post
|
26
31
|
end
|
27
32
|
|
28
33
|
private
|
@@ -47,12 +52,20 @@ module Jekyll
|
|
47
52
|
|
48
53
|
def extract_from_env
|
49
54
|
values = cred_fieldnames.map { |k| ENV[k.upcase] }
|
50
|
-
|
55
|
+
|
56
|
+
return nil if values.any? { |v| v.nil? || v.empty? }
|
57
|
+
|
58
|
+
Hash[cred_fieldnames.zip(values)]
|
51
59
|
end
|
52
60
|
|
53
61
|
def extract_from_config
|
54
|
-
values = cred_fieldnames.map
|
55
|
-
|
62
|
+
values = cred_fieldnames.map do |k|
|
63
|
+
Recker.shell(Configuration.twitter["#{k}_cmd"])
|
64
|
+
end
|
65
|
+
|
66
|
+
return nil if values.any? { |v| v.nil? || v.empty? }
|
67
|
+
|
68
|
+
Hash[cred_fieldnames.zip(values)]
|
56
69
|
end
|
57
70
|
|
58
71
|
def shell(cmd)
|
@@ -60,11 +73,11 @@ module Jekyll
|
|
60
73
|
end
|
61
74
|
|
62
75
|
def cred_fieldnames
|
63
|
-
[
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
76
|
+
%w[
|
77
|
+
access_token_secret
|
78
|
+
access_token
|
79
|
+
consumer_api_key
|
80
|
+
consumer_api_secret
|
68
81
|
]
|
69
82
|
end
|
70
83
|
end
|
data/lib/jekyll-recker.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'jekyll'
|
4
|
+
|
3
5
|
module Jekyll
|
4
6
|
# Recker
|
5
7
|
module Recker
|
6
8
|
require 'jekyll-recker/commands.rb'
|
7
9
|
require 'jekyll-recker/configuration.rb'
|
10
|
+
require 'jekyll-recker/error.rb'
|
8
11
|
require 'jekyll-recker/generators.rb'
|
9
12
|
require 'jekyll-recker/log.rb'
|
13
|
+
require 'jekyll-recker/shell.rb'
|
14
|
+
require 'jekyll-recker/slack.rb'
|
10
15
|
require 'jekyll-recker/stats.rb'
|
11
16
|
require 'jekyll-recker/tags.rb'
|
12
17
|
require 'jekyll-recker/twitter.rb'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jekyll-recker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Recker
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '3.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: slack-notifier
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: twitter
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -84,26 +98,20 @@ files:
|
|
84
98
|
- _layouts/page.html
|
85
99
|
- _layouts/post.html
|
86
100
|
- _sass/jekyll-recker.sass
|
101
|
+
- assets/images/example-slack.png
|
87
102
|
- assets/images/example-stats.png
|
88
103
|
- assets/images/example-tweet.png
|
89
104
|
- assets/images/me.jpg
|
90
105
|
- assets/images/words.png
|
91
106
|
- assets/jekyll-recker.scss
|
92
|
-
- lib/blog.rb
|
93
|
-
- lib/blog/cli.rb
|
94
|
-
- lib/blog/config.rb
|
95
|
-
- lib/blog/entry.rb
|
96
|
-
- lib/blog/git.rb
|
97
|
-
- lib/blog/jekyll.rb
|
98
|
-
- lib/blog/journal.rb
|
99
|
-
- lib/blog/log.rb
|
100
|
-
- lib/blog/slack.rb
|
101
|
-
- lib/blog/words.rb
|
102
107
|
- lib/jekyll-recker.rb
|
103
108
|
- lib/jekyll-recker/commands.rb
|
104
109
|
- lib/jekyll-recker/configuration.rb
|
110
|
+
- lib/jekyll-recker/error.rb
|
105
111
|
- lib/jekyll-recker/generators.rb
|
106
112
|
- lib/jekyll-recker/log.rb
|
113
|
+
- lib/jekyll-recker/shell.rb
|
114
|
+
- lib/jekyll-recker/slack.rb
|
107
115
|
- lib/jekyll-recker/stats.rb
|
108
116
|
- lib/jekyll-recker/tags.rb
|
109
117
|
- lib/jekyll-recker/twitter.rb
|
data/lib/blog/cli.rb
DELETED
@@ -1,102 +0,0 @@
|
|
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
DELETED
@@ -1,67 +0,0 @@
|
|
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
DELETED
@@ -1,68 +0,0 @@
|
|
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
DELETED
@@ -1,28 +0,0 @@
|
|
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
DELETED
@@ -1,17 +0,0 @@
|
|
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
DELETED
@@ -1,46 +0,0 @@
|
|
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
DELETED
@@ -1,30 +0,0 @@
|
|
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
|
data/lib/blog/slack.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'slack-notifier'
|
4
|
-
|
5
|
-
module Blog
|
6
|
-
# Slack
|
7
|
-
module Slacky
|
8
|
-
def self.post(entry, url, info)
|
9
|
-
notifier = ::Slack::Notifier.new(
|
10
|
-
url.strip,
|
11
|
-
channel: info['channel'],
|
12
|
-
username: info['username'],
|
13
|
-
icon_emoji: ':reckerbot:'
|
14
|
-
)
|
15
|
-
message = "#{entry.title} - #{entry.excerpt}\n#{entry.permalink}"
|
16
|
-
Slack::Notifier::Util::LinkFormatter.format(message)
|
17
|
-
notifier.post text: message
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
data/lib/blog/words.rb
DELETED
@@ -1,80 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Blog
|
4
|
-
# Words
|
5
|
-
module Words
|
6
|
-
def self.array_to_and_list(array)
|
7
|
-
case array.length
|
8
|
-
when 0
|
9
|
-
''
|
10
|
-
when 1
|
11
|
-
array.first
|
12
|
-
when 2
|
13
|
-
"#{array.first} and #{array.last}"
|
14
|
-
else
|
15
|
-
array[0...-1].join(', ') + ", and #{array.last}"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.and_list_to_array(str)
|
20
|
-
str = str.gsub(' and ', ', ')
|
21
|
-
str.split(',').map(&:strip).reject(&:empty?)
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.prettify_number(number)
|
25
|
-
number.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.prettify_path(path, home = nil)
|
29
|
-
home ||= File.expand_path('~/')
|
30
|
-
path.sub(home, '~')
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.to_word_list(str)
|
34
|
-
str.split(' ')
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.to_weighted_list(arr)
|
38
|
-
arr.uniq.map do |word|
|
39
|
-
[word, arr.count(word)]
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Array extensions
|
46
|
-
class Array
|
47
|
-
def to_and_list
|
48
|
-
Blog::Words.array_to_and_list(self)
|
49
|
-
end
|
50
|
-
|
51
|
-
def to_weighted_list
|
52
|
-
Blog::Words.to_weighted_list(self)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
# Integer extensions
|
57
|
-
class Integer
|
58
|
-
def pretty
|
59
|
-
Blog::Words.prettify_number(self)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
# String extensions
|
64
|
-
class String
|
65
|
-
def words
|
66
|
-
Blog::Words.to_word_list(self)
|
67
|
-
end
|
68
|
-
|
69
|
-
def word_count
|
70
|
-
Blog::Words.to_word_list(self).count
|
71
|
-
end
|
72
|
-
|
73
|
-
def pretty_path(home = nil)
|
74
|
-
Blog::Words.prettify_path(self, home)
|
75
|
-
end
|
76
|
-
|
77
|
-
def to_and_array
|
78
|
-
Blog::Words.and_list_to_array(self)
|
79
|
-
end
|
80
|
-
end
|
data/lib/blog.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Blog
|
4
|
-
module Blog
|
5
|
-
require_relative 'blog/cli'
|
6
|
-
require_relative 'blog/config'
|
7
|
-
require_relative 'blog/entry'
|
8
|
-
require_relative 'blog/git'
|
9
|
-
require_relative 'blog/jekyll'
|
10
|
-
require_relative 'blog/journal'
|
11
|
-
require_relative 'blog/log'
|
12
|
-
require_relative 'blog/slack'
|
13
|
-
require_relative 'blog/words'
|
14
|
-
end
|