snapgit 0.6.4

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.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.editorconfig +17 -0
  4. data/.gitattributes +1 -0
  5. data/.gitignore +14 -0
  6. data/.rubocop.yml +10 -0
  7. data/.rubocop_todo.yml +57 -0
  8. data/.travis.yml +42 -0
  9. data/Gemfile +4 -0
  10. data/LICENSE +165 -0
  11. data/README.md +28 -0
  12. data/Rakefile +79 -0
  13. data/bin/lolcommits +335 -0
  14. data/bin/snapgit +14 -0
  15. data/config/cucumber.yml +2 -0
  16. data/features/bugs.feature +63 -0
  17. data/features/lolcommits.feature +286 -0
  18. data/features/plugins.feature +20 -0
  19. data/features/step_definitions/lolcommits_steps.rb +142 -0
  20. data/features/support/env.rb +61 -0
  21. data/features/support/path_helpers.rb +39 -0
  22. data/lib/core_ext/class.rb +8 -0
  23. data/lib/core_ext/mini_magick/utilities.rb +15 -0
  24. data/lib/lolcommits.rb +34 -0
  25. data/lib/lolcommits/capturer.rb +24 -0
  26. data/lib/lolcommits/capturer/capture_cygwin.rb +22 -0
  27. data/lib/lolcommits/capturer/capture_fake.rb +9 -0
  28. data/lib/lolcommits/capturer/capture_linux.rb +45 -0
  29. data/lib/lolcommits/capturer/capture_linux_animated.rb +72 -0
  30. data/lib/lolcommits/capturer/capture_mac.rb +23 -0
  31. data/lib/lolcommits/capturer/capture_mac_animated.rb +73 -0
  32. data/lib/lolcommits/capturer/capture_windows.rb +22 -0
  33. data/lib/lolcommits/cli/fatals.rb +77 -0
  34. data/lib/lolcommits/cli/launcher.rb +29 -0
  35. data/lib/lolcommits/cli/process_runner.rb +48 -0
  36. data/lib/lolcommits/cli/timelapse_gif.rb +45 -0
  37. data/lib/lolcommits/configuration.rb +138 -0
  38. data/lib/lolcommits/git_info.rb +82 -0
  39. data/lib/lolcommits/init.rb +121 -0
  40. data/lib/lolcommits/installation.rb +128 -0
  41. data/lib/lolcommits/platform.rb +127 -0
  42. data/lib/lolcommits/plugin.rb +130 -0
  43. data/lib/lolcommits/plugins/dot_com.rb +50 -0
  44. data/lib/lolcommits/plugins/lol_protonet.rb +68 -0
  45. data/lib/lolcommits/plugins/lol_slack.rb +68 -0
  46. data/lib/lolcommits/plugins/lol_tumblr.rb +127 -0
  47. data/lib/lolcommits/plugins/lol_twitter.rb +155 -0
  48. data/lib/lolcommits/plugins/lol_yammer.rb +85 -0
  49. data/lib/lolcommits/plugins/lolsrv.rb +59 -0
  50. data/lib/lolcommits/plugins/loltext.rb +147 -0
  51. data/lib/lolcommits/plugins/snapgit.rb +167 -0
  52. data/lib/lolcommits/plugins/tranzlate.rb +115 -0
  53. data/lib/lolcommits/plugins/uploldz.rb +65 -0
  54. data/lib/lolcommits/runner.rb +134 -0
  55. data/lib/lolcommits/version.rb +5 -0
  56. data/snapgit.gemspec +63 -0
  57. data/test/images/test_image.jpg +0 -0
  58. data/test/lolcommits_test.rb +35 -0
  59. data/test/plugins_test.rb +52 -0
  60. data/vendor/ext/CommandCam/COPYING +674 -0
  61. data/vendor/ext/CommandCam/CommandCam.exe +0 -0
  62. data/vendor/ext/CommandCam/LICENSE +16 -0
  63. data/vendor/ext/imagesnap/ReadMeOrDont.rtf +117 -0
  64. data/vendor/ext/imagesnap/imagesnap +0 -0
  65. data/vendor/ext/videosnap/videosnap +0 -0
  66. data/vendor/fonts/Lato.ttf +0 -0
  67. data/vendor/logos/Snapgit.png +0 -0
  68. metadata +509 -0
