lace 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/lace +91 -0
- data/lib/cmd/activate.rb +12 -0
- data/lib/cmd/deactivate.rb +11 -0
- data/lib/cmd/fetch.rb +10 -0
- data/lib/cmd/help.rb +39 -0
- data/lib/cmd/inspect.rb +56 -0
- data/lib/cmd/install.rb +10 -0
- data/lib/cmd/list.rb +36 -0
- data/lib/cmd/remove.rb +11 -0
- data/lib/cmd/update.rb +10 -0
- data/lib/cmd/validate.rb +124 -0
- data/lib/extend/ARGV.rb +62 -0
- data/lib/extend/pathname.rb +247 -0
- data/lib/lace/download_strategy.rb +168 -0
- data/lib/lace/exceptions.rb +26 -0
- data/lib/lace/package.rb +205 -0
- data/lib/lace/utils.rb +107 -0
- data/lib/lace/version.rb +3 -0
- metadata +65 -0
data/bin/lace
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
std_trap = trap("INT") { exit! 130 } # no backtrace thanks
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
LIB_PATH = Pathname.new(__FILE__).realpath.dirname.parent.join("lib").to_s
|
6
|
+
$:.unshift(LIB_PATH)
|
7
|
+
|
8
|
+
require "lace/utils"
|
9
|
+
require "lace/exceptions"
|
10
|
+
require "lace/version"
|
11
|
+
|
12
|
+
require "extend/ARGV"
|
13
|
+
require "extend/pathname"
|
14
|
+
|
15
|
+
packages_folder = Pathname.new(ENV["HOME"]).join(".cassias")
|
16
|
+
|
17
|
+
if ENV["LACE_FOLDER"]
|
18
|
+
packages_folder = Pathname.new(ENV["LACE_FOLDER"])
|
19
|
+
end
|
20
|
+
LACE_PKGS_FOLDER = packages_folder
|
21
|
+
|
22
|
+
module Lace extend self
|
23
|
+
attr_accessor :failed
|
24
|
+
alias_method :failed?, :failed
|
25
|
+
end
|
26
|
+
|
27
|
+
ARGV.extend(LaceArgvExtension)
|
28
|
+
|
29
|
+
if ARGV.debug?
|
30
|
+
require "debugger"
|
31
|
+
end
|
32
|
+
|
33
|
+
case ARGV.first when '-h', '--help', '--usage', '-?', 'help', nil
|
34
|
+
require 'cmd/help'
|
35
|
+
puts Lace.help_s
|
36
|
+
exit ARGV.first ? 0 : 1
|
37
|
+
when '--version'
|
38
|
+
puts Lace::VERSION
|
39
|
+
exit 0
|
40
|
+
when '-v'
|
41
|
+
puts "lace #{Lace::VERSION}"
|
42
|
+
# Shift the -v to the end of the parameter list
|
43
|
+
ARGV << ARGV.shift
|
44
|
+
# If no other arguments, just quit here.
|
45
|
+
exit 0 if ARGV.length == 1
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
begin
|
50
|
+
trap("INT", std_trap) # restore default CTRL-C handler
|
51
|
+
if Process.uid.zero?
|
52
|
+
raise "Refusing to run as sudo"
|
53
|
+
end
|
54
|
+
|
55
|
+
aliases = {'ls' => 'list',
|
56
|
+
'rm' => 'remove'}
|
57
|
+
|
58
|
+
cmd = ARGV.shift
|
59
|
+
cmd = aliases[cmd] if aliases[cmd]
|
60
|
+
|
61
|
+
if require "cmd/" + cmd
|
62
|
+
Lace.send cmd.to_s.gsub('-', '_').downcase
|
63
|
+
else
|
64
|
+
onoe "Unknown command: #{cmd}"
|
65
|
+
exit 1
|
66
|
+
end
|
67
|
+
|
68
|
+
rescue ResourceNotSpecified
|
69
|
+
abort "This command requires a resource argument"
|
70
|
+
rescue UsageError
|
71
|
+
onoe "Invalid usage"
|
72
|
+
abort ARGV.usage
|
73
|
+
rescue SystemExit
|
74
|
+
puts "Kernel.exit" if ARGV.verbose?
|
75
|
+
raise
|
76
|
+
rescue Interrupt => e
|
77
|
+
puts # seemingly a newline is typical
|
78
|
+
exit 130
|
79
|
+
rescue RuntimeError, SystemCallError => e
|
80
|
+
raise if e.message.empty?
|
81
|
+
onoe e
|
82
|
+
puts e.backtrace if false
|
83
|
+
exit 1
|
84
|
+
rescue Exception => e
|
85
|
+
onoe e
|
86
|
+
puts "#{Tty.white}Please report this bug:"
|
87
|
+
puts e.backtrace
|
88
|
+
exit 1
|
89
|
+
else
|
90
|
+
exit 1 if Lace.failed?
|
91
|
+
end
|
data/lib/cmd/activate.rb
ADDED
data/lib/cmd/fetch.rb
ADDED
data/lib/cmd/help.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
HELP = <<-EOS
|
2
|
+
Example usage:
|
3
|
+
Synopsis:
|
4
|
+
lace <cmd> <pkg-uri/name> [<flavor>] [--name=<name>] [--version] [--no-hooks]
|
5
|
+
|
6
|
+
lace ls
|
7
|
+
|
8
|
+
lace fetch <pkg-uri>
|
9
|
+
lace fetch <pkg-uri>
|
10
|
+
|
11
|
+
lace install <pkg-uri>
|
12
|
+
lace install <pkg-uri> <flavor>
|
13
|
+
|
14
|
+
lace activate <pkg-name>
|
15
|
+
lace activate <pkg-name> <flavor>
|
16
|
+
|
17
|
+
lace deactivate <pkg-name>
|
18
|
+
lace deactivate <pkg-name> <flavor>
|
19
|
+
|
20
|
+
lace remove <pkg-name>
|
21
|
+
lace update <pkg-name>
|
22
|
+
|
23
|
+
Troubleshooting:
|
24
|
+
lace help
|
25
|
+
lace info <pkg-name>
|
26
|
+
lace validate <local-directory>
|
27
|
+
|
28
|
+
For further help visit:
|
29
|
+
https://github.com/kairichard/lace
|
30
|
+
EOS
|
31
|
+
|
32
|
+
module Lace extend self
|
33
|
+
def help
|
34
|
+
puts HELP
|
35
|
+
end
|
36
|
+
def help_s
|
37
|
+
HELP
|
38
|
+
end
|
39
|
+
end
|
data/lib/cmd/inspect.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
require 'lace/package'
|
4
|
+
require 'lace/exceptions'
|
5
|
+
|
6
|
+
INSPECT = <<-EOS
|
7
|
+
Inspection of simple:
|
8
|
+
active: <%= package.is_active? %>
|
9
|
+
flavors: <%= package.flavors %>
|
10
|
+
version: <%= package.version %>
|
11
|
+
upgradeable: <%= package.upgradeable? %>
|
12
|
+
manifest: <%= package.manifest %>
|
13
|
+
EOS
|
14
|
+
|
15
|
+
module Lace extend self
|
16
|
+
def inspect
|
17
|
+
resource = ARGV.shift
|
18
|
+
raise ResourceNotSpecified if not resource
|
19
|
+
package = PackagePresenter.new Package.new(resource, false)
|
20
|
+
puts ERB.new(INSPECT).result(binding)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class PackagePresenter
|
25
|
+
attr_accessor :pkg
|
26
|
+
|
27
|
+
def initialize obj
|
28
|
+
@pkg = obj
|
29
|
+
end
|
30
|
+
|
31
|
+
def is_active?
|
32
|
+
pkg.is_active?
|
33
|
+
end
|
34
|
+
|
35
|
+
def flavors
|
36
|
+
flavors_as_string.empty? ? "nil" : flavors_as_string
|
37
|
+
end
|
38
|
+
|
39
|
+
def flavors_as_string
|
40
|
+
if @pkg.facts.flavors
|
41
|
+
return @pkg.facts.flavors.join ", "
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def version
|
46
|
+
@pkg.facts.version or 'n/a'
|
47
|
+
end
|
48
|
+
|
49
|
+
def upgradeable?
|
50
|
+
@pkg.is_git_repo?
|
51
|
+
end
|
52
|
+
|
53
|
+
def manifest
|
54
|
+
return @pkg.facts.facts_file
|
55
|
+
end
|
56
|
+
end
|
data/lib/cmd/install.rb
ADDED
data/lib/cmd/list.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'lace/package'
|
2
|
+
|
3
|
+
module Lace extend self
|
4
|
+
|
5
|
+
def linked_files
|
6
|
+
home_dir = ENV["HOME"]
|
7
|
+
Dir.foreach(home_dir).map do |filename|
|
8
|
+
next if filename == '.' or filename == '..'
|
9
|
+
File.readlink File.join(home_dir, filename) if File.symlink? File.join(home_dir, filename)
|
10
|
+
end.compact.uniq
|
11
|
+
end
|
12
|
+
|
13
|
+
def active_dotties
|
14
|
+
linked_files.map do |path|
|
15
|
+
Pathname.new File.dirname(path)
|
16
|
+
end.uniq
|
17
|
+
end
|
18
|
+
|
19
|
+
def installed_dotties
|
20
|
+
Dir.glob(File.join(LACE_PKGS_FOLDER, "**")).sort.map do |p|
|
21
|
+
Pathname.new(p).basename.to_s
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def list
|
26
|
+
if installed_dotties.length > 0
|
27
|
+
installed_dotties.map do |d|
|
28
|
+
package = Package.new d, false
|
29
|
+
puts "- [#{Tty.green}#{package.is_active? ? "*" : " "}#{Tty.reset}] #{d}"
|
30
|
+
end
|
31
|
+
else
|
32
|
+
puts "There are no kits installed"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/lib/cmd/remove.rb
ADDED
data/lib/cmd/update.rb
ADDED
data/lib/cmd/validate.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
require 'lace/package'
|
4
|
+
require 'lace/utils'
|
5
|
+
require 'lace/exceptions'
|
6
|
+
|
7
|
+
VALIDATE = <<-EOS
|
8
|
+
Lace-Manifest Validation Report:
|
9
|
+
<% validation.errors.each do |error| -%>
|
10
|
+
<%= "%-58s [ %s ]" % [error[0] + ':', error[1]] %>
|
11
|
+
<% unless error[2].nil? -%>
|
12
|
+
<% error[2].each do |line| -%>
|
13
|
+
<%= Tty.gray %><%= '# '+line.to_s %><%= Tty.reset %>
|
14
|
+
<% end -%>
|
15
|
+
<% end -%>
|
16
|
+
<% end -%>
|
17
|
+
EOS
|
18
|
+
|
19
|
+
module Lace extend self
|
20
|
+
def validate
|
21
|
+
resource = ARGV.shift
|
22
|
+
raise ResourceNotSpecified if not resource
|
23
|
+
validation = PackageValidator.new Facts.new(resource), ARGV.shift
|
24
|
+
puts ERB.new(VALIDATE, nil, '-').result(binding)
|
25
|
+
Lace.failed = true if validation.has_errors?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class PackageValidator
|
30
|
+
attr_accessor :errors
|
31
|
+
|
32
|
+
class << self
|
33
|
+
attr_accessor :validations
|
34
|
+
def validate name, method_name
|
35
|
+
@validations ||= []
|
36
|
+
@validations << [name, method_name]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
validate 'config-files', :config_files_present
|
41
|
+
validate 'version', :version_present
|
42
|
+
validate 'homepage', :homepage_present
|
43
|
+
validate 'post-install hook', :post_install_hooks_ok
|
44
|
+
validate 'post-update hook', :post_update_hooks_ok
|
45
|
+
|
46
|
+
def initialize facts, flavor=nil
|
47
|
+
@facts = facts
|
48
|
+
@errors = []
|
49
|
+
if @facts.has_flavors? && flavor.nil?
|
50
|
+
raise RuntimeError.new FlavorArgumentMsg % @facts.flavors.join("\n- ")
|
51
|
+
elsif @facts.has_flavors? && flavor != false
|
52
|
+
@facts.flavor! flavor
|
53
|
+
end
|
54
|
+
validate
|
55
|
+
end
|
56
|
+
|
57
|
+
def check_hooks hook_cmd
|
58
|
+
hook_cmd.map do |cmd|
|
59
|
+
if !File.exist? cmd
|
60
|
+
"#{cmd} cannot be found"
|
61
|
+
elsif !File.executable? cmd
|
62
|
+
"#{cmd} is not executable"
|
63
|
+
end
|
64
|
+
end.compact
|
65
|
+
end
|
66
|
+
|
67
|
+
def hook_ok hook
|
68
|
+
hook_cmd = @facts.post(hook)
|
69
|
+
if hook_cmd.empty?
|
70
|
+
["#{Tty.green}skipped#{Tty.reset}", nil]
|
71
|
+
else
|
72
|
+
errors = check_hooks hook_cmd
|
73
|
+
if errors.length > 0
|
74
|
+
["#{Tty.red}error#{Tty.reset}", errors]
|
75
|
+
else
|
76
|
+
["ok", nil]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def post_install_hooks_ok
|
82
|
+
hook_ok :install
|
83
|
+
end
|
84
|
+
|
85
|
+
def post_update_hooks_ok
|
86
|
+
hook_ok :update
|
87
|
+
end
|
88
|
+
|
89
|
+
def homepage_present
|
90
|
+
if @facts.has_key? 'homepage'
|
91
|
+
["#{Tty.green}found#{Tty.reset}", nil]
|
92
|
+
else
|
93
|
+
["#{Tty.red}missing#{Tty.reset}", ['adding a homepage improves the credibility', 'of your package']]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def version_present
|
98
|
+
if @facts.has_key? 'version'
|
99
|
+
["#{Tty.green}found#{Tty.reset}", nil]
|
100
|
+
else
|
101
|
+
["#{Tty.red}missing#{Tty.reset}", ['adding a version to the manifest improves', 'a future update experince']]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def config_files_present
|
106
|
+
if @facts.config_files.empty?
|
107
|
+
["#{Tty.red}missing#{Tty.reset}", ['Add config_files see manual for more information']]
|
108
|
+
elsif @facts.config_files.any?{|f| !File.exist? f}
|
109
|
+
["#{Tty.red}error#{Tty.reset}", @facts.config_files.select{|f| !File.exist? f}.map{|f| "#{f.to_s.split("/").last} is missing from this package"}]
|
110
|
+
else
|
111
|
+
["#{Tty.green}found#{Tty.reset}", nil]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def validate
|
116
|
+
self.class.validations.each do |validation|
|
117
|
+
errors << [validation[0], *send(validation[1])]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def has_errors?
|
122
|
+
errors.any?{|e| !e[2].nil? }
|
123
|
+
end
|
124
|
+
end
|
data/lib/extend/ARGV.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
module LaceArgvExtension
|
2
|
+
def named
|
3
|
+
@named ||= reject{|arg| arg[0..0] == '-'}
|
4
|
+
end
|
5
|
+
|
6
|
+
def options_only
|
7
|
+
select {|arg| arg[0..0] == '-'}
|
8
|
+
end
|
9
|
+
|
10
|
+
# self documenting perhaps?
|
11
|
+
def include? arg
|
12
|
+
@n=index arg
|
13
|
+
end
|
14
|
+
|
15
|
+
def next
|
16
|
+
at @n+1 or raise UsageError
|
17
|
+
end
|
18
|
+
|
19
|
+
def value arg
|
20
|
+
arg = find {|o| o =~ /--#{arg}=(.+)/}
|
21
|
+
$1 if arg
|
22
|
+
end
|
23
|
+
|
24
|
+
def verbose?
|
25
|
+
flag? '--verbose' or !ENV['VERBOSE'].nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
def debug?
|
29
|
+
flag? '--debug'
|
30
|
+
end
|
31
|
+
|
32
|
+
def nohooks?
|
33
|
+
flag? '--no-hooks'
|
34
|
+
end
|
35
|
+
|
36
|
+
def interactive?
|
37
|
+
flag? '--interactive'
|
38
|
+
end
|
39
|
+
|
40
|
+
def flag? flag
|
41
|
+
options_only.any? do |arg|
|
42
|
+
arg == flag || arg[1..1] != '-' && arg.include?(flag[2..2])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# eg. `foo -ns -i --bar` has three switches, n, s and i
|
47
|
+
def switch? switch_character
|
48
|
+
return false if switch_character.length > 1
|
49
|
+
options_only.any? do |arg|
|
50
|
+
arg[1..1] != '-' && arg.include?(switch_character)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def downcased_unique_named
|
57
|
+
# Only lowercase names, not paths or URLs
|
58
|
+
@downcased_unique_named ||= named.map do |arg|
|
59
|
+
arg.include?("/") ? arg : arg.downcase
|
60
|
+
end.uniq
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
# we enhance pathname to make our code more readable
|
4
|
+
class Pathname
|
5
|
+
|
6
|
+
def cp dst
|
7
|
+
if file?
|
8
|
+
FileUtils.cp to_s, dst
|
9
|
+
else
|
10
|
+
FileUtils.cp_r to_s, dst
|
11
|
+
end
|
12
|
+
return dst
|
13
|
+
end
|
14
|
+
|
15
|
+
# extended to support common double extensions
|
16
|
+
alias extname_old extname
|
17
|
+
def extname(path=to_s)
|
18
|
+
BOTTLE_EXTNAME_RX.match(path)
|
19
|
+
return $1 if $1
|
20
|
+
/(\.(tar|cpio|pax)\.(gz|bz2|xz|Z))$/.match(path)
|
21
|
+
return $1 if $1
|
22
|
+
return File.extname(path)
|
23
|
+
end
|
24
|
+
|
25
|
+
# for filetypes we support, basename without extension
|
26
|
+
def stem
|
27
|
+
File.basename((path = to_s), extname(path))
|
28
|
+
end
|
29
|
+
|
30
|
+
# I don't trust the children.length == 0 check particularly, not to mention
|
31
|
+
# it is slow to enumerate the whole directory just to see if it is empty,
|
32
|
+
# instead rely on good ol' libc and the filesystem
|
33
|
+
def rmdir_if_possible
|
34
|
+
rmdir
|
35
|
+
true
|
36
|
+
rescue Errno::ENOTEMPTY
|
37
|
+
if (ds_store = self+'.DS_Store').exist? && children.length == 1
|
38
|
+
ds_store.unlink
|
39
|
+
retry
|
40
|
+
else
|
41
|
+
false
|
42
|
+
end
|
43
|
+
rescue Errno::EACCES, Errno::ENOENT
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
def chmod_R perms
|
48
|
+
require 'fileutils'
|
49
|
+
FileUtils.chmod_R perms, to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
def abv
|
53
|
+
out=''
|
54
|
+
n=`find #{to_s} -type f ! -name .DS_Store | wc -l`.to_i
|
55
|
+
out<<"#{n} files, " if n > 1
|
56
|
+
out<<`/usr/bin/du -hs #{to_s} | cut -d"\t" -f1`.strip
|
57
|
+
end
|
58
|
+
|
59
|
+
def compression_type
|
60
|
+
# Don't treat jars or wars as compressed
|
61
|
+
return nil if self.extname == '.jar'
|
62
|
+
return nil if self.extname == '.war'
|
63
|
+
|
64
|
+
# OS X installer package
|
65
|
+
return :pkg if self.extname == '.pkg'
|
66
|
+
|
67
|
+
# If the filename ends with .gz not preceded by .tar
|
68
|
+
# then we want to gunzip but not tar
|
69
|
+
return :gzip_only if self.extname == '.gz'
|
70
|
+
|
71
|
+
# Get enough of the file to detect common file types
|
72
|
+
# POSIX tar magic has a 257 byte offset
|
73
|
+
# magic numbers stolen from /usr/share/file/magic/
|
74
|
+
case open('rb') { |f| f.read(262) }
|
75
|
+
when /^PK\003\004/n then :zip
|
76
|
+
when /^\037\213/n then :gzip
|
77
|
+
when /^BZh/n then :bzip2
|
78
|
+
when /^\037\235/n then :compress
|
79
|
+
when /^.{257}ustar/n then :tar
|
80
|
+
when /^\xFD7zXZ\x00/n then :xz
|
81
|
+
when /^Rar!/n then :rar
|
82
|
+
when /^7z\xBC\xAF\x27\x1C/n then :p7zip
|
83
|
+
else
|
84
|
+
# This code so that bad-tarballs and zips produce good error messages
|
85
|
+
# when they don't unarchive properly.
|
86
|
+
case extname
|
87
|
+
when ".tar.gz", ".tgz", ".tar.bz2", ".tbz" then :tar
|
88
|
+
when ".zip" then :zip
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def text_executable?
|
94
|
+
%r[^#!\s*\S+] === open('r') { |f| f.read(1024) }
|
95
|
+
end
|
96
|
+
|
97
|
+
def incremental_hash(hasher)
|
98
|
+
incr_hash = hasher.new
|
99
|
+
buf = ""
|
100
|
+
open('rb') { |f| incr_hash << buf while f.read(1024, buf) }
|
101
|
+
incr_hash.hexdigest
|
102
|
+
end
|
103
|
+
|
104
|
+
def sha1
|
105
|
+
require 'digest/sha1'
|
106
|
+
incremental_hash(Digest::SHA1)
|
107
|
+
end
|
108
|
+
|
109
|
+
def sha256
|
110
|
+
require 'digest/sha2'
|
111
|
+
incremental_hash(Digest::SHA2)
|
112
|
+
end
|
113
|
+
|
114
|
+
if '1.9' <= RUBY_VERSION
|
115
|
+
alias_method :to_str, :to_s
|
116
|
+
end
|
117
|
+
|
118
|
+
def cd
|
119
|
+
Dir.chdir(self){ yield }
|
120
|
+
end
|
121
|
+
|
122
|
+
def subdirs
|
123
|
+
children.select{ |child| child.directory? }
|
124
|
+
end
|
125
|
+
|
126
|
+
def resolved_path
|
127
|
+
self.symlink? ? dirname+readlink : self
|
128
|
+
end
|
129
|
+
|
130
|
+
def resolved_path_exists?
|
131
|
+
link = readlink
|
132
|
+
rescue ArgumentError
|
133
|
+
# The link target contains NUL bytes
|
134
|
+
false
|
135
|
+
else
|
136
|
+
(dirname+link).exist?
|
137
|
+
end
|
138
|
+
|
139
|
+
# perhaps confusingly, this Pathname object becomes the symlink pointing to
|
140
|
+
# the src paramter.
|
141
|
+
def make_relative_symlink src
|
142
|
+
src = Pathname.new(src) unless src.kind_of? Pathname
|
143
|
+
|
144
|
+
self.dirname.mkpath
|
145
|
+
Dir.chdir self.dirname do
|
146
|
+
# NOTE only system ln -s will create RELATIVE symlinks
|
147
|
+
quiet_system 'ln', '-s', src.relative_path_from(self.dirname), self.basename
|
148
|
+
if not $?.success?
|
149
|
+
if self.exist?
|
150
|
+
raise <<-EOS.undent
|
151
|
+
Could not symlink file: #{src.expand_path}
|
152
|
+
Target #{self} already exists. You may need to delete it.
|
153
|
+
To force the link and overwrite all other conflicting files, do:
|
154
|
+
brew link --overwrite formula_name
|
155
|
+
|
156
|
+
To list all files that would be deleted:
|
157
|
+
brew link --overwrite --dry-run formula_name
|
158
|
+
EOS
|
159
|
+
# #exist? will return false for symlinks whose target doesn't exist
|
160
|
+
elsif self.symlink?
|
161
|
+
raise <<-EOS.undent
|
162
|
+
Could not symlink file: #{src.expand_path}
|
163
|
+
Target #{self} already exists as a symlink to #{readlink}.
|
164
|
+
If this file is from another formula, you may need to
|
165
|
+
`brew unlink` it. Otherwise, you may want to delete it.
|
166
|
+
To force the link and overwrite all other conflicting files, do:
|
167
|
+
brew link --overwrite formula_name
|
168
|
+
|
169
|
+
To list all files that would be deleted:
|
170
|
+
brew link --overwrite --dry-run formula_name
|
171
|
+
EOS
|
172
|
+
elsif !dirname.writable_real?
|
173
|
+
raise <<-EOS.undent
|
174
|
+
Could not symlink file: #{src.expand_path}
|
175
|
+
#{dirname} is not writable. You should change its permissions.
|
176
|
+
EOS
|
177
|
+
else
|
178
|
+
raise <<-EOS.undent
|
179
|
+
Could not symlink file: #{src.expand_path}
|
180
|
+
#{self} may already exist.
|
181
|
+
#{dirname} may not be writable.
|
182
|
+
EOS
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def / that
|
189
|
+
join that.to_s
|
190
|
+
end
|
191
|
+
|
192
|
+
def ensure_writable
|
193
|
+
saved_perms = nil
|
194
|
+
unless writable_real?
|
195
|
+
saved_perms = stat.mode
|
196
|
+
chmod 0644
|
197
|
+
end
|
198
|
+
yield
|
199
|
+
ensure
|
200
|
+
chmod saved_perms if saved_perms
|
201
|
+
end
|
202
|
+
|
203
|
+
# Writes an exec script in this folder for each target pathname
|
204
|
+
def write_exec_script *targets
|
205
|
+
targets.flatten!
|
206
|
+
if targets.empty?
|
207
|
+
opoo "tried to write exec scripts to #{self} for an empty list of targets"
|
208
|
+
end
|
209
|
+
targets.each do |target|
|
210
|
+
target = Pathname.new(target) # allow pathnames or strings
|
211
|
+
(self+target.basename()).write <<-EOS.undent
|
212
|
+
#!/bin/bash
|
213
|
+
exec "#{target}" "$@"
|
214
|
+
EOS
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# We redefine these private methods in order to add the /o modifier to
|
219
|
+
# the Regexp literals, which forces string interpolation to happen only
|
220
|
+
# once instead of each time the method is called. This is fixed in 1.9+.
|
221
|
+
if RUBY_VERSION <= "1.8.7"
|
222
|
+
alias_method :old_chop_basename, :chop_basename
|
223
|
+
def chop_basename(path)
|
224
|
+
base = File.basename(path)
|
225
|
+
if /\A#{Pathname::SEPARATOR_PAT}?\z/o =~ base
|
226
|
+
return nil
|
227
|
+
else
|
228
|
+
return path[0, path.rindex(base)], base
|
229
|
+
end
|
230
|
+
end
|
231
|
+
private :chop_basename
|
232
|
+
|
233
|
+
alias_method :old_prepend_prefix, :prepend_prefix
|
234
|
+
def prepend_prefix(prefix, relpath)
|
235
|
+
if relpath.empty?
|
236
|
+
File.dirname(prefix)
|
237
|
+
elsif /#{SEPARATOR_PAT}/o =~ prefix
|
238
|
+
prefix = File.dirname(prefix)
|
239
|
+
prefix = File.join(prefix, "") if File.basename(prefix + 'a') != 'a'
|
240
|
+
prefix + relpath
|
241
|
+
else
|
242
|
+
prefix + relpath
|
243
|
+
end
|
244
|
+
end
|
245
|
+
private :prepend_prefix
|
246
|
+
end
|
247
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
|
3
|
+
class AbstractDownloadStrategy
|
4
|
+
attr_reader :name, :resource, :target_folder
|
5
|
+
|
6
|
+
def initialize uri
|
7
|
+
@uri = uri
|
8
|
+
@target_folder = LACE_PKGS_FOLDER/name
|
9
|
+
end
|
10
|
+
|
11
|
+
# All download strategies are expected to implement these methods
|
12
|
+
def fetch; end
|
13
|
+
def stage; end
|
14
|
+
def name
|
15
|
+
ARGV.value "name"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
class LocalFileStrategy < AbstractDownloadStrategy
|
21
|
+
def fetch
|
22
|
+
ohai "Fetching #@uri into #@target_folder"
|
23
|
+
FileUtils.cp_r @uri, @target_folder
|
24
|
+
@target_folder
|
25
|
+
end
|
26
|
+
|
27
|
+
def name
|
28
|
+
super || File.basename(@uri)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
module GitCommands
|
34
|
+
def update_repo
|
35
|
+
safe_system 'git', 'fetch', 'origin'
|
36
|
+
end
|
37
|
+
|
38
|
+
def reset
|
39
|
+
safe_system 'git', "reset" , "--hard", "origin/HEAD"
|
40
|
+
end
|
41
|
+
|
42
|
+
def git_dir
|
43
|
+
@target_folder.join(".git")
|
44
|
+
end
|
45
|
+
|
46
|
+
def repo_valid?
|
47
|
+
quiet_system "git", "--git-dir", git_dir, "status", "-s"
|
48
|
+
end
|
49
|
+
|
50
|
+
def submodules?
|
51
|
+
@target_folder.join(".gitmodules").exist?
|
52
|
+
end
|
53
|
+
|
54
|
+
def clone_args
|
55
|
+
args = %w{clone}
|
56
|
+
args << @uri << @target_folder
|
57
|
+
end
|
58
|
+
|
59
|
+
def clone_repo
|
60
|
+
safe_system 'git', *clone_args
|
61
|
+
@target_folder.cd { update_submodules } if submodules?
|
62
|
+
end
|
63
|
+
|
64
|
+
def update_submodules
|
65
|
+
safe_system 'git', 'submodule', 'update', '--init'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
class GitUpdateStrategy
|
71
|
+
include GitCommands
|
72
|
+
|
73
|
+
def initialize name
|
74
|
+
@target_folder = LACE_PKGS_FOLDER/name
|
75
|
+
end
|
76
|
+
|
77
|
+
def update
|
78
|
+
if repo_valid?
|
79
|
+
puts "Updating #@target_folder"
|
80
|
+
@target_folder.cd do
|
81
|
+
update_repo
|
82
|
+
reset
|
83
|
+
update_submodules if submodules?
|
84
|
+
end
|
85
|
+
else
|
86
|
+
puts "Removing invalid .git repo"
|
87
|
+
FileUtils.rm_rf @target_folder
|
88
|
+
clone_repo
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
class GitDownloadStrategy < AbstractDownloadStrategy
|
95
|
+
include GitCommands
|
96
|
+
|
97
|
+
def fetch
|
98
|
+
ohai "Cloning #@uri"
|
99
|
+
|
100
|
+
if @target_folder.exist? && repo_valid?
|
101
|
+
puts "Updating #@target_folder"
|
102
|
+
@target_folder.cd do
|
103
|
+
update_repo
|
104
|
+
reset
|
105
|
+
update_submodules if submodules?
|
106
|
+
end
|
107
|
+
elsif @target_folder.exist?
|
108
|
+
puts "Removing invalid .git repo"
|
109
|
+
FileUtils.rm_rf @target_folder
|
110
|
+
clone_repo
|
111
|
+
else
|
112
|
+
clone_repo
|
113
|
+
end
|
114
|
+
@target_folder
|
115
|
+
end
|
116
|
+
|
117
|
+
def name
|
118
|
+
if super
|
119
|
+
super
|
120
|
+
elsif @uri.include? "github.com"
|
121
|
+
@uri.split("/")[-2]
|
122
|
+
elsif File.directory? @uri
|
123
|
+
File.basename(@uri)
|
124
|
+
else
|
125
|
+
raise "Cannot determine a proper name with #@uri"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
class DownloadStrategyDetector
|
133
|
+
def self.detect(uri, strategy=nil)
|
134
|
+
if strategy.nil?
|
135
|
+
detect_from_uri(uri)
|
136
|
+
elsif Symbol === strategy
|
137
|
+
detect_from_symbol(strategy)
|
138
|
+
else
|
139
|
+
raise TypeError,
|
140
|
+
"Unknown download strategy specification #{strategy.inspect}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.detect_from_uri(uri)
|
145
|
+
if File.directory?(uri) && !File.directory?(uri+"/.git")
|
146
|
+
return LocalFileStrategy
|
147
|
+
elsif File.directory?(uri+"/.git")
|
148
|
+
return GitDownloadStrategy
|
149
|
+
end
|
150
|
+
|
151
|
+
case uri
|
152
|
+
when %r[^git://] then GitDownloadStrategy
|
153
|
+
when %r[^https?://.+\.git$] then GitDownloadStrategy
|
154
|
+
# else CurlDownloadStrategy
|
155
|
+
else
|
156
|
+
raise "Cannot determine download startegy from #{uri}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.detect_from_symbol(symbol)
|
161
|
+
case symbol
|
162
|
+
when :git then GitDownloadStrategy
|
163
|
+
when :local_file then LocalFileStrategy
|
164
|
+
else
|
165
|
+
raise "Unknown download strategy #{strategy} was requested."
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class UsageError < RuntimeError; end
|
2
|
+
class ResourceNotSpecified < ArgumentError; end
|
3
|
+
class ErrorDuringExecution < RuntimeError; end
|
4
|
+
|
5
|
+
class OnlyGitReposCanBeUpdatedError < RuntimeError
|
6
|
+
def initialize
|
7
|
+
super "Only kits installed via git can be updated"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class AlreadyActiveError < RuntimeError
|
12
|
+
def initialize
|
13
|
+
super "Cannot activate an already active package, please deactivate first"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class NonActiveFlavorError < RuntimeError
|
18
|
+
def initialize
|
19
|
+
super "It looks like the flavor you tried to deactivate is not active after all"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
FlavorArgumentMsg = <<-EOS
|
24
|
+
Sorry, this command needs a flavor argument you can choose from the following:
|
25
|
+
- %s
|
26
|
+
EOS
|
data/lib/lace/package.rb
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
require 'lace/download_strategy'
|
6
|
+
require 'lace/exceptions'
|
7
|
+
|
8
|
+
class PackageUtils
|
9
|
+
def self.is_package_any_flavor_active name
|
10
|
+
@path = LACE_PKGS_FOLDER/name
|
11
|
+
facts = Facts.new @path
|
12
|
+
facts.flavors.any?{|f| Package.new(@name, f).is_active?}
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.fetch uri, argv
|
16
|
+
downloader = DownloadStrategyDetector.detect(uri).new(uri)
|
17
|
+
if downloader.target_folder.exist?
|
18
|
+
raise "Package already installed"
|
19
|
+
end
|
20
|
+
downloader.fetch
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.remove package_name, argv
|
24
|
+
ohai "Removing"
|
25
|
+
package = Package.new package_name, false
|
26
|
+
unless package.is_active?
|
27
|
+
FileUtils.rm_rf package.path
|
28
|
+
ohai "Successfully removed"
|
29
|
+
else
|
30
|
+
ofail "Cannot remove active kit, deactivate first"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.install uri, argv
|
35
|
+
downloader = DownloadStrategyDetector.detect(uri).new(uri)
|
36
|
+
if downloader.target_folder.exist?
|
37
|
+
raise "Package already installed"
|
38
|
+
end
|
39
|
+
downloader.fetch
|
40
|
+
package = Package.new downloader.name, ARGV.first
|
41
|
+
package.activate!
|
42
|
+
package.after_install
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.deactivate package_name, argv
|
46
|
+
package = Package.new package_name, ARGV.shift
|
47
|
+
raise NonActiveFlavorError.new unless package.is_active?
|
48
|
+
package.deactivate!
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.activate package_name, argv
|
52
|
+
package = Package.new package_name, ARGV.shift
|
53
|
+
raise AlreadyActiveError.new if Package.new(package_name, false).is_active?
|
54
|
+
package.activate!
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.update package_name, argv
|
58
|
+
package = Package.new package_name, false
|
59
|
+
raise OnlyGitReposCanBeUpdatedError.new unless package.is_git_repo?
|
60
|
+
updater = GitUpdateStrategy.new package_name
|
61
|
+
package.deactivate!
|
62
|
+
updater.update
|
63
|
+
package.read_facts!
|
64
|
+
package.activate!
|
65
|
+
package.after_update
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Facts
|
70
|
+
attr_reader :facts_file
|
71
|
+
def initialize location
|
72
|
+
@location = Pathname.new(location)
|
73
|
+
@facts_file = @location/".lace.yml"
|
74
|
+
raise RuntimeError.new "No package file found in #@location" unless @facts_file.exist?
|
75
|
+
@facts = YAML.load @facts_file.read
|
76
|
+
@_facts = YAML.load @facts_file.read
|
77
|
+
end
|
78
|
+
|
79
|
+
def config_files
|
80
|
+
if @_facts.nil? or @facts["config_files"].nil?
|
81
|
+
[]
|
82
|
+
else
|
83
|
+
@facts["config_files"].flatten.map do |file|
|
84
|
+
@location + file
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def has_flavors?
|
90
|
+
@_facts && !@_facts["flavors"].nil?
|
91
|
+
end
|
92
|
+
|
93
|
+
def has_key? key
|
94
|
+
@_facts && @_facts.has_key?(key)
|
95
|
+
end
|
96
|
+
|
97
|
+
def version
|
98
|
+
@_facts["version"] if @_facts.key? "version"
|
99
|
+
end
|
100
|
+
|
101
|
+
def flavors
|
102
|
+
if @_facts && @_facts.key?("flavors")
|
103
|
+
@_facts["flavors"].keys
|
104
|
+
else
|
105
|
+
[]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def flavor! which_flavor
|
110
|
+
raise RuntimeError.new "Flavor '#{which_flavor}' does not exist -> #{flavors.join(', ')} - use: lace <command> <kit-uri> <flavor>" unless flavors.include? which_flavor
|
111
|
+
@facts = @_facts["flavors"][which_flavor]
|
112
|
+
end
|
113
|
+
|
114
|
+
def unflavor!
|
115
|
+
@facts = @_facts
|
116
|
+
end
|
117
|
+
|
118
|
+
def post hook_point
|
119
|
+
if @_facts.nil? or !@facts.key? "post"
|
120
|
+
[]
|
121
|
+
else
|
122
|
+
post_hook = @facts["post"]
|
123
|
+
(post_hook[hook_point.to_s] || []).flatten
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class Package
|
129
|
+
include GitCommands
|
130
|
+
attr_reader :name, :facts, :path
|
131
|
+
|
132
|
+
def after_install
|
133
|
+
return if ARGV.nohooks?
|
134
|
+
@path.cd do
|
135
|
+
ENV["CURRENT_DOTTY"] = @path
|
136
|
+
facts.post(:install).each do |cmd|
|
137
|
+
safe_system cmd
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def after_update
|
143
|
+
return if ARGV.nohooks?
|
144
|
+
@path.cd do
|
145
|
+
ENV["CURRENT_DOTTY"] = @path
|
146
|
+
facts.post(:update).each do |cmd|
|
147
|
+
system cmd
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def initialize name, flavor=nil
|
153
|
+
require 'cmd/list'
|
154
|
+
raise "Package #{name} is not installed" unless Lace.installed_dotties.include? name
|
155
|
+
@name = name
|
156
|
+
@path = LACE_PKGS_FOLDER/name
|
157
|
+
@flavor = flavor
|
158
|
+
read_facts!
|
159
|
+
end
|
160
|
+
|
161
|
+
def is_git_repo?
|
162
|
+
@target_folder = @path
|
163
|
+
repo_valid?
|
164
|
+
end
|
165
|
+
|
166
|
+
def is_active?
|
167
|
+
if @facts.has_flavors? && @flavor == false
|
168
|
+
@facts.flavors.any?{|f| Package.new(@name, f).is_active?}
|
169
|
+
else
|
170
|
+
linked_files = Set.new Lace.linked_files.map(&:to_s)
|
171
|
+
config_files = Set.new @facts.config_files.map(&:to_s)
|
172
|
+
config_files.subset? linked_files
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def read_facts!
|
177
|
+
@facts = Facts.new @path
|
178
|
+
if @facts.has_flavors? && @flavor.nil?
|
179
|
+
raise RuntimeError.new FlavorArgumentMsg % @facts.flavors.join("\n- ")
|
180
|
+
elsif @facts.has_flavors? && @flavor != false
|
181
|
+
@facts.flavor! @flavor
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def deactivate!
|
186
|
+
ohai "Deactivating"
|
187
|
+
files = @facts.config_files
|
188
|
+
home_dir = ENV["HOME"]
|
189
|
+
files.each do |file|
|
190
|
+
pn = Pathname.new file
|
191
|
+
FileUtils.rm_f File.join(home_dir, "." + pn.basename)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def activate!
|
196
|
+
ohai "Activating"
|
197
|
+
files = @facts.config_files
|
198
|
+
home_dir = ENV["HOME"]
|
199
|
+
files.each do |file|
|
200
|
+
# if ends in erb -> generate it
|
201
|
+
pn = Pathname.new file
|
202
|
+
FileUtils.ln_s file, File.join(home_dir, "." + pn.basename)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
data/lib/lace/utils.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
class Tty
|
2
|
+
class << self
|
3
|
+
def blue; bold 34; end
|
4
|
+
def white; bold 39; end
|
5
|
+
def red; underline 31; end
|
6
|
+
def yellow; underline 33 ; end
|
7
|
+
def reset; escape 0; end
|
8
|
+
def em; underline 39; end
|
9
|
+
def green; color 92 end
|
10
|
+
def gray; bold 30 end
|
11
|
+
|
12
|
+
def width
|
13
|
+
`/usr/bin/tput cols`.strip.to_i
|
14
|
+
end
|
15
|
+
|
16
|
+
def truncate(str)
|
17
|
+
str.to_s[0, width - 4]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def color n
|
23
|
+
escape "0;#{n}"
|
24
|
+
end
|
25
|
+
def bold n
|
26
|
+
escape "1;#{n}"
|
27
|
+
end
|
28
|
+
def underline n
|
29
|
+
escape "4;#{n}"
|
30
|
+
end
|
31
|
+
def escape n
|
32
|
+
"\033[#{n}m" if $stdout.tty?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def ohai title, *sput
|
38
|
+
title = Tty.truncate(title) if $stdout.tty? && !ARGV.verbose?
|
39
|
+
puts "#{Tty.blue}==>#{Tty.white} #{title}#{Tty.reset}"
|
40
|
+
puts sput unless sput.empty?
|
41
|
+
end
|
42
|
+
|
43
|
+
def oh1 title
|
44
|
+
title = Tty.truncate(title) if $stdout.tty? && !ARGV.verbose?
|
45
|
+
puts "#{Tty.green}==>#{Tty.white} #{title}#{Tty.reset}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def opoo warning
|
49
|
+
STDERR.puts "#{Tty.red}Warning#{Tty.reset}: #{warning}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def onoe error
|
53
|
+
lines = error.to_s.split("\n")
|
54
|
+
STDERR.puts "#{Tty.red}Error#{Tty.reset}: #{lines.shift}"
|
55
|
+
STDERR.puts lines unless lines.empty?
|
56
|
+
end
|
57
|
+
|
58
|
+
def ofail error
|
59
|
+
onoe error
|
60
|
+
Lace.failed = true
|
61
|
+
end
|
62
|
+
|
63
|
+
def odie error
|
64
|
+
onoe error
|
65
|
+
exit 1
|
66
|
+
end
|
67
|
+
|
68
|
+
def determine_os
|
69
|
+
case RUBY_PLATFORM
|
70
|
+
when /darwin/ then :mac
|
71
|
+
when /linux/ then :linux
|
72
|
+
else raise InvalidOSError
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
module Lace extend self
|
77
|
+
def system cmd, *args
|
78
|
+
puts "#{cmd} #{args*' '}" if ARGV.verbose?
|
79
|
+
fork do
|
80
|
+
yield if block_given?
|
81
|
+
args.collect!{|arg| arg.to_s}
|
82
|
+
exec(cmd.to_s, *args) rescue nil
|
83
|
+
exit! 1 # never gets here unless exec failed
|
84
|
+
end
|
85
|
+
Process.wait
|
86
|
+
$?.success?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Kernel.system but with exceptions
|
91
|
+
def safe_system cmd, *args
|
92
|
+
unless Lace.system cmd, *args
|
93
|
+
args = args.map{ |arg| arg.to_s.gsub " ", "\\ " } * " "
|
94
|
+
raise ErrorDuringExecution, "Failure while executing: #{cmd} #{args}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# prints no output
|
99
|
+
def quiet_system cmd, *args
|
100
|
+
Lace.system(cmd, *args) do
|
101
|
+
# Redirect output streams to `/dev/null` instead of closing as some programs
|
102
|
+
# will fail to execute if they can't write to an open stream.
|
103
|
+
$stdout.reopen('/dev/null')
|
104
|
+
$stderr.reopen('/dev/null')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
data/lib/lace/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lace
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kai Richard Koenig
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-01-04 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: This is a simple/unfinished tool which i use to manage my dotfiles on
|
15
|
+
all the different machines
|
16
|
+
email: kai@kairichardkoenig.de
|
17
|
+
executables:
|
18
|
+
- lace
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- lib/cmd/activate.rb
|
23
|
+
- lib/cmd/deactivate.rb
|
24
|
+
- lib/cmd/fetch.rb
|
25
|
+
- lib/cmd/help.rb
|
26
|
+
- lib/cmd/inspect.rb
|
27
|
+
- lib/cmd/install.rb
|
28
|
+
- lib/cmd/list.rb
|
29
|
+
- lib/cmd/remove.rb
|
30
|
+
- lib/cmd/update.rb
|
31
|
+
- lib/cmd/validate.rb
|
32
|
+
- lib/extend/ARGV.rb
|
33
|
+
- lib/extend/pathname.rb
|
34
|
+
- lib/lace/download_strategy.rb
|
35
|
+
- lib/lace/exceptions.rb
|
36
|
+
- lib/lace/package.rb
|
37
|
+
- lib/lace/utils.rb
|
38
|
+
- lib/lace/version.rb
|
39
|
+
- bin/lace
|
40
|
+
homepage: https://github.com/kairichard/lace
|
41
|
+
licenses:
|
42
|
+
- MIT
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ! '>='
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.8.6
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 1.8.23
|
62
|
+
signing_key:
|
63
|
+
specification_version: 3
|
64
|
+
summary: Manage your .dotfiles
|
65
|
+
test_files: []
|