shell_helpers 0.1.0 → 0.6.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/.gitignore +6 -2
- data/.travis.yml +10 -0
- data/.yardopts +6 -1
- data/Gemfile +8 -0
- data/LICENSE.txt +1 -1
- data/README.md +27 -3
- data/Rakefile +7 -12
- data/TODO +2 -0
- data/bin/abs_to_rel.rb +42 -0
- data/bin/mv_and_ln.rb +54 -0
- data/gemspec.yml +5 -4
- data/lib/shell_helpers.rb +38 -11
- data/lib/shell_helpers/export.rb +169 -0
- data/lib/shell_helpers/logger.rb +83 -28
- data/lib/shell_helpers/options.rb +28 -0
- data/lib/shell_helpers/pathname.rb +583 -110
- data/lib/shell_helpers/run.rb +115 -29
- data/lib/shell_helpers/sh.rb +188 -39
- data/lib/shell_helpers/sysutils.rb +427 -0
- data/lib/shell_helpers/utils.rb +216 -119
- data/lib/shell_helpers/version.rb +1 -1
- data/shell_helpers.gemspec +13 -1
- data/test/helper.rb +12 -1
- data/test/test_export.rb +77 -0
- metadata +33 -8
- data/.document +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6049045c2830e61e0ea865aeb4905e0f4464cdd4b837ee0e7923667e8aae7900
|
4
|
+
data.tar.gz: 2ca9a6fceac250538239f8ff3b075d4d2813b02892f7dad411dfad0a8dbfd1e7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 809023e14ecf8fc382ce25f107eeeba053573e190fac4030641a496e74306d3f480ef6b4555f5ffb71b9e412bf4f95778c78ab7bdf27efa7a517f92c1239c69c
|
7
|
+
data.tar.gz: 6db15aa50cb2ee5d60ee85404a109b00f8106aad9491b993c6aed289f2f23fd838d65124501a617672cf3e9fa06360aef525177199fedaa1b47362622b6a673c
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/.yardopts
CHANGED
data/Gemfile
ADDED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
# shell_helpers
|
2
2
|
|
3
3
|
* [Homepage](https://github.com/DamienRobert/shell_helpers#readme)
|
4
|
-
* [Gems]("https://rubygems.org/gems/shell_helpers)
|
5
4
|
* [Issues](https://github.com/DamienRobert/shell_helpers/issues)
|
6
|
-
* [Documentation](http://rubydoc.info/gems/shell_helpers
|
5
|
+
* [Documentation](http://rubydoc.info/gems/shell_helpers)
|
7
6
|
* [Email](mailto:Damien.Olivier.Robert+gems at gmail.com)
|
8
7
|
|
8
|
+
[](https://rubygems.org/gems/shell_helpers)
|
9
|
+
[](https://travis-ci.org/DamienRobert/shell_helpers)
|
10
|
+
|
9
11
|
## Description
|
10
12
|
|
11
13
|
This gem contains a collection of libraries to ease working with the
|
@@ -23,13 +25,35 @@
|
|
23
25
|
this functionality from other command parsers than methadone
|
24
26
|
(like [gli](https://github.com/davetron5000/gli)).
|
25
27
|
|
28
|
+
## Features
|
29
|
+
|
30
|
+
One of the main feature is an extension of the class `Pathname` with a
|
31
|
+
lots of methods to help in shell related task.
|
32
|
+
|
33
|
+
## Examples
|
34
|
+
|
35
|
+
require 'shell_helpers'
|
36
|
+
SH::Pathname.new("foo").cp("bar") #calls FileUtils.cp("foo","bar")
|
37
|
+
SH::Pathname.new("foo/").on_cp("bar","baz") #copy 'bar' and 'baz' to 'foo/'
|
38
|
+
SH::Pathname.new("foo").on_rm(mode: :dangling_symlink) #remove 'foo' only if it is a dangling symlink
|
39
|
+
SH::Pathname.new("foo").squel("bar/baz", action: :on_ln_s) #create a symlink foo/bar/baz -> ../../bar/baz
|
40
|
+
|
41
|
+
#Symlink all files in a directory into another, while preserving the structure
|
42
|
+
SH::Pathname.new("foo").squel_dir("bar',action: :on_ln_s)
|
43
|
+
#Remove these symlinks
|
44
|
+
SH::Pathname.new("foo").squel_dir("bar") {|o,t| o.on_rm(mode: :symlink)}
|
45
|
+
|
46
|
+
## Warning
|
47
|
+
|
48
|
+
For now the API is experimental and some parts are not ready to use!
|
49
|
+
|
26
50
|
## Install
|
27
51
|
|
28
52
|
$ gem install shell_helpers
|
29
53
|
|
30
54
|
## Copyright
|
31
55
|
|
32
|
-
Copyright
|
56
|
+
Copyright © 2015–2018 Damien Robert
|
33
57
|
|
34
58
|
MIT License. See {file:LICENSE.txt} for details.
|
35
59
|
|
data/Rakefile
CHANGED
@@ -1,20 +1,16 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
require 'rubygems'
|
4
1
|
require 'rake'
|
5
2
|
|
6
3
|
begin
|
7
|
-
gem 'rubygems-tasks', '~> 0.2'
|
8
4
|
require 'rubygems/tasks'
|
9
|
-
|
10
|
-
|
5
|
+
Gem::Tasks.new(sign: {checksum: true, pgp: true},
|
6
|
+
scm: {status: true}) do |tasks|
|
7
|
+
tasks.console.command = 'pry'
|
8
|
+
end
|
11
9
|
rescue LoadError => e
|
12
10
|
warn e.message
|
13
|
-
warn "Run `gem install rubygems-tasks` to install Gem::Tasks."
|
14
11
|
end
|
15
12
|
|
16
13
|
require 'rake/testtask'
|
17
|
-
|
18
14
|
Rake::TestTask.new do |test|
|
19
15
|
test.libs << 'test'
|
20
16
|
test.pattern = 'test/**/test_*.rb'
|
@@ -22,13 +18,12 @@ Rake::TestTask.new do |test|
|
|
22
18
|
end
|
23
19
|
|
24
20
|
begin
|
25
|
-
gem 'yard', '~> 0.8'
|
26
21
|
require 'yard'
|
27
|
-
|
28
|
-
YARD::Rake::YardocTask.new
|
22
|
+
YARD::Rake::YardocTask.new
|
29
23
|
rescue LoadError => e
|
30
24
|
task :yard do
|
31
|
-
|
25
|
+
warn e.message
|
32
26
|
end
|
33
27
|
end
|
34
28
|
task :doc => :yard
|
29
|
+
|
data/bin/abs_to_rel.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'shell_helpers/pathname'
|
4
|
+
require 'shell_helpers/options'
|
5
|
+
|
6
|
+
opts = Slop.parse(ARGV) do |o|
|
7
|
+
o.bool "-v", "--verbose", "verbose"
|
8
|
+
o.bool "-t", "--test", "test"
|
9
|
+
o.symbol "-m", "--mode", "Conversion mode", default: :rel
|
10
|
+
o.string "-b", "--base", "Assume symlinks are relative to this base"
|
11
|
+
o.symbol "--base-mode", "Base conversion mode", default: :abs_realdir
|
12
|
+
o.on '--help' do
|
13
|
+
puts o
|
14
|
+
exit
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
pathname=ShellHelpers::Pathname
|
19
|
+
if opts[:verbose] and opts[:test]
|
20
|
+
pathname=ShellHelpers::Pathname::DryRun
|
21
|
+
elsif opts[:verbose] and !opts[:test]
|
22
|
+
pathname=ShellHelpers::Pathname::Verbose
|
23
|
+
elsif opts[:test]
|
24
|
+
pathname=ShellHelpers::Pathname::NoWrite
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.args.each do |l|
|
28
|
+
l=pathname.new(l)
|
29
|
+
if l.symlink?
|
30
|
+
base=(opts[:base]||l.dirname).convert_path(mode: opts[:'base-mode'])
|
31
|
+
oldpath=l.readlink
|
32
|
+
newpath=l.rel_path_to(oldpath, base: base, mode: opts[:mode])
|
33
|
+
if oldpath != newpath
|
34
|
+
puts "#{l}: #{oldpath} -> #{newpath}" if opts[:verbose]
|
35
|
+
l.on_ln_sf(newpath, dereference: :none)
|
36
|
+
else
|
37
|
+
puts "#{l}: #{newpath}" if opts[:verbose]
|
38
|
+
end
|
39
|
+
else
|
40
|
+
puts "! #{l} is not a symlink" if opts[:verbose]
|
41
|
+
end
|
42
|
+
end
|
data/bin/mv_and_ln.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'shell_helpers/pathname'
|
4
|
+
require 'shell_helpers/options'
|
5
|
+
|
6
|
+
opts = Slop.parse(ARGV) do |o|
|
7
|
+
o.bool "-v", "--verbose", "verbose"
|
8
|
+
o.int "--verbose-level", "verbose level"
|
9
|
+
o.bool "-t", "--test", "test"
|
10
|
+
o.symbol "-m", "--mode", "Conversion mode", default: :rel
|
11
|
+
o.symbol "--rm", "rm options", default: :noclobber
|
12
|
+
o.symbol "--dereference-mode", "dereference mode for dest"
|
13
|
+
o.bool "-L", "--dereference", "dereference dest"
|
14
|
+
o.bool "-f", "--force", "force remove"
|
15
|
+
o.on '--help' do
|
16
|
+
puts o
|
17
|
+
exit
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
if opts[:force]
|
22
|
+
opts[:rm] ||= :all
|
23
|
+
else
|
24
|
+
opts[:rm] ||= :noclobber
|
25
|
+
end
|
26
|
+
#dereference-mode can also take the values 'simple' and 'none'
|
27
|
+
opts[:"dereference-mode"] ||= opts[:dereference]
|
28
|
+
|
29
|
+
pathname=ShellHelpers::Pathname
|
30
|
+
if opts[:verbose] and opts[:test]
|
31
|
+
pathname=ShellHelpers::Pathname::DryRun
|
32
|
+
elsif opts[:verbose] and !opts[:test]
|
33
|
+
pathname=ShellHelpers::Pathname::Verbose
|
34
|
+
elsif opts[:test]
|
35
|
+
pathname=ShellHelpers::Pathname::NoWrite
|
36
|
+
end
|
37
|
+
|
38
|
+
*args,dest=opts.args
|
39
|
+
dest=pathname.new(dest)
|
40
|
+
if args.empty?
|
41
|
+
warn "You should specify at least two arguments"
|
42
|
+
exit 1
|
43
|
+
end
|
44
|
+
if args.length > 1 and !dest.directory?
|
45
|
+
warn "When specifying more than one target, dest should be a directory"
|
46
|
+
exit 1
|
47
|
+
end
|
48
|
+
args.each do |f|
|
49
|
+
f=pathname.new(f)
|
50
|
+
t=dest.directory? ? dest+f.basename : dest
|
51
|
+
t=f.rel_path_to(t, mode: opts[:mode])
|
52
|
+
puts "#{f} -> #{t}" if opts[:verbose]
|
53
|
+
dest.on_mv(f, mode: opts[:rm], verbose: opts[:verbose], dereference: opts[:"dereference-mode"]) and f.on_ln_s(t, verbose: opts[:verbose])
|
54
|
+
end
|
data/gemspec.yml
CHANGED
@@ -8,8 +8,9 @@ email: Damien.Olivier.Robert+gems@gmail.com
|
|
8
8
|
homepage: https://github.com/DamienRobert/shell_helpers#readme
|
9
9
|
|
10
10
|
dependencies:
|
11
|
-
drain: ~> 0.
|
11
|
+
drain: ~> 0.2
|
12
12
|
development_dependencies:
|
13
|
-
minitest: ~> 5.0
|
14
|
-
|
15
|
-
|
13
|
+
minitest: "~> 5.0"
|
14
|
+
rake: "~> 10"
|
15
|
+
rubygems-tasks: "~> 0.2"
|
16
|
+
yard: "~> 0.8"
|
data/lib/shell_helpers.rb
CHANGED
@@ -1,20 +1,28 @@
|
|
1
1
|
require 'shell_helpers/version'
|
2
2
|
|
3
|
-
require '
|
4
|
-
require 'dr/ruby_ext/core_ext'
|
3
|
+
require 'fileutils'
|
4
|
+
#require 'dr/ruby_ext/core_ext'
|
5
5
|
#load everything in shell_helpers/*.rb
|
6
|
-
dir=File.expand_path(File.basename(__FILE__).chomp('.rb'), File.dirname(__FILE__))
|
7
|
-
Dir.glob(File.expand_path('*.rb',dir)) do |file|
|
8
|
-
require file
|
9
|
-
end
|
6
|
+
#dir=File.expand_path(File.basename(__FILE__).chomp('.rb'), File.dirname(__FILE__))
|
7
|
+
#Dir.glob(File.expand_path('*.rb',dir)) do |file|
|
8
|
+
# require file
|
9
|
+
#end
|
10
|
+
require 'shell_helpers/logger'
|
11
|
+
require 'shell_helpers/run'
|
12
|
+
require 'shell_helpers/sh'
|
13
|
+
require 'shell_helpers/utils'
|
14
|
+
require 'shell_helpers/sysutils'
|
15
|
+
require 'shell_helpers/export'
|
16
|
+
require 'shell_helpers/pathname'
|
10
17
|
|
11
|
-
module
|
18
|
+
module ShellHelpers
|
12
19
|
include Run #run_command, run_output, run_status, run
|
13
20
|
include CLILogging #logger.{debug info warn error fatal}, log_and_do
|
14
21
|
include ExitNow #exit_now!
|
15
22
|
include Sh #sh, sh!
|
16
|
-
include
|
17
|
-
include
|
23
|
+
include Export #export
|
24
|
+
include Utils #find, run_pager, rsync...
|
25
|
+
include SysUtils #mount, find_devices...
|
18
26
|
extend self
|
19
27
|
#activates debug mode
|
20
28
|
def self.debug(level=Logger::DEBUG)
|
@@ -22,11 +30,30 @@ module SH
|
|
22
30
|
Pathname.send(:include, CLILogging)
|
23
31
|
logger.level=(level)
|
24
32
|
end
|
25
|
-
#
|
33
|
+
#include SH::FU to add FileUtils
|
26
34
|
module FU
|
27
35
|
include ::FileUtils
|
28
|
-
include ::
|
36
|
+
include ::ShellHelpers
|
29
37
|
extend self
|
30
38
|
end
|
39
|
+
|
40
|
+
# #include LogHelper to set up CLILogging with some convenience facilities
|
41
|
+
# module LogHelper
|
42
|
+
# include CLILogging
|
43
|
+
# CLILogging.logger.progname||=$0
|
44
|
+
# # #Activates Sh.sh in klass
|
45
|
+
# # def self.included(klass)
|
46
|
+
# # klass.const_set(:Sh,ShellHelpers::Sh)
|
47
|
+
# # end
|
48
|
+
# end
|
31
49
|
end
|
32
50
|
|
51
|
+
#for the lazy
|
52
|
+
SH=ShellHelpers
|
53
|
+
|
54
|
+
## # SHLog.sh to get logging
|
55
|
+
## module SHLog
|
56
|
+
## include ShellHelpers
|
57
|
+
## include ShellHelpers::ShLog
|
58
|
+
## end
|
59
|
+
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'dr/ruby_ext/core_ext' #for Hash#keyed_value
|
2
|
+
require 'dr/parse/simple_parser'
|
3
|
+
|
4
|
+
module ShellHelpers
|
5
|
+
module Export
|
6
|
+
extend self
|
7
|
+
ImportError=Class.new(StandardError)
|
8
|
+
ExportError=Class.new(StandardError)
|
9
|
+
|
10
|
+
#export a value for SHELL consumption
|
11
|
+
def export_value(v)
|
12
|
+
case v
|
13
|
+
when String
|
14
|
+
return v.shellescape
|
15
|
+
when Array
|
16
|
+
return "(#{v.map {|i| i.to_s.shellescape}.join(' ')})"
|
17
|
+
when Hash
|
18
|
+
return "(#{v.map {|k,v| k.to_s.shellescape+" "+v.to_s.shellescape}.join(' ')})"
|
19
|
+
when nil
|
20
|
+
return ""
|
21
|
+
when ->(x){x.respond_to?(:to_a)}
|
22
|
+
return export_value(v.to_a)
|
23
|
+
when ->(x){x.respond_to?(:to_h)}
|
24
|
+
return export_value(v.to_h)
|
25
|
+
else
|
26
|
+
return v.to_s.shellescape
|
27
|
+
end
|
28
|
+
end
|
29
|
+
def import_value(v, type: String, unquote: true)
|
30
|
+
#String === String => false
|
31
|
+
case type.to_s
|
32
|
+
when "String"
|
33
|
+
v.gsub!(/\A['"]+|['"]+\Z/, "") if unquote
|
34
|
+
v.to_s
|
35
|
+
when "Integer"
|
36
|
+
v.to_i
|
37
|
+
when "Symbol"
|
38
|
+
v.to_sym
|
39
|
+
when "Array"
|
40
|
+
#v is of the form (ploum plam)
|
41
|
+
#TODO: handle quotes in the array
|
42
|
+
eval "%w#{v}"
|
43
|
+
when "Hash"
|
44
|
+
import_value(v, type: Array).each_slice(2).to_h
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def escape_name(name, prefix:"", upcase: true)
|
49
|
+
name=name.to_s
|
50
|
+
name=name.upcase if upcase
|
51
|
+
(prefix+name).gsub('/','_')
|
52
|
+
end
|
53
|
+
|
54
|
+
#export_variable("ploum","plam") yields ploum="plam"
|
55
|
+
def export_variable(name, value, local: false, export: false, prefix:"",upcase:true)
|
56
|
+
r=""
|
57
|
+
name=escape_name(name,prefix:prefix,upcase:upcase)
|
58
|
+
r+="local #{name}\n" if local
|
59
|
+
r+="typeset -A #{name}\n" if Hash === value
|
60
|
+
r+="#{name}=#{export_value(value)}\n"
|
61
|
+
r+="export #{name}\n" if export
|
62
|
+
return r
|
63
|
+
end
|
64
|
+
|
65
|
+
def import_variable(namevalue, downcase:true, type: :auto)
|
66
|
+
namevalue.match(/(local|export)?\s*(\S*)=(.*)$/) do |m|
|
67
|
+
_match,_type,name,value=m.to_a
|
68
|
+
name=name.downcase if downcase
|
69
|
+
if type == :auto
|
70
|
+
if value=~/^\(.*\)$/
|
71
|
+
value=import_value(value, type: Array)
|
72
|
+
else
|
73
|
+
value=import_value(value)
|
74
|
+
end
|
75
|
+
else
|
76
|
+
value=import_value(value, type: type)
|
77
|
+
end
|
78
|
+
return name, value
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
#from {ploum: plim} return something like
|
83
|
+
#PLOUM=plim
|
84
|
+
#that can be evaluated by the shell
|
85
|
+
def export_variables(hash, local: false, export: false, prefix:"",upcase:true)
|
86
|
+
names=hash.keys.map {|s| escape_name(s,prefix:prefix,upcase:upcase)}
|
87
|
+
r=""
|
88
|
+
r+="local #{names.join(" ")}\n" if local
|
89
|
+
hash.each do |k,v|
|
90
|
+
r+=export_variable(k,v,prefix:prefix,upcase:upcase)
|
91
|
+
end
|
92
|
+
r+="export #{names.join(" ")}\n" if export
|
93
|
+
return r
|
94
|
+
end
|
95
|
+
|
96
|
+
#export_parse(hash,"name:value")
|
97
|
+
#will output name=$(hash[value])
|
98
|
+
#special cases: when value = '/' we return the full hash
|
99
|
+
# when value ends by /, we return the splitted hash (and name serves
|
100
|
+
# as a prefix)
|
101
|
+
#Ex: Numenor ~ $ ./mine/00COMPUTERS.rb --export=//
|
102
|
+
# HOSTNAME=Numenor;
|
103
|
+
# HOSTTYPE=perso;
|
104
|
+
# HOMEPATH=/home/dams;...
|
105
|
+
# Numenor ~ $ ./mine/00COMPUTERS.rb --export=syst/
|
106
|
+
# LAPTOP=true;
|
107
|
+
# ARCH=i686;...
|
108
|
+
#Remark: in name:value, we don't put name in uppercase
|
109
|
+
#But in split hash mode, we put the keys in uppercase (to prevent collisions)
|
110
|
+
def export_parse(hash,s)
|
111
|
+
r=""
|
112
|
+
args=DR::SimpleParser.parse_string(s.to_s)
|
113
|
+
args[:values].each do |k,v|
|
114
|
+
if v
|
115
|
+
name=k.to_s
|
116
|
+
else
|
117
|
+
#no name given
|
118
|
+
v=k.to_s
|
119
|
+
if v !='/' && v[-1]=='/'
|
120
|
+
#in split mode we don't need the name
|
121
|
+
name=""
|
122
|
+
else
|
123
|
+
#else since no name was given we reuse the variable
|
124
|
+
name=v
|
125
|
+
name="all" if v=="/"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
if v != '/' && v[-1]=='/'
|
129
|
+
all=true
|
130
|
+
v=v[0...-1]
|
131
|
+
end
|
132
|
+
value=hash.keyed_value(v)
|
133
|
+
opts=args[:opts][k]
|
134
|
+
if all
|
135
|
+
r+=export_variables(value, prefix: name, **opts)
|
136
|
+
else
|
137
|
+
r+=export_variable(name,value, **opts)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
return r
|
141
|
+
end
|
142
|
+
|
143
|
+
def import_parse(s, split_on: :auto, var_separator:'/', inline: false)
|
144
|
+
r={}
|
145
|
+
if split_on == :auto
|
146
|
+
split_on=","
|
147
|
+
split_on="\n" if s =~ /\n/
|
148
|
+
end
|
149
|
+
if s.is_a?(Enumerable)
|
150
|
+
instructions=s
|
151
|
+
else
|
152
|
+
instructions=s.split(split_on)
|
153
|
+
end
|
154
|
+
instructions.each do |namevalue|
|
155
|
+
if inline
|
156
|
+
name,value=DR::SimpleParser.parse_namevalue(optvalue,sep:'=')
|
157
|
+
else
|
158
|
+
name,value=import_variable(namevalue)
|
159
|
+
end
|
160
|
+
if name
|
161
|
+
r.set_keyed_value(name,value, sep: var_separator)
|
162
|
+
else
|
163
|
+
raise ImportError.new("Cannot parse #{s}")
|
164
|
+
end
|
165
|
+
end
|
166
|
+
r
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|