@@ -0,0 +1,147 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Lolcommits
3
+ class Loltext < Plugin
4
+ DEFAULT_FONT_PATH = File.join(Configuration::LOLCOMMITS_ROOT, 'vendor', 'fonts', 'Lato.ttf')
5
+ DEFAULT_LOGO_PATH = File.join(Configuration::LOLCOMMITS_ROOT, 'vendor', 'logos', 'Snapgit.png')
6
+
7
+ def self.name
8
+ 'loltext'
9
+ end
10
+
11
+ # enabled by default (if no configuration exists)
12
+ def enabled?
13
+ !configured? || super
14
+ end
15
+
16
+ def run_postcapture
17
+ debug 'Annotating image via MiniMagick'
18
+ image = MiniMagick::Image.open(runner.main_image)
19
+ annotate(image, :message, clean_msg(runner.message))
20
+ image = add_logo(image, DEFAULT_LOGO_PATH)
21
+ debug "Writing changed file to #{runner.main_image}"
22
+ image.write runner.main_image
23
+ end
24
+
25
+ def annotate(image, type, string)
26
+ debug("annotating #{type} text to image")
27
+
28
+ image.combine_options do |c|
29
+ c.strokewidth '2'
30
+ c.interline_spacing '-9'
31
+ c.stroke config_option(type, :stroke_color)
32
+ c.fill config_option(type, :color)
33
+ c.gravity position_transform(config_option(type, :position))
34
+ c.pointsize runner.animate? ? 24 : config_option(type, :size)
35
+ c.font config_option(type, :font)
36
+ c.annotate '0', string
37
+ end
38
+ end
39
+
40
+ def add_logo(image, logo_path)
41
+ @logo = MiniMagick::Image.open(logo_path)
42
+
43
+ padding = 5
44
+ left = image[:width] - @logo[:width] - padding
45
+ image.composite(@logo, 'png') do |c|
46
+ c.compose 'Over'
47
+ c.geometry "+#{left}+0"
48
+ end
49
+ end
50
+
51
+ def configure_options!
52
+ options = super
53
+ # ask user to configure text options when enabling
54
+ if options['enabled']
55
+ puts '------------------------------------------------------'
56
+ puts ' Text options '
57
+ puts
58
+ puts ' * blank options use the (default)'
59
+ puts ' * use full absolute path to fonts'
60
+ puts ' * valid positions are NE, NW, SE, SW, C (centered)'
61
+ puts ' * colors can be hex #FC0 value or a string \'white\''
62
+ puts '------------------------------------------------------'
63
+
64
+ options[:message] = configure_sub_options(:message)
65
+ options[:sha] = configure_sub_options(:sha)
66
+ end
67
+ options
68
+ end
69
+
70
+ # TODO: consider this type of configuration prompting in the base Plugin
71
+ # class, working with hash of defaults
72
+ def configure_sub_options(type)
73
+ print "#{type} text:\n"
74
+ defaults = config_defaults[type]
75
+
76
+ # sort option keys since different `Hash#keys` varys across Ruby versions
77
+ defaults.keys.sort_by(&:to_s).reduce({}) do |acc, opt|
78
+ print " #{opt.to_s.tr('_', ' ')} (#{defaults[opt]}): "
79
+ val = parse_user_input(STDIN.gets.strip)
80
+ acc.merge(opt => val)
81
+ end
82
+ end
83
+
84
+ def config_defaults
85
+ {
86
+ :message => {
87
+ :font => DEFAULT_FONT_PATH,
88
+ :size => 48,
89
+ :position => 'SW',
90
+ :color => 'white',
91
+ :stroke_color => 'black'
92
+ }
93
+ }
94
+ end
95
+
96
+ def config_option(type, option)
97
+ default_option = config_defaults[type][option]
98
+ if configuration[type]
99
+ configuration[type][option] || default_option
100
+ else
101
+ default_option
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ # explode psuedo-names for text position
108
+ def position_transform(position)
109
+ case position
110
+ when 'NE'
111
+ 'NorthEast'
112
+ when 'NW'
113
+ 'NorthWest'
114
+ when 'SE'
115
+ 'SouthEast'
116
+ when 'SW'
117
+ 'SouthWest'
118
+ when 'C'
119
+ 'Center'
120
+ end
121
+ end
122
+
123
+ # do whatever is required to commit message to get it clean and ready for imagemagick
124
+ def clean_msg(text)
125
+ wrapped_text = word_wrap text
126
+ escape_quotes wrapped_text
127
+ escape_ats wrapped_text
128
+ end
129
+
130
+ # conversion for quotation marks to avoid shell interpretation
131
+ # does not seem to be a safe way to escape cross-platform?
132
+ def escape_quotes(text)
133
+ text.gsub(/"/, "''")
134
+ end
135
+
136
+ def escape_ats(text)
137
+ text.gsub(/@/, '\@')
138
+ end
139
+
140
+ # convenience method for word wrapping
141
+ # based on https://github.com/cmdrkeene/memegen/blob/master/lib/meme_generator.rb
142
+ def word_wrap(text, col = 20)
143
+ wrapped = text.gsub(/(.{1,#{col + 4}})(\s+|\Z)/, "\\1\n")
144
+ wrapped.chomp!
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,167 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'yaml'
3
+ require 'oauth'
4
+
5
+ module Lolcommits
6
+ class Snapgit < Plugin
7
+ TWITTER_API_ENDPOINT = 'https://api.twitter.com'.freeze
8
+ TWITTER_CONSUMER_KEY = 'qc096dJJCxIiqDNUqEsqQ'.freeze
9
+ TWITTER_CONSUMER_SECRET = 'rvjNdtwSr1H0TvBvjpk6c4bvrNydHmmbvv7gXZQI'.freeze
10
+ TWITTER_RETRIES = 2
11
+ TWITTER_PIN_REGEX = /^\d{4,}$/ # 4 or more digits
12
+
13
+ def run_postcapture
14
+ return unless valid_configuration?
15
+
16
+ upload_twitter
17
+ upload_gravatar
18
+ end
19
+
20
+ def upload_twitter
21
+ require 'twitter'
22
+
23
+ attempts = 0
24
+ begin
25
+ attempts += 1
26
+ puts 'Updating profile picture...'
27
+ image = File.open(runner.main_image)
28
+ client.update_profile_image(image)
29
+ @twitter_user = client.user.screen_name # to be used with gravatar
30
+ puts "Successfully uploaded new profile picture 🌴"
31
+ rescue Twitter::Error::ServerError,
32
+ Twitter::Error::ClientError => e
33
+ debug "Upading avatar failed! #{e.class} - #{e.message}"
34
+ retry if attempts < TWITTER_RETRIES
35
+ puts "ERROR: Updating avatar FAILED! (after #{attempts} attempts) - #{e.message}"
36
+ end
37
+ end
38
+
39
+ def upload_gravatar
40
+ return if configuration['email'].to_s.empty?
41
+ return if configuration['password'].to_s.empty?
42
+
43
+ puts 'Uploading to Gravatar...'
44
+
45
+ # First we need to follow the redirects
46
+ url = "https://twitter.com/#{@twitter_user}/profile_image?size=original"
47
+ url = `curl -I #{url}`.match(/location: (.*)/)[1].strip
48
+
49
+ require 'gravatar-ultimate'
50
+
51
+ api = Gravatar.new(configuration['email'], :password => configuration['password'])
52
+ raise 'Could not login to Gravatar' unless api.exists? && api.addresses.count > 0
53
+
54
+ handle = api.save_url!(0, url) # upload the image (0 being the rating)
55
+
56
+ api.addresses.each do |email, _value|
57
+ api.use_user_image!(handle, email) # set it for all available email addresses
58
+ puts "Successfully updated Gravatar image for '#{email}' 🔑"
59
+ end
60
+ end
61
+
62
+ def configure_options!
63
+ options = super
64
+ # ask user to configure tokens if enabling
65
+ if options['enabled']
66
+ auth_config = configure_auth!
67
+ return unless auth_config
68
+ options = options.merge(auth_config)
69
+ end
70
+ options
71
+ end
72
+
73
+ # rubocop:disable Metrics/MethodLength
74
+ # rubocop:disable Metrics/AbcSize
75
+ def configure_auth!
76
+ puts '---------------------------'
77
+ puts 'Need to grab twitter tokens'
78
+ puts '---------------------------'
79
+
80
+ request_token = oauth_consumer.get_request_token
81
+ rtoken = request_token.token
82
+ rsecret = request_token.secret
83
+
84
+ print "\n1) Please open this url in your browser to get a PIN for lolcommits:\n\n"
85
+ puts request_token.authorize_url
86
+ print "\n2) Enter PIN, then press enter: "
87
+ twitter_pin = STDIN.gets.strip.downcase.to_s
88
+
89
+ unless twitter_pin =~ TWITTER_PIN_REGEX
90
+ puts "\nERROR: '#{twitter_pin}' is not a valid Twitter Auth PIN"
91
+ return
92
+ end
93
+
94
+ begin
95
+ debug "Requesting Twitter OAuth Token with PIN: #{twitter_pin}"
96
+ OAuth::RequestToken.new(oauth_consumer, rtoken, rsecret)
97
+ access_token = request_token.get_access_token(:oauth_verifier => twitter_pin)
98
+ rescue OAuth::Unauthorized
99
+ puts "\nERROR: Twitter PIN Auth FAILED!"
100
+ return
101
+ end
102
+
103
+ return unless access_token.token && access_token.secret
104
+
105
+ print "\n3) Your Gravatar email address: "
106
+ gravatar_email = STDIN.gets.strip.downcase.to_s
107
+ print "\n4) Your Gravatar password: "
108
+ gravatar_password = STDIN.gets.strip.downcase.to_s
109
+
110
+ puts ''
111
+ puts '------------------------------'
112
+ puts 'Successfully set up snapgit'
113
+ puts '------------------------------'
114
+ {
115
+ 'access_token' => access_token.token,
116
+ 'secret' => access_token.secret,
117
+ 'email' => gravatar_email,
118
+ 'password' => gravatar_password
119
+ }
120
+ end
121
+ # rubocop:enable Metrics/MethodLength
122
+ # rubocop:enable Metrics/AbcSize
123
+
124
+ def configured?
125
+ !configuration['enabled'].nil? &&
126
+ configuration['access_token'] &&
127
+ configuration['secret'] &&
128
+ configuration['email'] &&
129
+ configuration['password']
130
+ end
131
+
132
+ def client
133
+ @client ||= Twitter::REST::Client.new do |config|
134
+ config.consumer_key = TWITTER_CONSUMER_KEY
135
+ config.consumer_secret = TWITTER_CONSUMER_SECRET
136
+ config.access_token = configuration['access_token']
137
+ config.access_token_secret = configuration['secret']
138
+ end
139
+ end
140
+
141
+ def oauth_consumer
142
+ @oauth_consumer ||= OAuth::Consumer.new(
143
+ TWITTER_CONSUMER_KEY,
144
+ TWITTER_CONSUMER_SECRET,
145
+ :site => TWITTER_API_ENDPOINT,
146
+ :request_endpoint => TWITTER_API_ENDPOINT,
147
+ :sign_in => true
148
+ )
149
+ end
150
+
151
+ def config_with_default(key, default = nil)
152
+ if configuration[key]
153
+ configuration[key].strip.empty? ? default : configuration[key]
154
+ else
155
+ default
156
+ end
157
+ end
158
+
159
+ def self.name
160
+ 'snapgit'
161
+ end
162
+
163
+ def self.runner_order
164
+ :postcapture
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,115 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # Adapted and expanded from https://github.com/rwtnorton/moar-lolspeak
3
+ # which was largely taken from an old Perl script and is sadly is not
4
+ # available via rubygems
5
+
6
+ module Lolspeak
7
+ LOL_DICTIONARY = {
8
+ /what/ => %w(wut whut),
9
+ /you\b/ => %w(yu yous yoo u yooz),
10
+ /cture/ => %w(kshur),
11
+ /ss\b/ => %w(s zz z),
12
+ /the\b/ => %w(teh),
13
+ /more/ => %w(moar),
14
+ /my/ => %w(mah mai),
15
+ /are/ => %w(is ar),
16
+ /eese/ => %w(eez),
17
+ /ph/ => %w(f),
18
+ /as\b/ => %w(az),
19
+ /seriously/ => %w(srsly),
20
+ /sion/ => %w(shun),
21
+ /just/ => %w(jus),
22
+ /ose\b/ => %w(oze),
23
+ /eady/ => %w(eddy),
24
+ /ome?\b/ => %w(um),
25
+ /of\b/ => %w(of ov of),
26
+ /uestion/ => %w(wesjun),
27
+ /want/ => %w(wants),
28
+ /ead\b/ => %w(edd),
29
+ /ck/ => %w(kk kkk),
30
+ /cat|kitten|kitty/ => %w(kitteh kittehz cat fuzzeh fuzzyrumpus foozles fuzzbut fluffernutter beast mew),
31
+ /eak/ => %w(ekk),
32
+ /age/ => %w(uj),
33
+ /like/ => %w(likez liek licks),
34
+ /love/ => %w(lovez lub lubs luv lurve lurves),
35
+ /\bis\b/ => ['ar teh', 'ar'],
36
+ /nd\b/ => %w(n n'),
37
+ /who/ => %w(hoo),
38
+ /'/ => [''],
39
+ /ese\b/ => %w(eez),
40
+ /outh/ => %w(owf),
41
+ /scio/ => %w(shu),
42
+ /esque/ => %w(esk),
43
+ /ture/ => %w(chur),
44
+ /\btoo?\b/ => %w(to t 2 to t),
45
+ /tious/ => %w(shus),
46
+ /sure\b/ => %w(shur),
47
+ /tty\b/ => %w(tteh),
48
+ /were/ => %w(was),
49
+ /ok\b|okay/ => %w(kthxbye!),
50
+ /\ba\b/ => %w(uh),
51
+ /ym/ => %w(im),
52
+ /fish/ => %w(ghoti),
53
+ /thy\b/ => %w(fee),
54
+ /\wly\w/ => %w(li),
55
+ /que\w/ => %w(kwe),
56
+ /\both/ => %w(udd),
57
+ /though\b/ => %w(tho),
58
+ /(t|r|en)ough/ => %w(\1uff),
59
+ /ought/ => %w(awt),
60
+ /ease/ => %w(eez),
61
+ /ing\b/ => %w(in ins ng ing in'),
62
+ /have/ => ['haz', 'hav', 'haz a'],
63
+ /has/ => %w(haz gots),
64
+ /your/ => %w(yur ur yore yoar),
65
+ /ove\b/ => %w(oov ove uuv uv oove),
66
+ /for/ => %w(for 4 fr fur for foar),
67
+ /thank/ => %w(fank tank thx thnx),
68
+ /good/ => %w(gud goed guud gude gewd goot gut),
69
+ /really/ => %w(rly rily rilly rilleh),
70
+ /world/ => %w(wurrld whirld wurld wrld),
71
+ /i'?m\b/ => ['im', 'i yam', 'i iz'],
72
+ /(?!e)ight/ => %w(ite),
73
+ /(?!ues)tion/ => %w(shun),
74
+ /you'?re/ => %w(yore yr),
75
+ /er\b|are|ere/ => %w(r),
76
+ /y\b|ey\b/ => %w(eh),
77
+ /ea/ => %w(ee),
78
+ /can\si\s(?:ple(?:a|e)(?:s|z)e?)?\s?have\sa/ => ['i can haz'],
79
+ /(?:hello|\bhi\b|\bhey\b|howdy|\byo\b),?/ => ['oh hai,'],
80
+ /(?:god\b|allah|buddah?|diety|lord)/ => ['ceiling cat']
81
+ }.freeze
82
+
83
+ def tranzlate(str)
84
+ lolstr = str.dup
85
+ LOL_DICTIONARY.each do |english, lolspeak|
86
+ # ghetto ruby1.8/1.9 agnostic version of choice vs sample
87
+ lolstr.gsub!(english, lolspeak.shuffle.first)
88
+ end
89
+
90
+ lolstr << '! kthxbye!' if rand(10) == 2
91
+ lolstr.gsub!(/(\?|!|,|\.)+/, '!')
92
+
93
+ lolstr.upcase
94
+ end
95
+ end
96
+
97
+ module Lolcommits
98
+ class Tranzlate < Plugin
99
+ extend Lolspeak
100
+
101
+ def run_precapture
102
+ debug "Commit message before: #{runner.message}"
103
+ runner.message = self.class.tranzlate(runner.message)
104
+ debug "Commit message after: #{runner.message}"
105
+ end
106
+
107
+ def self.name
108
+ 'tranzlate'
109
+ end
110
+
111
+ def self.runner_order
112
+ :precapture
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,65 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'rest_client'
3
+ require 'base64'
4
+
5
+ module Lolcommits
6
+ class Uploldz < Plugin
7
+ attr_accessor :endpoint
8
+
9
+ def initialize(runner)
10
+ super
11
+ options.concat(
12
+ %w(
13
+ endpoint
14
+ optional_key
15
+ optional_http_auth_username
16
+ optional_http_auth_password
17
+ )
18
+ )
19
+ end
20
+
21
+ def run_postcapture
22
+ return unless valid_configuration?
23
+
24
+ if runner.git_info.repo.empty?
25
+ puts 'Repo is empty, skipping upload'
26
+ else
27
+ debug "Posting capture to #{configuration['endpoint']}"
28
+ RestClient.post(configuration['endpoint'],
29
+ {
30
+ :file => File.new(runner.main_image),
31
+ :message => runner.message,
32
+ :repo => runner.git_info.repo,
33
+ :author_name => runner.git_info.author_name,
34
+ :author_email => runner.git_info.author_email,
35
+ :sha => runner.sha,
36
+ :key => configuration['optional_key']
37
+ },
38
+ :Authorization => authorization_header
39
+ )
40
+ end
41
+ rescue => e
42
+ log_error(e, "ERROR: RestClient POST FAILED #{e.class} - #{e.message}")
43
+ end
44
+
45
+ def configured?
46
+ !configuration['enabled'].nil? && configuration['endpoint']
47
+ end
48
+
49
+ def authorization_header
50
+ user = configuration['optional_http_auth_username']
51
+ password = configuration['optional_http_auth_password']
52
+ return unless user || password
53
+
54
+ 'Basic ' + Base64.encode64("#{user}:#{password}").chomp
55
+ end
56
+
57
+ def self.name
58
+ 'uploldz'
59
+ end
60
+
61
+ def self.runner_order
62
+ :postcapture
63
+ end
64
+ end
65
+ end