scripto 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +6 -5
- data/.travis.yml +5 -3
- data/README.md +14 -5
- data/Rakefile +14 -9
- data/Vagrantfile +15 -120
- data/lib/scripto.rb +7 -7
- data/lib/scripto/csv_commands.rb +19 -15
- data/lib/scripto/file_commands.rb +45 -37
- data/lib/scripto/main.rb +1 -1
- data/lib/scripto/misc_commands.rb +7 -9
- data/lib/scripto/print_commands.rb +7 -7
- data/lib/scripto/run_commands.rb +18 -19
- data/lib/scripto/version.rb +1 -1
- data/scripto.gemspec +13 -13
- data/test/helper.rb +7 -7
- data/test/test_csv.rb +27 -17
- data/test/test_file.rb +51 -31
- data/test/test_misc.rb +16 -16
- data/test/test_print.rb +9 -9
- data/test/test_run.rb +20 -20
- metadata +25 -13
- data/vagrant_provision +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e0ea62ce310b383c38d173c5e3cd61b2eb4e7ea345c92f6b2f1874e7c6a3c93f
|
4
|
+
data.tar.gz: 0fc744955f01e99d7ee44f53b927d1a93ca6d46e35a7e1b9baf36f62e9939711
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fee85c4a32b32d4caf1f54aec0efac6e0634f2217154c1c6367b1c184878d08b14f9f4e035eda2d78b4157bef41d839b7044fbe230957bf81eba2df3bf451f8e
|
7
|
+
data.tar.gz: 7664c604a09572d1cecf1e4e7cc1d3b8845e6dfb5ff1ea1e37edf660a58007f19a17a46668786a8014a71f36c373e6eff7b8f811abe5d3b3f14dd854cb068e8f
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
# Scripto
|
2
2
|
|
3
|
+
[![Build Status](https://travis-ci.org/gurgeous/scripto.svg?branch=master)](https://travis-ci.org/gurgeous/scripto)
|
4
|
+
|
3
5
|
Scripto is a framework for writing command line applications. It fills in many of the blanks that Ruby's standard library is missing:
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
- **print to \$stderr** - Colored banners and a verbose mode to make your scripts louder.
|
8
|
+
- **file operations** - Mkdir, cp, mv, ln. These operations can take care of common edge cases, like creating the target directory before copying a file.
|
9
|
+
- **run commands** - Run external commands and raise errors on failure.
|
10
|
+
- **csv** - Read and write CSV files from hashes, Structs, or OpenStructs.
|
9
11
|
|
10
12
|
Rdoc at [rdoc.info](http://rdoc.info/github/gurgeous/scripto/). Thanks!
|
11
13
|
|
@@ -65,7 +67,7 @@ Install.new(verbose: true)
|
|
65
67
|
|
66
68
|
## Methods
|
67
69
|
|
68
|
-
### Print to
|
70
|
+
### Print to \$stderr
|
69
71
|
|
70
72
|
```
|
71
73
|
banner(str) - print a banner in green
|
@@ -151,3 +153,10 @@ md5_string(str) - calculate md5 for a string
|
|
151
153
|
prompt?(question) - ask the user a question, return true if they say yes
|
152
154
|
random_string(len) - calculate a random alphanumeric string
|
153
155
|
```
|
156
|
+
|
157
|
+
# Changelog
|
158
|
+
|
159
|
+
0.0.4 (late 2020)
|
160
|
+
|
161
|
+
- Added support for reading CSV with BOM.
|
162
|
+
- Only support Ruby 2.3+, since we moved to modern Bundler
|
data/Rakefile
CHANGED
@@ -1,16 +1,21 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rdoc/task'
|
4
|
+
require 'rubocop/rake_task'
|
4
5
|
|
5
6
|
Rake::TestTask.new(:test) do |test|
|
6
|
-
test.libs <<
|
7
|
+
test.libs << 'test'
|
7
8
|
end
|
8
9
|
task default: :test
|
9
10
|
|
10
11
|
RDoc::Task.new do |rdoc|
|
11
|
-
rdoc.rdoc_dir =
|
12
|
+
rdoc.rdoc_dir = 'rdoc'
|
12
13
|
rdoc.title = "scripto #{Scripto::VERSION}"
|
13
|
-
rdoc.main =
|
14
|
-
rdoc.rdoc_files.include(
|
15
|
-
rdoc.rdoc_files.include(
|
16
|
-
end
|
14
|
+
rdoc.main = 'README.md'
|
15
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
16
|
+
rdoc.rdoc_files.include('README.md')
|
17
|
+
end
|
18
|
+
|
19
|
+
RuboCop::RakeTask.new do |task|
|
20
|
+
task.options << '--display-cop-names'
|
21
|
+
end
|
data/Vagrantfile
CHANGED
@@ -1,121 +1,16 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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"
|
1
|
+
Vagrant.configure('2') do |config|
|
2
|
+
config.vm.box = 'ubuntu/xenial64'
|
3
|
+
config.vm.provision 'shell', privileged: false, inline: <<-EOF
|
4
|
+
export DEBIAN_FRONTEND=noninteractive
|
5
|
+
|
6
|
+
# ruby 2.3
|
7
|
+
sudo apt-add-repository ppa:brightbox/ruby-ng
|
8
|
+
sudo apt-get update
|
9
|
+
sudo apt-get install -y ruby2.3
|
10
|
+
sudo gem install bundler
|
11
|
+
|
12
|
+
# Gemfile
|
13
|
+
cd /vagrant
|
14
|
+
bundle install
|
15
|
+
EOF
|
121
16
|
end
|
data/lib/scripto.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
1
|
+
require 'scripto/csv_commands'
|
2
|
+
require 'scripto/file_commands'
|
3
|
+
require 'scripto/misc_commands'
|
4
|
+
require 'scripto/print_commands'
|
5
|
+
require 'scripto/run_commands'
|
6
|
+
require 'scripto/version'
|
7
|
+
require 'scripto/main'
|
8
8
|
|
9
9
|
module Scripto
|
10
10
|
extend CsvCommands
|
data/lib/scripto/csv_commands.rb
CHANGED
@@ -1,17 +1,25 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'csv'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'zlib'
|
3
4
|
|
4
5
|
module Scripto
|
5
6
|
module CsvCommands
|
6
7
|
# Read a csv from +path+. Returns an array of Structs, using the keys from
|
7
8
|
# the csv header row.
|
8
9
|
def csv_read(path)
|
9
|
-
lines =
|
10
|
-
|
11
|
-
|
10
|
+
lines = begin
|
11
|
+
if path =~ /\.gz$/
|
12
|
+
Zlib::GzipReader.open(path) do |f|
|
13
|
+
CSV.new(f).read
|
14
|
+
end
|
15
|
+
else
|
16
|
+
encoding = 'bom|utf-8'
|
17
|
+
if RUBY_VERSION >= "2.6.0"
|
18
|
+
CSV.read(path, encoding: encoding)
|
19
|
+
else
|
20
|
+
CSV.read(path, "r:#{encoding}")
|
21
|
+
end
|
12
22
|
end
|
13
|
-
else
|
14
|
-
CSV.read(path)
|
15
23
|
end
|
16
24
|
keys = lines.shift.map(&:to_sym)
|
17
25
|
klass = Struct.new(*keys)
|
@@ -23,12 +31,8 @@ module Scripto
|
|
23
31
|
# first row are used as the csv header. If +cols+ is specified, it will be
|
24
32
|
# used as the column keys instead.
|
25
33
|
def csv_write(path, rows, cols: nil)
|
26
|
-
|
27
|
-
tmp
|
28
|
-
CSV.open(tmp, "wb") { |f| csv_write0(f, rows, cols: cols) }
|
29
|
-
mv(tmp, path)
|
30
|
-
ensure
|
31
|
-
rm_if_necessary(tmp)
|
34
|
+
atomic_write(path) do |tmp|
|
35
|
+
CSV.open(tmp.path, 'wb') { |f| csv_write0(f, rows, cols: cols) }
|
32
36
|
end
|
33
37
|
end
|
34
38
|
|
@@ -39,7 +43,7 @@ module Scripto
|
|
39
43
|
|
40
44
|
# Returns a string containing +rows+ as a csv. Similar to csv_write.
|
41
45
|
def csv_to_s(rows, cols: nil)
|
42
|
-
string =
|
46
|
+
string = ''
|
43
47
|
f = CSV.new(StringIO.new(string))
|
44
48
|
csv_write0(f, rows, cols: cols)
|
45
49
|
string
|
@@ -60,4 +64,4 @@ module Scripto
|
|
60
64
|
end
|
61
65
|
end
|
62
66
|
end
|
63
|
-
end
|
67
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'etc'
|
2
|
+
require 'fileutils'
|
3
3
|
|
4
4
|
module Scripto
|
5
5
|
module FileCommands
|
@@ -36,13 +36,11 @@ module Scripto
|
|
36
36
|
# Like ln -sf +src+ +dst. The command will be printed out if
|
37
37
|
# verbose?.
|
38
38
|
def ln(src, dst)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
raise e if !(File.symlink?(dst) && src == File.readlink(dst))
|
45
|
-
end
|
39
|
+
FileUtils.ln_sf(src, dst, verbose: verbose?)
|
40
|
+
rescue Errno::EEXIST => e
|
41
|
+
# It's a race - this can occur because ln_sf removes the old
|
42
|
+
# dst, then creates the symlink. Raise if they don't match.
|
43
|
+
raise e if !(File.symlink?(dst) && src == File.readlink(dst))
|
46
44
|
end
|
47
45
|
|
48
46
|
# Like rm -f +file+. Like all file commands, the operation will be printed
|
@@ -55,45 +53,43 @@ module Scripto
|
|
55
53
|
# directory had to be created. This is useful with verbose?, to get an
|
56
54
|
# exact changelog.
|
57
55
|
def mkdir_if_necessary(dir, owner: nil, mode: nil)
|
58
|
-
if
|
59
|
-
|
60
|
-
|
61
|
-
|
56
|
+
return if File.exist?(dir) || File.symlink?(dir)
|
57
|
+
|
58
|
+
mkdir(dir, owner: owner, mode: mode)
|
59
|
+
true
|
62
60
|
end
|
63
61
|
|
64
62
|
# Runs #cp, but ONLY if +dst+ doesn't exist or differs from +src+. Returns
|
65
63
|
# true if the file had to be copied. This is useful with verbose?, to get
|
66
64
|
# an exact changelog.
|
67
65
|
def cp_if_necessary(src, dst, mkdir: false, owner: nil, mode: nil)
|
68
|
-
if
|
69
|
-
|
70
|
-
|
71
|
-
|
66
|
+
return if File.exist?(dst) && FileUtils.compare_file(src, dst)
|
67
|
+
|
68
|
+
cp(src, dst, mkdir: mkdir, owner: owner, mode: mode)
|
69
|
+
true
|
72
70
|
end
|
73
71
|
|
74
72
|
# Runs #ln, but ONLY if +dst+ isn't a symlink or differs from +src+.
|
75
73
|
# Returns true if the file had to be symlinked. This is useful with
|
76
74
|
# verbose?, to get an exact changelog.
|
77
75
|
def ln_if_necessary(src, dst)
|
78
|
-
|
79
|
-
|
80
|
-
|
76
|
+
if File.symlink?(dst)
|
77
|
+
return if src == File.readlink(dst)
|
78
|
+
|
81
79
|
rm(dst)
|
82
|
-
true
|
83
|
-
end
|
84
|
-
if ln
|
85
|
-
ln(src, dst)
|
86
|
-
true
|
87
80
|
end
|
81
|
+
|
82
|
+
ln(src, dst)
|
83
|
+
true
|
88
84
|
end
|
89
85
|
|
90
86
|
# Runs #rm, but ONLY if +file+ exists. Return true if the file had to be
|
91
87
|
# removed. This is useful with verbose?, to get an exact changelog.
|
92
88
|
def rm_if_necessary(file)
|
93
|
-
if File.exist?(file)
|
94
|
-
|
95
|
-
|
96
|
-
|
89
|
+
return if !File.exist?(file)
|
90
|
+
|
91
|
+
rm(file)
|
92
|
+
true
|
97
93
|
end
|
98
94
|
|
99
95
|
# Like chown user:user file. Like all file commands, the operation will be printed
|
@@ -103,23 +99,24 @@ module Scripto
|
|
103
99
|
@scripto_uids ||= {}
|
104
100
|
@scripto_uids[user] ||= Etc.getpwnam(user).uid
|
105
101
|
uid = @scripto_uids[user]
|
106
|
-
if File.stat(file).uid
|
107
|
-
|
108
|
-
|
102
|
+
return if File.stat(file).uid == uid
|
103
|
+
|
104
|
+
FileUtils.chown(uid, uid, file, verbose: verbose?)
|
109
105
|
end
|
110
106
|
|
111
107
|
# Like chmod mode file. Like all file commands, the operation will be
|
112
108
|
# printed out if verbose?.
|
113
109
|
def chmod(file, mode)
|
114
|
-
if File.stat(file).mode
|
115
|
-
|
116
|
-
|
110
|
+
return if File.stat(file).mode == mode
|
111
|
+
|
112
|
+
FileUtils.chmod(mode, file, verbose: verbose?)
|
117
113
|
end
|
118
114
|
|
119
115
|
# Like rm -rf && mkdir -p. Like all file commands, the operation will be
|
120
116
|
# printed out if verbose?.
|
121
117
|
def rm_and_mkdir(dir)
|
122
|
-
raise "don't do this" if dir ==
|
118
|
+
raise "don't do this" if dir == ''
|
119
|
+
|
123
120
|
FileUtils.rm_rf(dir, verbose: verbose?)
|
124
121
|
mkdir(dir)
|
125
122
|
end
|
@@ -131,5 +128,16 @@ module Scripto
|
|
131
128
|
File.chmod(stat.mode, dst)
|
132
129
|
File.utime(stat.atime, stat.mtime, dst)
|
133
130
|
end
|
131
|
+
|
132
|
+
# Atomically write to +path+. An open temp file is yielded.
|
133
|
+
def atomic_write(path)
|
134
|
+
tmp = Tempfile.new(File.basename(path))
|
135
|
+
yield(tmp)
|
136
|
+
tmp.close
|
137
|
+
chmod(tmp.path, 0o644)
|
138
|
+
mv(tmp.path, path)
|
139
|
+
ensure
|
140
|
+
rm_if_necessary(tmp.path)
|
141
|
+
end
|
134
142
|
end
|
135
|
-
end
|
143
|
+
end
|