scripto 0.0.3 → 0.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 682b3b4d477f4c653c2f4f28b650eeb539d7c7fd
4
- data.tar.gz: 1b9e0f393c6d9666f6cb374ca2c485ca95272fc6
2
+ SHA256:
3
+ metadata.gz: e0ea62ce310b383c38d173c5e3cd61b2eb4e7ea345c92f6b2f1874e7c6a3c93f
4
+ data.tar.gz: 0fc744955f01e99d7ee44f53b927d1a93ca6d46e35a7e1b9baf36f62e9939711
5
5
  SHA512:
6
- metadata.gz: 2780520e158ddcf2843a38361c1fd64a9d041c7c215901ad6fe95e5932c43acca75fbda8bf2af8bb6c3928b795e41edf46e2ef3e7cd913d758558d7d8428bcd2
7
- data.tar.gz: de8a0e1eb329fddfe4ff9e86efc65ecbaacc2765c6bbaed3877492b5e58f97b5f89af5d9d5f719fd4743a5171c9c31632c52b2fc63311b460f692bcfb50f5f32
6
+ metadata.gz: fee85c4a32b32d4caf1f54aec0efac6e0634f2217154c1c6367b1c184878d08b14f9f4e035eda2d78b4157bef41d839b7044fbe230957bf81eba2df3bf451f8e
7
+ data.tar.gz: 7664c604a09572d1cecf1e4e7cc1d3b8845e6dfb5ff1ea1e37edf660a58007f19a17a46668786a8014a71f36c373e6eff7b8f811abe5d3b3f14dd854cb068e8f
data/.gitignore CHANGED
@@ -1,14 +1,15 @@
1
- *.gem
2
- *.rbc
1
+ _yardoc
3
2
  .bundle
4
3
  .config
4
+ .ruby-version
5
5
  .vagrant
6
6
  .yardoc
7
- Gemfile.lock
8
- InstalledFiles
9
- _yardoc
7
+ *.gem
8
+ *.rbc
10
9
  coverage
11
10
  doc/
11
+ Gemfile.lock
12
+ InstalledFiles
12
13
  lib/bundler/man
13
14
  pkg
14
15
  rdoc
@@ -1,7 +1,9 @@
1
1
  language: ruby
2
+ before_install:
3
+ - gem install bundler
2
4
  rvm:
3
- - 2.0.0
4
- - 2.1.0
5
- - 2.2.0
6
5
  - 2.3.0
7
6
  - 2.4.0
7
+ - 2.5.0
8
+ - 2.6.0
9
+ - 2.7.0
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
- * **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
- * **run commands** - Run external commands and raise errors on failure.
8
- * **csv** - Read and write CSV files from hashes, Structs, or OpenStructs.
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 $stderr
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 "bundler/gem_tasks"
2
- require "rake/testtask"
3
- require "rdoc/task"
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 << "test"
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 = "rdoc"
12
+ rdoc.rdoc_dir = 'rdoc'
12
13
  rdoc.title = "scripto #{Scripto::VERSION}"
13
- rdoc.main = "README.md"
14
- rdoc.rdoc_files.include("lib/**/*.rb")
15
- rdoc.rdoc_files.include("README.md")
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
@@ -1,121 +1,16 @@
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"
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
@@ -1,10 +1,10 @@
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"
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
@@ -1,17 +1,25 @@
1
- require "csv"
2
- require "zlib"
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 = if path =~ /\.gz$/
10
- Zlib::GzipReader.open(path) do |f|
11
- CSV.new(f).read
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
- begin
27
- tmp = "/tmp/_scripto_csv.csv"
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 "etc"
2
- require "fileutils"
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
- begin
40
- FileUtils.ln_sf(src, dst, verbose: verbose?)
41
- rescue Errno::EEXIST => e
42
- # It's a race - this can occur because ln_sf removes the old
43
- # dst, then creates the symlink. Raise if they don't match.
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 !(File.exist?(dir) || File.symlink?(dir))
59
- mkdir(dir, owner: owner, mode: mode)
60
- true
61
- end
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 !(File.exist?(dst) && FileUtils.compare_file(src, dst))
69
- cp(src, dst, mkdir: mkdir, owner: owner, mode: mode)
70
- true
71
- end
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
- ln = if !File.symlink?(dst)
79
- true
80
- elsif File.readlink(dst) != src
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
- rm(file)
95
- true
96
- end
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 != uid
107
- FileUtils.chown(uid, uid, file, verbose: verbose?)
108
- end
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 != mode
115
- FileUtils.chmod(mode, file, verbose: verbose?)
116
- end
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