process-builder 1.0.0

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.
@@ -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 (0.5.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
+ = process-builder
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.0.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"
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.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jason Voegele
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70284807936260 !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: *70284807936260
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
64
+ test_files:
65
+ - spec/process_builder_spec.rb