lolcommits 0.5.7 → 0.5.8.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +8 -8
  2. data/.rubocop.yml +1 -0
  3. data/.rubocop_todo.yml +16 -161
  4. data/.travis.yml +11 -4
  5. data/Gemfile +2 -2
  6. data/Rakefile +24 -32
  7. data/bin/lolcommits +62 -125
  8. data/features/bugs.feature +24 -9
  9. data/features/lolcommits.feature +136 -145
  10. data/features/plugins.feature +16 -33
  11. data/features/step_definitions/lolcommits_steps.rb +91 -71
  12. data/features/support/env.rb +18 -48
  13. data/features/support/path_helpers.rb +9 -8
  14. data/lib/lolcommits.rb +3 -10
  15. data/lib/lolcommits/capturer.rb +10 -2
  16. data/lib/lolcommits/capturer/capture_linux.rb +1 -1
  17. data/lib/lolcommits/capturer/capture_linux_animated.rb +12 -13
  18. data/lib/lolcommits/capturer/capture_mac_animated.rb +12 -13
  19. data/lib/lolcommits/cli/fatals.rb +77 -0
  20. data/lib/lolcommits/cli/launcher.rb +29 -0
  21. data/lib/lolcommits/cli/process_runner.rb +48 -0
  22. data/lib/lolcommits/cli/timelapse_gif.rb +45 -0
  23. data/lib/lolcommits/configuration.rb +30 -133
  24. data/lib/lolcommits/git_info.rb +58 -28
  25. data/lib/lolcommits/installation.rb +11 -21
  26. data/lib/lolcommits/platform.rb +134 -0
  27. data/lib/lolcommits/plugin.rb +2 -2
  28. data/lib/lolcommits/plugins/dot_com.rb +15 -15
  29. data/lib/lolcommits/plugins/lol_protonet.rb +68 -0
  30. data/lib/lolcommits/plugins/lol_twitter.rb +12 -15
  31. data/lib/lolcommits/plugins/lol_yammer.rb +4 -6
  32. data/lib/lolcommits/plugins/lolsrv.rb +8 -11
  33. data/lib/lolcommits/plugins/loltext.rb +7 -7
  34. data/lib/lolcommits/plugins/tranzlate.rb +70 -70
  35. data/lib/lolcommits/plugins/uploldz.rb +8 -8
  36. data/lib/lolcommits/runner.rb +36 -35
  37. data/lib/lolcommits/version.rb +1 -1
  38. data/lolcommits.gemspec +11 -10
  39. data/test/lolcommits_test.rb +35 -0
  40. data/test/plugins_test.rb +52 -0
  41. metadata +41 -20
  42. data/test/test_lolcommits.rb +0 -78
@@ -12,7 +12,7 @@ module Lolcommits
12
12
  tmpdir = Dir.mktmpdir
13
13
 
14
14
  # Default delay is 1s
15
- delay = if capture_delay != 0 then capture_delay else 1 end
15
+ delay = capture_delay != 0 ? capture_delay : 1
16
16
 
17
17
  # There's no way to give a capture delay in mplayer, but a number of frame
18
18
  # mplayer's "delay" is actually a number of frames at 25 fps
@@ -8,21 +8,20 @@ module Lolcommits
8
8
 
9
9
  # capture the raw video with ffmpeg video4linux2
10
10
  system_call "ffmpeg -v quiet -y -f video4linux2 -video_size 320x240 -i #{capture_device_string} -t #{capture_duration} #{video_location} > /dev/null"
11
- if File.exists?(video_location)
12
- # convert raw video to png frames with ffmpeg
13
- system_call "ffmpeg #{capture_delay_string} -v quiet -i #{video_location} -t #{animated_duration} #{frames_location}/%09d.png > /dev/null"
11
+ return unless File.exist?(video_location)
12
+ # convert raw video to png frames with ffmpeg
13
+ system_call "ffmpeg #{capture_delay_string} -v quiet -i #{video_location} -t #{animated_duration} #{frames_location}/%09d.png > /dev/null"
14
14
 
