tty-command 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: 1fc9ae4bb4984bdcf9fdc7a49fd6b9b912ff60a2
4
+ data.tar.gz: d47f52018f538b780aac9eba1abafcf696d1ebd7
5
+ SHA512:
6
+ metadata.gz: 6b4b96a1351485b1b4fa436a58adc71d08f91f21e81f2f4cdeea78b10773bce7d5681e6340add08d963a57e17183b65799c17fc2d74ec32c48c5b325950994e6
7
+ data.tar.gz: ba6f5e69a2588cdf37f33bea99e10147ccc0b0d0b5f3f91dd6d0b24ec44cac7b24627184644ac07627557d91de950f42012c6cfdebab5eea644f900a72e8d598
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --color
3
+ --warnings
@@ -0,0 +1,25 @@
1
+ ---
2
+ language: ruby
3
+ sudo: false
4
+ cache: bundler
5
+ script: "bundle exec rake ci"
6
+ rvm:
7
+ - 1.9.3
8
+ - 2.0
9
+ - 2.1
10
+ - 2.2
11
+ - 2.3.0
12
+ - ruby-head
13
+ - jruby
14
+ - jruby-9000
15
+ - rbx-2
16
+ matrix:
17
+ allow_failures:
18
+ - rvm: ruby-head
19
+ - rvm: jruby
20
+ - rvm: jruby-9000
21
+ fast_finish: true
22
+ branches:
23
+ only: master
24
+ notifications:
25
+ email: false
@@ -0,0 +1,7 @@
1
+ # Change log
2
+
3
+ ## [v0.1.0] - 2016-05-29
4
+
5
+ * Initial implementation and release
6
+
7
+ [v0.1.0]: https://github.com/piotrmurach/tty-command/compare/v0.1.0
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at [email]. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'simplecov', '~> 0.10.0'
7
+ gem 'coveralls', '~> 0.8.2'
8
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Piotr Murach
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,392 @@
1
+ # TTY::Command [![Gitter](https://badges.gitter.im/Join%20Chat.svg)][gitter]
2
+ [![Gem Version](https://badge.fury.io/rb/tty-command.svg)][gem]
3
+ [![Build Status](https://secure.travis-ci.org/piotrmurach/tty-command.svg?branch=master)][travis]
4
+ [![Code Climate](https://codeclimate.com/github/piotrmurach/tty-command/badges/gpa.svg)][codeclimate]
5
+ [![Coverage Status](https://coveralls.io/repos/github/piotrmurach/tty-command/badge.svg)][coverage]
6
+ [![Inline docs](http://inch-ci.org/github/piotrmurach/tty-command.svg?branch=master)][inchpages]
7
+
8
+ [gitter]: https://gitter.im/piotrmurach/tty
9
+ [gem]: http://badge.fury.io/rb/tty-command
10
+ [travis]: http://travis-ci.org/piotrmurach/tty-command
11
+ [codeclimate]: https://codeclimate.com/github/piotrmurach/tty-command
12
+ [coverage]: https://coveralls.io/github/piotrmurach/tty-command
13
+ [inchpages]: http://inch-ci.org/github/piotrmurach/tty-command
14
+
15
+ > Run external commands with pretty output logging and capture stdout, stderr and exit status. Redirect stdin, stdout and stderr of each command to a file or a string.
16
+
17
+ **TTY::Command** provides independent command execution component for [TTY](https://github.com/piotrmurach/tty) toolkit.
18
+
19
+ ## Motivation
20
+
21
+ Complex software projects aren't just a single app. These projects usually spawn dozens or hundreds of supplementary standalone scripts which are just as important as the app itself. Examples include - data validation, deployment, monitoring, database maintenance, backup & restore, configuration management, crawling, ETL, analytics, log file processing, custom reports, etc. One of the contributors to **TTY::Command** counted 222 scripts in the `bin` directory for his startup.
22
+
23
+ Why should we be handcuffed to `sh` or `bash` for these scripts when we could be using Ruby? Ruby is easier to write and more fun, and we gain a lot by using a better language. It's nice for everyone to just use Ruby everywhere.
24
+
25
+ **TTY::Command** tries to add value in other ways. It'll halt automatically if a command fails. It's easy to get verbose or quiet output as appropriate, or even capture output and parse it with Ruby. Escaping arguments is a breeze. These are all areas where traditional shell scripts tend to fall flat.
26
+
27
+ ## Installation
28
+
29
+ Add this line to your application's Gemfile:
30
+
31
+ ```ruby
32
+ gem 'tty-command'
33
+ ```
34
+
35
+ And then execute:
36
+
37
+ $ bundle
38
+
39
+ Or install it yourself as:
40
+
41
+ $ gem install tty-command
42
+
43
+ ## Contents
44
+
45
+ * [1. Usage](#1-usage)
46
+ * [2. Interface](#2-interface)
47
+ * [2.1. Run](#21-run)
48
+ * [2.2. Run!](#22-run)
49
+ * [2.3. Test](#23-test)
50
+ * [2.4. Logging](#24-logging)
51
+ * [2.5. Dry run](#25-dry-run)
52
+ * [3. Advanced Interface](#3-advanced-interface)
53
+ * [3.1. Environment variables](#31-environment-variables)
54
+ * [3.2. Options](#32-options)
55
+ * [3.2.1. Current directory](#321-current-directory)
56
+ * [3.2.2. Redirection](#322-redirection)
57
+ * [3.2.3. Timeout](#323-timeout)
58
+ * [3.2.4. User](#324-user)
59
+ * [3.2.5. Group](#325-group)
60
+ * [3.2.6. Umask](#326-umask)
61
+ * [3.3. Result](#33-result)
62
+ * [3.3.1. success?](#331-success)
63
+ * [3.3.2. failure?](#332-failure)
64
+ * [3.3.3. exited?](#333-exited)
65
+ * [3.4. Custom printer](#34-custom-printer)
66
+ * [4. Example](#4-example)
67
+
68
+ ## 1. Usage
69
+
70
+ Create a command instance and then run some commands:
71
+
72
+ ```ruby
73
+ cmd = TTY::Command.new
74
+ cmd.run('ls -la')
75
+ cmd.run('echo Hello!')
76
+ ```
77
+
78
+ Note that `run` will throw an exception if the command fails. This is already an improvement over ordinary shell scripts, which just keep on going when things go bad. That usually makes things worse.
79
+
80
+ You can use the return value to capture stdout and stderr:
81
+
82
+ ```ruby
83
+ out, err = cmd.run('cat ~/.bashrc | grep alias')
84
+ ```
85
+
86
+ Instead of using a plain old string, you can break up the arguments and they'll get escaped if necessary:
87
+
88
+ ```ruby
89
+ path = "hello world"
90
+ FileUtils.touch(path)
91
+ cmd.run("sum #{path}") # this will fail due to bad escaping
92
+ cmd.run("sum", path) # this gets escaped automatically
93
+ ```
94
+
95
+ ## 2. Interface
96
+
97
+ ### 2.1 Run
98
+
99
+ Run starts the specified command and waits for it to complete.
100
+
101
+ The argument signature of `run` is as follows:
102
+
103
+ `run([env], command, [argv1, ...], [options])`
104
+
105
+ The `env`, `command` and `options` arguments are described in the following sections.
106
+
107
+ For example, to display file contents:
108
+
109
+ ```ruby
110
+ cmd.run('cat file.txt')
111
+ ```
112
+
113
+ If the command succeeds, a `TTY::Command::Result` is returned that records stdout and stderr:
114
+
115
+ ```ruby
116
+ out, err = cmd.run('date')
117
+ puts "The date is #{out}"
118
+ # => "The date is Tue 10 May 2016 22:30:15 BST\n"
119
+ ```
120
+
121
+ If the command fails (with a non-zero exit code), a `TTY::Command::ExitError` is raised. The `ExitError` message will include:
122
+
123
+ * the name of command executed
124
+ * the exit status
125
+ * stdout bytes
126
+ * stderr bytes
127
+
128
+ If the error output is very long, the stderr may contain only a prefix, number of omitted bytes and suffix.
129
+
130
+ ### 2.2 Run!
131
+
132
+ If you expect a command to fail occasionally, use `run!` instead. Then you can detect failures and respond appropriately. For example:
133
+
134
+ ```ruby
135
+ if cmd.run!('which xyzzy').failure?
136
+ cmd.run('brew install xyzzy')
137
+ end
138
+ ```
139
+
140
+ ### 2.3 Test
141
+
142
+ To simulate classic bash test command you case use `test` method with expression to check as a first argument:
143
+
144
+ ```ruby
145
+ if cmd.test '-e /etc/passwd'
146
+ puts "Sweet..."
147
+ else
148
+ puts "Ohh no! Where is it?"
149
+ exit 1
150
+ end
151
+ ```
152
+
153
+ ### 2.4 Logging
154
+
155
+ By default, when a command is run, the command and the output are printed to `stdout` using the `:pretty` printer. If you wish to change printer you can do so by passing a `:printer` option:
156
+
157
+ * `:null` - no output
158
+ * `:pretty` - colorful output
159
+ * `:progress` - minimal output with green dot for success and F for failure
160
+ * `:quiet` - only output actual command stdout and stderr
161
+
162
+ like so:
163
+
164
+ ```ruby
165
+ cmd = TTY::Command.new(printer: :progress)
166
+ ```
167
+
168
+ By default the printers log to `stdout` but this can be changed by passing an object that responds to `<<` message:
169
+
170
+ ```ruby
171
+ logger = Logger.new('dev.log')
172
+ cmd = TTY::Command.new(output: output)
173
+ ```
174
+
175
+ You can force the printer to always in print in color by passing the `:color` option:
176
+
177
+ ```ruby
178
+ cmd = TTY::Command.new(color: true)
179
+ ```
180
+
181
+ ### 2.5 Dry run
182
+
183
+ Sometimes it can be useful to put your script into a "dry run" mode that prints commands without actually running them. To simulate execution of the command use the `:dry_run` option:
184
+
185
+ ```ruby
186
+ cmd = TTY::Command.new(dry_run: true)
187
+ cmd.run(:rm, 'all_my_files')
188
+ # => [123abc] (dry run) rm all_my_files
189
+ ```
190
+
191
+ To check what mode the command is in use the `dry_run?` query helper:
192
+
193
+ ```ruby
194
+ cmd.dry_run? # => true
195
+ ```
196
+
197
+ ## 3. Advanced Interface
198
+
199
+ ### 3.1 Environment variables
200
+
201
+ The environment variables need to be provided as hash entries, that can be set directly as a first argument:
202
+
203
+ ```ruby
204
+ cmd.run({'RAILS_ENV' => 'PRODUCTION'}, :rails, 'server')
205
+ ```
206
+
207
+ or as an option with `:env` key:
208
+
209
+ ```ruby
210
+ cmd.run(:rails, 'server', env: {rails_env: :production})
211
+ ```
212
+
213
+ When a value in env is nil, the variable is unset in the child process:
214
+
215
+ ```ruby
216
+ cmd.run(:echo, 'hello', env: {foo: 'bar', baz: nil})
217
+ ```
218
+
219
+ ### 3.2 Options
220
+
221
+ When a hash is given in the last argument (options), it allows to specify a current directory, umask, user, group and and zero or more fd redirects for the child process.
222
+
223
+ #### 3.2.1 Current directory
224
+
225
+ To change directory in which the command is run pass the `:chidir` option:
226
+
227
+ ```ruby
228
+ cmd.run(:echo, 'hello', chdir: '/var/tmp')
229
+ ```
230
+
231
+ #### 3.2.2 Redirection
232
+
233
+ The streams can be redirected using hash keys `:in`, `:out`, `:err`, a fixnum, an IO and array. The keys specify a given file descriptor for the child process.
234
+
235
+ You can specify a filename for redirection as a hash value:
236
+
237
+ ```ruby
238
+ cmd.run(:ls, :in => "/dev/null") # read mode
239
+ cmd.run(:ls, :out => "/dev/null") # write mode
240
+ cmd.run(:ls, :err => "log") # write mode
241
+ cmd.run(:ls, [:out, :err] => "/dev/null") # write mode
242
+ cmd.run(:ls, 3 => "/dev/null") # read mode
243
+ ```
244
+
245
+ You can also provide actual file descriptor for redirection:
246
+
247
+ ```ruby
248
+ cmd.run(:cat, :in => open('/etc/passwd'))
249
+ ```
250
+
251
+ For example, to merge stderr into stdout you would do:
252
+
253
+ ```ruby
254
+ cmd.run(:ls, '-la', :stderr => :stdout)
255
+ cmd.run(:ls, '-la', 2 => 1)
256
+ ```
257
+
258
+ #### 3.2.3 Timeout
259
+
260
+ You can timeout command execuation by providing the `:timeout` option in seconds:
261
+
262
+ ```ruby
263
+ cmd.run("while test 1; sleep 1; done", timeout: 5)
264
+ ```
265
+
266
+ Please run `examples/timeout.rb` to see timeout in action.
267
+
268
+ #### 3.2.4 User
269
+
270
+ To run command as a given user do:
271
+
272
+ ```ruby
273
+ cmd.run(:echo, 'hello', user: 'piotr')
274
+ ```
275
+
276
+ #### 3.2.5 Group
277
+
278
+ To run command as part of group do:
279
+
280
+ ```ruby
281
+ cmd.run(:echo, 'hello', group: 'devs')
282
+ ```
283
+
284
+ #### 3.2.6 Umask
285
+
286
+ To run command with umask do:
287
+
288
+ ```ruby
289
+ cmd.run(:echo, 'hello', umask: '007')
290
+ ```
291
+
292
+ ### 3.3 Result
293
+
294
+ Each time you run command the stdout and stderr are captured and return as result. The result can be examined directly by casting it to tuple:
295
+
296
+ ```ruby
297
+ out, err = cmd.run(:echo, 'Hello')
298
+ ```
299
+
300
+ However, if you want to you can defer reading:
301
+
302
+ ```ruby
303
+ result = cmd.run(:echo, 'Hello')
304
+ result.out
305
+ result.err
306
+ ```
307
+
308
+ #### 3.3.1 success?
309
+
310
+ To check if command exited successfully use `success?`:
311
+
312
+ ```ruby
313
+ result = cmd.run(:echo, 'Hello')
314
+ result.success? # => true
315
+ ```
316
+
317
+ #### 3.3.2 failure?
318
+
319
+ To check if command exited unsuccessfully use `failure?` or `failed?`:
320
+
321
+ ```ruby
322
+ result = cmd.run(:echo, 'Hello')
323
+ result.failure? # => false
324
+ result.failed? # => false
325
+ ```
326
+
327
+ #### 3.3.3 exited?
328
+
329
+ To check if command run to complition use `exited?` or `complete?`:
330
+
331
+ ```ruby
332
+ result = cmd.run(:echo, 'Hello')
333
+ result.exited? # => true
334
+ result.complete? # => true
335
+ ```
336
+
337
+ ### 3.4 Custom printer
338
+
339
+ If the built-in printers do not meet your requirements you can create your own. At the very minimum you need to specify the `write` method that will be called during the lifecycle of command execution:
340
+
341
+ ```ruby
342
+ CustomPrinter < TTY::Command::Printers::Abstract
343
+ def write(message)
344
+ puts message
345
+ end
346
+ end
347
+
348
+ printer = CustomPrinter
349
+
350
+ cmd = TTY::Command.new(printer: printer)
351
+ ```
352
+
353
+ ## 4. Example
354
+
355
+ Here's a slightly more elaborate example to illustrate how tty-command can improve on plain old shell scripts. This example installs a new version of Ruby on an Ubuntu machine.
356
+
357
+ ```ruby
358
+ cmd = TTY::Command.new
359
+
360
+ # dependencies
361
+ cmd.run "apt-get -y install build-essential checkinstall"
362
+
363
+ # fetch ruby if necessary
364
+ if !File.exists?("ruby-2.3.0.tar.gz")
365
+ puts "Downloading..."
366
+ cmd.run "wget http://ftp.ruby-lang.org/pub/ruby/2.3/ruby-2.3.0.tar.gz"
367
+ cmd.run "tar xvzf ruby-2.3.0.tar.gz"
368
+ end
369
+
370
+ # now install
371
+ Dir.chdir("ruby-2.3.0") do
372
+ puts "Building..."
373
+ cmd.run "./configure --prefix=/usr/local"
374
+ cmd.run "make"
375
+ end
376
+ ```
377
+
378
+ ## Development
379
+
380
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
381
+
382
+ ## Contributing
383
+
384
+ Bug reports and pull requests are welcome on GitHub at https://github.com/piotrmurach/tty-command. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
385
+
386
+ ## License
387
+
388
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
389
+
390
+ ## Copyright
391
+
392
+ Copyright (c) 2016 Piotr Murach. See LICENSE for further details.