eksek 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 38c99ca643c947821f164f389981a9958424ede39ba1aeea5ba956265291d0ff
4
+ data.tar.gz: bc09955bf615924204cad88a4a7b840826633184b6830207cb1193e71f132346
5
+ SHA512:
6
+ metadata.gz: a91f53242d38998a7b2ab739050379b07108697c488f9dc69120e8538c6ee1299a66cc07ea203bca54aa76341d8c817f107af365cbaa669f6c365704ca780757
7
+ data.tar.gz: 2918aa0f4ecea9affba5a6d531a7fa7cfd89ae2186ae30f033efda40e9480a91157afe4d20879591e7e8013a92a19adea79284dcc8cbc966737d6b6624080f7f
data/.gemspec ADDED
@@ -0,0 +1,18 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'eksek'
3
+ s.version = '0.1.0'
4
+ s.authors = ['Johnny Lee-Othon', 'Thomas Bretzke']
5
+ s.homepage = 'https://github.com/taccon/eksek'
6
+ s.summary = 'A better backticks'
7
+ s.description = <<~END
8
+ Execute shell commands and easily get stdout, stderr, exit code, and more
9
+ END
10
+ s.license = 'ISC'
11
+
12
+ s.files = Dir['.gemspec', 'lib/**/*.rb', 'license.txt', 'readme.md']
13
+
14
+ s.required_ruby_version = '~> 2.3'
15
+ s.add_development_dependency 'rspec','~> 3'
16
+ s.add_development_dependency 'rubocop', '~> 0'
17
+ s.add_development_dependency 'bundler', '~> 1.0'
18
+ end
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # eksek
2
+
3
+ [![Travis](https://img.shields.io/travis/jleeothon/eksek.svg)](https://travis-ci.org/jleeothon/eksek)
4
+
5
+ `eksek` is a library to run shell commands synchronously and obtain any of the standard output, standard error, and exit code with flexibility.
6
+
7
+ ## Features
8
+
9
+ ### Basic usage
10
+
11
+ Use the `eksek` to execute a command:
12
+
13
+ ```ruby
14
+ eksek 'echo Hello'
15
+ ```
16
+
17
+ This returns an `EksekResult` object providing the following methods:
18
+
19
+ - `exit` returns the exit code.
20
+ - `stdout` returns the standard output as a string.
21
+ - `stderr` returns the standard error as a string.
22
+ - `success?` returns `true` or `false` depending of the exit code (`0` for `true`).
23
+ - `success!` throws an exception if the command exited with a non-0 code.
24
+
25
+ The `success!` method can be chained with any other of the above ones and it is wrapped in the convenience method `eksek!` to have a "fail or return" like so:
26
+
27
+ ```ruby
28
+ puts eksek!('echo Hello').stdout # Hello
29
+
30
+ # The above is essentially the same as:
31
+
32
+ puts eksek('echo Hello').success!.stdout
33
+ ```
34
+
35
+ ### Writing to stdin
36
+
37
+ To write into the standard input a block can be used:
38
+
39
+ ```ruby
40
+ r = eksek('read A; echo $A') { |stdin| stdin.write "Hello" }
41
+ r.success!
42
+ ```
43
+
44
+ If the block returns a `String` or `IO`, it will be written into the stdio.
45
+
46
+ ```ruby
47
+ r = eksek('read A; echo $A') { "Hello" }
48
+ r.success!
49
+
50
+ r = eksek('read A; echo $A') { File.open('myfile.txt') }
51
+ r.success!
52
+ ```
53
+
54
+ ### Passing other options
55
+
56
+ `eksek` has the same signature as `Process#spawn`. This means that:
57
+
58
+ - the first parameter can optionally be a hash, passed as the process environment;
59
+ - the command can be passed as a single string or as variable-length arguments;
60
+ - other options can be passed as hash arguments.
61
+
62
+ Additionally, the environment will have its keys stringified, so that symbols can be used too. For example:
63
+
64
+ ```ruby
65
+ r = eksek { A: 'Hello' }, 'echo $A'
66
+ puts r.stdout # Hello
67
+
68
+ r = eksek 'echo', 'Hello'
69
+ puts r.stdout # Hello
70
+
71
+ r = eksek 'echo $PWD', chdir: '/tmp'
72
+ puts r.stdout # /tmp
73
+ ```
74
+
75
+ ### Further information
76
+
77
+ In case you prefer an object oriented method, you can also use the `Eksekuter` class that is used by the `eksek` method directly. The following examples are basically the same:
78
+
79
+ ```ruby
80
+ # With eksek
81
+ eksek 'echo Hello' { 'This goes to stdin.' }
82
+ # With Eksekuter
83
+ Eksekuter.new('echo Hello').run { 'This goes to stdin.' }
84
+ ```
data/lib/eksek.rb ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'eksekuter'
4
+
5
+ # Executes shell commands and can be specified to return the standard output,
6
+ # standard error, etc.
7
+ # Accepts the same options as Process.spawn e.g. :chdir, :env.
8
+ module Kernel
9
+ private
10
+
11
+ def eksek *args, **opts, &block
12
+ Eksekuter.new(*args, **opts).run(&block)
13
+ end
14
+
15
+ def eksek! *args, **opts, &block
16
+ eksek(*args, **opts, &block).success!
17
+ end
18
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Represents an error in the executio of a system call when success! was
4
+ # required.
5
+ class EksekError < StandardError
6
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'eksek_error'
4
+
5
+ # Describes a result object to be used for evaluating
6
+ # return values from a command
7
+ class EksekResult
8
+ # rubocop:disable Metrics/ParameterLists
9
+ def initialize env, cmd, exit_code, success, stdout, stderr
10
+ @env = env
11
+ @cmd = cmd
12
+ @exit_code = exit_code
13
+ @success = success
14
+ @stdout = stdout
15
+ @stderr = stderr
16
+ end
17
+ # rubocop:enable Metrics/ParameterLists
18
+
19
+ attr_reader :exit_code, :stdout, :stderr
20
+
21
+ def success?
22
+ @success
23
+ end
24
+
25
+ def success!
26
+ raise EksekError, "Command failed: #{@cmd.inspect}" unless success?
27
+ self
28
+ end
29
+
30
+ def to_s
31
+ stdout
32
+ end
33
+ end
data/lib/eksekuter.rb ADDED
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ require_relative 'eksek_result'
6
+
7
+ # Class that command execution is delegated to by Eksek.
8
+ class Eksekuter
9
+ def initialize *args, **opts
10
+ @env = args[0].is_a?(Hash) ? args.shift : {}
11
+ @env = @env.each_with_object({}) { |(k, v), o| o[k.to_s] = v }
12
+ @cmd = args.size == 1 ? args.first : args
13
+ @opts = opts
14
+ end
15
+
16
+ def run &block
17
+ spawn_process
18
+ write_and_close_stdin(&block)
19
+ wait
20
+ read_and_close_stdout_stderr
21
+ assemble_result
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader(
27
+ :cmd,
28
+ :env,
29
+ :err_str,
30
+ :opts,
31
+ :out_str,
32
+ :process_status,
33
+ :stdin,
34
+ :wait_thr,
35
+ )
36
+
37
+ def stdout
38
+ @stdout ||= StringIO.new('', 'r')
39
+ end
40
+
41
+ def stderr
42
+ @stderr ||= StringIO.new('', 'r')
43
+ end
44
+
45
+ def spawn_process
46
+ @stdin, @stdout, @stderr, @wait_thr = Open3.popen3(env, *cmd, opts)
47
+ nil
48
+ end
49
+
50
+ def write_and_close_stdin
51
+ return if stdin.closed? || !block_given?
52
+ block_result = yield stdin
53
+ block_result = StringIO.new(block_result) if block_result.is_a? String
54
+ IO.copy_stream(block_result, stdin) if block_result.respond_to? :read
55
+ stdin.close
56
+ nil
57
+ end
58
+
59
+ def wait
60
+ @process_status = wait_thr.value
61
+ end
62
+
63
+ def read_and_close_stdout_stderr
64
+ streams = [stdout, stderr]
65
+ @out_str, @err_str = streams.map(&:read).map(&:chomp)
66
+ streams.each(&:close)
67
+ nil
68
+ end
69
+
70
+ def assemble_result
71
+ EksekResult.new(
72
+ env,
73
+ cmd,
74
+ process_status.exitstatus,
75
+ process_status.success?,
76
+ out_str,
77
+ err_str,
78
+ )
79
+ end
80
+ end
data/license.txt ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2018, Johnny Enrique Lee-Othon
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: eksek
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Johnny Lee-Othon
8
+ - Thomas Bretzke
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2018-04-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '3'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '3'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rubocop
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: bundler
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '1.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '1.0'
56
+ description: 'Execute shell commands and easily get stdout, stderr, exit code, and
57
+ more
58
+
59
+ '
60
+ email:
61
+ executables: []
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - ".gemspec"
66
+ - README.md
67
+ - lib/eksek.rb
68
+ - lib/eksek_error.rb
69
+ - lib/eksek_result.rb
70
+ - lib/eksekuter.rb
71
+ - license.txt
72
+ homepage: https://github.com/taccon/eksek
73
+ licenses:
74
+ - ISC
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - "~>"
83
+ - !ruby/object:Gem::Version
84
+ version: '2.3'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.7.6
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: A better backticks
96
+ test_files: []