scripto 0.0.3 → 1.0.0
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 +5 -5
- data/.github/workflows/ci.yml +22 -0
- data/.gitignore +0 -17
- data/.rubocop.yml +15 -0
- data/.vscode/extensions.json +3 -0
- data/.vscode/settings.json +3 -0
- data/Gemfile +10 -2
- data/Gemfile.lock +97 -0
- data/README.md +31 -14
- data/Rakefile +2 -13
- data/justfile +54 -0
- data/lib/scripto/csv_commands.rb +17 -19
- data/lib/scripto/file_commands.rb +42 -34
- data/lib/scripto/log_commands.rb +112 -0
- data/lib/scripto/main.rb +2 -5
- data/lib/scripto/misc_commands.rb +2 -8
- data/lib/scripto/run_commands.rb +22 -25
- data/lib/scripto/version.rb +1 -1
- data/lib/scripto.rb +2 -2
- data/logo.svg +12 -0
- data/mise.toml +2 -0
- data/scripto.gemspec +18 -20
- metadata +22 -48
- data/.travis.yml +0 -7
- data/Vagrantfile +0 -121
- data/lib/scripto/print_commands.rb +0 -54
- data/test/helper.rb +0 -26
- data/test/test_csv.rb +0 -60
- data/test/test_file.rb +0 -200
- data/test/test_misc.rb +0 -53
- data/test/test_print.rb +0 -28
- data/test/test_run.rb +0 -103
- 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: 7cdd03bc1a558ff5c9f151d972205bde0816ad6feb89de992ae141bd637ebc54
|
|
4
|
+
data.tar.gz: b5c45a7c274391597e1c6dcd2e91579cdb09cc028bda6179a1762f010d005a73
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a2b6883573f231fe24d7c6566d62510e95d4672842f0c580fb29e2634877e3020db88fe71b9d683e571ca23a2ef4e1479a0f0cf2de9f52d3d1faab0137d88001
|
|
7
|
+
data.tar.gz: 5934be60e97a589d0099a136027ee3888a6d7c3d7a7f3589df70fe76a525f5286ee3aad2812a0e373f6b7248371aabece0d7c68c2256329ca3eb7e092709b934
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
paths-ignore:
|
|
6
|
+
- "**.md"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
ruby-version: [3.4]
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v5
|
|
17
|
+
- uses: taiki-e/install-action@just
|
|
18
|
+
- uses: ruby/setup-ruby@v1
|
|
19
|
+
with:
|
|
20
|
+
bundler-cache: true
|
|
21
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
22
|
+
- run: just check
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require:
|
|
2
|
+
- standard
|
|
3
|
+
|
|
4
|
+
inherit_gem:
|
|
5
|
+
standard: config/base.yml
|
|
6
|
+
|
|
7
|
+
AllCops:
|
|
8
|
+
Exclude:
|
|
9
|
+
- vendor/**/* # for ci - https://github.com/rubocop/rubocop/issues/9832
|
|
10
|
+
NewCops: enable
|
|
11
|
+
SuggestExtensions: false
|
|
12
|
+
TargetRubyVersion: 3.1
|
|
13
|
+
|
|
14
|
+
# tweaks
|
|
15
|
+
Style/HashSyntax: { EnforcedShorthandSyntax: always }
|
data/Gemfile
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
source
|
|
1
|
+
source "https://rubygems.org"
|
|
2
2
|
|
|
3
|
-
# Specify your gem's dependencies in scripto.gemspec
|
|
4
3
|
gemspec
|
|
4
|
+
|
|
5
|
+
group :development, :test do
|
|
6
|
+
gem "minitest", "~> 5.0"
|
|
7
|
+
gem "mocha"
|
|
8
|
+
gem "pry", require: false
|
|
9
|
+
gem "rake", require: false
|
|
10
|
+
gem "ruby-lsp", require: false
|
|
11
|
+
gem "standard", require: false
|
|
12
|
+
end
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
scripto (1.0.0)
|
|
5
|
+
csv (~> 3.3)
|
|
6
|
+
ostruct (~> 0.0)
|
|
7
|
+
|
|
8
|
+
GEM
|
|
9
|
+
remote: https://rubygems.org/
|
|
10
|
+
specs:
|
|
11
|
+
ast (2.4.3)
|
|
12
|
+
coderay (1.1.3)
|
|
13
|
+
csv (3.3.5)
|
|
14
|
+
io-console (0.8.2)
|
|
15
|
+
json (2.18.0)
|
|
16
|
+
language_server-protocol (3.17.0.5)
|
|
17
|
+
lint_roller (1.1.0)
|
|
18
|
+
logger (1.7.0)
|
|
19
|
+
method_source (1.1.0)
|
|
20
|
+
minitest (5.27.0)
|
|
21
|
+
mocha (3.0.1)
|
|
22
|
+
ruby2_keywords (>= 0.0.5)
|
|
23
|
+
ostruct (0.6.3)
|
|
24
|
+
parallel (1.27.0)
|
|
25
|
+
parser (3.3.10.1)
|
|
26
|
+
ast (~> 2.4.1)
|
|
27
|
+
racc
|
|
28
|
+
prism (1.9.0)
|
|
29
|
+
pry (0.16.0)
|
|
30
|
+
coderay (~> 1.1)
|
|
31
|
+
method_source (~> 1.0)
|
|
32
|
+
reline (>= 0.6.0)
|
|
33
|
+
racc (1.8.1)
|
|
34
|
+
rainbow (3.1.1)
|
|
35
|
+
rake (13.3.1)
|
|
36
|
+
rbs (3.10.2)
|
|
37
|
+
logger
|
|
38
|
+
regexp_parser (2.11.3)
|
|
39
|
+
reline (0.6.3)
|
|
40
|
+
io-console (~> 0.5)
|
|
41
|
+
rubocop (1.82.1)
|
|
42
|
+
json (~> 2.3)
|
|
43
|
+
language_server-protocol (~> 3.17.0.2)
|
|
44
|
+
lint_roller (~> 1.1.0)
|
|
45
|
+
parallel (~> 1.10)
|
|
46
|
+
parser (>= 3.3.0.2)
|
|
47
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
48
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
49
|
+
rubocop-ast (>= 1.48.0, < 2.0)
|
|
50
|
+
ruby-progressbar (~> 1.7)
|
|
51
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
52
|
+
rubocop-ast (1.49.0)
|
|
53
|
+
parser (>= 3.3.7.2)
|
|
54
|
+
prism (~> 1.7)
|
|
55
|
+
rubocop-performance (1.26.1)
|
|
56
|
+
lint_roller (~> 1.1)
|
|
57
|
+
rubocop (>= 1.75.0, < 2.0)
|
|
58
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
59
|
+
ruby-lsp (0.26.5)
|
|
60
|
+
language_server-protocol (~> 3.17.0)
|
|
61
|
+
prism (>= 1.2, < 2.0)
|
|
62
|
+
rbs (>= 3, < 5)
|
|
63
|
+
ruby-progressbar (1.13.0)
|
|
64
|
+
ruby2_keywords (0.0.5)
|
|
65
|
+
standard (1.53.0)
|
|
66
|
+
language_server-protocol (~> 3.17.0.2)
|
|
67
|
+
lint_roller (~> 1.0)
|
|
68
|
+
rubocop (~> 1.82.0)
|
|
69
|
+
standard-custom (~> 1.0.0)
|
|
70
|
+
standard-performance (~> 1.8)
|
|
71
|
+
standard-custom (1.0.2)
|
|
72
|
+
lint_roller (~> 1.0)
|
|
73
|
+
rubocop (~> 1.50)
|
|
74
|
+
standard-performance (1.9.0)
|
|
75
|
+
lint_roller (~> 1.1)
|
|
76
|
+
rubocop-performance (~> 1.26.0)
|
|
77
|
+
unicode-display_width (3.2.0)
|
|
78
|
+
unicode-emoji (~> 4.1)
|
|
79
|
+
unicode-emoji (4.2.0)
|
|
80
|
+
|
|
81
|
+
PLATFORMS
|
|
82
|
+
arm64-darwin-20
|
|
83
|
+
arm64-darwin-21
|
|
84
|
+
arm64-darwin-24
|
|
85
|
+
x86_64-linux
|
|
86
|
+
|
|
87
|
+
DEPENDENCIES
|
|
88
|
+
minitest (~> 5.0)
|
|
89
|
+
mocha
|
|
90
|
+
pry
|
|
91
|
+
rake
|
|
92
|
+
ruby-lsp
|
|
93
|
+
scripto!
|
|
94
|
+
standard
|
|
95
|
+
|
|
96
|
+
BUNDLED WITH
|
|
97
|
+
2.5.21
|
data/README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
[](https://github.com/gurgeous/scripto/actions/workflows/ci.yml)
|
|
2
|
+
|
|
3
|
+

|
|
2
4
|
|
|
3
|
-
Scripto
|
|
5
|
+
# Scripto
|
|
4
6
|
|
|
5
|
-
|
|
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
|
+
Scripto is a framework for writing Ruby command line applications. It fills in many of the blanks that Ruby's standard library is missing:
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
- **printing** - Colored banners and a verbose mode to make your scripts louder.
|
|
10
|
+
- **file operations** - Mkdir, cp, mv, ln. These operations can take care of common edge cases, like creating the target directory before copying a file.
|
|
11
|
+
- **run commands** - Run external commands and raise errors on failure.
|
|
12
|
+
- **csv** - Read and write CSV files from hashes, Structs, or OpenStructs.
|
|
11
13
|
|
|
12
14
|
## Getting Started
|
|
13
15
|
|
|
@@ -41,7 +43,7 @@ require "scripto"
|
|
|
41
43
|
|
|
42
44
|
class Install < Scripto::Main
|
|
43
45
|
def initialize(options = {})
|
|
44
|
-
|
|
46
|
+
super
|
|
45
47
|
|
|
46
48
|
banner("Starting installation...")
|
|
47
49
|
cp("here.txt", "there.txt")
|
|
@@ -65,17 +67,18 @@ Install.new(verbose: true)
|
|
|
65
67
|
|
|
66
68
|
## Methods
|
|
67
69
|
|
|
68
|
-
###
|
|
70
|
+
### Printing
|
|
71
|
+
|
|
72
|
+
Scripto has a built-in logger that wraps $stdout. The logger runs in INFO mode
|
|
73
|
+
by default, but will switch to DEBUG if options[:verbose] is true. Or you can
|
|
74
|
+
quiet it down to ERROR if options[:quiet] is true. `banner`, `warning` and
|
|
75
|
+
`fatal` use Scripto's logger.
|
|
69
76
|
|
|
70
77
|
```
|
|
71
78
|
banner(str) - print a banner in green
|
|
72
79
|
warning(str) - print a warning in yellow
|
|
73
80
|
fatal(str) - print a fatal error in red, then exit(1)
|
|
74
|
-
|
|
75
|
-
verbose! - turn on verbose mode
|
|
76
|
-
vbanner(str) - print a colored banner in green if verbose is on
|
|
77
|
-
vprintf(str) - printf if verbose is on
|
|
78
|
-
vputs(str) - puts if verbose is on
|
|
81
|
+
verbose! - force verbose
|
|
79
82
|
```
|
|
80
83
|
|
|
81
84
|
### File operations
|
|
@@ -151,3 +154,17 @@ md5_string(str) - calculate md5 for a string
|
|
|
151
154
|
prompt?(question) - ask the user a question, return true if they say yes
|
|
152
155
|
random_string(len) - calculate a random alphanumeric string
|
|
153
156
|
```
|
|
157
|
+
|
|
158
|
+
# Changelog
|
|
159
|
+
|
|
160
|
+
### 1.0.0 (early 2026)
|
|
161
|
+
|
|
162
|
+
- banner and friends use Logger (breaking)
|
|
163
|
+
- only support Ruby 3.4+ (breaking)
|
|
164
|
+
- [standardrb](https://github.com/testdouble/standard) and a [justfile](https://github.com/casey/just)
|
|
165
|
+
- update deps, switch to Github actions, etc.
|
|
166
|
+
|
|
167
|
+
### 0.0.4 (late 2020)
|
|
168
|
+
|
|
169
|
+
- Added support for reading CSV with BOM.
|
|
170
|
+
- Only support Ruby 2.3+, since we moved to modern Bundler
|
data/Rakefile
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
|
-
require "bundler/
|
|
1
|
+
require "bundler/setup"
|
|
2
2
|
require "rake/testtask"
|
|
3
|
-
require "rdoc/task"
|
|
4
3
|
|
|
5
|
-
Rake::TestTask.new(:test)
|
|
6
|
-
test.libs << "test"
|
|
7
|
-
end
|
|
4
|
+
Rake::TestTask.new(:test)
|
|
8
5
|
task default: :test
|
|
9
|
-
|
|
10
|
-
RDoc::Task.new do |rdoc|
|
|
11
|
-
rdoc.rdoc_dir = "rdoc"
|
|
12
|
-
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
|
data/justfile
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
|
|
2
|
+
# read gem version
|
|
3
|
+
gemver := `cat lib/scripto/version.rb | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+"`
|
|
4
|
+
|
|
5
|
+
set quiet := true
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# dev
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
default: test
|
|
12
|
+
|
|
13
|
+
check: lint test
|
|
14
|
+
|
|
15
|
+
fmt:
|
|
16
|
+
bundle exec rubocop -a
|
|
17
|
+
|
|
18
|
+
lint:
|
|
19
|
+
just banner lint...
|
|
20
|
+
bundle exec rubocop
|
|
21
|
+
|
|
22
|
+
pry:
|
|
23
|
+
bundle exec pry -I lib -r scripto.rb
|
|
24
|
+
|
|
25
|
+
test:
|
|
26
|
+
just banner test...
|
|
27
|
+
bundle exec rake test
|
|
28
|
+
|
|
29
|
+
watch:
|
|
30
|
+
watchexec --watch lib --watch test --clear bundle exec rake test
|
|
31
|
+
|
|
32
|
+
#
|
|
33
|
+
# gem tasks
|
|
34
|
+
#
|
|
35
|
+
|
|
36
|
+
gem-push: # check-git-status
|
|
37
|
+
just banner gem build...
|
|
38
|
+
gem build scripto.gemspec
|
|
39
|
+
just banner tag...
|
|
40
|
+
# git tag -a "v{{gemver}}" -m "Tagging {{gemver}}"
|
|
41
|
+
# git push --tags
|
|
42
|
+
just banner gem push...
|
|
43
|
+
gem push "scripto-{{gemver}}.gem"
|
|
44
|
+
|
|
45
|
+
#
|
|
46
|
+
# util
|
|
47
|
+
#
|
|
48
|
+
|
|
49
|
+
GREEN := '\e[1;38;2;255;255;255;48;2;64;160;43m'
|
|
50
|
+
banner *ARGS:
|
|
51
|
+
printf '{{GREEN}}[%s] %-72s \e[m\n' "$(date +%H:%M:%S)" "{{ARGS}}"
|
|
52
|
+
|
|
53
|
+
check-git-status:
|
|
54
|
+
if [ ! -z "$(git status --porcelain)" ]; then echo "git status is dirty, bailing."; exit 1; fi
|
data/lib/scripto/csv_commands.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require "csv"
|
|
2
|
+
require "tempfile"
|
|
2
3
|
require "zlib"
|
|
3
4
|
|
|
4
5
|
module Scripto
|
|
@@ -6,16 +7,17 @@ module Scripto
|
|
|
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
|
-
|
|
10
|
-
Zlib::GzipReader.open(path) do
|
|
11
|
-
CSV.new(
|
|
10
|
+
rows = if /\.gz$/.match?(path)
|
|
11
|
+
Zlib::GzipReader.open(path) do
|
|
12
|
+
CSV.new(_1).read
|
|
12
13
|
end
|
|
13
14
|
else
|
|
14
|
-
CSV.read(path)
|
|
15
|
+
CSV.read(path, encoding: "bom|utf-8")
|
|
15
16
|
end
|
|
16
|
-
|
|
17
|
+
|
|
18
|
+
keys = rows.shift.map(&:to_sym)
|
|
17
19
|
klass = Struct.new(*keys)
|
|
18
|
-
|
|
20
|
+
rows.map { klass.new(*_1) }
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
# Write +rows+ to +path+ as csv. Rows can be an array of hashes, Structs,
|
|
@@ -23,26 +25,22 @@ module Scripto
|
|
|
23
25
|
# first row are used as the csv header. If +cols+ is specified, it will be
|
|
24
26
|
# used as the column keys instead.
|
|
25
27
|
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)
|
|
28
|
+
atomic_write(path) do |tmp|
|
|
29
|
+
CSV.open(tmp.path, "wb") { csv_write0(_1, rows, cols:) }
|
|
32
30
|
end
|
|
33
31
|
end
|
|
34
32
|
|
|
35
33
|
# Write +rows+ to $stdout as a csv. Similar to csv_write.
|
|
36
34
|
def csv_to_stdout(rows, cols: nil)
|
|
37
|
-
CSV($stdout) {
|
|
35
|
+
CSV($stdout) { csv_write0(_1, rows, cols:) }
|
|
38
36
|
end
|
|
39
37
|
|
|
40
38
|
# Returns a string containing +rows+ as a csv. Similar to csv_write.
|
|
41
39
|
def csv_to_s(rows, cols: nil)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
string
|
|
40
|
+
StringIO.new.tap do
|
|
41
|
+
f = CSV.new(_1)
|
|
42
|
+
csv_write0(f, rows, cols:)
|
|
43
|
+
end.string
|
|
46
44
|
end
|
|
47
45
|
|
|
48
46
|
protected
|
|
@@ -56,8 +54,8 @@ module Scripto
|
|
|
56
54
|
# rows
|
|
57
55
|
rows.each do |row|
|
|
58
56
|
row = row.to_h
|
|
59
|
-
csv << cols.map {
|
|
57
|
+
csv << cols.map { row[_1] }
|
|
60
58
|
end
|
|
61
59
|
end
|
|
62
60
|
end
|
|
63
|
-
end
|
|
61
|
+
end
|
|
@@ -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:, 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:, owner:, 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
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
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
require "logger"
|
|
2
|
+
|
|
3
|
+
module Scripto
|
|
4
|
+
module LogCommands
|
|
5
|
+
# https://catppuccin.com/palette/
|
|
6
|
+
RESET = "\e[0m"
|
|
7
|
+
GREEN = "\e[1;38;2;255;255;255;48;2;64;160;43m"
|
|
8
|
+
YELLOW = "\e[1;38;2;255;255;255;48;2;251;100;11m"
|
|
9
|
+
RED = "\e[1;38;2;255;255;255;48;2;210;15;57m"
|
|
10
|
+
|
|
11
|
+
#
|
|
12
|
+
# logger
|
|
13
|
+
#
|
|
14
|
+
|
|
15
|
+
# Returns the built-in logger. If none has been set, create a new one
|
|
16
|
+
# wrapped around $stdout. Used by banner/warning/fatal.
|
|
17
|
+
def logger
|
|
18
|
+
@logger ||= begin
|
|
19
|
+
level = if options[:verbose]
|
|
20
|
+
Logger::DEBUG
|
|
21
|
+
elsif options[:quiet]
|
|
22
|
+
Logger::ERROR
|
|
23
|
+
else
|
|
24
|
+
Logger::INFO
|
|
25
|
+
end
|
|
26
|
+
Logger.new($stdout, level:, formatter: Formatter.new)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Set the built-in logger.
|
|
31
|
+
def logger=(value)
|
|
32
|
+
@logger = value
|
|
33
|
+
@log_with_color = false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
#
|
|
37
|
+
# options
|
|
38
|
+
#
|
|
39
|
+
|
|
40
|
+
# Get options
|
|
41
|
+
def options
|
|
42
|
+
@options ||= {}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Set options
|
|
46
|
+
def options=(value)
|
|
47
|
+
@options = value.dup
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Should we log in verbose mode?
|
|
51
|
+
def verbose?
|
|
52
|
+
logger.level == Logger::DEBUG
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Should we log in quiet mode?
|
|
56
|
+
def quiet?
|
|
57
|
+
logger.level == Logger::ERROR
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Set logging to verbose (DEBUG)
|
|
61
|
+
def verbose!
|
|
62
|
+
logger.level = Logger::DEBUG
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Set logging to quiet (ERROR)
|
|
66
|
+
def quiet!
|
|
67
|
+
logger.level = Logger::ERROR
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Should we use color? Inferred from $stdout.tty? if not set.
|
|
71
|
+
def log_with_color?
|
|
72
|
+
return @log_with_color if defined?(@log_with_color)
|
|
73
|
+
$stdout.tty?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Set whether we should use color.
|
|
77
|
+
def log_with_color=(value)
|
|
78
|
+
@log_with_color = value
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
#
|
|
82
|
+
# banner/warning/fatal
|
|
83
|
+
#
|
|
84
|
+
|
|
85
|
+
# Log a colored banner in green.
|
|
86
|
+
def banner(str, log_level: Logger::INFO, color: GREEN)
|
|
87
|
+
s = "#{str} ".ljust(72, " ")
|
|
88
|
+
s = "[#{Time.new.strftime("%H:%M:%S")}] #{s}"
|
|
89
|
+
s = "#{color}#{s}#{RESET}" if log_with_color?
|
|
90
|
+
logger.add(log_level, s)
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Log a yellow warning banner.
|
|
95
|
+
def warning(str)
|
|
96
|
+
banner("Warning: #{str}", log_level: Logger::WARN, color: YELLOW)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Log a red error banner, then exit.
|
|
100
|
+
def fatal(str)
|
|
101
|
+
banner(str, log_level: Logger::FATAL, color: RED)
|
|
102
|
+
exit(1)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Simple log formatter with no timestamp.
|
|
106
|
+
class Formatter < Logger::Formatter
|
|
107
|
+
def call(_severity, _time, _progname, msg)
|
|
108
|
+
"#{msg}\n"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
data/lib/scripto/main.rb
CHANGED
|
@@ -4,15 +4,12 @@ module Scripto
|
|
|
4
4
|
class Main
|
|
5
5
|
include CsvCommands
|
|
6
6
|
include FileCommands
|
|
7
|
+
include LogCommands
|
|
7
8
|
include MiscCommands
|
|
8
|
-
include PrintCommands
|
|
9
9
|
include RunCommands
|
|
10
10
|
|
|
11
|
-
attr_accessor :options
|
|
12
|
-
|
|
13
11
|
def initialize(options = {})
|
|
14
12
|
self.options = options
|
|
15
|
-
self.verbose = options[:verbose]
|
|
16
13
|
end
|
|
17
14
|
end
|
|
18
|
-
end
|
|
15
|
+
end
|