15
- # use fps to set delay and number of frames to skip (for lower filesized gifs)
16
- fps = video_fps(video_location)
17
- skip = frame_skip(fps)
18
- delay = frame_delay(fps, skip)
19
- debug "Capturer: anaimated gif choosing every #{skip} frames with a frame delay of #{delay}"
15
+ # use fps to set delay and number of frames to skip (for lower filesized gifs)
16
+ fps = video_fps(video_location)
17
+ skip = frame_skip(fps)
18
+ delay = frame_delay(fps, skip)
19
+ debug "Capturer: anaimated gif choosing every #{skip} frames with a frame delay of #{delay}"
20
20
 
21
- # create the looping animated gif from frames (picks nth frame with seq)
22
- seq_command = "seq -f #{frames_location}/%09g.png 1 #{skip} #{Dir["#{frames_location}/*"].length}"
23
- # convert to animated gif with delay and gif optimisation
24
- system_call "convert -layers OptimizeTransparency -delay #{delay} -loop 0 `#{seq_command}` -coalesce #{snapshot_location} > /dev/null"
25
- end
21
+ # create the looping animated gif from frames (picks nth frame with seq)
22
+ seq_command = "seq -f #{frames_location}/%09g.png 1 #{skip} #{Dir["#{frames_location}/*"].length}"
23
+ # convert to animated gif with delay and gif optimisation
24
+ system_call "convert -layers OptimizeTransparency -delay #{delay} -loop 0 `#{seq_command}` -coalesce #{snapshot_location} > /dev/null"
26
25
  end
27
26
 
28
27
  private
@@ -8,21 +8,20 @@ module Lolcommits
8
8
 
9
9
  # capture the raw video with videosnap
10
10
  system_call "#{executable_path} -s 240 #{capture_device_string}#{capture_delay_string}-t #{animated_duration} --no-audio #{video_location} > /dev/null"
11
- if File.exists?(video_location)
12
- # convert raw video to png frames with ffmpeg
13
- system_call "ffmpeg -v quiet -i #{video_location} -t #{animated_duration} #{frames_location}/%09d.png"
11
+ return unless File.exist?(video_location)
12
+ # convert raw video to png frames with ffmpeg
13
+ system_call "ffmpeg -v quiet -i #{video_location} -t #{animated_duration} #{frames_location}/%09d.png"
14
14
 
15
- # use fps to set delay and number of frames to skip (for lower filesized gifs)
16
- fps = video_fps(video_location)
17
- skip = frame_skip(fps)
18
- delay = frame_delay(fps, skip)
19
- debug "Capturer: anaimated gif choosing every #{skip} frames with a frame delay of #{delay}"
15
+ # use fps to set delay and number of frames to skip (for lower filesized gifs)
16
+ fps = video_fps(video_location)
17
+ skip = frame_skip(fps)
18
+ delay = frame_delay(fps, skip)
19
+ debug "Capturer: anaimated gif choosing every #{skip} frames with a frame delay of #{delay}"
20
20
 
21
- # create the looping animated gif from frames (picks nth frame with seq)
22
- seq_command = "seq -f #{frames_location}/%09g.png 1 #{skip} #{Dir["#{frames_location}/*"].length}"
23
- # convert to animated gif with delay and gif optimisation
24
- system_call "convert -layers OptimizeTransparency -delay #{delay} -loop 0 `#{seq_command}` -coalesce #{snapshot_location}"
25
- end
21
+ # create the looping animated gif from frames (picks nth frame with seq)
22
+ seq_command = "seq -f #{frames_location}/%09g.png 1 #{skip} #{Dir["#{frames_location}/*"].length}"
23
+ # convert to animated gif with delay and gif optimisation
24
+ system_call "convert -layers OptimizeTransparency -delay #{delay} -loop 0 `#{seq_command}` -coalesce #{snapshot_location}"
26
25
  end
27
26
 
28
27
  def executable_path
