shell_helpers 0.1.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://img.shields.io/gem/v/shell_helpers.svg)](https://rubygems.org/gems/shell_helpers)
|
9
|
+
[![Build Status](https://travis-ci.org/DamienRobert/shell_helpers.svg?branch=master)](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
|