scripto 0.0.1
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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +14 -0
- data/lib/scripto.rb +15 -0
- data/lib/scripto/csv_commands.rb +56 -0
- data/lib/scripto/file_commands.rb +100 -0
- data/lib/scripto/main.rb +16 -0
- data/lib/scripto/misc_commands.rb +40 -0
- data/lib/scripto/print_commands.rb +45 -0
- data/lib/scripto/run_commands.rb +86 -0
- data/lib/scripto/version.rb +3 -0
- data/scripto.gemspec +25 -0
- data/test/helper.rb +26 -0
- data/test/test_csv.rb +60 -0
- data/test/test_file.rb +200 -0
- data/test/test_misc.rb +53 -0
- data/test/test_print.rb +28 -0
- data/test/test_run.rb +103 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4303784647ebd8ea25f1231f1c457490a15aebb8
|
4
|
+
data.tar.gz: 8503790cde1ec50455970da18a6d038ad6699cf1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5d9162c98b08f8f027d9233f5eca62db3485616dccecbb42a502e2d38db6734296575642a2f31563a39d04f0789a745ce62d5acfe95e1cb86a7c85bc74605b98
|
7
|
+
data.tar.gz: d047ea1eca14ef498dc563cb26c20b1864763d509a5649e798390193825a4e1031fd6554d8ab77803847d0e2ed68c301523873db23735c3256d869b3d97a24f2
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 amd
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Scripto
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'scripto'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install scripto
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it ( http://github.com/<my-github-username>/scripto/fork )
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
require "rdoc/task"
|
4
|
+
|
5
|
+
Rake::TestTask.new(:test) do |test|
|
6
|
+
test.libs << "test"
|
7
|
+
end
|
8
|
+
task default: :test
|
9
|
+
|
10
|
+
RDoc::Task.new do |rdoc|
|
11
|
+
rdoc.rdoc_dir = "rdoc"
|
12
|
+
rdoc.title = "scripto #{Scripto::VERSION}"
|
13
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
14
|
+
end
|
data/lib/scripto.rb
ADDED
@@ -0,0 +1,15 @@
|
|
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
|
+
|
9
|
+
module Scripto
|
10
|
+
extend CsvCommands
|
11
|
+
extend FileCommands
|
12
|
+
extend MiscCommands
|
13
|
+
extend PrintCommands
|
14
|
+
extend RunCommands
|
15
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "csv"
|
2
|
+
require "ostruct"
|
3
|
+
require "zlib"
|
4
|
+
|
5
|
+
module Scripto
|
6
|
+
module CsvCommands
|
7
|
+
def csv_read(path)
|
8
|
+
lines = if path =~ /\.gz$/
|
9
|
+
Zlib::GzipReader.open(path) do |f|
|
10
|
+
CSV.new(f).read
|
11
|
+
end
|
12
|
+
else
|
13
|
+
CSV.read(path)
|
14
|
+
end
|
15
|
+
keys = lines.shift.map(&:to_sym)
|
16
|
+
klass = Struct.new(*keys)
|
17
|
+
lines.map { |i| klass.new(*i) }
|
18
|
+
end
|
19
|
+
|
20
|
+
# write rows to path as CSV
|
21
|
+
def csv_write(path, rows, cols: nil)
|
22
|
+
begin
|
23
|
+
tmp = "/tmp/_scripto_csv.csv"
|
24
|
+
CSV.open(tmp, "wb") { |f| csv_write0(f, rows, cols: cols) }
|
25
|
+
mv(tmp, path)
|
26
|
+
ensure
|
27
|
+
rm_if_necessary(tmp)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def csv_to_stdout(rows, cols: nil)
|
32
|
+
CSV($stdout) { |f| csv_write0(f, rows, cols: cols) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def csv_to_s(rows, cols: nil)
|
36
|
+
string = ""
|
37
|
+
f = CSV.new(StringIO.new(string))
|
38
|
+
csv_write0(f, rows, cols: cols)
|
39
|
+
string
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def csv_write0(csv, rows, cols: nil)
|
45
|
+
# cols
|
46
|
+
cols ||= rows.first.to_h.keys
|
47
|
+
csv << cols
|
48
|
+
|
49
|
+
# rows
|
50
|
+
rows.each do |row|
|
51
|
+
row = row.to_h
|
52
|
+
csv << cols.map { |i| row[i] }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require "etc"
|
2
|
+
require "fileutils"
|
3
|
+
|
4
|
+
module Scripto
|
5
|
+
module FileCommands
|
6
|
+
def mkdir(dir, owner: nil, mode: nil)
|
7
|
+
FileUtils.mkdir_p(dir, verbose: verbose?)
|
8
|
+
chown(dir, owner) if owner
|
9
|
+
chmod(dir, mode) if mode
|
10
|
+
end
|
11
|
+
|
12
|
+
def cp(src, dst, mkdir: false, owner: nil, mode: nil)
|
13
|
+
mkdir_if_necessary(File.dirname(dst)) if mkdir
|
14
|
+
FileUtils.cp_r(src, dst, preserve: true, verbose: verbose?)
|
15
|
+
chown(dst, owner) if owner && !File.symlink?(dst)
|
16
|
+
chmod(dst, mode) if mode
|
17
|
+
end
|
18
|
+
|
19
|
+
def mv(src, dst, mkdir: false)
|
20
|
+
mkdir_if_necessary(File.dirname(dst)) if mkdir
|
21
|
+
FileUtils.mv(src, dst, verbose: verbose?)
|
22
|
+
end
|
23
|
+
|
24
|
+
def ln(src, dst)
|
25
|
+
begin
|
26
|
+
FileUtils.ln_sf(src, dst, verbose: verbose?)
|
27
|
+
rescue Errno::EEXIST => e
|
28
|
+
# It's a race - this can occur because ln_sf removes the old
|
29
|
+
# dst, then creates the symlink. Raise if they don't match.
|
30
|
+
raise e if !(File.symlink?(dst) && src == File.readlink(dst))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def rm(file)
|
35
|
+
FileUtils.rm_f(file, verbose: verbose?)
|
36
|
+
end
|
37
|
+
|
38
|
+
def mkdir_if_necessary(dir, owner: nil, mode: nil)
|
39
|
+
if !(File.exists?(dir) || File.symlink?(dir))
|
40
|
+
mkdir(dir, owner: owner, mode: mode)
|
41
|
+
true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def cp_if_necessary(src, dst, mkdir: false, owner: nil, mode: nil)
|
46
|
+
if !(File.exists?(dst) && FileUtils.compare_file(src, dst))
|
47
|
+
cp(src, dst, mkdir: mkdir, owner: owner, mode: mode)
|
48
|
+
true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def ln_if_necessary(src, dst)
|
53
|
+
ln = if !File.symlink?(dst)
|
54
|
+
true
|
55
|
+
elsif File.readlink(dst) != src
|
56
|
+
rm(dst)
|
57
|
+
true
|
58
|
+
end
|
59
|
+
if ln
|
60
|
+
ln(src, dst)
|
61
|
+
true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def rm_if_necessary(file)
|
66
|
+
if File.exists?(file)
|
67
|
+
rm(file)
|
68
|
+
true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def chown(file, user)
|
73
|
+
# who is the current owner?
|
74
|
+
@scripto_uids ||= {}
|
75
|
+
@scripto_uids[user] ||= Etc.getpwnam(user).uid
|
76
|
+
uid = @scripto_uids[user]
|
77
|
+
if File.stat(file).uid != uid
|
78
|
+
FileUtils.chown(uid, uid, file, verbose: verbose?)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def chmod(file, mode)
|
83
|
+
if File.stat(file).mode != mode
|
84
|
+
FileUtils.chmod(mode, file, verbose: verbose?)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def rm_and_mkdir(dir)
|
89
|
+
raise "don't do this" if dir == ""
|
90
|
+
FileUtils.rm_rf(dir, verbose: verbose?)
|
91
|
+
mkdir(dir)
|
92
|
+
end
|
93
|
+
|
94
|
+
def copy_metadata(src, dst)
|
95
|
+
stat = File.stat(src)
|
96
|
+
File.chmod(stat.mode, dst)
|
97
|
+
File.utime(stat.atime, stat.mtime, dst)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/scripto/main.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Scripto
|
2
|
+
class Main
|
3
|
+
include CsvCommands
|
4
|
+
include FileCommands
|
5
|
+
include MiscCommands
|
6
|
+
include PrintCommands
|
7
|
+
include RunCommands
|
8
|
+
|
9
|
+
attr_accessor :options
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
self.options = options
|
13
|
+
self.verbose = options[:verbose]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "digest/md5"
|
2
|
+
require "etc"
|
3
|
+
|
4
|
+
module Scripto
|
5
|
+
module MiscCommands
|
6
|
+
BASE_62 = ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a
|
7
|
+
|
8
|
+
def whoami
|
9
|
+
@scripto_whoami ||= Etc.getpwuid(Process.uid).name
|
10
|
+
end
|
11
|
+
|
12
|
+
def root?
|
13
|
+
whoami == "root"
|
14
|
+
end
|
15
|
+
|
16
|
+
def md5_file(path)
|
17
|
+
File.open(path) do |f|
|
18
|
+
digest, buf = Digest::MD5.new, ""
|
19
|
+
while f.read(4096, buf)
|
20
|
+
digest.update(buf)
|
21
|
+
end
|
22
|
+
digest.hexdigest
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def md5_string(s)
|
27
|
+
Digest::MD5.hexdigest(s.to_s)
|
28
|
+
end
|
29
|
+
|
30
|
+
def prompt?(question)
|
31
|
+
$stderr.write("#{question} (y/n) ")
|
32
|
+
$stderr.flush
|
33
|
+
$stdin.gets =~ /^y/i
|
34
|
+
end
|
35
|
+
|
36
|
+
def random_string(len)
|
37
|
+
(1..len).map { BASE_62.sample }.join
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Scripto
|
2
|
+
module PrintCommands
|
3
|
+
RESET = "\e[0m"
|
4
|
+
GREEN = "\e[1;37;42m"
|
5
|
+
YELLOW = "\e[1;37;43m"
|
6
|
+
RED = "\e[1;37;41m"
|
7
|
+
|
8
|
+
attr_accessor :verbose
|
9
|
+
|
10
|
+
def verbose?
|
11
|
+
!!@verbose
|
12
|
+
end
|
13
|
+
|
14
|
+
def verbose!
|
15
|
+
@verbose = true
|
16
|
+
end
|
17
|
+
|
18
|
+
def vbanner(s = nil)
|
19
|
+
banner(s) if verbose?
|
20
|
+
end
|
21
|
+
|
22
|
+
def vputs(s = nil)
|
23
|
+
$stderr.puts(s) if verbose?
|
24
|
+
end
|
25
|
+
|
26
|
+
def vprintf(s, *args)
|
27
|
+
$stderr.printf(s, *args) if verbose?
|
28
|
+
end
|
29
|
+
|
30
|
+
def banner(s, color: GREEN)
|
31
|
+
now = Time.new.strftime("%H:%M:%S")
|
32
|
+
s = "#{s} ".ljust(72, " ")
|
33
|
+
$stderr.puts "#{color}[#{now}] #{s}#{RESET}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def warning(msg)
|
37
|
+
banner("Warning: #{msg}", color: YELLOW)
|
38
|
+
end
|
39
|
+
|
40
|
+
def fatal(msg)
|
41
|
+
banner(msg, color: RED)
|
42
|
+
exit(1)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require "english"
|
2
|
+
require "shellwords"
|
3
|
+
|
4
|
+
module Scripto
|
5
|
+
module RunCommands
|
6
|
+
class Error < StandardError ; end
|
7
|
+
|
8
|
+
def run(command, args = nil)
|
9
|
+
cmd = CommandLine.new(command, args)
|
10
|
+
vputs(cmd)
|
11
|
+
cmd.run
|
12
|
+
end
|
13
|
+
|
14
|
+
def run_capture(command, args = nil)
|
15
|
+
CommandLine.new(command, args).capture
|
16
|
+
end
|
17
|
+
|
18
|
+
def run_quietly(command, args = nil)
|
19
|
+
cmd = CommandLine.new(command, args)
|
20
|
+
run("#{cmd} > /dev/null 2> /dev/null")
|
21
|
+
end
|
22
|
+
|
23
|
+
def run_succeeds?(command, args = nil)
|
24
|
+
begin
|
25
|
+
run_quietly(command, args)
|
26
|
+
true
|
27
|
+
rescue Error
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def run_fails?(command, args = nil)
|
33
|
+
!run_succeeds?(command, args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def shellescape(s)
|
37
|
+
Shellwords.escape(s)
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
class CommandLine
|
43
|
+
attr_accessor :command, :args
|
44
|
+
|
45
|
+
def initialize(command, args)
|
46
|
+
self.command = command
|
47
|
+
self.args = if args
|
48
|
+
args.map(&:to_s)
|
49
|
+
else
|
50
|
+
[ ]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def run
|
55
|
+
system(command, *args)
|
56
|
+
raise!($CHILD_STATUS) if $CHILD_STATUS != 0
|
57
|
+
end
|
58
|
+
|
59
|
+
def capture
|
60
|
+
begin
|
61
|
+
captured = `#{to_s}`
|
62
|
+
rescue Errno::ENOENT
|
63
|
+
raise Error, "#{self} failed : ENOENT (No such file or directory)"
|
64
|
+
end
|
65
|
+
raise!($CHILD_STATUS) if $CHILD_STATUS != 0
|
66
|
+
captured
|
67
|
+
end
|
68
|
+
|
69
|
+
def raise!(status)
|
70
|
+
if status.termsig == Signal.list["INT"]
|
71
|
+
raise "#{self} interrupted"
|
72
|
+
end
|
73
|
+
raise Error, "#{self} failed : #{status.to_i / 256}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_s
|
77
|
+
if args.length > 0
|
78
|
+
escaped = args.map { |i| Shellwords.escape(i) }
|
79
|
+
"#{command} #{escaped.join(" ")}"
|
80
|
+
else
|
81
|
+
command
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/scripto.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'scripto/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "scripto"
|
8
|
+
spec.version = Scripto::VERSION
|
9
|
+
spec.platform = Gem::Platform::RUBY
|
10
|
+
spec.required_ruby_version = ">= 2.0.0"
|
11
|
+
spec.authors = ["Adam Doppelt"]
|
12
|
+
spec.email = ["amd@gurge.com"]
|
13
|
+
spec.summary = "Helpers for writing command line scripts."
|
14
|
+
spec.homepage = "http://github.com/gurgeous/scripto"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0")
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
23
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "minitest/autorun"
|
3
|
+
require "minitest/pride"
|
4
|
+
|
5
|
+
$LOAD_PATH << File.expand_path("../../lib", __FILE__)
|
6
|
+
require "scripto"
|
7
|
+
|
8
|
+
module Helper
|
9
|
+
TMP_DIR = "/tmp/_scripto_test"
|
10
|
+
|
11
|
+
# attr_accessor :main
|
12
|
+
|
13
|
+
def setup
|
14
|
+
# self.main = Scripto::Main.new
|
15
|
+
FileUtils.rm_rf(TMP_DIR)
|
16
|
+
FileUtils.mkdir_p(TMP_DIR)
|
17
|
+
@pwd = Dir.pwd
|
18
|
+
Dir.chdir(TMP_DIR)
|
19
|
+
Scripto.verbose = false
|
20
|
+
end
|
21
|
+
|
22
|
+
def teardown
|
23
|
+
FileUtils.rm_rf(TMP_DIR)
|
24
|
+
Dir.chdir(@pwd)
|
25
|
+
end
|
26
|
+
end
|
data/test/test_csv.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
class TestCsv < Minitest::Test
|
4
|
+
include Helper
|
5
|
+
|
6
|
+
FILE = "test.csv"
|
7
|
+
|
8
|
+
ROWS = [ { a: "1", b: "2", c: "3" }, { a: "4", b: "5", c: "6" } ]
|
9
|
+
ROWS_EXP = "a,b,c\n1,2,3\n4,5,6\n"
|
10
|
+
ROWS_REVERSED_EXP = "c,b,a\n3,2,1\n6,5,4\n"
|
11
|
+
|
12
|
+
def test_types
|
13
|
+
assert_equal(ROWS_EXP, Scripto.csv_to_s(ROWS))
|
14
|
+
assert_equal(ROWS_EXP, Scripto.csv_to_s(structs))
|
15
|
+
assert_equal(ROWS_EXP, Scripto.csv_to_s(ostructs))
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_cols
|
19
|
+
assert_equal(ROWS_REVERSED_EXP, Scripto.csv_to_s(ROWS, cols: %i(c b a)))
|
20
|
+
assert_equal(ROWS_REVERSED_EXP, Scripto.csv_to_s(structs, cols: %i(c b a)))
|
21
|
+
assert_equal(ROWS_REVERSED_EXP, Scripto.csv_to_s(ostructs, cols: %i(c b a)))
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_csv_stdout
|
25
|
+
assert_output(ROWS_EXP) { Scripto.csv_to_stdout(ROWS) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_csv_read_write
|
29
|
+
Scripto.csv_write(FILE, ROWS)
|
30
|
+
assert_equal(ROWS_EXP, File.read(FILE))
|
31
|
+
assert_equal(ROWS, Scripto.csv_read(FILE).map(&:to_h))
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_gz
|
35
|
+
Scripto.csv_write(FILE, ROWS)
|
36
|
+
Scripto.run("gzip #{FILE}")
|
37
|
+
assert_equal(ROWS, Scripto.csv_read("#{FILE}.gz").map(&:to_h))
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_atomic
|
41
|
+
magic = "can't touch this"
|
42
|
+
File.write(FILE, magic)
|
43
|
+
begin
|
44
|
+
Util.csv_write(TMP, rows, cols: %i(bad_column))
|
45
|
+
rescue
|
46
|
+
end
|
47
|
+
assert_equal(magic, File.read(FILE))
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def structs
|
53
|
+
klass = Struct.new(*ROWS.first.keys)
|
54
|
+
ROWS.map { |i| klass.new(*i.values) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def ostructs
|
58
|
+
ROWS.map { |i| OpenStruct.new(i) }
|
59
|
+
end
|
60
|
+
end
|
data/test/test_file.rb
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
class TestFile < Minitest::Test
|
4
|
+
include Helper
|
5
|
+
|
6
|
+
DIR = "dir"
|
7
|
+
SRC, DST = "src.txt", "dst.txt"
|
8
|
+
SRC2, DST2 = "src2.txt", "dst2.txt"
|
9
|
+
|
10
|
+
def setup
|
11
|
+
super
|
12
|
+
File.write(SRC, "something")
|
13
|
+
File.write(SRC2, "another thing")
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# basics
|
18
|
+
#
|
19
|
+
|
20
|
+
def test_mkdir
|
21
|
+
Scripto.mkdir(DIR)
|
22
|
+
assert(File.directory?(DIR))
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_mkdir_mode
|
26
|
+
Scripto.mkdir(DIR, mode: 0644)
|
27
|
+
assert_equal(0644, File.stat(DIR).mode & 0644)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_cp
|
31
|
+
Scripto.cp(SRC, DST)
|
32
|
+
assert_equal(File.read(SRC), File.read(DST))
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_cp_mode
|
36
|
+
Scripto.cp(SRC, DST, mode: 0644)
|
37
|
+
assert_equal(0644, File.stat(DST).mode & 0644)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_cp_mkdir
|
41
|
+
in_dir = "#{DIR}/in_dir"
|
42
|
+
Scripto.cp(SRC, in_dir, mkdir: true)
|
43
|
+
assert_equal(File.read(SRC), File.read(in_dir))
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_mv
|
47
|
+
Scripto.mv(SRC, DST)
|
48
|
+
assert_equal("something", File.read(DST))
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_mv_mkdir
|
52
|
+
in_dir = "#{DIR}/in_dir"
|
53
|
+
Scripto.mv(SRC, in_dir, mkdir: true)
|
54
|
+
assert_equal("something", File.read(in_dir))
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_ln
|
58
|
+
Scripto.ln(SRC, DST)
|
59
|
+
assert_equal(SRC, File.readlink(DST))
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_rm
|
63
|
+
Scripto.rm(SRC)
|
64
|
+
assert(!File.exists?(SRC))
|
65
|
+
Scripto.rm("this_file_doesnt_exist") # shouldn't complain
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# if necessary (this is useful for printing)
|
70
|
+
#
|
71
|
+
|
72
|
+
def test_if_necessary
|
73
|
+
# should return true
|
74
|
+
assert Scripto.mkdir_if_necessary(DIR)
|
75
|
+
assert Scripto.cp_if_necessary(SRC, DST)
|
76
|
+
assert Scripto.ln_if_necessary(SRC, DST2)
|
77
|
+
|
78
|
+
assert(File.directory?(DIR))
|
79
|
+
assert_equal(File.read(SRC), File.read(DST))
|
80
|
+
assert_equal(SRC, File.readlink(DST2))
|
81
|
+
|
82
|
+
# should be silent
|
83
|
+
assert_fu_output(nil, "") do
|
84
|
+
assert_nil Scripto.mkdir_if_necessary(DIR)
|
85
|
+
assert_nil Scripto.cp_if_necessary(SRC, DST)
|
86
|
+
assert_nil Scripto.ln_if_necessary(SRC, DST2)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_cp_if_necessary_differs
|
91
|
+
File.write(DST, "this is different")
|
92
|
+
assert_fu_output(nil, "cp -rp #{SRC} #{DST}\n") do
|
93
|
+
assert Scripto.cp_if_necessary(SRC, DST)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_ln_if_necessary_differs
|
98
|
+
File.symlink(SRC2, DST)
|
99
|
+
assert_fu_output(nil, "rm -f #{DST}\nln -sf #{SRC} #{DST}\n") do
|
100
|
+
assert Scripto.ln_if_necessary(SRC, DST)
|
101
|
+
assert_equal(SRC, File.readlink(DST))
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_chmod
|
106
|
+
Scripto.chmod(SRC, 0644)
|
107
|
+
assert_equal(0644, File.stat(SRC).mode & 0644)
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_rm_and_mkdir
|
111
|
+
Dir.mkdir(DIR)
|
112
|
+
File.write("#{DIR}/file", "this is a test")
|
113
|
+
assert Dir["#{DIR}/*"].length == 1
|
114
|
+
Scripto.rm_and_mkdir(DIR)
|
115
|
+
assert Dir["#{DIR}/*"].length == 0
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_copy_metadata
|
119
|
+
File.chmod(0644, SRC)
|
120
|
+
File.utime(1234, 5678, SRC)
|
121
|
+
Scripto.copy_metadata(SRC, SRC2)
|
122
|
+
assert_equal(0644, File.stat(SRC2).mode & 0644)
|
123
|
+
assert_equal(1234, File.stat(SRC2).atime.to_i)
|
124
|
+
assert_equal(5678, File.stat(SRC2).mtime.to_i)
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
# chown rests - must be root
|
129
|
+
#
|
130
|
+
|
131
|
+
def test_mkdir_owner
|
132
|
+
skip if !root?
|
133
|
+
Scripto.mkdir(DIR, owner: "nobody")
|
134
|
+
assert(Etc.getpwnam("nobody").uid, File.stat(DIR).uid)
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_cp_owner
|
138
|
+
skip if !root?
|
139
|
+
Scripto.cp(SRC, DST, owner: "nobody")
|
140
|
+
assert(Etc.getpwnam("nobody").uid, File.stat(DST).uid)
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_chown
|
144
|
+
skip if !root?
|
145
|
+
Scripto.chown(SRC, "nobody")
|
146
|
+
assert(Etc.getpwnam("nobody").uid, File.stat(SRC).uid)
|
147
|
+
end
|
148
|
+
|
149
|
+
#
|
150
|
+
# verbosity
|
151
|
+
#
|
152
|
+
|
153
|
+
def test_mkdir_verbose
|
154
|
+
assert_fu_output(nil, "mkdir -p #{DIR}\n") do
|
155
|
+
Scripto.mkdir(DIR)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_cp_verbose
|
160
|
+
assert_fu_output(nil, "cp -rp #{SRC} #{DST}\n") do
|
161
|
+
Scripto.cp(SRC, DST)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_mv_verbose
|
166
|
+
assert_fu_output(nil, "mv #{SRC} #{DST}\n") do
|
167
|
+
Scripto.mv(SRC, DST)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_ln_verbose
|
172
|
+
assert_fu_output(nil, "ln -sf #{SRC} #{DST}\n") do
|
173
|
+
Scripto.ln(SRC, DST)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_rm_verbose
|
178
|
+
assert_fu_output(nil, "rm -f #{SRC}\n") do
|
179
|
+
Scripto.rm(SRC)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
protected
|
184
|
+
|
185
|
+
def root?
|
186
|
+
if !defined?(@root)
|
187
|
+
@root = `whoami`.strip == "root"
|
188
|
+
end
|
189
|
+
@root
|
190
|
+
end
|
191
|
+
|
192
|
+
def assert_fu_output(stdout = nil, stderr = nil, &block)
|
193
|
+
Scripto.verbose!
|
194
|
+
assert_output(stdout, stderr) do
|
195
|
+
# FileUtils squirrels this away so we have to set it manually
|
196
|
+
FileUtils.instance_eval("@fileutils_output = $stderr")
|
197
|
+
yield
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
data/test/test_misc.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
class TestMisc < Minitest::Test
|
4
|
+
include Helper
|
5
|
+
|
6
|
+
def test_whoami
|
7
|
+
assert_equal(`whoami`.strip, Scripto.whoami)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_root?
|
11
|
+
assert_equal(`whoami`.strip == "root", Scripto.root?)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_md5_string
|
15
|
+
assert_equal("ba73632f801ac2c72d78134722f2cb84", Scripto.md5_string("gub"))
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_md5_file
|
19
|
+
File.write("test.txt", "gub")
|
20
|
+
assert_equal("ba73632f801ac2c72d78134722f2cb84", Scripto.md5_file("test.txt"))
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_prompt?
|
24
|
+
with_fake_stdin("YES") do
|
25
|
+
assert_output(nil, "question (y/n) ") do
|
26
|
+
assert Scripto.prompt?("question")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
with_fake_stdin("y") do
|
30
|
+
assert_output(nil, "question (y/n) ") do
|
31
|
+
assert Scripto.prompt?("question")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
with_fake_stdin("no") do
|
35
|
+
assert_output(nil, "question (y/n) ") do
|
36
|
+
assert !Scripto.prompt?("question")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def with_fake_stdin(str, &block)
|
44
|
+
old_stdin = $stdin
|
45
|
+
begin
|
46
|
+
$stdin = StringIO.new(str)
|
47
|
+
$stdin.rewind
|
48
|
+
yield
|
49
|
+
ensure
|
50
|
+
$stdin = old_stdin
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/test/test_print.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
class TestPrint < Minitest::Test
|
4
|
+
include Helper
|
5
|
+
|
6
|
+
def test_verbose?
|
7
|
+
assert(!Scripto.verbose?)
|
8
|
+
Scripto.verbose!
|
9
|
+
assert(Scripto.verbose?)
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_quiet
|
13
|
+
assert_silent { Scripto.vbanner "gub" }
|
14
|
+
assert_silent { Scripto.vprintf "gub" }
|
15
|
+
assert_silent { Scripto.vputs "gub" }
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_loud
|
19
|
+
Scripto.verbose!
|
20
|
+
assert_output(nil, /gub/) { Scripto.vbanner "gub" }
|
21
|
+
assert_output(nil, /gub/) { Scripto.vprintf "gub" }
|
22
|
+
assert_output(nil, /gub/) { Scripto.vputs "gub" }
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_warning
|
26
|
+
assert_output(nil, /gub/) { Scripto.warning "gub" }
|
27
|
+
end
|
28
|
+
end
|
data/test/test_run.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
class TestRun < Minitest::Test
|
4
|
+
include Helper
|
5
|
+
|
6
|
+
SUCCEEDS = "echo gub"
|
7
|
+
FAILS = "cat scripto_bogus_file 2> /dev/null"
|
8
|
+
BAD_COMMAND = "this_command_doesnt_exist"
|
9
|
+
SRC = "_scripto_src"
|
10
|
+
DST = "_scripto with spaces"
|
11
|
+
ARGS = [ "-f", SRC, DST ]
|
12
|
+
|
13
|
+
def setup
|
14
|
+
super
|
15
|
+
File.write(SRC, "something")
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# basic methods
|
20
|
+
#
|
21
|
+
|
22
|
+
def test_run
|
23
|
+
Scripto.run("#{SUCCEEDS} > #{SRC}")
|
24
|
+
assert_equal("gub", File.read(SRC).strip)
|
25
|
+
assert("gub", Scripto.run_capture(SUCCEEDS))
|
26
|
+
Scripto.run_quietly(SUCCEEDS)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_run_succeeds?
|
30
|
+
assert_succeeds(SUCCEEDS)
|
31
|
+
assert_fails(FAILS)
|
32
|
+
assert_fails(BAD_COMMAND)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_shellescape
|
36
|
+
assert_equal("gub", Scripto.shellescape("gub"))
|
37
|
+
assert_equal("gub\\ zub", Scripto.shellescape("gub zub"))
|
38
|
+
end
|
39
|
+
|
40
|
+
# verbosity
|
41
|
+
def test_verbose
|
42
|
+
Scripto.verbose!
|
43
|
+
cmd = "#{SUCCEEDS} > /dev/null"
|
44
|
+
assert_output(nil, "#{cmd}\n") do
|
45
|
+
Scripto.run(cmd)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# commands that fail
|
50
|
+
def test_failures
|
51
|
+
assert_raises(Scripto::RunCommands::Error) { Scripto.run(BAD_COMMAND) }
|
52
|
+
assert_raises(Scripto::RunCommands::Error) { Scripto.run_capture(BAD_COMMAND) }
|
53
|
+
assert_raises(Scripto::RunCommands::Error) { Scripto.run(FAILS) }
|
54
|
+
assert_raises(Scripto::RunCommands::Error) { Scripto.run_capture(FAILS) }
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# args
|
59
|
+
#
|
60
|
+
|
61
|
+
def test_run_args
|
62
|
+
# make sure SRC is copied to DST in all cases
|
63
|
+
assert_cp { Scripto.run("cp", ARGS) }
|
64
|
+
assert_cp { Scripto.run_quietly("cp", ARGS) }
|
65
|
+
assert_cp { assert_succeeds("cp", ARGS) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_capture_escaping
|
69
|
+
tricky = "\"'!tricky!'\""
|
70
|
+
assert_equal("#{tricky} #{tricky}\n", Scripto.run_capture("echo", [tricky, tricky]))
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_args_succeeds_fails
|
74
|
+
assert_fails(BAD_COMMAND, ARGS)
|
75
|
+
end
|
76
|
+
|
77
|
+
# is output escaped properly with verbose?
|
78
|
+
def test_args_verbose
|
79
|
+
Scripto.verbose!
|
80
|
+
assert_output(nil, "cp -f #{SRC} #{DST.gsub(" ", "\\ ")}\n") do
|
81
|
+
Scripto.run("cp", ARGS)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
def assert_cp(&block)
|
88
|
+
File.unlink(DST) if File.exists?(DST)
|
89
|
+
yield
|
90
|
+
assert_equal(File.read(SRC), File.read(DST))
|
91
|
+
File.unlink(DST)
|
92
|
+
end
|
93
|
+
|
94
|
+
def assert_succeeds(command, args = nil)
|
95
|
+
assert_equal(true, Scripto.run_succeeds?(command, args))
|
96
|
+
assert_equal(false, Scripto.run_fails?(command, args))
|
97
|
+
end
|
98
|
+
|
99
|
+
def assert_fails(command, args = nil)
|
100
|
+
assert_equal(false, Scripto.run_succeeds?(command, args))
|
101
|
+
assert_equal(true, Scripto.run_fails?(command, args))
|
102
|
+
end
|
103
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scripto
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Doppelt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- amd@gurge.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE.txt
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- lib/scripto.rb
|
68
|
+
- lib/scripto/csv_commands.rb
|
69
|
+
- lib/scripto/file_commands.rb
|
70
|
+
- lib/scripto/main.rb
|
71
|
+
- lib/scripto/misc_commands.rb
|
72
|
+
- lib/scripto/print_commands.rb
|
73
|
+
- lib/scripto/run_commands.rb
|
74
|
+
- lib/scripto/version.rb
|
75
|
+
- scripto.gemspec
|
76
|
+
- test/helper.rb
|
77
|
+
- test/test_csv.rb
|
78
|
+
- test/test_file.rb
|
79
|
+
- test/test_misc.rb
|
80
|
+
- test/test_print.rb
|
81
|
+
- test/test_run.rb
|
82
|
+
homepage: http://github.com/gurgeous/scripto
|
83
|
+
licenses:
|
84
|
+
- MIT
|
85
|
+
metadata: {}
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: 2.0.0
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubyforge_project:
|
102
|
+
rubygems_version: 2.2.1
|
103
|
+
signing_key:
|
104
|
+
specification_version: 4
|
105
|
+
summary: Helpers for writing command line scripts.
|
106
|
+
test_files:
|
107
|
+
- test/helper.rb
|
108
|
+
- test/test_csv.rb
|
109
|
+
- test/test_file.rb
|
110
|
+
- test/test_misc.rb
|
111
|
+
- test/test_print.rb
|
112
|
+
- test/test_run.rb
|