lolcommits 0.9.2 → 0.9.3.pre1
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/.rubocop_todo.yml +6 -0
- data/CHANGELOG.md +243 -105
- data/CONTRIBUTING.md +2 -2
- data/README.md +10 -3
- data/bin/lolcommits +9 -16
- data/features/step_definitions/lolcommits_steps.rb +0 -1
- data/features/support/env.rb +0 -1
- data/features/support/path_helpers.rb +0 -1
- data/lib/core_ext/mini_magick/utilities.rb +0 -1
- data/lib/lolcommits.rb +16 -16
- data/lib/lolcommits/backends/git_info.rb +0 -1
- data/lib/lolcommits/backends/installation_git.rb +0 -1
- data/lib/lolcommits/backends/installation_mercurial.rb +0 -1
- data/lib/lolcommits/backends/mercurial_info.rb +0 -1
- data/lib/lolcommits/capturer.rb +0 -1
- data/lib/lolcommits/capturer/capture_cygwin.rb +0 -1
- data/lib/lolcommits/capturer/capture_fake.rb +0 -1
- data/lib/lolcommits/capturer/capture_linux.rb +0 -1
- data/lib/lolcommits/capturer/capture_linux_animated.rb +0 -1
- data/lib/lolcommits/capturer/capture_mac.rb +0 -1
- data/lib/lolcommits/capturer/capture_mac_animated.rb +0 -1
- data/lib/lolcommits/capturer/capture_windows.rb +0 -1
- data/lib/lolcommits/cli/fatals.rb +0 -8
- data/lib/lolcommits/cli/launcher.rb +0 -1
- data/lib/lolcommits/cli/process_runner.rb +0 -2
- data/lib/lolcommits/cli/timelapse_gif.rb +0 -1
- data/lib/lolcommits/configuration.rb +10 -7
- data/lib/lolcommits/gem_plugin.rb +46 -0
- data/lib/lolcommits/installation.rb +0 -1
- data/lib/lolcommits/platform.rb +0 -1
- data/lib/lolcommits/plugin/base.rb +110 -0
- data/lib/lolcommits/plugin/dot_com.rb +50 -0
- data/lib/lolcommits/plugin/lol_flowdock.rb +69 -0
- data/lib/lolcommits/plugin/lol_hipchat.rb +124 -0
- data/lib/lolcommits/plugin/lol_protonet.rb +68 -0
- data/lib/lolcommits/plugin/lol_slack.rb +68 -0
- data/lib/lolcommits/plugin/lol_tumblr.rb +129 -0
- data/lib/lolcommits/plugin/lol_twitter.rb +176 -0
- data/lib/lolcommits/plugin/lol_yammer.rb +84 -0
- data/lib/lolcommits/plugin/lolsrv.rb +58 -0
- data/lib/lolcommits/plugin/loltext.rb +190 -0
- data/lib/lolcommits/plugin/term_output.rb +55 -0
- data/lib/lolcommits/{plugins → plugin}/tranzlate.rb +14 -15
- data/lib/lolcommits/plugin/uploldz.rb +65 -0
- data/lib/lolcommits/plugin_manager.rb +48 -0
- data/lib/lolcommits/runner.rb +4 -5
- data/lib/lolcommits/test_helpers/fake_io.rb +20 -0
- data/lib/lolcommits/test_helpers/git_repo.rb +44 -0
- data/lib/lolcommits/vcs_info.rb +0 -1
- data/lib/lolcommits/version.rb +2 -2
- data/lolcommits.gemspec +2 -2
- data/test/lolcommits_test.rb +1 -2
- data/test/plugins_test.rb +7 -8
- metadata +22 -19
- data/lib/core_ext/class.rb +0 -8
- data/lib/lolcommits/plugin.rb +0 -123
- data/lib/lolcommits/plugins/dot_com.rb +0 -51
- data/lib/lolcommits/plugins/lol_flowdock.rb +0 -70
- data/lib/lolcommits/plugins/lol_hipchat.rb +0 -125
- data/lib/lolcommits/plugins/lol_protonet.rb +0 -69
- data/lib/lolcommits/plugins/lol_slack.rb +0 -69
- data/lib/lolcommits/plugins/lol_tumblr.rb +0 -129
- data/lib/lolcommits/plugins/lol_twitter.rb +0 -176
- data/lib/lolcommits/plugins/lol_yammer.rb +0 -85
- data/lib/lolcommits/plugins/lolsrv.rb +0 -58
- data/lib/lolcommits/plugins/loltext.rb +0 -184
- data/lib/lolcommits/plugins/term_output.rb +0 -54
- data/lib/lolcommits/plugins/uploldz.rb +0 -66
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'oauth'
|
3
|
+
require 'simple_oauth'
|
4
|
+
require 'rest_client'
|
5
|
+
require 'addressable/uri'
|
6
|
+
|
7
|
+
module Lolcommits
|
8
|
+
module Plugin
|
9
|
+
class LolTwitter < Base
|
10
|
+
TWITTER_API_ENDPOINT = 'https://api.twitter.com'.freeze
|
11
|
+
TWITTER_CONSUMER_KEY = 'qc096dJJCxIiqDNUqEsqQ'.freeze
|
12
|
+
TWITTER_CONSUMER_SECRET = 'rvjNdtwSr1H0TvBvjpk6c4bvrNydHmmbvv7gXZQI'.freeze
|
13
|
+
TWITTER_RESERVED_MEDIA_CHARS = 24
|
14
|
+
TWITTER_RETRIES = 2
|
15
|
+
TWITTER_PIN_REGEX = /^\d{4,}$/ # 4 or more digits
|
16
|
+
DEFAULT_SUFFIX = '#lolcommits'.freeze
|
17
|
+
|
18
|
+
def run_postcapture
|
19
|
+
tweet = build_tweet(runner.message)
|
20
|
+
|
21
|
+
attempts = 0
|
22
|
+
begin
|
23
|
+
attempts += 1
|
24
|
+
puts "Tweeting: #{tweet}"
|
25
|
+
debug "--> Tweeting! (attempt: #{attempts}, tweet length: #{tweet.length} chars)"
|
26
|
+
post_tweet(tweet, File.open(runner.main_image, 'r'))
|
27
|
+
rescue StandardError => e
|
28
|
+
debug "Tweet FAILED! #{e.class} - #{e.message}"
|
29
|
+
retry if attempts < TWITTER_RETRIES
|
30
|
+
puts "ERROR: Tweet FAILED! (after #{attempts} attempts) - #{e.message}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def post_url
|
35
|
+
# TODO: this endpoint is deprecated, use the new approach instead
|
36
|
+
# https://dev.twitter.com/rest/reference/post/statuses/update_with_mediath_media
|
37
|
+
@post_url ||= TWITTER_API_ENDPOINT + '/1.1/statuses/update_with_media.json'
|
38
|
+
end
|
39
|
+
|
40
|
+
def post_tweet(status, media)
|
41
|
+
RestClient.post(
|
42
|
+
post_url,
|
43
|
+
{
|
44
|
+
'media[]' => media,
|
45
|
+
'status' => status
|
46
|
+
}, Authorization: oauth_header
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
def build_tweet(commit_message)
|
51
|
+
prefix = config_with_default('prefix', '')
|
52
|
+
suffix = " #{config_with_default('suffix', DEFAULT_SUFFIX)}"
|
53
|
+
prefix = "#{prefix} " unless prefix.empty?
|
54
|
+
|
55
|
+
available_commit_msg_size = max_tweet_size - (prefix.length + suffix.length)
|
56
|
+
if commit_message.length > available_commit_msg_size
|
57
|
+
commit_message = "#{commit_message[0..(available_commit_msg_size - 3)]}..."
|
58
|
+
end
|
59
|
+
|
60
|
+
"#{prefix}#{commit_message}#{suffix}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def configure_options!
|
64
|
+
options = super
|
65
|
+
# ask user to configure tokens if enabling
|
66
|
+
if options['enabled']
|
67
|
+
auth_config = configure_auth!
|
68
|
+
return unless auth_config
|
69
|
+
options = options.merge(auth_config).merge(configure_prefix_suffix)
|
70
|
+
end
|
71
|
+
options
|
72
|
+
end
|
73
|
+
|
74
|
+
def configure_auth!
|
75
|
+
puts '---------------------------'
|
76
|
+
puts 'Need to grab twitter tokens'
|
77
|
+
puts '---------------------------'
|
78
|
+
|
79
|
+
request_token = oauth_consumer.get_request_token
|
80
|
+
rtoken = request_token.token
|
81
|
+
rsecret = request_token.secret
|
82
|
+
|
83
|
+
print "\n1) Please open this url in your browser to get a PIN for lolcommits:\n\n"
|
84
|
+
puts request_token.authorize_url
|
85
|
+
print "\n2) Enter PIN, then press enter: "
|
86
|
+
twitter_pin = gets.strip.downcase.to_s
|
87
|
+
|
88
|
+
unless twitter_pin =~ TWITTER_PIN_REGEX
|
89
|
+
puts "\nERROR: '#{twitter_pin}' is not a valid Twitter Auth PIN"
|
90
|
+
return
|
91
|
+
end
|
92
|
+
|
93
|
+
begin
|
94
|
+
debug "Requesting Twitter OAuth Token with PIN: #{twitter_pin}"
|
95
|
+
OAuth::RequestToken.new(oauth_consumer, rtoken, rsecret)
|
96
|
+
access_token = request_token.get_access_token(oauth_verifier: twitter_pin)
|
97
|
+
rescue OAuth::Unauthorized
|
98
|
+
puts "\nERROR: Twitter PIN Auth FAILED!"
|
99
|
+
return
|
100
|
+
end
|
101
|
+
|
102
|
+
return unless access_token.token && access_token.secret
|
103
|
+
puts ''
|
104
|
+
puts '------------------------------'
|
105
|
+
puts 'Thanks! Twitter Auth Succeeded'
|
106
|
+
puts '------------------------------'
|
107
|
+
{
|
108
|
+
'access_token' => access_token.token,
|
109
|
+
'secret' => access_token.secret
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
def configure_prefix_suffix
|
114
|
+
print "\n3) Prefix all tweets with something? e.g. @user (leave blank for no prefix): "
|
115
|
+
prefix = gets.strip
|
116
|
+
print "\n4) End all tweets with something? e.g. #hashtag (leave blank for default suffix #{DEFAULT_SUFFIX}): "
|
117
|
+
suffix = gets.strip
|
118
|
+
|
119
|
+
config = {}
|
120
|
+
config['prefix'] = prefix unless prefix.empty?
|
121
|
+
config['suffix'] = suffix unless suffix.empty?
|
122
|
+
config
|
123
|
+
end
|
124
|
+
|
125
|
+
def configured?
|
126
|
+
!configuration['enabled'].nil? &&
|
127
|
+
configuration['access_token'] &&
|
128
|
+
configuration['secret']
|
129
|
+
end
|
130
|
+
|
131
|
+
def oauth_header
|
132
|
+
uri = Addressable::URI.parse(post_url)
|
133
|
+
SimpleOAuth::Header.new(:post, uri, {}, oauth_credentials)
|
134
|
+
end
|
135
|
+
|
136
|
+
def oauth_credentials
|
137
|
+
{
|
138
|
+
consumer_key: TWITTER_CONSUMER_KEY,
|
139
|
+
consumer_secret: TWITTER_CONSUMER_SECRET,
|
140
|
+
token: configuration['access_token'],
|
141
|
+
token_secret: configuration['secret']
|
142
|
+
}
|
143
|
+
end
|
144
|
+
|
145
|
+
def oauth_consumer
|
146
|
+
@oauth_consumer ||= OAuth::Consumer.new(
|
147
|
+
TWITTER_CONSUMER_KEY,
|
148
|
+
TWITTER_CONSUMER_SECRET,
|
149
|
+
site: TWITTER_API_ENDPOINT,
|
150
|
+
request_endpoint: TWITTER_API_ENDPOINT,
|
151
|
+
sign_in: true
|
152
|
+
)
|
153
|
+
end
|
154
|
+
|
155
|
+
def config_with_default(key, default = nil)
|
156
|
+
if configuration[key]
|
157
|
+
configuration[key].strip.empty? ? default : configuration[key]
|
158
|
+
else
|
159
|
+
default
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def max_tweet_size
|
164
|
+
139 - TWITTER_RESERVED_MEDIA_CHARS
|
165
|
+
end
|
166
|
+
|
167
|
+
def self.name
|
168
|
+
'twitter'
|
169
|
+
end
|
170
|
+
|
171
|
+
def self.runner_order
|
172
|
+
:postcapture
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'yammer'
|
2
|
+
require 'rest_client'
|
3
|
+
|
4
|
+
# https://developer.yammer.com/oauth2-quickstart/
|
5
|
+
YAMMER_CLIENT_ID = 'bgORyeKtnjZJSMwp8oln9g'.freeze
|
6
|
+
YAMMER_CLIENT_SECRET = 'oer2WdGzh74a5QBbW3INUxblHK3yg9KvCZmiBa2r0'.freeze
|
7
|
+
YAMMER_ACCESS_TOKEN_URL = 'https://www.yammer.com/oauth2/access_token.json'.freeze
|
8
|
+
YAMMER_RETRY_COUNT = 2
|
9
|
+
|
10
|
+
module Lolcommits
|
11
|
+
module Plugin
|
12
|
+
class LolYammer < Base
|
13
|
+
def self.name
|
14
|
+
'yammer'
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.runner_order
|
18
|
+
:postcapture
|
19
|
+
end
|
20
|
+
|
21
|
+
def configured?
|
22
|
+
!configuration['access_token'].nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def configure_access_token
|
26
|
+
print "Open the URL below and copy the `code` param from query after redirected, enter it as `access_token`:\n"
|
27
|
+
print "https://www.yammer.com/dialog/oauth?client_id=#{YAMMER_CLIENT_ID}&response_type=code\n"
|
28
|
+
print 'Enter code param from the redirected URL, then press enter: '
|
29
|
+
code = gets.to_s.strip
|
30
|
+
|
31
|
+
url = YAMMER_ACCESS_TOKEN_URL
|
32
|
+
debug "access_token url: #{url}"
|
33
|
+
params = {
|
34
|
+
'client_id' => YAMMER_CLIENT_ID,
|
35
|
+
'client_secret' => YAMMER_CLIENT_SECRET,
|
36
|
+
'code' => code
|
37
|
+
}
|
38
|
+
debug "params : #{params.inspect}"
|
39
|
+
result = JSON.parse(RestClient.post(url, params))
|
40
|
+
debug "response : #{result.inspect}"
|
41
|
+
# no need for 'return', last line is always the return value
|
42
|
+
{ 'access_token' => result['access_token']['token'] }
|
43
|
+
end
|
44
|
+
|
45
|
+
def configure_options!
|
46
|
+
options = super
|
47
|
+
# ask user to configure tokens if enabling
|
48
|
+
if options['enabled']
|
49
|
+
auth_config = configure_access_token
|
50
|
+
return unless auth_config
|
51
|
+
options.merge!(auth_config)
|
52
|
+
end
|
53
|
+
options
|
54
|
+
end
|
55
|
+
|
56
|
+
def run_postcapture
|
57
|
+
commit_msg = runner.message
|
58
|
+
post = "#{commit_msg} #lolcommits"
|
59
|
+
puts "Yammer post: #{post}" unless runner.capture_stealth
|
60
|
+
|
61
|
+
Yammer.configure do |c|
|
62
|
+
c.client_id = YAMMER_CLIENT_ID
|
63
|
+
c.client_secret = YAMMER_CLIENT_SECRET
|
64
|
+
end
|
65
|
+
|
66
|
+
client = Yammer::Client.new(access_token: configuration['access_token'])
|
67
|
+
|
68
|
+
retries = YAMMER_RETRY_COUNT
|
69
|
+
begin
|
70
|
+
lolimage = File.new(runner.main_image)
|
71
|
+
response = client.create_message(post, attachment1: lolimage)
|
72
|
+
debug response.body.inspect
|
73
|
+
puts "\t--> Status posted!" if response
|
74
|
+
rescue => e
|
75
|
+
retries -= 1
|
76
|
+
retry if retries > 0
|
77
|
+
puts "Status not posted - #{e.message}"
|
78
|
+
puts 'Try running config again:'
|
79
|
+
puts "\tlolcommits --config --plugin yammer"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'pp'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Lolcommits
|
6
|
+
module Plugin
|
7
|
+
class Lolsrv < Base
|
8
|
+
def initialize(runner)
|
9
|
+
super
|
10
|
+
options << 'server'
|
11
|
+
end
|
12
|
+
|
13
|
+
def run_postcapture
|
14
|
+
fork { sync }
|
15
|
+
end
|
16
|
+
|
17
|
+
def configured?
|
18
|
+
!configuration['enabled'].nil? && configuration['server']
|
19
|
+
end
|
20
|
+
|
21
|
+
def sync
|
22
|
+
existing = existing_lols
|
23
|
+
return unless existing.nil?
|
24
|
+
Dir[runner.config.loldir + '/*.{jpg,gif}'].each do |item|
|
25
|
+
sha = File.basename(item, '.*')
|
26
|
+
upload(item, sha) unless existing.include?(sha) || sha == 'tmp_snapshot'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def existing_lols
|
31
|
+
lols = JSON.parse(RestClient.get(configuration['server'] + '/lols'))
|
32
|
+
lols.map { |lol| lol['sha'] }
|
33
|
+
rescue => e
|
34
|
+
log_error(e, "ERROR: existing lols could not be retrieved #{e.class} - #{e.message}")
|
35
|
+
return nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def upload(file, sha)
|
39
|
+
RestClient.post(configuration['server'] + '/uplol',
|
40
|
+
lol: File.new(file),
|
41
|
+
url: runner.vcs_info.url + sha,
|
42
|
+
repo: runner.vcs_info.repo,
|
43
|
+
date: File.ctime(file),
|
44
|
+
sha: sha)
|
45
|
+
rescue => e
|
46
|
+
log_error(e, "ERROR: Upload of lol #{sha} FAILED #{e.class} - #{e.message}")
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.name
|
50
|
+
'lolsrv'
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.runner_order
|
54
|
+
:postcapture
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module Lolcommits
|
2
|
+
module Plugin
|
3
|
+
class Loltext < Base
|
4
|
+
DEFAULT_FONT_PATH = File.join(Configuration::LOLCOMMITS_ROOT, 'vendor', 'fonts', 'Impact.ttf')
|
5
|
+
|
6
|
+
def self.name
|
7
|
+
'loltext'
|
8
|
+
end
|
9
|
+
|
10
|
+
# enabled by default (if no configuration exists)
|
11
|
+
def enabled?
|
12
|
+
!configured? || super
|
13
|
+
end
|
14
|
+
|
15
|
+
# valid by default (if no config exists)
|
16
|
+
def valid_configuration?
|
17
|
+
!configured? || super
|
18
|
+
end
|
19
|
+
|
20
|
+
def run_postcapture
|
21
|
+
debug 'Annotating image via MiniMagick'
|
22
|
+
image = MiniMagick::Image.open(runner.main_image)
|
23
|
+
if config_option(:overlay, :enabled)
|
24
|
+
image.combine_options do |c|
|
25
|
+
c.fill config_option(:overlay, :overlay_colors).sample
|
26
|
+
c.colorize config_option(:overlay, :overlay_percent)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
annotate(image, :message, clean_msg(runner.message))
|
31
|
+
annotate(image, :sha, runner.sha)
|
32
|
+
debug "Writing changed file to #{runner.main_image}"
|
33
|
+
image.write runner.main_image
|
34
|
+
end
|
35
|
+
|
36
|
+
def annotate(image, type, string)
|
37
|
+
debug("annotating #{type} to image with #{string}")
|
38
|
+
|
39
|
+
transformed_position = position_transform(config_option(type, :position))
|
40
|
+
annotate_location = '0'
|
41
|
+
if transformed_position == 'South'
|
42
|
+
annotate_location = '+0+20' # Move South gravity off the edge of the image.
|
43
|
+
end
|
44
|
+
|
45
|
+
string.upcase! if config_option(type, :uppercase)
|
46
|
+
|
47
|
+
image.combine_options do |c|
|
48
|
+
c.strokewidth runner.capture_animated? ? '1' : '2'
|
49
|
+
c.interline_spacing(-(config_option(type, :size) / 5))
|
50
|
+
c.stroke config_option(type, :stroke_color)
|
51
|
+
c.fill config_option(type, :color)
|
52
|
+
c.gravity transformed_position
|
53
|
+
c.pointsize runner.capture_animated? ? (config_option(type, :size) / 2) : config_option(type, :size)
|
54
|
+
c.font config_option(type, :font)
|
55
|
+
c.annotate annotate_location, string
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def configure_options!
|
60
|
+
options = super
|
61
|
+
# ask user to configure text options when enabling
|
62
|
+
if options['enabled']
|
63
|
+
puts '---------------------------------------------------------------'
|
64
|
+
puts ' LolText options '
|
65
|
+
puts ''
|
66
|
+
puts ' * any blank options will use the (default)'
|
67
|
+
puts ' * always use the full absolute path to fonts'
|
68
|
+
puts ' * valid text positions are NE, NW, SE, SW, S, C (centered)'
|
69
|
+
puts ' * colors can be hex #FC0 value or a string \'white\''
|
70
|
+
puts ' - use `none` for no stroke color'
|
71
|
+
puts ' * overlay fills your image with a random color'
|
72
|
+
puts ' - set one or more overlay_colors with a comma seperator'
|
73
|
+
puts ' - overlay_percent (0-100) sets the fill colorize strength'
|
74
|
+
puts '---------------------------------------------------------------'
|
75
|
+
|
76
|
+
options[:message] = configure_sub_options(:message)
|
77
|
+
options[:sha] = configure_sub_options(:sha)
|
78
|
+
options[:overlay] = configure_sub_options(:overlay)
|
79
|
+
end
|
80
|
+
options
|
81
|
+
end
|
82
|
+
|
83
|
+
# TODO: consider this type of configuration prompting in the base Plugin
|
84
|
+
# class, working with hash of defaults
|
85
|
+
def configure_sub_options(type)
|
86
|
+
print "#{type}:\n"
|
87
|
+
defaults = config_defaults[type]
|
88
|
+
|
89
|
+
# sort option keys since different `Hash#keys` varys across Ruby versions
|
90
|
+
defaults.keys.sort_by(&:to_s).reduce({}) do |acc, opt|
|
91
|
+
# if we have an enabled key set to false, abort asking for any more options
|
92
|
+
if acc.key?(:enabled) && acc[:enabled] != true
|
93
|
+
acc
|
94
|
+
else
|
95
|
+
print " #{opt.to_s.tr('_', ' ')} (#{defaults[opt]}): "
|
96
|
+
val = parse_user_input(gets.chomp.strip)
|
97
|
+
# handle array options (comma seperated string)
|
98
|
+
if defaults[opt].is_a?(Array) && !val.nil?
|
99
|
+
val = val.split(',').map(&:strip).delete_if(&:empty?)
|
100
|
+
end
|
101
|
+
acc.merge(opt => val)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def config_defaults
|
107
|
+
{
|
108
|
+
message: {
|
109
|
+
color: 'white',
|
110
|
+
font: DEFAULT_FONT_PATH,
|
111
|
+
position: 'SW',
|
112
|
+
size: 48,
|
113
|
+
stroke_color: 'black',
|
114
|
+
uppercase: false
|
115
|
+
},
|
116
|
+
sha: {
|
117
|
+
color: 'white',
|
118
|
+
font: DEFAULT_FONT_PATH,
|
119
|
+
position: 'NE',
|
120
|
+
size: 32,
|
121
|
+
stroke_color: 'black',
|
122
|
+
uppercase: false
|
123
|
+
},
|
124
|
+
overlay: {
|
125
|
+
enabled: false,
|
126
|
+
overlay_colors: [
|
127
|
+
'#2e4970', '#674685', '#ca242f', '#1e7882', '#2884ae', '#4ba000',
|
128
|
+
'#187296', '#7e231f', '#017d9f', '#e52d7b', '#0f5eaa', '#e40087',
|
129
|
+
'#5566ac', '#ed8833', '#f8991c', '#408c93', '#ba9109'
|
130
|
+
],
|
131
|
+
overlay_percent: 50
|
132
|
+
}
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
def config_option(type, option)
|
137
|
+
default_option = config_defaults[type][option]
|
138
|
+
if configuration[type]
|
139
|
+
configuration[type][option] || default_option
|
140
|
+
else
|
141
|
+
default_option
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
# explode psuedo-names for text position
|
148
|
+
def position_transform(position)
|
149
|
+
case position
|
150
|
+
when 'NE'
|
151
|
+
'NorthEast'
|
152
|
+
when 'NW'
|
153
|
+
'NorthWest'
|
154
|
+
when 'SE'
|
155
|
+
'SouthEast'
|
156
|
+
when 'SW'
|
157
|
+
'SouthWest'
|
158
|
+
when 'C'
|
159
|
+
'Center'
|
160
|
+
when 'S'
|
161
|
+
'South'
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# do whatever is required to commit message to get it clean and ready for imagemagick
|
166
|
+
def clean_msg(text)
|
167
|
+
wrapped_text = word_wrap text
|
168
|
+
escape_quotes wrapped_text
|
169
|
+
escape_ats wrapped_text
|
170
|
+
end
|
171
|
+
|
172
|
+
# conversion for quotation marks to avoid shell interpretation
|
173
|
+
# does not seem to be a safe way to escape cross-platform?
|
174
|
+
def escape_quotes(text)
|
175
|
+
text.gsub(/"/, "''")
|
176
|
+
end
|
177
|
+
|
178
|
+
def escape_ats(text)
|
179
|
+
text.gsub(/@/, '\@')
|
180
|
+
end
|
181
|
+
|
182
|
+
# convenience method for word wrapping
|
183
|
+
# based on https://github.com/cmdrkeene/memegen/blob/master/lib/meme_generator.rb
|
184
|
+
def word_wrap(text, col = 27)
|
185
|
+
wrapped = text.gsub(/(.{1,#{col + 4}})(\s+|\Z)/, "\\1\n")
|
186
|
+
wrapped.chomp!
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|