em-systemcommand 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use --create --install 1.9.3@em-systemcommand
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in em-systemcommand.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,15 @@
1
+ guard 'rspec', :version => 2 do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+
6
+ # Rails example
7
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
8
+ watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
9
+ watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
10
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
11
+ watch('config/routes.rb') { "spec/routing" }
12
+ watch('app/controllers/application_controller.rb') { "spec/controllers" }
13
+ # Capybara request specs
14
+ watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
15
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Arthur Andersen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # EM::SystemCommand
2
+
3
+ `EM::SystemCommand` is an `popen3` abstraction for eventmachine to easily create subprocesses with eventmachine.
4
+ The goal is to provide an easy way to invoke system commands and to read and handle their outputs. When creating an
5
+ `EM::SystemCommand` object its basically like a popen. It has `#stdin`, `#stdout` and `#stderr`.
6
+ Which are related to `EM::Connection`.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'em-systemcommand'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install em-systemcommand
21
+
22
+ ## Usage
23
+
24
+ To simply run a process you can instatiate an `EM::SystemCommand`
25
+ object and set up the callbacks in the yielded block.
26
+
27
+ EM.run do
28
+ EM::SystemCommand 'my_command' do |on|
29
+ on.success do |ps|
30
+ puts "Success!"
31
+ end
32
+
33
+ on.failure do |ps|
34
+ puts "Failure with status #{ps.status.exitstatus}"
35
+ end
36
+ end
37
+ end
38
+
39
+ When you want to retreive output, you can use the methods
40
+ `#update`, `#line` and `#data` on a pipe object like so:
41
+
42
+ EM.run do
43
+ EM::SystemCommand 'my_command' do |on|
44
+ on.success do |ps|
45
+ puts "Success!"
46
+ end
47
+
48
+ on.stdout.data do |data|
49
+ puts "Data: #{data}"
50
+ end
51
+
52
+ on.stdout.line do |line|
53
+ puts "Line: #{line}"
54
+ end
55
+
56
+ # `#output` gets the whole output buffer.
57
+ # This means, it has theoretically the screen you´d get when
58
+ # invoking the command in the shell. Although only \r is used.
59
+ on.stdout.update do |output|
60
+ puts output
61
+ end
62
+ end
63
+ end
64
+
65
+ `Pipe` objects even have a nice convenient method `#match` which lets
66
+ you match output against a regular expression:
67
+
68
+ EM.run do
69
+ EM::SystemCommand 'echo "25%\n"; sleep 1; echo "50%\n"; sleep 1; echo "75%\n"; sleep 1; echo "100%\n"; exit 0;' do |on|
70
+ on.success do |ps|
71
+ puts "Success!"
72
+ end
73
+ .
74
+ on.stdout.match /([0-9]+)%/, in: :line do |match, progress|
75
+ puts "Percentage: #{progress}"
76
+ end
77
+ end
78
+ end
79
+
80
+ ## Contributing
81
+
82
+ 1. Fork it
83
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
84
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
85
+ 4. Push to the branch (`git push origin my-new-feature`)
86
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/em-systemcommand/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Arthur Andersen"]
6
+ gem.email = ["leoc.git@gmail.com"]
7
+ gem.description = %q{}
8
+ gem.summary = %q{}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "em-systemcommand"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Em::Systemcommand::VERSION
17
+
18
+ gem.add_development_dependency 'rspec'
19
+ gem.add_development_dependency 'guard'
20
+ gem.add_development_dependency 'guard-rspec'
21
+
22
+ gem.add_dependency 'eventmachine'
23
+ end
@@ -0,0 +1,122 @@
1
+ module EventMachine
2
+ class SystemCommand
3
+ class Pipe < EM::Connection
4
+
5
+ def initialize(master, name)
6
+ @master = master
7
+ @name = name
8
+ @master.pipes[name] = self
9
+ begin
10
+ @outputbuffer = StringIO.new()
11
+ rescue Exception => e
12
+ puts "Uninitialized constant StringIO. This may happen when you forgot to use bundler. Use `bundle exec`."
13
+ end
14
+ end
15
+
16
+ def output
17
+ @outputbuffer.string
18
+ end
19
+
20
+ # Convenience method to create a callback that matches a regular expression
21
+ def match regexp, opt = {}, &block
22
+ opt = { in: :line }.merge opt
23
+ (opt[:in] == :output ? receive_update_callbacks : receive_line_callbacks) << lambda do |data|
24
+ if m = data.match(regexp)
25
+ block.call m.to_a
26
+ end
27
+ end
28
+ end
29
+
30
+ # Invoked when line was received
31
+ def receive_line line
32
+ receive_line_callbacks.each do |callback|
33
+ callback.call line
34
+ end
35
+ end
36
+
37
+ # Adds a callback for `receive_line` events.
38
+ def line &block
39
+ receive_line_callbacks << block
40
+ end
41
+
42
+ # Invoked when a line was written, but `\r` was received without
43
+ # a line-break in the end.
44
+ def receive_update line
45
+ receive_update_callbacks.each do |callback|
46
+ callback.call line
47
+ end
48
+ end
49
+
50
+ # Adds a callback for `receive_update` events.
51
+ def update &block
52
+ receive_update_callbacks << block
53
+ end
54
+
55
+ # Invoked when data was received.
56
+ def receive_data data
57
+ receive_data_callbacks.each do |callback|
58
+ callback.call data
59
+ end
60
+
61
+ @lt2_linebuffer ||= []
62
+
63
+ ix = data.index("\r")
64
+ if ix and data[ix+1] != "\n"
65
+ @lt2_linebuffer << data[0...ix]
66
+ ln = @lt2_linebuffer.join
67
+ @lt2_linebuffer.clear
68
+ @outputbuffer.print ln
69
+ @outputbuffer.pos -= ln.length
70
+ receive_line ln
71
+ receive_update @outputbuffer.string
72
+ receive_data data[(ix+1)..-1] # receive rest data
73
+ elsif ix = data.index("\n")
74
+ @lt2_linebuffer << data[0...ix]
75
+ ln = @lt2_linebuffer.join
76
+ @lt2_linebuffer.clear
77
+ ln.chomp!
78
+ @outputbuffer.puts ln
79
+ receive_line ln
80
+ receive_update @outputbuffer.string
81
+ receive_data data[(ix+1)..-1] # receive rest data
82
+ else
83
+ @lt2_linebuffer << data
84
+ end
85
+ end
86
+
87
+ # Adds a callback for `receive_data` events.
88
+ def data &block
89
+ receive_data_callbacks << block
90
+ end
91
+
92
+ # Close the attached IO object.
93
+ def close
94
+ begin
95
+ @io.close unless @io.closed?
96
+ rescue Exception => e
97
+ # ignore errors, when the io object might be closed already
98
+ end
99
+ end
100
+
101
+ # Invoked when the connection is terminated. Calls
102
+ # `unbind(@name)` on master.
103
+ def unbind
104
+ self.close
105
+ @master.unbind(@name)
106
+ end
107
+
108
+ private
109
+ def receive_line_callbacks
110
+ @receive_line_callbacks ||= []
111
+ end
112
+
113
+ def receive_update_callbacks
114
+ @receive_update_callbacks ||= []
115
+ end
116
+
117
+ def receive_data_callbacks
118
+ @receive_data_callbacks ||= []
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,33 @@
1
+ module EventMachine
2
+ class SystemCommand
3
+ module PipeHandler
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ def attach_pipe_handler name, io_object
9
+ EM.attach(io_object, pipe_handler_class(name), self, name)
10
+ end
11
+
12
+ def pipe_handler_class name
13
+ self.class.pipe_handlers[name]
14
+ end
15
+
16
+ module ClassMethods
17
+
18
+ def pipe_handler name, klass
19
+ pipe_handlers[name] = klass
20
+ end
21
+
22
+ def pipe_handler_class name
23
+ pipe_handlers[name]
24
+ end
25
+
26
+ def pipe_handlers
27
+ @pipe_handlers ||= {}
28
+ end
29
+
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,5 @@
1
+ module Em
2
+ module Systemcommand
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,65 @@
1
+ require 'open3'
2
+
3
+ require "em-systemcommand/version"
4
+
5
+ require "em-systemcommand/pipe"
6
+ require "em-systemcommand/pipe_handler"
7
+
8
+ module EventMachine
9
+ class SystemCommand
10
+ include EM::SystemCommand::PipeHandler
11
+ include EM::Deferrable
12
+
13
+ pipe_handler :stdin, EM::SystemCommand::Pipe
14
+ pipe_handler :stdout, EM::SystemCommand::Pipe
15
+ pipe_handler :stderr, EM::SystemCommand::Pipe
16
+
17
+ attr_accessor :pipes, :stdin, :stdout, :stderr
18
+
19
+ def initialize *arguments
20
+
21
+ @pipes = {}
22
+
23
+ stdin, stdout, stderr, @wait_thr = Open3.popen3(*arguments)
24
+
25
+ @stdin = attach_pipe_handler :stdin, stdin
26
+ @stdout = attach_pipe_handler :stdout, stdout
27
+ @stderr = attach_pipe_handler :stderr, stderr
28
+
29
+ yield self if block_given?
30
+
31
+ end
32
+
33
+ def pid
34
+ @wait_thr.pid
35
+ end
36
+
37
+ def status
38
+ @wait_thr.value
39
+ end
40
+
41
+ alias_method :success, :callback
42
+ alias_method :failure, :errback
43
+
44
+ def unbind name
45
+ pipes.delete name
46
+ if pipes.empty?
47
+ if status.exitstatus == 0
48
+ succeed
49
+ else
50
+ fail status
51
+ end
52
+ end
53
+ end
54
+
55
+ def kill signal = 'TERM', wait = false
56
+ Process.kill signal, self.pid
57
+ val = status if wait
58
+ @stdin.close
59
+ @stdout.close
60
+ @stderr.close
61
+ val
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe EM::SystemCommand do
4
+
5
+ it 'should take a success callback' do
6
+ called = false
7
+ EM.run do
8
+ EM::SystemCommand.new 'echo "123"; exit 0;' do |on|
9
+ on.success do |ps|
10
+ called = true
11
+ end
12
+ end
13
+
14
+ EM.assertions do
15
+ called.should == true
16
+ end
17
+ end
18
+ end
19
+
20
+ it 'should take a failure callback' do
21
+ called = false
22
+ EM.run do
23
+ EM::SystemCommand.new 'echo "123"; exit 1;' do |on|
24
+ on.failure do |ps|
25
+ called = true
26
+ end
27
+ end
28
+
29
+ EM.assertions do
30
+ called.should == true
31
+ end
32
+ end
33
+ end
34
+
35
+ it 'should have stdin pipe' do
36
+ EM.run do
37
+ ps = EM::SystemCommand.new 'echo "123"; exit 1;'
38
+ ps.stdin.should be_a EM::SystemCommand::Pipe
39
+ EM.stop_event_loop
40
+ end
41
+ end
42
+
43
+ it 'should have stdout pipe' do
44
+ EM.run do
45
+ ps = EM::SystemCommand.new 'echo "123"; exit 1;'
46
+ ps.stdout.should be_a EM::SystemCommand::Pipe
47
+ EM.stop_event_loop
48
+ end
49
+ end
50
+
51
+ it 'should have stderr pipe' do
52
+ EM.run do
53
+ ps = EM::SystemCommand.new 'echo "123"; exit 1;'
54
+ ps.stderr.should be_a EM::SystemCommand::Pipe
55
+ EM.stop_event_loop
56
+ end
57
+ end
58
+
59
+ end
@@ -0,0 +1,24 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper.rb"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+
8
+ require 'eventmachine'
9
+ require 'em-systemcommand'
10
+
11
+ EventMachine.instance_eval do
12
+ def assertions time = 1
13
+ EM.add_timer(time) do
14
+ EM.stop_event_loop
15
+ yield
16
+ end
17
+ end
18
+ end unless EM.respond_to?(:assertions)
19
+
20
+ RSpec.configure do |config|
21
+ config.treat_symbols_as_metadata_keys_with_true_values = true
22
+ config.run_all_when_everything_filtered = true
23
+ config.filter_run :focus
24
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-systemcommand
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Arthur Andersen
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &18939980 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *18939980
25
+ - !ruby/object:Gem::Dependency
26
+ name: guard
27
+ requirement: &18939560 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *18939560
36
+ - !ruby/object:Gem::Dependency
37
+ name: guard-rspec
38
+ requirement: &18939000 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *18939000
47
+ - !ruby/object:Gem::Dependency
48
+ name: eventmachine
49
+ requirement: &18938420 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *18938420
58
+ description: ''
59
+ email:
60
+ - leoc.git@gmail.com
61
+ executables: []
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - .gitignore
66
+ - .rspec
67
+ - .rvmrc
68
+ - Gemfile
69
+ - Guardfile
70
+ - LICENSE
71
+ - README.md
72
+ - Rakefile
73
+ - em-systemcommand.gemspec
74
+ - lib/em-systemcommand.rb
75
+ - lib/em-systemcommand/pipe.rb
76
+ - lib/em-systemcommand/pipe_handler.rb
77
+ - lib/em-systemcommand/version.rb
78
+ - spec/em-systmcommand_spec.rb
79
+ - spec/spec_helper.rb
80
+ homepage: ''
81
+ licenses: []
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ segments:
93
+ - 0
94
+ hash: -1466650369422800238
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ segments:
102
+ - 0
103
+ hash: -1466650369422800238
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 1.8.17
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: ''
110
+ test_files:
111
+ - spec/em-systmcommand_spec.rb
112
+ - spec/spec_helper.rb