sys_cmd 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 307b77667d06748740cea3b10c0b0112475dac60
4
+ data.tar.gz: d584ecfcfb2cbbdce499adede7c68bf59014cd24
5
+ SHA512:
6
+ metadata.gz: 24290cf375b93fb4414d85f6534535285fd528b11be30f37fa5662226382df7eaae663006ce6c01133dff20fd1e1ab13c4878ec0df1ab04079e1af41e0a961fe
7
+ data.tar.gz: 85b2d3089a50c50e30d25bd70b9a0ea3ce19c0661d8d486a8d2d10759a988eecd224ca4d1a4f8cb15c4c5b0c699389ad9bea53b7aaa630e7891dcef8b4e05dfc
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /rdoc/
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sys_cmd.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Javier Goizueta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,53 @@
1
+ # SysCmd
2
+
3
+ SysCmd is a DSL to define commands to be executed by the system.
4
+
5
+ The command arguments will we escaped properly for bash or
6
+ Windows cmd.exe.
7
+
8
+ The commands can be executed capturing its exit status and output.
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'sys_cmd'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install sys_cmd
25
+
26
+ ## Usage
27
+
28
+ Example:
29
+
30
+ cmd = SysCmd.command 'ffmpeg' do
31
+ option '-i', file: 'input video file.mkv'
32
+ option '-vcodec', 'mjpeg'
33
+ file 'output.mkv'
34
+ end
35
+ puts cmd.to_s # ffmpeg -i input\ video\ file.mkv -vcodec mjpeg output.mkv
36
+ cmd.run
37
+ if cmd.success?
38
+ puts cmd.output
39
+ end
40
+
41
+ ## Development
42
+
43
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
44
+
45
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
46
+
47
+ ## Contributing
48
+
49
+ 1. Fork it ( https://github.com/[my-github-username]/sys_cmd/fork )
50
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
51
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
52
+ 4. Push to the branch (`git push origin my-new-feature`)
53
+ 5. Create a new Pull Request
@@ -0,0 +1,22 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << 'lib' << 'test'
6
+ test.pattern = 'test/**/test_*.rb'
7
+ test.verbose = true
8
+ end
9
+
10
+ require 'rdoc/task'
11
+ Rake::RDocTask.new do |rdoc|
12
+ version = SysCmd::VERSION
13
+
14
+ rdoc.rdoc_dir = 'rdoc'
15
+ rdoc.title = "SysCmd #{version}"
16
+ rdoc.main = "README.md"
17
+ rdoc.rdoc_files.include('README*')
18
+ rdoc.rdoc_files.include('lib/**/*.rb')
19
+ rdoc.markup = 'markdown'
20
+ end
21
+
22
+ task :default => :test
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "sys_cmd"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,397 @@
1
+ require "sys_cmd/version"
2
+
3
+ require 'shellwords'
4
+ require 'open3'
5
+ require 'os'
6
+
7
+ module SysCmd
8
+
9
+ # This class has methods to build a command to be executed by the system
10
+ #
11
+ # The target OS can be defined with the +:os+ option, which accepts
12
+ # values +:unix+ and +:windows+, and which defaults to the host system's type.
13
+ #
14
+ # All the methods to define command arguments (option, file, etc.)
15
+ # accept options +:only_on+, +:except_on+ to conditionally
16
+ # include the element depending on what the target OS is for the command.
17
+ #
18
+ class Definition
19
+
20
+ def initialize(command, options = {})
21
+ @shell = Shell.new(options)
22
+ @options = options.dup
23
+ @options.merge!(@options.delete(@shell.type) || {})
24
+ @command = ''
25
+ @command << (@options[command] || command)
26
+ @last_arg = :command
27
+ end
28
+
29
+ attr_reader :command
30
+
31
+ def to_s
32
+ command
33
+ end
34
+
35
+ # Add an option. If the option is nor prefixed by - or /
36
+ # then the default system option switch will be used.
37
+ #
38
+ # option 'x' # will produce -x or /x
39
+ # option '-x' # will always produce -x
40
+ #
41
+ # A value can be given as an option and will be space-separated from
42
+ # the option name:
43
+ #
44
+ # option '-x', value: 123 # -x 123
45
+ #
46
+ # To avoid spacing the value use the +:join_value+ option
47
+ #
48
+ # option '-x', join_value: 123 # -x123
49
+ #
50
+ # And to use an equal sign as separator, use +:equal_value+
51
+ #
52
+ # option '-x', equal_value: 123 # -x=123
53
+ #
54
+ # If the option value is a file name, use the analogous
55
+ # +:file+, +:join_file+ or +:equal_file+ options:
56
+ #
57
+ # option '-i', file: 'path/filename'
58
+ #
59
+ # Several of this options can be given simoultaneusly:
60
+ #
61
+ # option '-d', join_value: 'x', equal_value: '1' # -dx=1
62
+ #
63
+ def option(option, *args)
64
+ options = args.pop if args.last.is_a?(Hash)
65
+ options ||= {}
66
+ raise "Invalid number of arguments (0 or 1 expected)" if args.size > 1
67
+ return unless @shell.applicable?(options)
68
+ value = args.shift || options[:value]
69
+ if /\A[a-z]/i =~ option
70
+ option = @shell.option_switch + option
71
+ else
72
+ option = option.dup
73
+ end
74
+ option << @shell.escape_value(value) if value
75
+ option << @shell.escape_value(options[:join_value]) if options[:join_value]
76
+ option << '=' << @shell.escape_value(options[:equal_value]) if options[:join_value]
77
+ if file = options[:file]
78
+ file_sep = ' '
79
+ elsif file = options[:join_file]
80
+ file_sep = ''
81
+ elsif file = options[:equal_file]
82
+ file_sep = '='
83
+ end
84
+ if file
85
+ option << file_sep << @shell.escape_filename(file)
86
+ end
87
+ @command << ' ' << option
88
+ @last_arg = :option
89
+ end
90
+
91
+ # Add a filename to the command.
92
+ #
93
+ # file 'path/output'
94
+ #
95
+ def file(filename, options = {})
96
+ return unless @shell.applicable?(options)
97
+ @command << ' ' << @shell.escape_filename(filename)
98
+ @last_arg = :file
99
+ end
100
+
101
+ # Add a filename to the command, joinning it without a separator to
102
+ # the previous option added.
103
+ #
104
+ # option '-i'
105
+ # join_file 'path/output' # -ipath/output
106
+ #
107
+ def join_file(filename, options = {})
108
+ return unless @shell.applicable?(options)
109
+ raise "An option is required for join_file" unless @last_arg == :option
110
+ @command << @shell.escape_filename(filename)
111
+ @last_arg = :file
112
+ end
113
+
114
+ # Add a filename to the command, attaching it with an equal sign to
115
+ # the previous option added.
116
+ #
117
+ # option '-i'
118
+ # equal_file 'path/output' # -i=path/output
119
+ #
120
+ def equal_file(filename, options = {})
121
+ return unless @shell.applicable?(options)
122
+ raise "An option is required for equal_file" unless @last_arg == :option
123
+ @command << '=' << @shell.escape_filename(filename)
124
+ @last_arg = :file
125
+ end
126
+
127
+ # Add the value of an option.
128
+ #
129
+ # option '-x'
130
+ # join_value 123 # -x 123
131
+ #
132
+ def value(value, options = {})
133
+ return unless @shell.applicable?(options)
134
+ raise "An option is required for value" unless @last_arg == :option
135
+ @command << ' ' << @shell.escape_filename(value.to_s)
136
+ @last_arg = :value
137
+ end
138
+
139
+ # Add the value of an option, joinning it without a separator to
140
+ # the previous option added.
141
+ #
142
+ # option '-x'
143
+ # join_value 123 # -x123
144
+ #
145
+ def join_value(value, options = {})
146
+ return unless @shell.applicable?(options)
147
+ raise "An option is required for join_value" unless @last_arg == :option
148
+ @command << @shell.escape_value(value)
149
+ @last_arg = :value
150
+ end
151
+
152
+ # Add the value of an option, attaching it with an equal sign to
153
+ # the previous option added.
154
+ #
155
+ # option '-x'
156
+ # equal_value 123 # -x=123
157
+ #
158
+ def equal_value(value, options = {})
159
+ return unless @shell.applicable?(options)
160
+ raise "An option is required for equal_value" unless @last_arg == :option
161
+ @command << '=' << @shell.escape_value(value)
162
+ @last_arg = :value
163
+ end
164
+
165
+ # Add a generic argument to the command.
166
+ def argument(value, options = {})
167
+ return unless @shell.applicable?(options)
168
+ @command << ' ' << @shell.escape_value(value)
169
+ @last_arg = :argument
170
+ end
171
+
172
+ end
173
+
174
+ # An executable system command
175
+ class Command
176
+ def initialize(command)
177
+ @command = command
178
+ @output = nil
179
+ @status = nil
180
+ @error_output = nil
181
+ @error = nil
182
+ end
183
+
184
+ attr_reader :command, :output, :status, :error_output, :error
185
+
186
+ # Execute the command.
187
+ #
188
+ # The exit status of the command is retained in the +status+ attribute
189
+ # (and its numeric value in the +status_value+ attribute).
190
+ #
191
+ # The standard output of the command is captured and retained in the
192
+ # +output+ attribute.
193
+ #
194
+ # By default, the standar error output of the command is not
195
+ # captured, so it will be shown on the console unless redirected.
196
+ #
197
+ # Standard error can be captured and interleaved with the standard
198
+ # output passing the option
199
+ #
200
+ # error_output: :mix
201
+ #
202
+ # Error output can be captured and keep separate inthe +error_output+
203
+ # attribute with this option:
204
+ #
205
+ # error_output: :separate
206
+ #
207
+ # The value returned is by defaut, like in Kernel#system,
208
+ # true if the command gives zero exit status, false for non zero exit status,
209
+ # and nil if command execution fails.
210
+ #
211
+ # The +:return+ option can be used to make this method return other
212
+ # attribute of the executed command.
213
+ #
214
+ def run(options = {})
215
+ @output = @status = @error_output = @error = nil
216
+ begin
217
+ case options[:error_output]
218
+ when :mix # mix stderr with stdout
219
+ @output, @status = Open3.capture2e(@command)
220
+ when :separate
221
+ @output, @error_output, @status = Open3.capture3(@command)
222
+ else # :console (do not capture stderr output)
223
+ @output, @status = Open3.capture2(@command)
224
+ end
225
+ rescue => error
226
+ @error = error.dup
227
+ end
228
+ case options[:return]
229
+ when :status
230
+ @status
231
+ when :status_value
232
+ status_value
233
+ when :output
234
+ @output
235
+ when :error_output
236
+ @error_output
237
+ else
238
+ @error ? nil : @status.success? ? true : false
239
+ end
240
+ end
241
+
242
+ def status_value
243
+ @status && @status.exitstatus
244
+ end
245
+
246
+ # did the command execution caused an exception?
247
+ def error?
248
+ !@error.nil?
249
+ end
250
+
251
+ # did the command execute without error and returned a success status?
252
+ def success?
253
+ !error? && @status.success?
254
+ end
255
+
256
+ def to_s
257
+ command
258
+ end
259
+
260
+ end
261
+
262
+ # Build a command.
263
+ #
264
+ # See the Definition class.
265
+ #
266
+ def self.command(command, options = {}, &block)
267
+ definition = Definition.new(command, options)
268
+ definition.instance_eval &block if block
269
+ Command.new definition.command
270
+ end
271
+
272
+ # Build and run a command
273
+ def self.run(command, options = {}, &block)
274
+ command(command, options, &block).run
275
+ end
276
+
277
+ # Get the type of OS of the host.
278
+ def self.local_os_type
279
+ if OS.windows?
280
+ :windows
281
+ else
282
+ :unix
283
+ end
284
+ end
285
+
286
+ OS_TYPE_SYNONIMS = {
287
+ unix: :unix,
288
+ windows: :windows,
289
+ bash: :unix,
290
+ linux: :unix,
291
+ osx: :unix,
292
+ cmd: :windows
293
+ }
294
+
295
+ def self.os_type(options = {})
296
+ options[:os] || local_os_type
297
+ end
298
+
299
+ def self.escape(text, options = {})
300
+ case os_type(options)
301
+ when :windows
302
+ '"' + text.gsub('"', '""') + '"'
303
+ else
304
+ Shellwords.shellescape(text)
305
+ end
306
+ end
307
+
308
+ def self.line_separator(options = {})
309
+ case os_type(options)
310
+ when :windows
311
+ '^\n'
312
+ else
313
+ '\\\n'
314
+ end
315
+ end
316
+
317
+ def self.option_switch(options = {})
318
+ case os_type(options)
319
+ when :windows
320
+ '/'
321
+ else
322
+ '-'
323
+ end
324
+ end
325
+
326
+ class Shell
327
+
328
+ def initialize(options = {})
329
+ @type = SysCmd.os_type(options)
330
+ end
331
+
332
+ attr_reader :type
333
+
334
+ def escape(text)
335
+ SysCmd.escape(text, os: @type)
336
+ end
337
+
338
+ def escape_filename(name)
339
+ escape name
340
+ end
341
+
342
+ def escape_value(value)
343
+ escape value.to_s
344
+ end
345
+
346
+ def line_separator
347
+ SysCmd.line_separator(os: @type)
348
+ end
349
+
350
+ def option_switch
351
+ SysCmd.option_switch(os: @type)
352
+ end
353
+
354
+ def applicable?(options = {})
355
+ applicable = true
356
+ only_on = Array(options[:only_on])
357
+ unless only_on.empty?
358
+ applicable = false unless only_on.include?(@type)
359
+ end
360
+ except_on = Array(options[:except_on])
361
+ unless except_on.empty?
362
+ applicable = false if except_on.include?(@type)
363
+ end
364
+ applicable
365
+ end
366
+
367
+ end
368
+
369
+ # Execute a block of code only on some systems
370
+ #
371
+ # cmd = nil
372
+ # SysCmd.only_on :unix do
373
+ # cmd = CmdSys.command('ls')
374
+ # end
375
+ # SysCmd.only_on :windows do
376
+ # cmd = CmdSys.command('dir')
377
+ # end
378
+ # cmd.run
379
+ # files = cmd.output
380
+ #
381
+ def self.only_on(*os_types)
382
+ return if os_types.empty?
383
+ yield if Shell.new.applicable?(only_on: os_types)
384
+ end
385
+
386
+ # Execute a block of code except on some system(s)
387
+ def self.except_on(*os_types)
388
+ yield if Shell.new.applicable?(except_on: os_types)
389
+ end
390
+
391
+ # Execute a block of code if the options +:only_on+,
392
+ # +:except_on+ are satisfied in the host system.
393
+ def self.execute(options = {})
394
+ yield if Shell.new.applicable?(options)
395
+ end
396
+
397
+ end
@@ -0,0 +1,3 @@
1
+ module SysCmd
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sys_cmd/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sys_cmd"
8
+ spec.version = SysCmd::VERSION
9
+ spec.authors = ["Javier Goizueta"]
10
+ spec.email = ["jgoizueta@gmail.com"]
11
+
12
+ spec.summary = %q{Execute shell commands.}
13
+ spec.description = %q{Define and execute shell commands on Unix-like & Windows systems.}
14
+ spec.homepage = "https://github.com/jgoizueta/sys_cmd"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "os", "~> 0.9.6"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.9"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "minitest", "~> 5.4"
27
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sys_cmd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Javier Goizueta
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-04-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: os
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.9.6
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.9.6
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.9'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.4'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.4'
69
+ description: Define and execute shell commands on Unix-like & Windows systems.
70
+ email:
71
+ - jgoizueta@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".travis.yml"
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - bin/console
83
+ - bin/setup
84
+ - lib/sys_cmd.rb
85
+ - lib/sys_cmd/version.rb
86
+ - sys_cmd.gemspec
87
+ homepage: https://github.com/jgoizueta/sys_cmd
88
+ licenses:
89
+ - MIT
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 2.4.6
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: Execute shell commands.
111
+ test_files: []