@@ -0,0 +1,77 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'lolcommits/platform'
3
+ require 'methadone'
4
+
5
+ module Lolcommits
6
+ module CLI
7
+ # Helper methods for failing on error conditions in the lolcommits CLI.
8
+ module Fatals
9
+ include Lolcommits
10
+ include Methadone::CLILogging
11
+
12
+ # Check for platform related conditions that could prevent lolcommits from
13
+ # working properly.
14
+ #
15
+ # Die with an informative error message if any occur.
16
+ def self.die_on_fatal_platform_conditions!
17
+ # make sure the capture binaries are in a good state
18
+ if Platform.platform_mac?
19
+ %w(imagesnap videosnap).each do |executable|
20
+ next if File.executable? File.join(Configuration::LOLCOMMITS_ROOT, 'vendor', 'ext', executable, executable)
21
+ fatal "Couldn't properly execute #{executable} for some reason, "\
22
+ 'please file a bug?!'
23
+ exit 1
24
+ end
25
+ elsif Platform.platform_linux?
26
+ unless Platform.command_which('mplayer')
27
+ fatal "Couldn't find mplayer in your PATH!"
28
+ exit 1
29
+ end
30
+ end
31
+
32
+ # make sure we can find the Impact truetype font
33
+ unless File.readable? File.join(Configuration::LOLCOMMITS_ROOT, 'vendor', 'fonts', 'Impact.ttf')
34
+ fatal "Couldn't properly read Impact font from gem package, "\
35
+ 'please file a bug?!'
36
+ exit 1
37
+ end
38
+
39
+ # make sure imagemagick is around and good to go
40
+ unless Platform.valid_imagemagick_installed?
41
+ fatal 'FATAL: ImageMagick does not appear to be properly installed!'\
42
+ '(or version is too old)'
43
+ exit 1
44
+ end
45
+
46
+ # check for a error condition with git config affecting ruby-git
47
+ if Platform.git_config_color_always?
48
+ fatal 'Due to a bug in the ruby-git library, git config for color.ui'\
49
+ " cannot be set to 'always'."
50
+ fatal "Try setting it to 'auto' instead!"
51
+ exit 1
52
+ end
53
+ end
54
+
55
+ # Die with an informative error message if ffmpeg is not installed.
56
+ # This is only used for certain functions (such as animation), so only run
57
+ # this when you know the user wants to perform one of them.
58
+ def self.die_if_no_valid_ffmpeg_installed!
59
+ unless Platform.valid_ffmpeg_installed?
60
+ fatal 'FATAL: ffmpeg does not appear to be properly installed!'
61
+ exit 1
62
+ end
63
+ end
64
+
65
+ # If we are not in a git repo, we can't do git related things!
66
+ # Die with an informative error message in that case.
67
+ def self.die_if_not_git_repo!
68
+ debug 'Checking for valid git repo'
69
+ Git.open('.') # FIXME: should be extracted to GitInfo class
70
+ rescue ArgumentError
71
+ # ruby-git throws an argument error if path isnt for a valid git repo.
72
+ fatal "Erm? Can't do that since we're not in a valid git repository!"
73
+ exit 1
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'launchy'
3
+
4
+ module Lolcommits
5
+ module CLI
6
+ # Helper class for wrapping the opening of files on the desktop in a
7
+ # cross-platform way.
8
+ #
9
+ # Right now this is mostly just a wrapper for Launchy, in case we want
10
+ # to factor out it's dependency later or swap it out.
11
+ class Launcher
12
+ def self.open_image(path)
13
+ open_with_launchy(path)
14
+ end
15
+
16
+ def self.open_folder(path)
17
+ open_with_launchy(path)
18
+ end
19
+
20
+ # Opens with Launchy, which knows how to open pretty much anything
21
+ # local files, urls, etc.
22
+ #
23
+ # Private so we replace it later easier if we want.
24
+ def self.open_with_launchy(thing)
25
+ Launchy.open(thing)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,48 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Lolcommits
4
+ module CLI
5
+ # Helper class for forking lolcommits process to the background (or not).
6
+ class ProcessRunner
7
+ # Initializes a new process runner.
8
+ #
9
+ # @param config [Lolcommits::Configuration]
10
+ def initialize(config)
11
+ @configuration = config
12
+ end
13
+
14
+ # Forks the lolcommits process if requested.
15
+ #
16
+ # Writes the PID of the lolcommits process to the filesystem when
17
+ # backgrounded, for monitoring purposes.
18
+ #
19
+ # @param please [Boolean] whether or not to fork lolcommits process
20
+ # @yield the main event loop for lolcommits
21
+ def fork_me?(please, &block)
22
+ if please
23
+ $stdout.sync = true
24
+ write_pid fork {
25
+ yield block
26
+ delete_pid
27
+ }
28
+ else
29
+ yield block
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def write_pid(pid)
36
+ File.open(pid_file, 'w') { |f| f.write(pid) }
37
+ end
38
+
39
+ def delete_pid
40
+ File.delete(pid_file) if File.exist?(pid_file)
41
+ end
42
+
43
+ def pid_file
44
+ File.join(@configuration.loldir, 'lolcommits.pid')
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,45 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'lolcommits/cli/fatals'
3
+
4
+ require 'mini_magick'
5
+
6
+ module Lolcommits
7
+ module CLI
8
+ # Creates an animated timeline GIF of lolcommits history.
9
+ class TimelapseGif
10
+ # param config [Lolcommits::Configuration]
11
+ def initialize(config)
12
+ @configuration = config
13
+ end
14
+
15
+ # Runs the history timeline animator task thingy
16
+ # param args [String] the arg passed to the gif command on CLI (optional)
17
+ def run(args = nil)
18
+ Fatals.die_if_not_git_repo!
19
+
20
+ case args
21
+ when 'today'
22
+ lolimages = @configuration.jpg_images_today
23
+ filename = "#{Date.today}.gif"
24
+ else
25
+ lolimages = @configuration.jpg_images
26
+ filename = 'archive.gif'
27
+ end
28
+
29
+ if lolimages.empty?
30
+ warn 'No lolcommits have been captured for this time yet.'
31
+ exit 1
32
+ end
33
+
34
+ puts '*** Generating animated gif.'
35
+
36
+ gif = MiniMagick::Image.new File.join @configuration.archivedir, filename
37
+
38
+ # This is for ruby 1.8.7, *lolimages just doesn't work with ruby 187
39
+ gif.run_command('convert', *['-delay', '50', '-loop', '0', lolimages, "#{gif.path}"].flatten)
40
+
41
+ puts "*** #{gif.path} generated."
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,4 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
+
2
3
  module Lolcommits
