lace 0.2.1
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.
- 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: []
|