briefcase 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README.rdoc +104 -0
- data/Rakefile +9 -0
- data/bin/briefcase +3 -0
- data/briefcase.gemspec +33 -0
- data/lib/briefcase.rb +42 -0
- data/lib/briefcase/commands.rb +6 -0
- data/lib/briefcase/commands/base.rb +94 -0
- data/lib/briefcase/commands/core/files.rb +81 -0
- data/lib/briefcase/commands/core/output.rb +24 -0
- data/lib/briefcase/commands/core/secrets.rb +50 -0
- data/lib/briefcase/commands/generate.rb +65 -0
- data/lib/briefcase/commands/git.rb +37 -0
- data/lib/briefcase/commands/import.rb +81 -0
- data/lib/briefcase/commands/redact.rb +76 -0
- data/lib/briefcase/commands/sync.rb +66 -0
- data/lib/briefcase/main.rb +44 -0
- data/lib/briefcase/version.rb +3 -0
- data/spec/bin/editor +7 -0
- data/spec/generate_spec.rb +78 -0
- data/spec/git_spec.rb +21 -0
- data/spec/helpers/assertions.rb +84 -0
- data/spec/helpers/commands.rb +42 -0
- data/spec/helpers/files.rb +56 -0
- data/spec/helpers/stubbing.rb +25 -0
- data/spec/import_spec.rb +145 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/sync_spec.rb +52 -0
- metadata +183 -0
data/spec/git_spec.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Briefcase::Commands::Git do
|
4
|
+
|
5
|
+
before do
|
6
|
+
create_dotfiles_directory
|
7
|
+
create_git_repo
|
8
|
+
end
|
9
|
+
|
10
|
+
after do
|
11
|
+
cleanup_dotfiles_directory
|
12
|
+
end
|
13
|
+
|
14
|
+
it "creates links to existing files" do
|
15
|
+
create_file(dotfiles_path + '/test.txt', 'testing git integration')
|
16
|
+
run_command("git status")
|
17
|
+
|
18
|
+
output_must_contain(/Running git status/)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
def directory_must_exist(path)
|
2
|
+
File.directory?(path).must_equal(true, "Expected directory to exist at #{path}")
|
3
|
+
end
|
4
|
+
|
5
|
+
def directory_must_not_exist(path)
|
6
|
+
File.directory?(path).must_equal(false, "Did not expect directory to exist at #{path}")
|
7
|
+
end
|
8
|
+
|
9
|
+
def file_must_exist(path)
|
10
|
+
File.file?(path).must_equal(true, "Expected file to exist at #{path}")
|
11
|
+
end
|
12
|
+
|
13
|
+
def file_must_contain(path, text)
|
14
|
+
file_must_exist(path)
|
15
|
+
# strip trailing whitespace to make it easier to use multiline strings
|
16
|
+
File.read(path).strip.must_equal(text.strip)
|
17
|
+
end
|
18
|
+
|
19
|
+
def file_must_not_match(path, regex)
|
20
|
+
file_must_exist(path)
|
21
|
+
File.read(path).strip.wont_match(regex)
|
22
|
+
end
|
23
|
+
|
24
|
+
def file_must_not_exist(path)
|
25
|
+
File.file?(path).must_equal(false, "Expected file to not exist at #{path}")
|
26
|
+
end
|
27
|
+
|
28
|
+
def symlink_must_exist(path, target)
|
29
|
+
File.exist?(path).must_equal(true, "Expected symlink to exist at #{path}")
|
30
|
+
actual = File.readlink(path)
|
31
|
+
actual.must_equal(target, "Expected symlink to #{target}, was #{actual}")
|
32
|
+
end
|
33
|
+
|
34
|
+
def symlink_must_not_exist(path)
|
35
|
+
if File.exist?(path)
|
36
|
+
File.file?(path).must_equal true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def file_must_have_moved(original_path, new_path)
|
41
|
+
file_must_exist(new_path)
|
42
|
+
birthplace = File.open(new_path) do |file|
|
43
|
+
file.read
|
44
|
+
end
|
45
|
+
birthplace.must_equal(original_path, "Expected file at #{new_path} to have been moved from #{original_path}")
|
46
|
+
end
|
47
|
+
|
48
|
+
def file_must_not_have_moved(path)
|
49
|
+
file_must_exist(path)
|
50
|
+
birthplace = File.open(path) { |file| file.read }
|
51
|
+
birthplace.must_equal(path, "Did not expect file at #{path} to have been moved from #{birthplace}")
|
52
|
+
end
|
53
|
+
|
54
|
+
def git_ignore_must_include(path)
|
55
|
+
git_ignore_path = File.join(dotfiles_path, '.gitignore')
|
56
|
+
file_must_exist(git_ignore_path)
|
57
|
+
ignore_contents = File.open(git_ignore_path) { |file| file.read }
|
58
|
+
ignore_contents.must_match %r{^#{File.basename(path)}$}
|
59
|
+
end
|
60
|
+
|
61
|
+
def secret_must_be_stored(yaml_key, key, value)
|
62
|
+
file_must_exist(secrets_path)
|
63
|
+
@secrets = YAML.load_file(secrets_path)
|
64
|
+
@secrets[yaml_key].wont_equal(nil, "Expected YAML secrets file to contain value for key '#{yaml_key}'")
|
65
|
+
@secrets[yaml_key][key].must_equal(value)
|
66
|
+
end
|
67
|
+
|
68
|
+
def array_matches_regex(array, regex)
|
69
|
+
array.any? do |element|
|
70
|
+
element.gsub(/\e\[\d+m/, '') =~ regex
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def output_must_contain(*regexes)
|
75
|
+
regexes.all? do |regex|
|
76
|
+
array_matches_regex(@output.lines, regex).must_equal(true, "Could not find #{regex} in: \n#{@output}")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def output_must_not_contain(*regexes)
|
81
|
+
regexes.any? do |regex|
|
82
|
+
array_matches_regex(@output.lines, regex).must_equal(false, "Found #{regex} in: \n#{@output}")
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "open4"
|
2
|
+
|
3
|
+
def run_command(command, expected_status=0, &block)
|
4
|
+
@output = ''
|
5
|
+
|
6
|
+
responses = []
|
7
|
+
def responses.response(regex, text)
|
8
|
+
push([regex, text])
|
9
|
+
end
|
10
|
+
|
11
|
+
if block_given?
|
12
|
+
block.call(responses)
|
13
|
+
end
|
14
|
+
|
15
|
+
ENV['BRIEFCASE_DOTFILES_PATH'] = dotfiles_path
|
16
|
+
ENV['BRIEFCASE_HOME_PATH'] = home_path
|
17
|
+
ENV['BRIEFCASE_SECRETS_PATH'] = secrets_path
|
18
|
+
ENV['BRIEFCASE_TESTING'] = 'true'
|
19
|
+
ENV['RUBYOPT'] = 'rubygems'
|
20
|
+
ENV['BRIEFCASE_EDITOR'] = File.expand_path('../bin/editor', File.dirname(__FILE__))
|
21
|
+
|
22
|
+
full_command = "./bin/briefcase #{command}"
|
23
|
+
full_command << " --trace" if ENV['BRIEFCASE_TEST_TRACE']
|
24
|
+
|
25
|
+
status = Open4.popen4(full_command) do |pid, stdin, stdout, stderr|
|
26
|
+
while output = stdout.gets() || stderr.gets()
|
27
|
+
puts output if ENV['BRIEFCASE_VERBOSE_TEST']
|
28
|
+
@output << output
|
29
|
+
responses.each do |response|
|
30
|
+
regex, text = response
|
31
|
+
stdin.write(text + "\n") if output =~ regex
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
exit_status = status.exitstatus
|
37
|
+
unless exit_status == expected_status
|
38
|
+
puts "\n" + @output + "\n" if ENV['BRIEFCASE_TEST_TRACE']
|
39
|
+
fail("Expected exist status of #{expected_status}, got #{exit_status}")
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
include FileUtils
|
3
|
+
|
4
|
+
SPEC_ROOT = "/tmp/briefcase_spec_work"
|
5
|
+
|
6
|
+
def home_path
|
7
|
+
File.expand_path('briefcase_home', SPEC_ROOT)
|
8
|
+
end
|
9
|
+
|
10
|
+
def dotfiles_path
|
11
|
+
File.expand_path('briefcase_dotfiles', SPEC_ROOT)
|
12
|
+
end
|
13
|
+
|
14
|
+
def secrets_path
|
15
|
+
File.expand_path('.briefcase_secrets', home_path)
|
16
|
+
end
|
17
|
+
|
18
|
+
def editor_responses_path
|
19
|
+
File.expand_path('briefcase_editor_responses', SPEC_ROOT)
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_home_directory
|
23
|
+
mkdir_p(home_path)
|
24
|
+
end
|
25
|
+
|
26
|
+
def cleanup_home_directory
|
27
|
+
rm_rf(home_path)
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_git_repo
|
31
|
+
mkdir_p(File.join(dotfiles_path, '.git'))
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_dotfiles_directory
|
35
|
+
mkdir_p(dotfiles_path)
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_secrets(hash={})
|
39
|
+
File.open(secrets_path, "w") do |file|
|
40
|
+
file.write(hash.to_yaml)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def cleanup_dotfiles_directory
|
45
|
+
rm_rf(dotfiles_path)
|
46
|
+
end
|
47
|
+
|
48
|
+
def create_file(path, text='')
|
49
|
+
File.open(path, "w") do |file|
|
50
|
+
file.write(text)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_trackable_file(path)
|
55
|
+
create_file(path, path)
|
56
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
def stub_editor_response(file, text)
|
2
|
+
response_file = file.gsub(/\//, '_')
|
3
|
+
mkdir_p(editor_responses_path)
|
4
|
+
File.open(File.join(editor_responses_path, response_file), 'w') do |file|
|
5
|
+
file.write(text)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def cleanup_editor_responses
|
10
|
+
rm_rf(editor_responses_path)
|
11
|
+
end
|
12
|
+
|
13
|
+
class Object
|
14
|
+
def stub(method_name, return_value=nil, &block)
|
15
|
+
(class << self; self; end).class_eval do
|
16
|
+
define_method method_name do |*args|
|
17
|
+
if block_given?
|
18
|
+
block.call(*args)
|
19
|
+
else
|
20
|
+
return_value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/spec/import_spec.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Briefcase::Commands::Import do
|
4
|
+
|
5
|
+
before do
|
6
|
+
create_home_directory
|
7
|
+
|
8
|
+
@original_path = File.join(home_path, '.test')
|
9
|
+
@destination_path = File.join(dotfiles_path, 'test')
|
10
|
+
end
|
11
|
+
|
12
|
+
after do
|
13
|
+
cleanup_home_directory
|
14
|
+
cleanup_dotfiles_directory
|
15
|
+
cleanup_editor_responses
|
16
|
+
end
|
17
|
+
|
18
|
+
it "creates a .dotfiles directory if it doesn't exist" do
|
19
|
+
create_trackable_file(@original_path)
|
20
|
+
|
21
|
+
run_command("import #{@original_path}") do |c|
|
22
|
+
c.response(/create one now?/, 'create')
|
23
|
+
end
|
24
|
+
|
25
|
+
output_must_contain(/Creating/)
|
26
|
+
output_must_contain(/Initialized/)
|
27
|
+
directory_must_exist(dotfiles_path)
|
28
|
+
|
29
|
+
directory_must_exist(File.join(dotfiles_path, '.git'))
|
30
|
+
end
|
31
|
+
|
32
|
+
it "does not create a .dotfiles directory when a users cancels" do
|
33
|
+
create_trackable_file(@original_path)
|
34
|
+
|
35
|
+
run_command("import #{@original_path}", 255) do |c|
|
36
|
+
c.response(/create one now?/, 'abort')
|
37
|
+
end
|
38
|
+
|
39
|
+
output_must_not_contain(/Creating/)
|
40
|
+
output_must_not_contain(/Initializing/)
|
41
|
+
directory_must_not_exist(dotfiles_path)
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "with an existing dotfiles directory" do
|
45
|
+
|
46
|
+
before do
|
47
|
+
create_dotfiles_directory
|
48
|
+
create_git_repo
|
49
|
+
end
|
50
|
+
|
51
|
+
after do
|
52
|
+
cleanup_dotfiles_directory
|
53
|
+
end
|
54
|
+
|
55
|
+
it "does not import a nonexistent dotfile" do
|
56
|
+
run_command("import .test", 255)
|
57
|
+
output_must_contain(/does not exist/)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "imports a dotfile" do
|
61
|
+
create_trackable_file(@original_path)
|
62
|
+
|
63
|
+
run_command("import #{@original_path}")
|
64
|
+
|
65
|
+
output_must_contain(/Importing/, /Moving/)
|
66
|
+
file_must_have_moved(@original_path, @destination_path)
|
67
|
+
symlink_must_exist(@original_path, @destination_path)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "imports a redacted dotfile" do
|
71
|
+
redacted_path = File.join(dotfiles_path, 'test.redacted')
|
72
|
+
create_file @original_path, <<-TEXT
|
73
|
+
setting: ABCDEFG
|
74
|
+
TEXT
|
75
|
+
|
76
|
+
stub_editor_response redacted_path, <<-TEXT
|
77
|
+
# Edit the file below, replacing and sensitive information to turn this:
|
78
|
+
#
|
79
|
+
# password: superSecretPassword
|
80
|
+
#
|
81
|
+
# Into:
|
82
|
+
#
|
83
|
+
# password: # briefcase(password)
|
84
|
+
#
|
85
|
+
########################################################################
|
86
|
+
setting: # briefcase(token)
|
87
|
+
TEXT
|
88
|
+
|
89
|
+
run_command("redact #{@original_path}")
|
90
|
+
|
91
|
+
output_must_contain(/Importing/, /Moving/, /Creating redacted version at/, /Storing secret value for key: token/)
|
92
|
+
secret_must_be_stored('test', 'token', 'ABCDEFG')
|
93
|
+
symlink_must_exist(@original_path, @destination_path)
|
94
|
+
file_must_not_match(redacted_path, 'replacing and sensitive information')
|
95
|
+
git_ignore_must_include(@destination_path)
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "collision handling" do
|
99
|
+
|
100
|
+
before do
|
101
|
+
@relocated_path = File.join(dotfiles_path, 'test.old.1')
|
102
|
+
create_trackable_file(@original_path)
|
103
|
+
create_trackable_file(@destination_path)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "renames an existing dotfile when importing a duplicate and instructed to replace it" do
|
107
|
+
run_command("import #{@original_path}") do |c|
|
108
|
+
c.response(/Do you want to replace it\?/, 'replace')
|
109
|
+
end
|
110
|
+
|
111
|
+
output_must_contain(/Moving/, /Symlinking/, /already exists as a dotfile/)
|
112
|
+
file_must_exist(@destination_path)
|
113
|
+
file_must_exist(@relocated_path)
|
114
|
+
symlink_must_exist(@original_path, @destination_path)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "renames an existing duplicate dotfile when importing a duplicate and instructed to replace it" do
|
118
|
+
duplicate_path = File.join(dotfiles_path, 'test.old.2')
|
119
|
+
create_trackable_file(@relocated_path)
|
120
|
+
|
121
|
+
run_command("import #{@original_path}") do |c|
|
122
|
+
c.response(/Do you want to replace it\?/, 'replace')
|
123
|
+
end
|
124
|
+
|
125
|
+
file_must_have_moved(@destination_path, duplicate_path)
|
126
|
+
file_must_not_have_moved(@relocated_path)
|
127
|
+
end
|
128
|
+
|
129
|
+
it "does not modify an existing dotfile when instructed not to" do
|
130
|
+
run_command("import #{@original_path}", 255) do |c|
|
131
|
+
c.response(/Do you want to replace it\?/, 'abort')
|
132
|
+
end
|
133
|
+
|
134
|
+
output_must_contain(/already exists as a dotfile/)
|
135
|
+
output_must_not_contain(/Moving/, /Symlinking/)
|
136
|
+
file_must_not_have_moved(@original_path)
|
137
|
+
file_must_not_have_moved(@destination_path)
|
138
|
+
file_must_not_exist(@relocated_path)
|
139
|
+
symlink_must_not_exist(@original_path)
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'minitest/spec'
|
5
|
+
# require 'turn/autorun/minitest'
|
6
|
+
require 'minitest/mock'
|
7
|
+
|
8
|
+
MiniTest::Unit.autorun
|
9
|
+
|
10
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
11
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
12
|
+
require 'briefcase'
|
13
|
+
|
14
|
+
require 'helpers/assertions'
|
15
|
+
require 'helpers/files'
|
16
|
+
require 'helpers/stubbing'
|
17
|
+
require 'helpers/commands'
|
data/spec/sync_spec.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Briefcase::Commands::Sync do
|
4
|
+
|
5
|
+
before do
|
6
|
+
create_home_directory
|
7
|
+
create_dotfiles_directory
|
8
|
+
|
9
|
+
@file_path = File.join(dotfiles_path, 'test')
|
10
|
+
@link_path = File.join(home_path, '.test')
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
cleanup_home_directory
|
15
|
+
cleanup_dotfiles_directory
|
16
|
+
end
|
17
|
+
|
18
|
+
it "creates links to existing files" do
|
19
|
+
create_file(@file_path)
|
20
|
+
|
21
|
+
run_command("sync")
|
22
|
+
|
23
|
+
output_must_contain(/Synchronizing dotfiles/, /Symlinking/)
|
24
|
+
|
25
|
+
file_must_exist(@file_path)
|
26
|
+
symlink_must_exist(@link_path, @file_path)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "does not create links to existing dynamic files" do
|
30
|
+
redacted_path = File.join(dotfiles_path, 'test.redacted')
|
31
|
+
dynamic_link_path = File.join(home_path, '.test.redacted')
|
32
|
+
create_file(redacted_path)
|
33
|
+
|
34
|
+
run_command("sync")
|
35
|
+
|
36
|
+
output_must_not_contain(/Symlinking/)
|
37
|
+
file_must_not_exist(dynamic_link_path)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "handles finding real dotfiles where symlinks would be" do
|
41
|
+
file_path = File.join(home_path, '.test')
|
42
|
+
link_path = File.join(dotfiles_path, 'test')
|
43
|
+
create_file(file_path)
|
44
|
+
create_file(link_path)
|
45
|
+
|
46
|
+
run_command("sync")
|
47
|
+
|
48
|
+
output_must_not_contain(/Symlinking/)
|
49
|
+
output_must_contain(/skipping/)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|