3
4
  class Configuration
4
5
  LOLCOMMITS_BASE = ENV['LOLCOMMITS_DIR'] || File.join(ENV['HOME'], '.lolcommits')
@@ -7,20 +8,17 @@ module Lolcommits
7
8
 
8
9
  def initialize(attributes = {})
9
10
  attributes.each do |attr, val|
10
- self.send("#{attr}=", val)
11
+ send("#{attr}=", val)
11
12
  end
12
13
  end
13
14
 
14
15
  def read_configuration
15
- if File.exists?(configuration_file)
16
- YAML.load(File.open(configuration_file))
17
- else
18
- nil
19
- end
16
+ return unless File.exist?(configuration_file)
17
+ YAML.load(File.open(configuration_file))
20
18
  end
21
19
 
22
20
  def configuration_file
23
- "#{self.loldir}/config.yml"
21
+ "#{loldir}/config.yml"
24
22
  end
25
23
 
26
24
  def loldir
@@ -32,51 +30,40 @@ module Lolcommits
32
30
 
33
31
  def archivedir
34
32
  dir = File.join(loldir, 'archive')
35
- if not File.directory? dir
36
- FileUtils.mkdir_p dir
37
- end
33
+ FileUtils.mkdir_p dir unless File.directory? dir
38
34
  dir
39
35
  end
40
36
 
