process_builder 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
19
+ html/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in process_builder.gemspec
4
+ gemspec
@@ -0,0 +1,24 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ process_builder (1.1.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.1.3)
10
+ rspec (2.10.0)
11
+ rspec-core (~> 2.10.0)
12
+ rspec-expectations (~> 2.10.0)
13
+ rspec-mocks (~> 2.10.0)
14
+ rspec-core (2.10.1)
15
+ rspec-expectations (2.10.0)
16
+ diff-lcs (~> 1.1.3)
17
+ rspec-mocks (2.10.1)
18
+
19
+ PLATFORMS
20
+ ruby
21
+
22
+ DEPENDENCIES
23
+ process_builder!
24
+ rspec
@@ -0,0 +1,34 @@
1
+ = ProcessBuilder
2
+
3
+ Simple object-oriented wrapper around Ruby's Process.spawn and Open3 library.
4
+
5
+ ProcessBuilder allows you to build a process description which can then be
6
+ used to spawn and interact with an external process.
7
+
8
+ == Usage
9
+
10
+ require 'process_builder'
11
+
12
+ # Build a process description with the ProcessBuilder.build method.
13
+ process = ProcessBuilder.build('oggenc', 'track01.cdda.wav') do |builder|
14
+ # The builder object passed to this block has methods for setting various
15
+ # attributes of the process. See the RDoc for full details.
16
+ builder.environment['LOG_DIR'] = '/var/log'
17
+ builder.directory = "#{ENV['HOME']}/Music"
18
+ end
19
+
20
+ # Once the process description is built there are a variety of ways
21
+ # to spawn the process. One way is to call spawn:
22
+ pid = process.spawn
23
+ Process.wait(pid)
24
+
25
+ # Another way is to use any of the popen or capture methods defined by Open3:
26
+ stdin, stdout, stderr, wait_thread = process.popen3
27
+ status = wait_thread.value
28
+ stdin.close; stdout.close; stderr.close
29
+
30
+ # or in block form:
31
+ process.popen3 do |stdin, stdout, stderr, wait_thread|
32
+ status = wait_thread.value
33
+ end
34
+
@@ -0,0 +1,13 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rdoc/task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ task :default => :spec
7
+
8
+ Rake::RDocTask.new do |rd|
9
+ rd.main = "README.rdoc"
10
+ rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
11
+ rd.title = 'Process Builder'
12
+ end
13
+
@@ -0,0 +1,217 @@
1
+ require 'pathname'
2
+ require 'open3'
3
+
4
+ # Object-oriented wrapper around Process.spawn and the Open3 library.
5
+ # ProcessBuilder is an immutable description of a process and its various
6
+ # attributes. ProcessBuilder objects are created with the
7
+ # ProcessBuilder.build method, and after having been created can be used
8
+ # to spawn the process in various ways, such as #spawn or #popen3.
9
+ #
10
+ # See the descriptions of the methods of this class for further details,
11
+ # and the documentation for Process.spawn and Open3 for full details of
12
+ # the arguments that each understands.
13
+ class ProcessBuilder
14
+ VERSION = '1.1.0'
15
+
16
+ # The command and arguments passed to Process.spawn
17
+ attr_reader :command_line
18
+
19
+ # Working directory for the process. Corresponds to the :chdir option of
20
+ # Process.spawn
21
+ attr_reader :directory
22
+
23
+ # Hash representing environment variables for the process. Passed as the
24
+ # optional [env] argument of Process.spawn.
25
+ attr_reader :environment
26
+
27
+ # If true, clear environment variables, other than specified explicitly
28
+ # in environment.
29
+ attr_reader :unsetenv_others
30
+ alias unsetenv_others? unsetenv_others
31
+
32
+ # Process group, corresponding to the :pgroup option of Process.spawn.
33
+ attr_reader :pgroup
34
+
35
+ # Hash specifying IO redirection for the child process, corresponding to
36
+ # the redirection options of Process.spawn. Generally not used if using any
37
+ # of the Open3 mechanisms for spawning the process.
38
+ attr_reader :redirection
39
+
40
+ # File descriptor inheritance, corresponding to the :close_others option of
41
+ # Process.spawn.
42
+ attr_reader :close_others
43
+
44
+ # umask for the child process, corresponding to the :umask option of
45
+ # Process.spawn.
46
+ attr_reader :umask
47
+
48
+ # Hash specifying resource limits. Process.spawn expects resource limits to
49
+ # be specified as options in the form :rlimit_resourcename, where
50
+ # resourcename is one of the resources understood by Process.setrlimit,
51
+ # such as :rlimit_core for example.
52
+ # The keys of this Hash will be used to construct the options, so setting
53
+ # \rlimit[:core] would result in an option named :rlimit_core.
54
+ attr_reader :rlimit
55
+
56
+ # Build a new process description using the given args as the process
57
+ # command line. If a block is given, this method will yield a mutable
58
+ # ProcessBuilder::Builder object to it so that the block can specify the
59
+ # attributes of the process. The object returned by this method, however,
60
+ # is an immutable (and frozen) instance of ProcessBuilder.
61
+ def self.build(*args, &block)
62
+ new(*args, &block)
63
+ end
64
+
65
+ # Build a new process description copied from the given other ProcessBuilder.
66
+ # Like the build method, this method yields to the given block to allow for
67
+ # customization of the process attributes.
68
+ def self.copy(other)
69
+ raise ArgumentError unless other.is_a?(ProcessBuilder)
70
+ if block_given?
71
+ builder = Builder.new(other)
72
+ if block_given?
73
+ yield builder
74
+ end
75
+ new(builder)
76
+ else
77
+ new(other)
78
+ end
79
+ end
80
+
81
+ def initialize(*args)
82
+ if args.size == 1 && args.first.is_a?(ProcessBuilder)
83
+ self.copy_fields(args.first)
84
+ else
85
+ @command_line = array_copy(args)
86
+ @environment = Hash.new
87
+ @redirection = Hash.new
88
+ @rlimit = Hash.new
89
+ if block_given?
90
+ builder = Builder.new(self)
91
+ yield builder
92
+ self.copy_fields(builder)
93
+ end
94
+ end
95
+ self.freeze
96
+ end
97
+
98
+ def initialize_copy(other)
99
+ super
100
+ self.copy_fields(other)
101
+ end
102
+
103
+ # Spawn the process described by this ProcessBuilder using Process.spawn.
104
+ # Returns the PID of the spawned process.
105
+ def spawn
106
+ Process.spawn(*spawn_args)
107
+ end
108
+
109
+ # Spawn the process described by this ProcessBuilder using Open3.popen2.
110
+ def popen2(&block)
111
+ Open3.popen2(*spawn_args, &block)
112
+ end
113
+
114
+ # Spawn the process described by this ProcessBuilder using Open3.popen2e.
115
+ def popen2e(&block)
116
+ Open3.popen2e(*spawn_args, &block)
117
+ end
118
+
119
+ # Spawn the process described by this ProcessBuilder using Open3.popen3.
120
+ def popen3(&block)
121
+ Open3.popen3(*spawn_args, &block)
122
+ end
123
+
124
+ # Execute the process described by this ProcessBuilder using Open3.capture2.
125
+ # The argument to this method is used as the :stdin_data argument of
126
+ # Open3.capture2.
127
+ def capture2(stdin_data, &block)
128
+ args = self.spawn_args
129
+ args.last[:stdin_data] = stdin_data.to_s.dup
130
+ Open3.capture2(*args, &block)
131
+ end
132
+
133
+ # Execute the process described by this ProcessBuilder using Open3.capture3.
134
+ # The argument to this method is used as the :stdin_data argument of
135
+ # Open3.capture3.
136
+ def capture3(stdin_data, &block)
137
+ args = self.spawn_args
138
+ args.last[:stdin_data] = stdin_data.to_s.dup
139
+ Open3.capture3(*args, &block)
140
+ end
141
+
142
+ # Returns an array of arguments as understood by Process.spawn.
143
+ def spawn_args
144
+ result = Array.new
145
+ unless environment.empty?
146
+ result << environment
147
+ end
148
+ result.concat(command_line)
149
+ opts = Hash.new
150
+ opts[:chdir] = directory.to_s unless directory.nil?
151
+ opts[:pgroup] = pgroup unless pgroup.nil?
152
+ opts[:umask] = umask unless umask.nil?
153
+ opts[:unsetenv_others] = unsetenv_others unless unsetenv_others.nil?
154
+ opts[:close_others] = close_others unless close_others.nil?
155
+ rlimit.each do |key, value|
156
+ opts["rlimit_#{key}".to_sym] = value
157
+ end
158
+ redirection.each do |key, value|
159
+ opts[key] = value
160
+ end
161
+ result << opts
162
+ result
163
+ end
164
+
165
+ protected
166
+
167
+ # :nodoc:
168
+ def copy_fields(other)
169
+ @command_line = array_copy(other.command_line)
170
+ @directory = Pathname(other.directory )if other.directory
171
+ @environment = hash_copy(other.environment)
172
+ @unsetenv_others = other.unsetenv_others
173
+ @pgroup = other.pgroup
174
+ @redirection = hash_copy(other.redirection)
175
+ @close_others = other.close_others
176
+ @umask = other.umask
177
+ @rlimit = hash_copy(other.rlimit)
178
+ end
179
+
180
+ private
181
+
182
+ def copy_val(val)
183
+ case val
184
+ when NilClass, TrueClass, FalseClass, Numeric, Symbol
185
+ val
186
+ else
187
+ val.dup.freeze
188
+ end
189
+ end
190
+
191
+ def array_copy(array)
192
+ [array].flatten.compact.map { |arg|
193
+ copy_val(arg)
194
+ }
195
+ end
196
+
197
+ def hash_copy(hash)
198
+ hash ||= Hash.new
199
+ result = Hash.new
200
+ hash.each do |key, value|
201
+ result[key] = copy_val(value)
202
+ end
203
+ result
204
+ end
205
+
206
+ # Builder object that is passed to the block given to the ProcessBuilder.build
207
+ # and ProcessBuilder.copy methods. The Builder object has writable attributes
208
+ # allowing the block to customize the attributes of the process.
209
+ class Builder < ProcessBuilder
210
+ attr_writer :command_line, :directory, :environment, :pgroup,
211
+ :rlimit_resourcename, :umask, :close_others
212
+
213
+ def initialize(initial_state)
214
+ self.copy_fields(initial_state)
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "process_builder"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "process_builder"
7
+ s.version = ProcessBuilder::VERSION
8
+ s.authors = ["Jason Voegele"]
9
+ s.email = ["jason@jvoegele.com"]
10
+ s.homepage = "https://github.com/jvoegele/process_builder"
11
+ s.summary = "Simple object-oriented wrapper around Process.spawn and Open3"
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.require_paths = ["lib"]
17
+
18
+ s.add_development_dependency "rspec"
19
+ end
@@ -0,0 +1,156 @@
1
+ require 'process_builder'
2
+
3
+ describe ProcessBuilder do
4
+ context "build" do
5
+ it "returns a new ProcessBuilder object" do
6
+ p = ProcessBuilder.build
7
+ p.should be_kind_of(ProcessBuilder)
8
+ end
9
+
10
+ it "optionally takes command line arguments" do
11
+ p = ProcessBuilder.build('command', 'arg1', 'arg2')
12
+ p.command_line.should == %w[command arg1 arg2]
13
+ end
14
+
15
+ it "yields a ProcessBuilder::Builder object if given a block" do
16
+ yielded = false
17
+ process = ProcessBuilder.build do |p|
18
+ yielded = true
19
+ p.should be_kind_of(ProcessBuilder::Builder)
20
+ end
21
+ yielded.should be_true
22
+ end
23
+
24
+ it "copies the values set on the builder object in the block" do
25
+ process = ProcessBuilder.build('command', 'arg1') do |builder|
26
+ builder.command_line << 'arg2'
27
+ builder.directory = "#{ENV['HOME']}/fakedir"
28
+ builder.environment['ULTIMATE_ANSWER'] = 42
29
+ builder.environment['NILVAR'] = nil
30
+ end
31
+ process.command_line.should == %w[command arg1 arg2]
32
+ process.directory.should == Pathname("#{ENV['HOME']}/fakedir")
33
+ process.environment.should == {
34
+ 'ULTIMATE_ANSWER' => 42,
35
+ 'NILVAR' => nil
36
+ }
37
+ end
38
+ end
39
+
40
+ context "copy" do
41
+ let(:initializer) {
42
+ ->(builder) do
43
+ builder.directory = 'somedir'
44
+ builder.environment['SOMEVAR'] = 'someval'
45
+ end
46
+ }
47
+ it "copies all attributes of the other process builder" do
48
+ p1 = ProcessBuilder.build('command', 'arg1', &initializer)
49
+ p2 = ProcessBuilder.copy(p1) do |p|
50
+ p.directory = 'anotherdir'
51
+ p.environment['ANOTHERVAR'] = 'anotherval'
52
+ end
53
+ p2.command_line.should == %w[command arg1]
54
+ p2.directory.should == Pathname('anotherdir')
55
+ p2.environment.should == {
56
+ 'SOMEVAR' => 'someval',
57
+ 'ANOTHERVAR' => 'anotherval'
58
+ }
59
+ end
60
+ end
61
+
62
+ context "#spawn_args" do
63
+ let(:process) {
64
+ ProcessBuilder.build('command', 'arg1') do |p|
65
+ p.directory = 'somedir'
66
+ p.environment['SOMEVAR'] = 'someval'
67
+ p.pgroup = true
68
+ p.redirection[:err] = 'error.log'
69
+ p.umask = 42
70
+ p.rlimit['core'] = [0, 100]
71
+ p.rlimit[:nice] = 20
72
+ end
73
+ }
74
+
75
+ it "converts all attributes into arguments for Process.spawn" do
76
+ spawn_args = process.spawn_args
77
+ spawn_args.should == [
78
+ {'SOMEVAR' => 'someval'},
79
+ 'command', 'arg1',
80
+ {
81
+ :chdir => 'somedir',
82
+ :pgroup => true,
83
+ :err => 'error.log',
84
+ :umask => 42,
85
+ :rlimit_core => [0, 100],
86
+ :rlimit_nice => 20
87
+ }
88
+ ]
89
+ end
90
+ end
91
+
92
+ it "can spawn a new process" do
93
+ p = ProcessBuilder.build('ruby', '--version') do |p|
94
+ p.redirection[:err] = :close
95
+ end
96
+ pid = p.spawn
97
+ pid.should be_kind_of(Integer)
98
+ end
99
+
100
+ context "popen" do
101
+ let(:process) {
102
+ ProcessBuilder.build('ruby', '--version')
103
+ }
104
+
105
+ it "supports popen2" do
106
+ stdin, stdout, wait_thread = process.popen2
107
+ stdin.should be_kind_of(IO)
108
+ stdout.should be_kind_of(IO)
109
+ wait_thread.should be_kind_of(Thread)
110
+ stdin.close
111
+ stdout.close
112
+
113
+ process.popen2 do |stdin, stdout, wait_thread|
114
+ stdin.should be_kind_of(IO)
115
+ stdout.should be_kind_of(IO)
116
+ wait_thread.should be_kind_of(Thread)
117
+ end
118
+ end
119
+
120
+ it "supports popen3" do
121
+ stdin, stdout, stderr, wait_thread = process.popen3
122
+ stdin.should be_kind_of(IO)
123
+ stdout.should be_kind_of(IO)
124
+ stderr.should be_kind_of(IO)
125
+ wait_thread.should be_kind_of(Thread)
126
+ stdin.close
127
+ stdout.close
128
+ stderr.close
129
+
130
+ process.popen3 do |stdin, stdout, stderr, wait_thread|
131
+ stdin.should be_kind_of(IO)
132
+ stdout.should be_kind_of(IO)
133
+ stderr.should be_kind_of(IO)
134
+ wait_thread.should be_kind_of(Thread)
135
+ end
136
+ end
137
+ end
138
+
139
+ context "capture" do
140
+ it "supports capture2" do
141
+ process = ProcessBuilder.build('ruby')
142
+ stdout_string, status = process.capture2("puts('Hello world!')")
143
+ stdout_string.should == "Hello world!\n"
144
+ status.should be_kind_of(Process::Status)
145
+ end
146
+
147
+ it "supports capture3" do
148
+ process = ProcessBuilder.build('ruby')
149
+ ruby_code = %Q{puts("Hello world!"); $stderr.puts("42")}
150
+ stdout_string, stderr_string, status = process.capture3(ruby_code)
151
+ stdout_string.should == "Hello world!\n"
152
+ stderr_string.should == "42\n"
153
+ status.should be_kind_of(Process::Status)
154
+ end
155
+ end
156
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: process_builder
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jason Voegele
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70235751354520 !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: *70235751354520
25
+ description:
26
+ email:
27
+ - jason@jvoegele.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - .gitignore
33
+ - Gemfile
34
+ - Gemfile.lock
35
+ - README.rdoc
36
+ - Rakefile
37
+ - lib/process_builder.rb
38
+ - process_builder.gemspec
39
+ - spec/process_builder_spec.rb
40
+ homepage: https://github.com/jvoegele/process_builder
41
+ licenses: []
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 1.8.15
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: Simple object-oriented wrapper around Process.spawn and Open3
64
+ test_files:
65
+ - spec/process_builder_spec.rb