lace 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/lace +1 -1
- data/lib/cmd/apply.rb +42 -0
- data/lib/cmd/diff.rb +29 -0
- data/lib/cmd/help.rb +1 -0
- data/lib/cmd/setup.rb +5 -1
- data/lib/cmd/validate.rb +32 -23
- data/lib/extend/ARGV.rb +10 -0
- data/lib/lace/download_strategy.rb +53 -60
- data/lib/lace/package/facts.rb +40 -27
- data/lib/lace/package/package.rb +34 -25
- data/lib/lace/package/utils.rb +72 -32
- data/lib/lace/package_cli.rb +4 -0
- data/lib/lace/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e9374d4a207b650f55ec19e285891ac00fc55da706d5a87b8ce5ff642315f30
|
4
|
+
data.tar.gz: 457594f9250c5e7045a00b227596c41f461a8487e3be6c95240f6891b8067e6e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f546f2835196c35b71522b3683376c79c3deee6b82529c24108639d44fa148bca4a1127aefcab3d6e38209b8c7cda917a535019fb1f2068db92d06427817b8b2
|
7
|
+
data.tar.gz: 386d3fb4712ad53fe2fc1f06cfa853c1fd552d5a23b0d46fc8160925b62ed5ff09f19ee41d48309ba5ae55bce9eb1964c4b1f6ce65b25507f3615eb0da2fc824
|
data/bin/lace
CHANGED
data/lib/cmd/apply.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'lace/package'
|
4
|
+
require 'lace/exceptions'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module Lace
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def apply
|
11
|
+
package_name = ARGV.shift
|
12
|
+
raise ResourceNotSpecified unless package_name
|
13
|
+
ohai "Applying package #{package_name}"
|
14
|
+
package = Package.new(package_name, ARGV.first)
|
15
|
+
|
16
|
+
diff = PackageUtils.diff(package_name)
|
17
|
+
|
18
|
+
if diff.added.empty? && diff.removed.empty?
|
19
|
+
puts 'Everything is already in sync. Nothing to do.'
|
20
|
+
return
|
21
|
+
end
|
22
|
+
|
23
|
+
diff.added.each do |f|
|
24
|
+
dest = f.as_dotfile(Dir.home, package.path)
|
25
|
+
dest_path = dest.to_path.gsub(Dir.home, '$HOME')
|
26
|
+
puts "Link #{f.basename} to #{dest_path}?"
|
27
|
+
next unless ARGV.confirm?("Link #{f.basename} to #{dest_path}?")
|
28
|
+
|
29
|
+
package.link_file_or_directory(f, dest, force: true)
|
30
|
+
ohai "Linked"
|
31
|
+
end
|
32
|
+
|
33
|
+
diff.removed.each do |f|
|
34
|
+
dest = f.as_dotfile(Dir.home, package.path)
|
35
|
+
dest_path = dest.to_path.gsub(Dir.home, '$HOME')
|
36
|
+
next unless ARGV.confirm?("Remove link for #{f.basename} from #{dest_path}?")
|
37
|
+
|
38
|
+
package.unlink_file(f, force: true)
|
39
|
+
ohai "Removed"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/cmd/diff.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'lace/package'
|
4
|
+
require 'lace/exceptions'
|
5
|
+
|
6
|
+
module Lace
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def diff
|
10
|
+
package_name = ARGV.shift
|
11
|
+
raise ResourceNotSpecified unless package_name
|
12
|
+
|
13
|
+
package = Package.new(package_name, ARGV.first)
|
14
|
+
diff = PackageUtils.diff package_name
|
15
|
+
|
16
|
+
if diff.added.empty? && diff.removed.empty?
|
17
|
+
ohai 'Everything is already in sync.'
|
18
|
+
return
|
19
|
+
end
|
20
|
+
|
21
|
+
ohai 'The following files would be added [+] or removed [-]'
|
22
|
+
diff.added.each do |f|
|
23
|
+
puts "[+] #{f.basename} -> #{f.as_dotfile(Dir.home, package.path).to_path.gsub(Dir.home, '$HOME')}"
|
24
|
+
end
|
25
|
+
diff.removed.each do |f|
|
26
|
+
puts "[-] #{f.basename} -> #{f.as_dotfile(Dir.home, package.path).to_path.gsub(Dir.home, '$HOME')}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/cmd/help.rb
CHANGED
data/lib/cmd/setup.rb
CHANGED
data/lib/cmd/validate.rb
CHANGED
@@ -1,28 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'erb'
|
2
4
|
|
3
5
|
require 'lace/package'
|
4
6
|
require 'lace/utils'
|
5
7
|
require 'lace/exceptions'
|
6
8
|
|
7
|
-
VALIDATE =
|
8
|
-
Lace-Manifest Validation Report:
|
9
|
-
<% validation.errors.each do |error| -%>
|
10
|
-
|
11
|
-
<% unless error[2].nil? -%>
|
12
|
-
<% error[2].each do |line| -%>
|
13
|
-
|
14
|
-
<% end -%>
|
15
|
-
<% end -%>
|
16
|
-
<% end -%>
|
9
|
+
VALIDATE = <<~EOS
|
10
|
+
Lace-Manifest Validation Report:
|
11
|
+
<% validation.errors.each do |error| -%>
|
12
|
+
<%= "%-58s [ %s ]" % [error[0] + ':', error[1]] %>
|
13
|
+
<% unless error[2].nil? -%>
|
14
|
+
<% error[2].each do |line| -%>
|
15
|
+
<%= Tty.gray %><%= '# '+line.to_s %><%= Tty.reset %>
|
16
|
+
<% end -%>
|
17
|
+
<% end -%>
|
18
|
+
<% end -%>
|
17
19
|
EOS
|
18
20
|
|
19
|
-
module Lace
|
21
|
+
module Lace
|
22
|
+
module_function
|
23
|
+
|
20
24
|
def validate
|
21
25
|
resource = ARGV.shift
|
22
26
|
flavor = ARGV.shift
|
23
27
|
raise ResourceNotSpecified unless resource
|
28
|
+
|
24
29
|
validation = PackageValidator.new(PackageFacts.new(resource), flavor)
|
25
|
-
puts ERB.new(VALIDATE,
|
30
|
+
puts ERB.new(VALIDATE, trim_mode: '-').result(binding)
|
26
31
|
Lace.failed = true if validation.has_errors?
|
27
32
|
end
|
28
33
|
end
|
@@ -32,7 +37,8 @@ class PackageValidator
|
|
32
37
|
|
33
38
|
class << self
|
34
39
|
attr_accessor :validations
|
35
|
-
|
40
|
+
|
41
|
+
def validate(name, method_name)
|
36
42
|
@validations ||= []
|
37
43
|
@validations << [name, method_name]
|
38
44
|
end
|
@@ -44,18 +50,19 @@ class PackageValidator
|
|
44
50
|
validate 'setup', :setup_ok
|
45
51
|
validate 'post-update hook', :post_update_hooks_ok
|
46
52
|
|
47
|
-
def initialize
|
53
|
+
def initialize(facts, flavor = nil)
|
48
54
|
@facts = facts
|
49
55
|
@errors = []
|
50
56
|
if @facts.has_flavors? && flavor.nil?
|
51
|
-
raise
|
57
|
+
raise FlavorArgumentMsg % @facts.flavors.join("\n- ").to_s
|
52
58
|
elsif @facts.has_flavors? && flavor != false
|
53
59
|
@facts.flavor! flavor
|
54
60
|
end
|
61
|
+
|
55
62
|
validate
|
56
63
|
end
|
57
64
|
|
58
|
-
def check_hooks
|
65
|
+
def check_hooks(hook_cmd)
|
59
66
|
hook_cmd.map do |cmd|
|
60
67
|
if !File.exist? cmd
|
61
68
|
"#{cmd} cannot be found"
|
@@ -65,13 +72,13 @@ class PackageValidator
|
|
65
72
|
end.compact
|
66
73
|
end
|
67
74
|
|
68
|
-
def hook_ok
|
75
|
+
def hook_ok(config_files)
|
69
76
|
hook_cmd = config_files
|
70
77
|
if hook_cmd.empty?
|
71
78
|
["#{Tty.green}skipped#{Tty.reset}", nil]
|
72
79
|
else
|
73
80
|
errors = check_hooks hook_cmd
|
74
|
-
if errors.length
|
81
|
+
if errors.length.positive?
|
75
82
|
["#{Tty.red}error#{Tty.reset}", errors]
|
76
83
|
else
|
77
84
|
["#{Tty.green}ok#{Tty.reset}", nil]
|
@@ -88,7 +95,7 @@ class PackageValidator
|
|
88
95
|
end
|
89
96
|
|
90
97
|
def homepage_present
|
91
|
-
if @facts.
|
98
|
+
if @facts.key? 'homepage'
|
92
99
|
["#{Tty.green}found#{Tty.reset}", nil]
|
93
100
|
else
|
94
101
|
["#{Tty.red}missing#{Tty.reset}", ['adding a homepage improves the credibility', 'of your package']]
|
@@ -96,7 +103,7 @@ class PackageValidator
|
|
96
103
|
end
|
97
104
|
|
98
105
|
def version_present
|
99
|
-
if @facts.
|
106
|
+
if @facts.key? 'version'
|
100
107
|
["#{Tty.green}found#{Tty.reset}", nil]
|
101
108
|
else
|
102
109
|
["#{Tty.red}missing#{Tty.reset}", ['adding a version to the manifest improves', 'a future update experince']]
|
@@ -106,8 +113,10 @@ class PackageValidator
|
|
106
113
|
def config_files_present
|
107
114
|
if @facts.config_files.empty?
|
108
115
|
["#{Tty.red}missing#{Tty.reset}", ['Add config_files see manual for more information']]
|
109
|
-
elsif @facts.config_files.any?{|f| !File.exist? f}
|
110
|
-
["#{Tty.red}error#{Tty.reset}", @facts.config_files.
|
116
|
+
elsif @facts.config_files.any? { |f| !File.exist? f }
|
117
|
+
["#{Tty.red}error#{Tty.reset}", @facts.config_files.reject do |f|
|
118
|
+
File.exist? f
|
119
|
+
end.map { |f| "#{f.to_s.split('/').last} is missing from this package" }]
|
111
120
|
else
|
112
121
|
["#{Tty.green}found#{Tty.reset}", nil]
|
113
122
|
end
|
@@ -120,6 +129,6 @@ class PackageValidator
|
|
120
129
|
end
|
121
130
|
|
122
131
|
def has_errors?
|
123
|
-
errors.any?{|e| !e[2].nil? }
|
132
|
+
errors.any? { |e| !e[2].nil? }
|
124
133
|
end
|
125
134
|
end
|
data/lib/extend/ARGV.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2009-2014 Max Howell and other contributors.
|
2
4
|
#
|
3
|
-
#Redistribution and use in source and binary forms, with or without
|
4
|
-
#modification, are permitted provided that the following conditions
|
5
|
-
#are met:
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions
|
7
|
+
# are met:
|
6
8
|
#
|
7
9
|
# 1. Redistributions of source code must retain the above copyright
|
8
10
|
# notice, this list of conditions and the following disclaimer.
|
@@ -10,45 +12,41 @@
|
|
10
12
|
# notice, this list of conditions and the following disclaimer in the
|
11
13
|
# documentation and/or other materials provided with the distribution.
|
12
14
|
#
|
13
|
-
#THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
14
|
-
#IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
15
|
-
#OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
16
|
-
#IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
17
|
-
#INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
18
|
-
#NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
19
|
-
#DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
20
|
-
#THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
21
|
-
#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
22
|
-
#THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
23
|
-
|
24
|
-
require
|
15
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
16
|
+
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
17
|
+
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
18
|
+
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
19
|
+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
20
|
+
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
21
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
22
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
23
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
24
|
+
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
25
|
+
|
26
|
+
require 'fileutils'
|
25
27
|
|
26
28
|
class AbstractDownloadStrategy
|
27
|
-
attr_reader :
|
29
|
+
attr_reader :resource, :target_folder, :uri
|
28
30
|
|
29
|
-
def initialize
|
31
|
+
def initialize(uri, desired_package_name = nil)
|
30
32
|
@desired_package_name = desired_package_name
|
31
33
|
@uri = uri
|
32
|
-
@target_folder = Lace.pkgs_folder/name
|
33
|
-
end
|
34
|
-
|
35
|
-
def uri
|
36
|
-
@uri
|
34
|
+
@target_folder = Lace.pkgs_folder / name
|
37
35
|
end
|
38
36
|
|
39
37
|
# All download strategies are expected to implement these methods
|
40
38
|
def fetch; end
|
41
39
|
def stage; end
|
40
|
+
|
42
41
|
def name
|
43
42
|
@desired_package_name
|
44
43
|
end
|
45
44
|
end
|
46
45
|
|
47
|
-
|
48
46
|
class LocalFileStrategy < AbstractDownloadStrategy
|
49
47
|
def fetch
|
50
|
-
ohai "Fetching
|
51
|
-
FileUtils.cp_r @uri, @target_folder, :
|
48
|
+
ohai "Fetching #{@uri} into #{@target_folder}"
|
49
|
+
FileUtils.cp_r @uri, @target_folder, preserve: true
|
52
50
|
@target_folder
|
53
51
|
end
|
54
52
|
|
@@ -57,22 +55,21 @@ class LocalFileStrategy < AbstractDownloadStrategy
|
|
57
55
|
end
|
58
56
|
end
|
59
57
|
|
60
|
-
|
61
58
|
module GitCommands
|
62
59
|
def update_repo
|
63
60
|
safe_system 'git', 'fetch', 'origin'
|
64
61
|
end
|
65
62
|
|
66
63
|
def reset
|
67
|
-
safe_system 'git',
|
64
|
+
safe_system 'git', 'reset', '--hard', 'origin/HEAD'
|
68
65
|
end
|
69
66
|
|
70
67
|
def git_dir
|
71
|
-
@target_folder.join(
|
68
|
+
@target_folder.join('.git')
|
72
69
|
end
|
73
70
|
|
74
71
|
def repo_valid?
|
75
|
-
quiet_system
|
72
|
+
quiet_system 'git', '--git-dir', git_dir, 'status', '-s'
|
76
73
|
end
|
77
74
|
|
78
75
|
def repo_modified?
|
@@ -85,11 +82,11 @@ module GitCommands
|
|
85
82
|
end
|
86
83
|
|
87
84
|
def submodules?
|
88
|
-
@target_folder.join(
|
85
|
+
@target_folder.join('.gitmodules').exist?
|
89
86
|
end
|
90
87
|
|
91
88
|
def clone_args
|
92
|
-
args = %w
|
89
|
+
args = %w[clone]
|
93
90
|
args << @uri << @target_folder
|
94
91
|
end
|
95
92
|
|
@@ -103,46 +100,44 @@ module GitCommands
|
|
103
100
|
end
|
104
101
|
end
|
105
102
|
|
106
|
-
|
107
103
|
class GitUpdateStrategy
|
108
104
|
include GitCommands
|
109
105
|
|
110
|
-
def initialize
|
111
|
-
@target_folder = Lace.pkgs_folder/name
|
106
|
+
def initialize(name)
|
107
|
+
@target_folder = Lace.pkgs_folder / name
|
112
108
|
end
|
113
109
|
|
114
110
|
def update
|
115
111
|
if repo_valid?
|
116
|
-
puts "Updating
|
112
|
+
puts "Updating #{@target_folder}"
|
117
113
|
@target_folder.cd do
|
118
114
|
update_repo
|
119
115
|
reset
|
120
116
|
update_submodules if submodules?
|
121
117
|
end
|
122
118
|
else
|
123
|
-
puts
|
119
|
+
puts 'Removing invalid .git repo'
|
124
120
|
FileUtils.rm_rf @target_folder
|
125
121
|
clone_repo
|
126
122
|
end
|
127
123
|
end
|
128
124
|
end
|
129
125
|
|
130
|
-
|
131
126
|
class GitDownloadStrategy < AbstractDownloadStrategy
|
132
127
|
include GitCommands
|
133
128
|
|
134
129
|
def fetch
|
135
|
-
ohai "Cloning
|
130
|
+
ohai "Cloning #{@uri}"
|
136
131
|
|
137
132
|
if @target_folder.exist? && repo_valid?
|
138
|
-
puts "Updating
|
133
|
+
puts "Updating #{@target_folder}"
|
139
134
|
@target_folder.cd do
|
140
135
|
update_repo
|
141
136
|
reset
|
142
137
|
update_submodules if submodules?
|
143
138
|
end
|
144
139
|
elsif @target_folder.exist?
|
145
|
-
puts
|
140
|
+
puts 'Removing invalid .git repo'
|
146
141
|
FileUtils.rm_rf @target_folder
|
147
142
|
clone_repo
|
148
143
|
else
|
@@ -154,47 +149,45 @@ class GitDownloadStrategy < AbstractDownloadStrategy
|
|
154
149
|
def name
|
155
150
|
if super
|
156
151
|
super
|
157
|
-
elsif @uri.include?
|
158
|
-
@uri.split(
|
152
|
+
elsif @uri.include? 'github.com'
|
153
|
+
@uri.split('/')[-2]
|
159
154
|
elsif File.directory? @uri
|
160
155
|
File.basename(@uri)
|
161
156
|
else
|
162
|
-
raise "Cannot determine a proper name with
|
157
|
+
raise "Cannot determine a proper name with #{@uri}"
|
163
158
|
end
|
164
159
|
end
|
165
|
-
|
166
160
|
end
|
167
161
|
|
168
|
-
class
|
169
|
-
def initialize
|
170
|
-
unless uri.end_with?(
|
171
|
-
uri = "#{uri}.git"
|
172
|
-
end
|
162
|
+
class GitHubDownloadStrategy < GitDownloadStrategy
|
163
|
+
def initialize(uri, desired_package_name = nil)
|
164
|
+
uri = "#{uri}.git" unless uri.end_with?('.git')
|
173
165
|
uri = "https://github.com/#{uri}"
|
174
|
-
super
|
166
|
+
super
|
175
167
|
end
|
176
168
|
end
|
177
169
|
|
178
|
-
|
179
170
|
class DownloadStrategyDetector
|
180
171
|
def self.detect(uri)
|
181
|
-
|
172
|
+
detect_from_uri(uri)
|
182
173
|
end
|
183
174
|
|
184
175
|
def self.detect_from_uri(uri)
|
185
|
-
is_git_dir = File.directory?(uri
|
186
|
-
has_single_slash = uri.scan(
|
187
|
-
|
176
|
+
is_git_dir = File.directory?("#{uri}/.git")
|
177
|
+
has_single_slash = uri.scan('/').count == 1
|
178
|
+
via_ssh = uri.start_with?('git@')
|
179
|
+
|
180
|
+
if File.directory?(uri) && !is_git_dir && !via_ssh
|
188
181
|
return LocalFileStrategy
|
189
182
|
elsif is_git_dir
|
190
183
|
return GitDownloadStrategy
|
191
|
-
elsif has_single_slash
|
192
|
-
return
|
184
|
+
elsif has_single_slash && !via_ssh
|
185
|
+
return GitHubDownloadStrategy
|
193
186
|
end
|
194
187
|
|
195
188
|
case uri
|
196
|
-
when
|
197
|
-
when %r
|
189
|
+
when /^git@/ then GitDownloadStrategy
|
190
|
+
when %r{^https?://.+\.git$} then GitDownloadStrategy
|
198
191
|
# else CurlDownloadStrategy
|
199
192
|
else
|
200
193
|
raise "Cannot determine download startegy from #{uri}"
|
data/lib/lace/package/facts.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'yaml'
|
2
4
|
require 'erb'
|
3
5
|
|
@@ -8,17 +10,18 @@ class PackageFacts
|
|
8
10
|
"#<Facts:#{@package_path}>"
|
9
11
|
end
|
10
12
|
|
11
|
-
def initialize
|
13
|
+
def initialize(package_path)
|
12
14
|
@package_path = Pathname.new(package_path)
|
13
|
-
@facts_file = @package_path/
|
14
|
-
raise PackageFactsNotFound
|
15
|
+
@facts_file = @package_path / '.lace.yml'
|
16
|
+
raise PackageFactsNotFound, @package_path unless @facts_file.exist?
|
17
|
+
|
15
18
|
@facts = facts_file_to_hash
|
16
19
|
@unflavorable_facts = facts_file_to_hash
|
17
20
|
end
|
18
21
|
|
19
|
-
def flavor_with
|
22
|
+
def flavor_with(the_flavor)
|
20
23
|
if has_flavors? && the_flavor.nil?
|
21
|
-
raise FlavorArgumentRequired
|
24
|
+
raise FlavorArgumentRequired, flavors
|
22
25
|
elsif has_flavors? && the_flavor != false
|
23
26
|
flavor! the_flavor
|
24
27
|
end
|
@@ -26,78 +29,88 @@ class PackageFacts
|
|
26
29
|
|
27
30
|
def config_files
|
28
31
|
if has_config_files?
|
29
|
-
@facts[
|
32
|
+
@facts['config_files'].flatten.map do |path|
|
30
33
|
Pathname.glob(@package_path + path).delete_if do |match|
|
31
|
-
match.directory? and path.include?
|
34
|
+
match.directory? and path.include? '*'
|
32
35
|
end
|
33
36
|
end.flatten
|
34
|
-
else
|
37
|
+
else
|
38
|
+
[] end
|
39
|
+
end
|
40
|
+
|
41
|
+
def globbed_folder
|
42
|
+
if has_config_files?
|
43
|
+
@facts['config_files'].flatten.select { |f| f.include? '*' }
|
44
|
+
else
|
45
|
+
[] end
|
35
46
|
end
|
36
47
|
|
37
48
|
def has_config_files?
|
38
|
-
|
49
|
+
key? 'config_files'
|
39
50
|
end
|
40
51
|
|
41
52
|
def has_flavors?
|
42
|
-
@unflavorable_facts && !@unflavorable_facts[
|
53
|
+
@unflavorable_facts && !@unflavorable_facts['flavors'].nil?
|
43
54
|
end
|
44
55
|
|
45
|
-
def
|
46
|
-
@unflavorable_facts && (@unflavorable_facts.
|
56
|
+
def key?(key)
|
57
|
+
@unflavorable_facts && (@unflavorable_facts.key?(key) or @facts.key?(key))
|
47
58
|
end
|
48
59
|
|
49
60
|
def version
|
50
|
-
@unflavorable_facts[
|
61
|
+
@unflavorable_facts['version'] if @unflavorable_facts.key? 'version'
|
51
62
|
end
|
52
63
|
|
53
64
|
def setup_files
|
54
|
-
@facts[
|
65
|
+
@facts['setup'].flatten
|
66
|
+
rescue StandardError
|
67
|
+
[]
|
55
68
|
end
|
56
69
|
|
57
70
|
def homepage
|
58
|
-
@unflavorable_facts[
|
71
|
+
@unflavorable_facts['homepage'] if @unflavorable_facts.key? 'homepage'
|
59
72
|
end
|
60
73
|
|
61
74
|
def flavors
|
62
|
-
if @unflavorable_facts
|
63
|
-
@unflavorable_facts[
|
75
|
+
if @unflavorable_facts&.key?('flavors')
|
76
|
+
@unflavorable_facts['flavors'].keys.sort
|
64
77
|
else
|
65
78
|
[]
|
66
79
|
end
|
67
80
|
end
|
68
81
|
|
69
|
-
def flavor!
|
82
|
+
def flavor!(the_flavor)
|
70
83
|
raise PackageFlavorDoesNotExist.new(the_flavor, flavors) unless flavors.include? the_flavor
|
71
|
-
|
84
|
+
|
85
|
+
@facts = @unflavorable_facts['flavors'][the_flavor]
|
72
86
|
end
|
73
87
|
|
74
88
|
def unflavor!
|
75
89
|
@facts = @unflavorable_facts
|
76
90
|
end
|
77
91
|
|
78
|
-
def post
|
79
|
-
if @unflavorable_facts.nil?
|
92
|
+
def post(hook_point)
|
93
|
+
if @unflavorable_facts.nil? || !@facts.key?('post')
|
80
94
|
[]
|
81
95
|
else
|
82
|
-
post_hook = @facts[
|
96
|
+
post_hook = @facts['post']
|
83
97
|
(post_hook[hook_point.to_s] || []).flatten
|
84
98
|
end
|
85
99
|
end
|
86
100
|
|
87
101
|
protected
|
102
|
+
|
88
103
|
def facts_file_to_hash
|
89
104
|
begin
|
90
105
|
rendered_manifest = ERB.new(@facts_file.read, trim_mode: '-').result(binding)
|
91
106
|
rescue Exception => e
|
92
107
|
raise ManifestErbError.new(self, e)
|
93
108
|
end
|
94
|
-
value = YAML.
|
95
|
-
if value.is_a?(String) && value ==
|
96
|
-
|
109
|
+
value = YAML.safe_load rendered_manifest, aliases: true
|
110
|
+
if value.is_a?(String) && value == '---'
|
111
|
+
{}
|
97
112
|
else
|
98
113
|
value
|
99
114
|
end
|
100
115
|
end
|
101
116
|
end
|
102
|
-
|
103
|
-
|
data/lib/lace/package/package.rb
CHANGED
@@ -1,31 +1,35 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'set'
|
3
4
|
require 'lace/download_strategy'
|
4
5
|
|
5
6
|
class Package
|
6
7
|
include GitCommands
|
7
8
|
attr_reader :name, :facts, :path
|
8
9
|
|
9
|
-
def initialize
|
10
|
+
def initialize(name, flavor = nil)
|
10
11
|
@name = name
|
11
|
-
@path = Lace.pkgs_folder/name
|
12
|
-
raise PackageNotInstalled
|
12
|
+
@path = Lace.pkgs_folder / name
|
13
|
+
raise PackageNotInstalled, name unless @path.exist?
|
14
|
+
|
13
15
|
@flavor = flavor
|
14
16
|
read_facts!
|
15
17
|
end
|
16
18
|
|
17
19
|
def setup
|
18
20
|
return if ARGV.nohooks?
|
21
|
+
|
19
22
|
ENV['LACEPKG_PATH'] = @path
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
23
|
+
@path.cd do
|
24
|
+
facts.setup_files.each do |cmd|
|
25
|
+
safe_system cmd
|
26
|
+
end
|
27
|
+
end
|
25
28
|
end
|
26
29
|
|
27
30
|
def after_update
|
28
31
|
return if ARGV.nohooks?
|
32
|
+
|
29
33
|
@path.cd do
|
30
34
|
facts.post(:update).each do |cmd|
|
31
35
|
system cmd
|
@@ -40,17 +44,18 @@ class Package
|
|
40
44
|
|
41
45
|
def is_modified?
|
42
46
|
return false unless is_git_repo?
|
47
|
+
|
43
48
|
@target_folder = @path
|
44
49
|
repo_modified?
|
45
50
|
end
|
46
51
|
|
47
52
|
def is_active?
|
48
|
-
home_dir =
|
53
|
+
home_dir = Dir.home
|
49
54
|
if @facts.has_flavors? && @flavor == false
|
50
|
-
@facts.flavors.any?{|f| Package.new(@name, f).is_active?}
|
55
|
+
@facts.flavors.any? { |f| Package.new(@name, f).is_active? }
|
51
56
|
else
|
52
57
|
config_files = Set.new @facts.config_files
|
53
|
-
config_files.all? do |p|
|
58
|
+
config_files.all? do |p|
|
54
59
|
dotfile = p.as_dotfile(home_dir, @path)
|
55
60
|
dotfile.exist? and dotfile.symlink? and dotfile.readlink.dirname.to_s.include?(@path)
|
56
61
|
end
|
@@ -64,11 +69,8 @@ class Package
|
|
64
69
|
|
65
70
|
def deactivate!
|
66
71
|
files = @facts.config_files
|
67
|
-
home_dir = ENV["HOME"]
|
68
72
|
files.each do |file|
|
69
|
-
|
70
|
-
dotfile = file.as_dotfile(home_dir, @path)
|
71
|
-
FileUtils.rm_f dotfile if dotfile.exist? && dotfile.readlink == file
|
73
|
+
unlink_file file
|
72
74
|
end
|
73
75
|
end
|
74
76
|
|
@@ -79,23 +81,30 @@ class Package
|
|
79
81
|
end
|
80
82
|
end
|
81
83
|
|
82
|
-
def
|
83
|
-
|
84
|
+
def unlink_file(file, force: ARGV.force?)
|
85
|
+
dotfile = file.as_dotfile(Dir.home, @path)
|
86
|
+
if (dotfile.exist? || force) && dotfile.symlink? && dotfile.readlink == file
|
87
|
+
FileUtils.rm_f(dotfile)
|
88
|
+
return true
|
89
|
+
end
|
90
|
+
false
|
91
|
+
end
|
92
|
+
|
93
|
+
def link_file(file)
|
94
|
+
home_dir = Dir.home
|
84
95
|
# if ends in erb -> generate it
|
85
96
|
src = file
|
86
97
|
dest = src.as_dotfile(home_dir, @path)
|
87
98
|
if dest.exist? && dest.directory?
|
88
99
|
raise WouldOverwriteError.new(dest, src) unless ARGV.force?
|
100
|
+
|
89
101
|
FileUtils.mv dest, dest.as_backup
|
90
102
|
end
|
91
|
-
link_file_or_directory src, dest
|
103
|
+
link_file_or_directory src, dest, force: ARGV.force?
|
92
104
|
end
|
93
105
|
|
94
|
-
def link_file_or_directory(src, dest)
|
95
|
-
|
96
|
-
|
97
|
-
dest.dirname.mkpath
|
98
|
-
end
|
99
|
-
FileUtils.ln_s src, dest, :force => ARGV.force?
|
106
|
+
def link_file_or_directory(src, dest, force: ARGV.force?)
|
107
|
+
dest.dirname.mkpath unless dest.dirname.exist?
|
108
|
+
FileUtils.ln_s src, dest, force: force
|
100
109
|
end
|
101
110
|
end
|
data/lib/lace/package/utils.rb
CHANGED
@@ -1,59 +1,99 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require('lace/exceptions')
|
4
|
+
|
5
|
+
Diff = Struct.new(:added, :removed) do
|
6
|
+
end
|
4
7
|
|
5
|
-
|
8
|
+
class PackageUtils
|
9
|
+
def self.fetch(uri, desired_package_name = nil)
|
6
10
|
downloader_cls = DownloadStrategyDetector.detect(uri)
|
7
11
|
downloader = downloader_cls.new(uri, desired_package_name)
|
8
|
-
raise
|
12
|
+
raise(PackageAlreadyInstalled, downloader.target_folder) if downloader.target_folder.exist?
|
13
|
+
|
9
14
|
downloader.fetch
|
10
|
-
|
15
|
+
[downloader.name, downloader.target_folder]
|
11
16
|
end
|
12
17
|
|
13
|
-
def self.remove
|
18
|
+
def self.remove(package_name)
|
14
19
|
package = Package.new(package_name, false)
|
15
|
-
raise
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
20
|
+
raise(CannotRemoveActivePackage) if package.is_active?
|
21
|
+
|
22
|
+
ohai('Removing')
|
23
|
+
FileUtils.rm_rf(package.path)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.setup(package_name)
|
27
|
+
package = Package.new(package_name, ARGV.first)
|
28
|
+
package.activate!
|
29
|
+
package.setup
|
30
|
+
rescue StandardError => e
|
31
|
+
onoe(e.message)
|
32
|
+
odie('Package remains installed but was not activated')
|
29
33
|
end
|
30
34
|
|
31
|
-
def self.deactivate
|
35
|
+
def self.deactivate(package_name)
|
32
36
|
package = Package.new(package_name, ARGV.first)
|
33
|
-
raise
|
34
|
-
|
37
|
+
raise(NonActiveFlavorError) unless package.is_active? || ARGV.force?
|
38
|
+
|
39
|
+
ohai('Deactivating')
|
35
40
|
package.deactivate!
|
36
41
|
end
|
37
42
|
|
38
|
-
def self.activate
|
43
|
+
def self.activate(package_name)
|
39
44
|
package = Package.new(package_name, ARGV.first)
|
40
|
-
raise
|
41
|
-
|
45
|
+
raise(AlreadyActiveError) if Package.new(package_name, false).is_active?
|
46
|
+
|
47
|
+
ohai('Activating')
|
42
48
|
package.activate!
|
43
49
|
end
|
44
50
|
|
45
|
-
def self.update
|
51
|
+
def self.update(package_name)
|
46
52
|
package = Package.new(package_name, false)
|
47
|
-
raise
|
53
|
+
raise(OnlyGitReposCanBeUpdatedError) unless package.is_git_repo?
|
54
|
+
|
48
55
|
updater = GitUpdateStrategy.new(package_name)
|
49
56
|
was_active_before_update = package.is_active?
|
50
|
-
|
51
|
-
ohai
|
57
|
+
deactivate(package_name) if was_active_before_update
|
58
|
+
ohai('Updating')
|
52
59
|
updater.update
|
53
|
-
|
54
|
-
package = Package.new
|
60
|
+
activate(package_name) if was_active_before_update
|
61
|
+
package = Package.new(package_name, false)
|
55
62
|
package.after_update
|
56
63
|
end
|
64
|
+
|
65
|
+
def self.diff(package_name)
|
66
|
+
home_dir = Dir.home
|
67
|
+
package = Package.new(package_name, ARGV.first)
|
68
|
+
config_files = Set.new(package.facts.config_files)
|
69
|
+
files_pointing_to_package = symlinks_to_package(package)
|
70
|
+
files_from_manifest_not_in_home = Set.new(config_files.reject do |f|
|
71
|
+
f.as_dotfile(home_dir, package.path).exist?
|
72
|
+
end)
|
73
|
+
Diff.new(files_from_manifest_not_in_home, (files_pointing_to_package - config_files))
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.symlinks_to_package(package)
|
77
|
+
home_dir = Dir.home
|
78
|
+
found_links = Set.new
|
79
|
+
traverse_directory(home_dir, package) { |entry| (found_links << Pathname.new(File.readlink(entry))) }
|
80
|
+
found_links
|
81
|
+
end
|
57
82
|
end
|
58
83
|
|
84
|
+
COMMON_CONFIG_FOLDERS = ['config'].freeze
|
85
|
+
|
86
|
+
def traverse_directory(directory, package, &block)
|
87
|
+
package_path = package.path
|
88
|
+
whitelisted_folders = package.facts.globbed_folder + COMMON_CONFIG_FOLDERS
|
89
|
+
Dir.foreach(directory) do |entry|
|
90
|
+
next if ['.', '..'].include?(entry)
|
59
91
|
|
92
|
+
entry_path = File.join(directory, entry)
|
93
|
+
if File.symlink?(entry_path) && File.readlink(entry_path).include?(package_path)
|
94
|
+
block.call(entry_path)
|
95
|
+
elsif File.directory?(entry_path) && whitelisted_folders.any? { |f| entry_path.include?(f) }
|
96
|
+
traverse_directory(entry_path, package, &block)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/lace/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lace
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kai Richard Koenig
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-06-17 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Lace lets you manage your dotfiles when using them on multiple machines
|
14
14
|
email: kai.richard.koenig@gmail.com
|
@@ -19,7 +19,9 @@ extra_rdoc_files: []
|
|
19
19
|
files:
|
20
20
|
- bin/lace
|
21
21
|
- lib/cmd/activate.rb
|
22
|
+
- lib/cmd/apply.rb
|
22
23
|
- lib/cmd/deactivate.rb
|
24
|
+
- lib/cmd/diff.rb
|
23
25
|
- lib/cmd/fetch.rb
|
24
26
|
- lib/cmd/help.rb
|
25
27
|
- lib/cmd/inspect.rb
|
@@ -39,6 +41,7 @@ files:
|
|
39
41
|
- lib/lace/package/facts.rb
|
40
42
|
- lib/lace/package/package.rb
|
41
43
|
- lib/lace/package/utils.rb
|
44
|
+
- lib/lace/package_cli.rb
|
42
45
|
- lib/lace/utils.rb
|
43
46
|
- lib/lace/version.rb
|
44
47
|
homepage: https://github.com/kairichard/lace
|