scripto 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +138 -16
- data/Rakefile +2 -0
- data/Vagrantfile +121 -0
- data/lib/scripto/csv_commands.rb +9 -2
- data/lib/scripto/file_commands.rb +35 -0
- data/lib/scripto/main.rb +2 -0
- data/lib/scripto/misc_commands.rb +9 -2
- data/lib/scripto/print_commands.rb +21 -12
- data/lib/scripto/run_commands.rb +20 -4
- data/lib/scripto/version.rb +2 -1
- data/scripto.gemspec +1 -1
- data/test/test_print.rb +2 -2
- data/vagrant_provision +50 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd230249468a8975ef75cad139685d21a535842b
|
4
|
+
data.tar.gz: f9c7c8a34a06b1892b2302a70dbf09ce8c6be066
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 353e0017433d1a40c4a02c31543a174774f3261d70ec722af53703499f24e65a94cd7d024ef41de9479522865f70907c292e783f72f444b8fccf0831e3ae262c
|
7
|
+
data.tar.gz: 22d9cae0e3a74b4aa41e410cd1cb9d6671481d58a61477e7c1af681311abd0c04db79365f025122b6b6fd92bed1aff37bfc2436e09d9bd42257527f99615fced
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,29 +1,151 @@
|
|
1
1
|
# Scripto
|
2
2
|
|
3
|
-
|
3
|
+
Scripto is a framework for writing command line applications. It fills in many of the blanks that Ruby's standard library is missing:
|
4
4
|
|
5
|
-
|
5
|
+
* **print to $stderr** - Colored banners and a verbose mode to make your scripts louder.
|
6
|
+
* **file operations** - Mkdir, cp, mv, ln. These operations can take care of common edge cases, like creating the target directory before copying a file.
|
7
|
+
* **csv** - Read and write CSV files from hashes, Structs, or OpenStructs.
|
8
|
+
* **run commands** - Run external commands and raise errors on failure.
|
6
9
|
|
7
|
-
|
10
|
+
## Getting Started
|
8
11
|
|
9
|
-
|
12
|
+
You can call Scripto directly:
|
10
13
|
|
11
|
-
|
14
|
+
```ruby
|
15
|
+
require "scripto"
|
12
16
|
|
13
|
-
|
17
|
+
Scripto.banner("Starting installation...")
|
18
|
+
Scripto.cp("here.txt", "there.txt")
|
19
|
+
Scripto.mv("there.txt", "and_back.txt")
|
14
20
|
|
15
|
-
|
21
|
+
rows = [ ]
|
22
|
+
rows << { name: "Adam", score: 100 }
|
23
|
+
rows << { name: "Patrick", score: 99 }
|
24
|
+
Scripto.csv_write("scores.csv", rows)
|
16
25
|
|
17
|
-
|
26
|
+
if Scripto.fails("which git")
|
27
|
+
Scripto.run("brew install git")
|
28
|
+
end
|
18
29
|
|
19
|
-
|
30
|
+
Scripto.run("touch script_complete.txt")
|
31
|
+
```
|
20
32
|
|
21
|
-
|
33
|
+
You can also subclass `Scripto::Main`:
|
22
34
|
|
23
|
-
|
35
|
+
```ruby
|
36
|
+
#!/usr/bin/env ruby
|
24
37
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
38
|
+
require "scripto"
|
39
|
+
|
40
|
+
class Install < Scripto::Main
|
41
|
+
def initialize(options = {})
|
42
|
+
verbose! if options[:verbose]
|
43
|
+
|
44
|
+
banner("Starting installation...")
|
45
|
+
cp("here.txt", "there.txt")
|
46
|
+
mv("there.txt", "and_back.txt")
|
47
|
+
|
48
|
+
rows = [ ]
|
49
|
+
rows << { name: "Adam", score: 100 }
|
50
|
+
rows << { name: "Patrick", score: 99 }
|
51
|
+
csv_write("scores.csv", rows)
|
52
|
+
|
53
|
+
if fails("which git")
|
54
|
+
run("brew install git")
|
55
|
+
end
|
56
|
+
|
57
|
+
run("touch script_complete.txt")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
Install.new(verbose: true)
|
62
|
+
```
|
63
|
+
|
64
|
+
## Methods
|
65
|
+
|
66
|
+
### Print to $stderr
|
67
|
+
|
68
|
+
```
|
69
|
+
banner(str) - print a banner in green
|
70
|
+
warning(str) - print a warning in yellow
|
71
|
+
fatal(str) - print a fatal error in red, then exit(1)
|
72
|
+
|
73
|
+
verbose! - turn on verbose mode
|
74
|
+
vbanner(str) - print a colored banner in green if verbose is on
|
75
|
+
vprintf(str) - printf if verbose is on
|
76
|
+
vputs(str) - puts if verbose is on
|
77
|
+
```
|
78
|
+
|
79
|
+
### File operations
|
80
|
+
|
81
|
+
These operations are silent by default. If verbose is turned on, each command will be echoed first.
|
82
|
+
|
83
|
+
```
|
84
|
+
mkdir(dir) - mkdir -p dir
|
85
|
+
cp(src, dst, mkdir: false) - cp -p src dst, mkdir if necessary
|
86
|
+
mv(src, dst, mkdir: false) - mv src dst, mkdir if necessary
|
87
|
+
ln(src, dst, mkdir: false) - ln -sf src dst, mkdir if necessary
|
88
|
+
rm(file) - rm -f file
|
89
|
+
```
|
90
|
+
|
91
|
+
Each of these operations also has an "if_necessary" variant that only runs the operation if necessary. For example, `mkdir_if_necessary` only calls `mkdir` if the directory doesn't exist. When combined with verbose mode, this produces **a nice changelog** as the operations are run. You can run your script repeatedly and it'll only output actual changes.
|
92
|
+
|
93
|
+
Plus a few more:
|
94
|
+
|
95
|
+
```
|
96
|
+
chown(file, user) - chown user:user file
|
97
|
+
chmod(file, mode) - chmod mode file
|
98
|
+
rm_and_mkdir(dir) - rm -rf dir && mkdir -p dir (for tmp dirs)
|
99
|
+
copy_metadata(src, dst) - copy mode/atime/mtime from src to dst
|
100
|
+
```
|
101
|
+
|
102
|
+
### Run external commands (system, backtick, etc)
|
103
|
+
|
104
|
+
Each of these methods takes a `command` and an optional array of `args`. Use it like so:
|
105
|
+
|
106
|
+
```
|
107
|
+
run("echo hello world") # this will use a shell
|
108
|
+
run("echo, ["hello", "world"]) # NO shell, runs echo directly
|
109
|
+
```
|
110
|
+
|
111
|
+
This is like `Kernel#system` works - you can use the second variant (the array of arguments) to sidestep shell escaping issues. For example, these are the same but the second variant doesn't require those pesky single quotes:
|
112
|
+
|
113
|
+
```
|
114
|
+
run("cp file 'hello world")
|
115
|
+
run("cp, ["file", "hello world"])
|
116
|
+
```
|
117
|
+
|
118
|
+
Here are the various run commands:
|
119
|
+
|
120
|
+
```
|
121
|
+
run(command, args = nil) - run a command, raise an error
|
122
|
+
run_capture(command, args = nil) - capture the output similar to backtick, raise on error
|
123
|
+
run_quietly(command, args = nil) - redirect stdout/stderr to /dev/null, raise on error
|
124
|
+
|
125
|
+
run_succeeds?(command, args = nil) - return true if command succeeds
|
126
|
+
run_fails?(command, args = nil) - return true if command fails
|
127
|
+
```
|
128
|
+
|
129
|
+
### CSV read/write
|
130
|
+
|
131
|
+
Light wrappers around the CSV class built into Ruby. Used exclusively with hash-like objects, not arrays. The header row turns into the keys for the hash.
|
132
|
+
|
133
|
+
```
|
134
|
+
csv_read(path) - read a CSV, returns an array of Structs. Path can be a .gz.
|
135
|
+
csv_write(path, rows) - write rows to a CSV. Rows can be hashes, Structs or OpenStructs.
|
136
|
+
csv_to_stdout(rows) - write rows to stdout, like csv_write
|
137
|
+
csv_to_s(rows) - write rows to a string, like csv_write
|
138
|
+
```
|
139
|
+
|
140
|
+
### Misc
|
141
|
+
|
142
|
+
There are a few more useful tidbits in here:
|
143
|
+
|
144
|
+
```
|
145
|
+
whoami - just like the command
|
146
|
+
root? - return true if we're running as root
|
147
|
+
md5_file(path) - calculate md5 for a file
|
148
|
+
md5_string(str) - calculate md5 for a string
|
149
|
+
prompt?(question) - ask the user a question, return true if they say yes
|
150
|
+
random_string(len) - calculate a random alphanumeric string
|
151
|
+
```
|
data/Rakefile
CHANGED
data/Vagrantfile
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
# -*- mode: ruby -*-
|
2
|
+
# vi: set ft=ruby :
|
3
|
+
|
4
|
+
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
|
5
|
+
VAGRANTFILE_API_VERSION = "2"
|
6
|
+
|
7
|
+
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
8
|
+
# All Vagrant configuration is done here. The most common configuration
|
9
|
+
# options are documented and commented below. For a complete reference,
|
10
|
+
# please see the online documentation at vagrantup.com.
|
11
|
+
|
12
|
+
# Every Vagrant virtual environment requires a box to build off of.
|
13
|
+
config.vm.box = "precise32"
|
14
|
+
|
15
|
+
# The url from where the 'config.vm.box' box will be fetched if it
|
16
|
+
# doesn't already exist on the user's system.
|
17
|
+
config.vm.box_url = "http://files.vagrantup.com/precise32.box"
|
18
|
+
|
19
|
+
# amd
|
20
|
+
config.vm.provision :shell, path: "vagrant_provision", privileged: false
|
21
|
+
|
22
|
+
# Create a forwarded port mapping which allows access to a specific port
|
23
|
+
# within the machine from a port on the host machine. In the example below,
|
24
|
+
# accessing "localhost:8080" will access port 80 on the guest machine.
|
25
|
+
# config.vm.network :forwarded_port, guest: 80, host: 8080
|
26
|
+
|
27
|
+
# Create a private network, which allows host-only access to the machine
|
28
|
+
# using a specific IP.
|
29
|
+
# config.vm.network :private_network, ip: "192.168.33.10"
|
30
|
+
|
31
|
+
# Create a public network, which generally matched to bridged network.
|
32
|
+
# Bridged networks make the machine appear as another physical device on
|
33
|
+
# your network.
|
34
|
+
# config.vm.network :public_network
|
35
|
+
|
36
|
+
# If true, then any SSH connections made will enable agent forwarding.
|
37
|
+
# Default value: false
|
38
|
+
# config.ssh.forward_agent = true
|
39
|
+
|
40
|
+
# Share an additional folder to the guest VM. The first argument is
|
41
|
+
# the path on the host to the actual folder. The second argument is
|
42
|
+
# the path on the guest to mount the folder. And the optional third
|
43
|
+
# argument is a set of non-required options.
|
44
|
+
# config.vm.synced_folder "../data", "/vagrant_data"
|
45
|
+
|
46
|
+
# Provider-specific configuration so you can fine-tune various
|
47
|
+
# backing providers for Vagrant. These expose provider-specific options.
|
48
|
+
# Example for VirtualBox:
|
49
|
+
#
|
50
|
+
# config.vm.provider :virtualbox do |vb|
|
51
|
+
# # Don't boot with headless mode
|
52
|
+
# vb.gui = true
|
53
|
+
#
|
54
|
+
# # Use VBoxManage to customize the VM. For example to change memory:
|
55
|
+
# vb.customize ["modifyvm", :id, "--memory", "1024"]
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# View the documentation for the provider you're using for more
|
59
|
+
# information on available options.
|
60
|
+
|
61
|
+
# Enable provisioning with Puppet stand alone. Puppet manifests
|
62
|
+
# are contained in a directory path relative to this Vagrantfile.
|
63
|
+
# You will need to create the manifests directory and a manifest in
|
64
|
+
# the file precise32.pp in the manifests_path directory.
|
65
|
+
#
|
66
|
+
# An example Puppet manifest to provision the message of the day:
|
67
|
+
#
|
68
|
+
# # group { "puppet":
|
69
|
+
# # ensure => "present",
|
70
|
+
# # }
|
71
|
+
# #
|
72
|
+
# # File { owner => 0, group => 0, mode => 0644 }
|
73
|
+
# #
|
74
|
+
# # file { '/etc/motd':
|
75
|
+
# # content => "Welcome to your Vagrant-built virtual machine!
|
76
|
+
# # Managed by Puppet.\n"
|
77
|
+
# # }
|
78
|
+
#
|
79
|
+
# config.vm.provision :puppet do |puppet|
|
80
|
+
# puppet.manifests_path = "manifests"
|
81
|
+
# puppet.manifest_file = "site.pp"
|
82
|
+
# end
|
83
|
+
|
84
|
+
# Enable provisioning with chef solo, specifying a cookbooks path, roles
|
85
|
+
# path, and data_bags path (all relative to this Vagrantfile), and adding
|
86
|
+
# some recipes and/or roles.
|
87
|
+
#
|
88
|
+
# config.vm.provision :chef_solo do |chef|
|
89
|
+
# chef.cookbooks_path = "../my-recipes/cookbooks"
|
90
|
+
# chef.roles_path = "../my-recipes/roles"
|
91
|
+
# chef.data_bags_path = "../my-recipes/data_bags"
|
92
|
+
# chef.add_recipe "mysql"
|
93
|
+
# chef.add_role "web"
|
94
|
+
#
|
95
|
+
# # You may also specify custom JSON attributes:
|
96
|
+
# chef.json = { :mysql_password => "foo" }
|
97
|
+
# end
|
98
|
+
|
99
|
+
# Enable provisioning with chef server, specifying the chef server URL,
|
100
|
+
# and the path to the validation key (relative to this Vagrantfile).
|
101
|
+
#
|
102
|
+
# The Opscode Platform uses HTTPS. Substitute your organization for
|
103
|
+
# ORGNAME in the URL and validation key.
|
104
|
+
#
|
105
|
+
# If you have your own Chef Server, use the appropriate URL, which may be
|
106
|
+
# HTTP instead of HTTPS depending on your configuration. Also change the
|
107
|
+
# validation key to validation.pem.
|
108
|
+
#
|
109
|
+
# config.vm.provision :chef_client do |chef|
|
110
|
+
# chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME"
|
111
|
+
# chef.validation_key_path = "ORGNAME-validator.pem"
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# If you're using the Opscode platform, your validator client is
|
115
|
+
# ORGNAME-validator, replacing ORGNAME with your organization name.
|
116
|
+
#
|
117
|
+
# If you have your own Chef Server, the default validation client name is
|
118
|
+
# chef-validator, unless you changed the configuration.
|
119
|
+
#
|
120
|
+
# chef.validation_client_name = "ORGNAME-validator"
|
121
|
+
end
|
data/lib/scripto/csv_commands.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
require "csv"
|
2
|
-
require "ostruct"
|
3
2
|
require "zlib"
|
4
3
|
|
5
4
|
module Scripto
|
6
5
|
module CsvCommands
|
6
|
+
# Read a csv from +path+. Returns an array of Structs, using the keys from
|
7
|
+
# the csv header row.
|
7
8
|
def csv_read(path)
|
8
9
|
lines = if path =~ /\.gz$/
|
9
10
|
Zlib::GzipReader.open(path) do |f|
|
@@ -17,7 +18,10 @@ module Scripto
|
|
17
18
|
lines.map { |i| klass.new(*i) }
|
18
19
|
end
|
19
20
|
|
20
|
-
#
|
21
|
+
# Write +rows+ to +path+ as csv. Rows can be an array of hashes, Structs,
|
22
|
+
# OpenStructs, or anything else that responds to to_h. The keys from the
|
23
|
+
# first row are used as the csv header. If +cols+ is specified, it will be
|
24
|
+
# used as the column keys instead.
|
21
25
|
def csv_write(path, rows, cols: nil)
|
22
26
|
begin
|
23
27
|
tmp = "/tmp/_scripto_csv.csv"
|
@@ -28,10 +32,12 @@ module Scripto
|
|
28
32
|
end
|
29
33
|
end
|
30
34
|
|
35
|
+
# Write +rows+ to $stdout as a csv. Similar to csv_write.
|
31
36
|
def csv_to_stdout(rows, cols: nil)
|
32
37
|
CSV($stdout) { |f| csv_write0(f, rows, cols: cols) }
|
33
38
|
end
|
34
39
|
|
40
|
+
# Returns a string containing +rows+ as a csv. Similar to csv_write.
|
35
41
|
def csv_to_s(rows, cols: nil)
|
36
42
|
string = ""
|
37
43
|
f = CSV.new(StringIO.new(string))
|
@@ -41,6 +47,7 @@ module Scripto
|
|
41
47
|
|
42
48
|
protected
|
43
49
|
|
50
|
+
# :nodoc:
|
44
51
|
def csv_write0(csv, rows, cols: nil)
|
45
52
|
# cols
|
46
53
|
cols ||= rows.first.to_h.keys
|
@@ -3,12 +3,21 @@ require "fileutils"
|
|
3
3
|
|
4
4
|
module Scripto
|
5
5
|
module FileCommands
|
6
|
+
# Like mkdir -p +dir+. If +owner+ is specified, the directory will be
|
7
|
+
# chowned to owner. If +mode+ is specified, the directory will be chmodded
|
8
|
+
# to mode. Like all file commands, the operation will be printed out if
|
9
|
+
# verbose?.
|
6
10
|
def mkdir(dir, owner: nil, mode: nil)
|
7
11
|
FileUtils.mkdir_p(dir, verbose: verbose?)
|
8
12
|
chown(dir, owner) if owner
|
9
13
|
chmod(dir, mode) if mode
|
10
14
|
end
|
11
15
|
|
16
|
+
# Like cp -pr +src+ +dst. If +mkdir+ is true, the dst directoy will be
|
17
|
+
# created if necessary before the copy. If +owner+ is specified, the
|
18
|
+
# directory will be chowned to owner. If +mode+ is specified, the
|
19
|
+
# directory will be chmodded to mode. Like all file commands, the
|
20
|
+
# operation will be printed out if verbose?.
|
12
21
|
def cp(src, dst, mkdir: false, owner: nil, mode: nil)
|
13
22
|
mkdir_if_necessary(File.dirname(dst)) if mkdir
|
14
23
|
FileUtils.cp_r(src, dst, preserve: true, verbose: verbose?)
|
@@ -16,11 +25,16 @@ module Scripto
|
|
16
25
|
chmod(dst, mode) if mode
|
17
26
|
end
|
18
27
|
|
28
|
+
# Like mv +src+ +dst. If +mkdir+ is true, the dst directoy will be created
|
29
|
+
# if necessary before the copy. Like all file commands, the operation will
|
30
|
+
# be printed out if verbose?.
|
19
31
|
def mv(src, dst, mkdir: false)
|
20
32
|
mkdir_if_necessary(File.dirname(dst)) if mkdir
|
21
33
|
FileUtils.mv(src, dst, verbose: verbose?)
|
22
34
|
end
|
23
35
|
|
36
|
+
# Like ln -sf +src+ +dst. The command will be printed out if
|
37
|
+
# verbose?.
|
24
38
|
def ln(src, dst)
|
25
39
|
begin
|
26
40
|
FileUtils.ln_sf(src, dst, verbose: verbose?)
|
@@ -31,10 +45,15 @@ module Scripto
|
|
31
45
|
end
|
32
46
|
end
|
33
47
|
|
48
|
+
# Like rm -f +file+. Like all file commands, the operation will be printed
|
49
|
+
# out if verbose?.
|
34
50
|
def rm(file)
|
35
51
|
FileUtils.rm_f(file, verbose: verbose?)
|
36
52
|
end
|
37
53
|
|
54
|
+
# Runs #mkdir, but ONLY if +dir+ doesn't already exist. Returns true if
|
55
|
+
# directory had to be created. This is useful with verbose?, to get an
|
56
|
+
# exact changelog.
|
38
57
|
def mkdir_if_necessary(dir, owner: nil, mode: nil)
|
39
58
|
if !(File.exists?(dir) || File.symlink?(dir))
|
40
59
|
mkdir(dir, owner: owner, mode: mode)
|
@@ -42,6 +61,9 @@ module Scripto
|
|
42
61
|
end
|
43
62
|
end
|
44
63
|
|
64
|
+
# Runs #cp, but ONLY if +dst+ doesn't exist or differs from +src+. Returns
|
65
|
+
# true if the file had to be copied. This is useful with verbose?, to get
|
66
|
+
# an exact changelog.
|
45
67
|
def cp_if_necessary(src, dst, mkdir: false, owner: nil, mode: nil)
|
46
68
|
if !(File.exists?(dst) && FileUtils.compare_file(src, dst))
|
47
69
|
cp(src, dst, mkdir: mkdir, owner: owner, mode: mode)
|
@@ -49,6 +71,9 @@ module Scripto
|
|
49
71
|
end
|
50
72
|
end
|
51
73
|
|
74
|
+
# Runs #ln, but ONLY if +dst+ isn't a symlink or differs from +src+.
|
75
|
+
# Returns true if the file had to be symlinked. This is useful with
|
76
|
+
# verbose?, to get an exact changelog.
|
52
77
|
def ln_if_necessary(src, dst)
|
53
78
|
ln = if !File.symlink?(dst)
|
54
79
|
true
|
@@ -62,6 +87,8 @@ module Scripto
|
|
62
87
|
end
|
63
88
|
end
|
64
89
|
|
90
|
+
# Runs #rm, but ONLY if +file+ exists. Return true if the file had to be
|
91
|
+
# removed. This is useful with verbose?, to get an exact changelog.
|
65
92
|
def rm_if_necessary(file)
|
66
93
|
if File.exists?(file)
|
67
94
|
rm(file)
|
@@ -69,6 +96,8 @@ module Scripto
|
|
69
96
|
end
|
70
97
|
end
|
71
98
|
|
99
|
+
# Like chown user:user file. Like all file commands, the operation will be printed
|
100
|
+
# out if verbose?.
|
72
101
|
def chown(file, user)
|
73
102
|
# who is the current owner?
|
74
103
|
@scripto_uids ||= {}
|
@@ -79,18 +108,24 @@ module Scripto
|
|
79
108
|
end
|
80
109
|
end
|
81
110
|
|
111
|
+
# Like chmod mode file. Like all file commands, the operation will be
|
112
|
+
# printed out if verbose?.
|
82
113
|
def chmod(file, mode)
|
83
114
|
if File.stat(file).mode != mode
|
84
115
|
FileUtils.chmod(mode, file, verbose: verbose?)
|
85
116
|
end
|
86
117
|
end
|
87
118
|
|
119
|
+
# Like rm -rf && mkdir -p. Like all file commands, the operation will be
|
120
|
+
# printed out if verbose?.
|
88
121
|
def rm_and_mkdir(dir)
|
89
122
|
raise "don't do this" if dir == ""
|
90
123
|
FileUtils.rm_rf(dir, verbose: verbose?)
|
91
124
|
mkdir(dir)
|
92
125
|
end
|
93
126
|
|
127
|
+
# Copy mode, atime and mtime from +src+ to +dst+. This one is rarely used
|
128
|
+
# and doesn't echo.
|
94
129
|
def copy_metadata(src, dst)
|
95
130
|
stat = File.stat(src)
|
96
131
|
File.chmod(stat.mode, dst)
|
data/lib/scripto/main.rb
CHANGED
@@ -5,14 +5,17 @@ module Scripto
|
|
5
5
|
module MiscCommands
|
6
6
|
BASE_62 = ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a
|
7
7
|
|
8
|
+
# Who is the current user?
|
8
9
|
def whoami
|
9
10
|
@scripto_whoami ||= Etc.getpwuid(Process.uid).name
|
10
11
|
end
|
11
12
|
|
13
|
+
# Return true if the current user is "root".
|
12
14
|
def root?
|
13
15
|
whoami == "root"
|
14
16
|
end
|
15
17
|
|
18
|
+
# Return the md5 checksum for the file at +path+.
|
16
19
|
def md5_file(path)
|
17
20
|
File.open(path) do |f|
|
18
21
|
digest, buf = Digest::MD5.new, ""
|
@@ -23,16 +26,20 @@ module Scripto
|
|
23
26
|
end
|
24
27
|
end
|
25
28
|
|
26
|
-
|
27
|
-
|
29
|
+
# Return the md5 checksum for +str+.
|
30
|
+
def md5_string(str)
|
31
|
+
Digest::MD5.hexdigest(str.to_s)
|
28
32
|
end
|
29
33
|
|
34
|
+
# Ask the user a question via stderr, then return true if they enter YES,
|
35
|
+
# yes, y, etc.
|
30
36
|
def prompt?(question)
|
31
37
|
$stderr.write("#{question} (y/n) ")
|
32
38
|
$stderr.flush
|
33
39
|
$stdin.gets =~ /^y/i
|
34
40
|
end
|
35
41
|
|
42
|
+
# Return a random alphanumeric string of length +len+.
|
36
43
|
def random_string(len)
|
37
44
|
(1..len).map { BASE_62.sample }.join
|
38
45
|
end
|
@@ -7,38 +7,47 @@ module Scripto
|
|
7
7
|
|
8
8
|
attr_accessor :verbose
|
9
9
|
|
10
|
+
# Is verbose mode turned on?
|
10
11
|
def verbose?
|
11
12
|
!!@verbose
|
12
13
|
end
|
13
14
|
|
15
|
+
# Turn on verbose mode. #vbanner, #vputs and #vprintf will start printing
|
16
|
+
# now, and file ops will be printed too.
|
14
17
|
def verbose!
|
15
18
|
@verbose = true
|
16
19
|
end
|
17
20
|
|
18
|
-
|
19
|
-
|
21
|
+
# Print a colored banner to $stderr, but only if #verbose?.
|
22
|
+
def vbanner(str = nil)
|
23
|
+
banner(str) if verbose?
|
20
24
|
end
|
21
25
|
|
22
|
-
|
23
|
-
|
26
|
+
# Puts to $stderr, but only if #verbose?.
|
27
|
+
def vputs(str = nil)
|
28
|
+
$stderr.puts(str) if verbose?
|
24
29
|
end
|
25
30
|
|
26
|
-
|
27
|
-
|
31
|
+
# Printf to $stderr, but only if #verbose?.
|
32
|
+
def vprintf(str, *args)
|
33
|
+
$stderr.printf(str, *args) if verbose?
|
28
34
|
end
|
29
35
|
|
30
|
-
|
36
|
+
# Print a colored banner to $stderr in green.
|
37
|
+
def banner(str, color: GREEN)
|
31
38
|
now = Time.new.strftime("%H:%M:%S")
|
32
|
-
s = "#{
|
39
|
+
s = "#{str} ".ljust(72, " ")
|
33
40
|
$stderr.puts "#{color}[#{now}] #{s}#{RESET}"
|
34
41
|
end
|
35
42
|
|
36
|
-
|
37
|
-
|
43
|
+
# Print a yellow warning banner to $stderr.
|
44
|
+
def warning(str)
|
45
|
+
banner("Warning: #{str}", color: YELLOW)
|
38
46
|
end
|
39
47
|
|
40
|
-
|
41
|
-
|
48
|
+
# Print a red error banner to $stderr, then exit.
|
49
|
+
def fatal(str)
|
50
|
+
banner(str, color: RED)
|
42
51
|
exit(1)
|
43
52
|
end
|
44
53
|
end
|
data/lib/scripto/run_commands.rb
CHANGED
@@ -1,25 +1,38 @@
|
|
1
|
-
require "
|
1
|
+
require "English"
|
2
2
|
require "shellwords"
|
3
3
|
|
4
4
|
module Scripto
|
5
5
|
module RunCommands
|
6
|
-
|
6
|
+
# The error thrown by #run, #run_capture and #run_quietly on failure.
|
7
|
+
class Error < StandardError
|
8
|
+
end
|
7
9
|
|
10
|
+
# Run an external command. Raise Error if something goes wrong. The
|
11
|
+
# command will be echoed if verbose?.
|
12
|
+
#
|
13
|
+
# Usage is similar to Kernel#system. If +args+ is nil, +command+ will be
|
14
|
+
# passed to the shell. If +args+ are included, the +command+ and +args+
|
15
|
+
# will be run directly without the shell.
|
8
16
|
def run(command, args = nil)
|
9
17
|
cmd = CommandLine.new(command, args)
|
10
18
|
vputs(cmd)
|
11
19
|
cmd.run
|
12
20
|
end
|
13
21
|
|
22
|
+
# Run a command and capture the output like backticks. See #run
|
23
|
+
# for details.
|
14
24
|
def run_capture(command, args = nil)
|
15
25
|
CommandLine.new(command, args).capture
|
16
26
|
end
|
17
27
|
|
28
|
+
# Run a command and suppress output by redirecting to /dev/null. See #run
|
29
|
+
# for details.
|
18
30
|
def run_quietly(command, args = nil)
|
19
31
|
cmd = CommandLine.new(command, args)
|
20
32
|
run("#{cmd} > /dev/null 2> /dev/null")
|
21
33
|
end
|
22
34
|
|
35
|
+
# Returns true if the command succeeds. See #run for details.
|
23
36
|
def run_succeeds?(command, args = nil)
|
24
37
|
begin
|
25
38
|
run_quietly(command, args)
|
@@ -29,16 +42,19 @@ module Scripto
|
|
29
42
|
end
|
30
43
|
end
|
31
44
|
|
45
|
+
# Returns true if the command fails. See #run for details.
|
32
46
|
def run_fails?(command, args = nil)
|
33
47
|
!run_succeeds?(command, args)
|
34
48
|
end
|
35
49
|
|
36
|
-
|
37
|
-
|
50
|
+
# Escape str if necessary. Useful for passing arguments to a shell.
|
51
|
+
def shellescape(str)
|
52
|
+
Shellwords.escape(str)
|
38
53
|
end
|
39
54
|
|
40
55
|
protected
|
41
56
|
|
57
|
+
# :nodoc:
|
42
58
|
class CommandLine
|
43
59
|
attr_accessor :command, :args
|
44
60
|
|
data/lib/scripto/version.rb
CHANGED
data/scripto.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.required_ruby_version = ">= 2.0.0"
|
11
11
|
spec.authors = ["Adam Doppelt"]
|
12
12
|
spec.email = ["amd@gurge.com"]
|
13
|
-
spec.summary = "Helpers for writing command line scripts."
|
13
|
+
spec.summary = "Helpers for writing command line scripts. An extraction from Dwellable."
|
14
14
|
spec.homepage = "http://github.com/gurgeous/scripto"
|
15
15
|
spec.license = "MIT"
|
16
16
|
|
data/test/test_print.rb
CHANGED
@@ -11,14 +11,14 @@ class TestPrint < Minitest::Test
|
|
11
11
|
|
12
12
|
def test_quiet
|
13
13
|
assert_silent { Scripto.vbanner "gub" }
|
14
|
-
assert_silent { Scripto.vprintf
|
14
|
+
assert_silent { Scripto.vprintf("gub %d", 123) }
|
15
15
|
assert_silent { Scripto.vputs "gub" }
|
16
16
|
end
|
17
17
|
|
18
18
|
def test_loud
|
19
19
|
Scripto.verbose!
|
20
20
|
assert_output(nil, /gub/) { Scripto.vbanner "gub" }
|
21
|
-
assert_output(nil, /gub/) { Scripto.vprintf "gub" }
|
21
|
+
assert_output(nil, /gub/) { Scripto.vprintf("zub %s", "gub") }
|
22
22
|
assert_output(nil, /gub/) { Scripto.vputs "gub" }
|
23
23
|
end
|
24
24
|
|
data/vagrant_provision
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# bail on errors
|
4
|
+
set -eu
|
5
|
+
|
6
|
+
function banner() {
|
7
|
+
printf '\e[1;37;43m[%s] %-72s\e[0m\n' `date '+%H:%M:%S'` "vagrant_provision: $1"
|
8
|
+
}
|
9
|
+
|
10
|
+
# packages needed for building ruby
|
11
|
+
banner "Packages..."
|
12
|
+
sudo apt-get -y install git-core
|
13
|
+
sudo apt-get -y install build-essential checkinstall libffi-dev libreadline-gplv2-dev libncurses5-dev libssl-dev libyaml-dev zlib1g-dev
|
14
|
+
|
15
|
+
# rbenv
|
16
|
+
banner "Rbenv..."
|
17
|
+
if [ ! -d ~/.rbenv ] ; then
|
18
|
+
git clone git://github.com/sstephenson/rbenv.git ~/.rbenv
|
19
|
+
fi
|
20
|
+
if [ ! -d ~/.rbenv/plugins/ruby-build ] ; then
|
21
|
+
git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
|
22
|
+
fi
|
23
|
+
|
24
|
+
# add rbenv to .bashrc
|
25
|
+
if ! grep -q 'vagrant provisioning' ~/.bashrc ; then
|
26
|
+
cat >> ~/.bashrc <<"EOF"
|
27
|
+
# added by vagrant provisioning
|
28
|
+
export PATH="$HOME/.rbenv/bin:$PATH"
|
29
|
+
eval "$(rbenv init -)"
|
30
|
+
EOF
|
31
|
+
|
32
|
+
# make sure we pickup rbenv too!
|
33
|
+
export PATH="$HOME/.rbenv/bin:$PATH"
|
34
|
+
eval "$(rbenv init -)"
|
35
|
+
fi
|
36
|
+
|
37
|
+
# no rdoc, please
|
38
|
+
echo 'gem: --no-ri --no-rdoc' > ~/.gemrc
|
39
|
+
|
40
|
+
# now install ruby
|
41
|
+
banner "Ruby..."
|
42
|
+
rbenv install 2.0.0-p353
|
43
|
+
rbenv global 2.0.0-p353
|
44
|
+
ruby -v
|
45
|
+
|
46
|
+
# and gems
|
47
|
+
banner "Gems..."
|
48
|
+
gem update --system
|
49
|
+
gem install bundler
|
50
|
+
rbenv rehash
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scripto
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Doppelt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-02-
|
11
|
+
date: 2014-02-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -64,6 +64,7 @@ files:
|
|
64
64
|
- LICENSE.txt
|
65
65
|
- README.md
|
66
66
|
- Rakefile
|
67
|
+
- Vagrantfile
|
67
68
|
- lib/scripto.rb
|
68
69
|
- lib/scripto/csv_commands.rb
|
69
70
|
- lib/scripto/file_commands.rb
|
@@ -79,6 +80,7 @@ files:
|
|
79
80
|
- test/test_misc.rb
|
80
81
|
- test/test_print.rb
|
81
82
|
- test/test_run.rb
|
83
|
+
- vagrant_provision
|
82
84
|
homepage: http://github.com/gurgeous/scripto
|
83
85
|
licenses:
|
84
86
|
- MIT
|
@@ -102,7 +104,7 @@ rubyforge_project:
|
|
102
104
|
rubygems_version: 2.2.1
|
103
105
|
signing_key:
|
104
106
|
specification_version: 4
|
105
|
-
summary: Helpers for writing command line scripts.
|
107
|
+
summary: Helpers for writing command line scripts. An extraction from Dwellable.
|
106
108
|
test_files:
|
107
109
|
- test/helper.rb
|
108
110
|
- test/test_csv.rb
|