41
37
  def most_recent
42
- Dir.glob(File.join self.loldir, '*.{jpg,gif}').max_by { |f| File.mtime(f) }
38
+ Dir.glob(File.join loldir, '*.{jpg,gif}').max_by { |f| File.mtime(f) }
43
39
  end
44
40
 
45
41
  def jpg_images
46
- Dir.glob(File.join self.loldir, '*.jpg').sort_by { |f| File.mtime(f) }
42
+ Dir.glob(File.join loldir, '*.jpg').sort_by { |f| File.mtime(f) }
47
43
  end
48
44
 
49
45
  def jpg_images_today
50
- jpg_images.select { |f| Date.parse(File.mtime(f).to_s) === Date.today }
46
+ jpg_images.select { |f| Date.parse(File.mtime(f).to_s) == Date.today }
51
47
  end
52
48
 
53
49
  def raw_image(image_file_type = 'jpg')
54
- File.join self.loldir, "tmp_snapshot.#{image_file_type}"
50
+ File.join loldir, "tmp_snapshot.#{image_file_type}"
55
51
  end
56
52
 
57
53
  def main_image(commit_sha, image_file_type = 'jpg')
58
- File.join self.loldir, "#{commit_sha}.#{image_file_type}"
54
+ File.join loldir, "#{commit_sha}.#{image_file_type}"
59
55
  end
60
56
 
61
57
  def video_loc
62
- File.join(self.loldir, 'tmp_video.mov')
58
+ File.join(loldir, 'tmp_video.mov')
63
59
  end
64
60
 
65
61
  def frames_loc
66
- File.join(self.loldir, 'tmp_frames')
67
- end
68
-
69
- def puts_devices
70
- # TODO: handle other platforms here (linux/windows)
71
- if self.class.platform_mac?
72
- capturer = Lolcommits::CaptureMacAnimated.new
73
- puts `#{capturer.executable_path} -l`
74
- puts "Specify a device with --device=\"{device name}\" or set the LOLCOMMITS_DEVICE env variable"
75
- end
62
+ File.join(loldir, 'tmp_frames')
76
63
  end
77
64
 
78
65
  def puts_plugins
79
- puts "Available plugins: #{Lolcommits::PLUGINS.map(&:name).join(', ')}"
66
+ puts "Available plugins: #{Lolcommits::Runner.plugins.map(&:name).join(', ')}"
80
67
  end
81
68
 
82
69
  def ask_for_plugin_name
@@ -86,10 +73,8 @@ module Lolcommits
86
73
  end
87
74
 
88
75
  def find_plugin(plugin_name)
89
- Lolcommits::PLUGINS.each do |plugin|
90
- if plugin.name == plugin_name
91
- return plugin.new(nil)
92
- end
76
+ Lolcommits::Runner.plugins.each do |plugin|
77
+ return plugin.new(nil) if plugin.name == plugin_name
93
78
  end
94
79
 
95
80
  puts "Unable to find plugin: '#{plugin_name}'"
@@ -97,30 +82,27 @@ module Lolcommits
97
82
  end
98
83
 
99
84
  def do_configure!(plugin_name)
100
- if plugin_name.to_s.strip.empty?
101
- plugin_name = ask_for_plugin_name
102
- end
85
+ plugin_name = ask_for_plugin_name if plugin_name.to_s.strip.empty?
103
86
 
104
87
  plugin = find_plugin(plugin_name)
105
- if plugin
106
- config = self.read_configuration || {}
107
- plugin_config = plugin.configure_options!
108
- # having a plugin_config, means configuring went OK
109
- if plugin_config
110
- # save plugin and print config
111
- config[plugin_name] = plugin_config
112
- save(config)
113
- puts self
114
- puts "\nSuccessfully configured plugin: #{plugin_name}"
115
- else
116
- puts "\nAborted plugin configuration for: #{plugin_name}"
117
- end
88
+ return unless plugin
89
+ config = read_configuration || {}
90
+ plugin_config = plugin.configure_options!
91
+ # having a plugin_config, means configuring went OK
92
+ if plugin_config
93
+ # save plugin and print config
94
+ config[plugin_name] = plugin_config
95
+ save(config)
96
+ puts self
97
+ puts "\nSuccessfully configured plugin: #{plugin_name}"
98
+ else
99
+ puts "\nAborted plugin configuration for: #{plugin_name}"
118
100
  end
119
101
  end
120
102
 
121
103
  def save(config)
122
104
  config_file_contents = config.to_yaml
123
- File.open(self.configuration_file, 'w') do |f|
105
+ File.open(configuration_file, 'w') do |f|
124
106
  f.write(config_file_contents)
125
107
  end
126
108
  end
@@ -131,24 +113,6 @@ module Lolcommits
131
113
 
132
114
  # class methods
133
115
 
134
- def self.platform
135
- if platform_fakeplatform?
136
- ENV['LOLCOMMITS_FAKEPLATFORM']
137
- elsif platform_fakecapture?
138
- 'Fake'
139
- elsif platform_mac?
140
- 'Mac'
141
- elsif platform_linux?
142
- 'Linux'
143
- elsif platform_windows?
144
- 'Windows'
145
- elsif platform_cygwin?
146
- 'Cygwin'
147
- else
148
- fail 'Unknown / Unsupported Platform.'
149
- end
150
- end
151
-
152
116
  def self.loldir_for(basename)
153
117
  loldir = File.join(LOLCOMMITS_BASE, basename)
154
118
 
@@ -167,72 +131,5 @@ module Lolcommits
167
131
  end
168
132
  loldir
169
133
  end
170
-
171
- def self.platform_mac?
172
- RUBY_PLATFORM.to_s.downcase.include?('darwin')
173
- end
174
-
175
- def self.platform_linux?
176
- RUBY_PLATFORM.to_s.downcase.include?('linux')
177
- end
178
-
179
- def self.platform_windows?
180
- !!RUBY_PLATFORM.match(/(win|w)32/)
181
- end
182
-
183
- def self.platform_cygwin?
184
- RUBY_PLATFORM.to_s.downcase.include?('cygwin')
185
- end
186
-
187
- def self.platform_fakecapture?
188
- (ENV['LOLCOMMITS_FAKECAPTURE'] == '1' || false)
189
- end
190
-
191
- def self.platform_fakeplatform?
192
- ENV['LOLCOMMITS_FAKEPLATFORM']
193
- end
194
-
195
- def self.valid_imagemagick_installed?
196
- return false unless self.command_which('identify')
197
- return false unless self.command_which('mogrify')
198
- # you'd expect the below to work on its own, but it only handles old versions
199
- # and will throw an exception if IM is not installed in PATH
200
- MiniMagick.valid_version_installed?
201
- rescue
202
- puts "FATAL: ImageMagick >= #{MiniMagick.minimum_image_magick_version} required (http://imagemagick.org)"
203
- puts 'Please check the following command works and returns a valid version number'
204
- puts '=> mogrify --version'
205
- exit 1
206
- end
207
-
208
- def self.valid_ffmpeg_installed?
209
- self.command_which('ffmpeg')
210
- end
211
-
212
- def self.git_config_color_always?
213
- `git config color.ui`.chomp =~ /always/
214
- end
215
-
216
- def self.can_animate?
217
- ['Mac', 'Linux'].include? platform
218
- end
219
-
220
- # Cross-platform way of finding an executable in the $PATH.
221
- # idea taken from http://bit.ly/qDaTbY, if only_path is true, only the path
222
- # is returned (not the path and command)
223
- #
224
- # which('ruby') #=> /usr/bin/ruby
225
- def self.command_which(cmd, only_path = false)
226
- exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
227
- ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
228
- exts.each do |ext|
229
- exe = "#{path}/#{cmd}#{ext}"
230
- if File.executable? exe
231
- return only_path ? path : exe
232
- end
233
- end
234
- end
235
- nil
236
- end
237
134
  end
238
135
  end