exogenesis 0.0.1 → 0.2.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.
- checksums.yaml +7 -0
- data/.hound.yml +117 -0
- data/.rspec +2 -0
- data/.travis.yml +16 -0
- data/CHANGELOG.md +6 -0
- data/README.md +70 -8
- data/Rakefile +6 -1
- data/exogenesis.gemspec +10 -8
- data/lib/exogenesis.rb +14 -6
- data/lib/exogenesis/passengers/dotfile.rb +28 -0
- data/lib/exogenesis/passengers/fonts.rb +44 -0
- data/lib/exogenesis/passengers/git_repo.rb +36 -0
- data/lib/exogenesis/passengers/homebrew.rb +78 -0
- data/lib/exogenesis/passengers/homebrew_cask.rb +70 -0
- data/lib/exogenesis/passengers/npm.rb +43 -0
- data/lib/exogenesis/passengers/python.rb +37 -0
- data/lib/exogenesis/passengers/rbenv.rb +51 -0
- data/lib/exogenesis/passengers/rvm.rb +55 -0
- data/lib/exogenesis/passengers/vundle.rb +37 -0
- data/lib/exogenesis/support/executor.rb +195 -0
- data/lib/exogenesis/support/output.rb +93 -0
- data/lib/exogenesis/support/passenger.rb +57 -0
- data/lib/exogenesis/support/ship.rb +30 -0
- data/lib/exogenesis/support/spacesuit.rb +31 -0
- data/lib/exogenesis/support/task_skipped.rb +9 -0
- data/lib/exogenesis/version.rb +1 -1
- data/spec/spec_helper.rb +20 -0
- data/spec/support/executor_double.rb +28 -0
- data/spec/unit/dotfile_spec.rb +40 -0
- data/spec/unit/fonts_spec.rb +54 -0
- data/spec/unit/git_repo_spec.rb +44 -0
- data/spec/unit/homebrew_cask_spec.rb +96 -0
- data/spec/unit/homebrew_spec.rb +107 -0
- data/spec/unit/npm_spec.rb +58 -0
- data/spec/unit/python_spec.rb +64 -0
- data/spec/unit/rbenv_spec.rb +52 -0
- data/spec/unit/rvm_spec.rb +79 -0
- data/spec/unit/vundle_spec.rb +62 -0
- metadata +89 -30
- data/lib/exogenesis/abstract_package_manager.rb +0 -25
- data/lib/exogenesis/dotfile.rb +0 -45
- data/lib/exogenesis/homebrew.rb +0 -45
- data/lib/exogenesis/oh-my-zsh.rb +0 -28
- data/lib/exogenesis/rvm.rb +0 -32
- data/lib/exogenesis/vundle.rb +0 -53
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'megingiard/centered_canvas'
|
3
|
+
require 'megingiard/emoji_node'
|
4
|
+
require 'megingiard/bold_node'
|
5
|
+
require 'megingiard/color_node'
|
6
|
+
require 'megingiard/node'
|
7
|
+
|
8
|
+
# Output is a Singleton. Get the instance via `Output.instance`
|
9
|
+
class Output
|
10
|
+
include Singleton
|
11
|
+
include Megingiard
|
12
|
+
|
13
|
+
WARNING = 'The output of exogenesis is not configurable, the methods for that will be removed in the next version'
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@canvas = CenteredCanvas.new(STDOUT)
|
17
|
+
@success_node = EmojiNode.new(:thumbsup)
|
18
|
+
@failure_node = EmojiNode.new(:thumbsdown)
|
19
|
+
@skipped_node = EmojiNode.new(:point_right)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.fancy
|
23
|
+
puts WARNING
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.activate_centering
|
27
|
+
puts WARNING
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.activate_decoration
|
31
|
+
puts WARNING
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.activate_utf8
|
35
|
+
puts WARNING
|
36
|
+
end
|
37
|
+
|
38
|
+
# Print the text as a decorated header
|
39
|
+
def decorated_header(text, emoji_name)
|
40
|
+
emoji = EmojiNode.new(emoji_name)
|
41
|
+
header = Node.new(emoji, ' ', BoldNode.new(text), ' ', emoji)
|
42
|
+
@canvas.draw_centered_row(header)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Print the left side of an output
|
46
|
+
def left(text)
|
47
|
+
text_with_colon = Node.new(text, ':')
|
48
|
+
left = ColorNode.new(:white, BoldNode.new(text_with_colon))
|
49
|
+
@canvas.draw_left_column(left)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Print the right side with a success message
|
53
|
+
def success(_info)
|
54
|
+
success = Node.new(' ', @success_node)
|
55
|
+
@canvas.draw_right_column(success)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Print the right side with a failure message
|
59
|
+
def failure(info)
|
60
|
+
failure = Node.new(' ', info, ' ', @failure_node)
|
61
|
+
@canvas.draw_right_column(failure)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Print the right side with a skipped message
|
65
|
+
def skipped(_info)
|
66
|
+
skipped = Node.new(' ', @skipped_node)
|
67
|
+
@canvas.draw_right_column(skipped)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Print some arbitrary information on the right
|
71
|
+
def info(info)
|
72
|
+
info_node = Node.new(' ', info)
|
73
|
+
@canvas.draw_right_column(info_node)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Draw the upper bound of a border
|
77
|
+
def start_border(info)
|
78
|
+
width = (terminal_width - 4 - info.length) / 2
|
79
|
+
puts "\u250C#{("\u2500" * width)} #{info} #{("\u2500" * width)}\u2510"
|
80
|
+
end
|
81
|
+
|
82
|
+
# Draw the lower bound of a border
|
83
|
+
def end_border
|
84
|
+
puts "\u2514#{"\u2500" * (terminal_width - 2)}\u2518"
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# Determine the width of the terminal
|
90
|
+
def terminal_width
|
91
|
+
Integer(`tput cols`)
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'exogenesis/support/executor'
|
3
|
+
|
4
|
+
class Passenger
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :passengers
|
9
|
+
|
10
|
+
def by_name(name)
|
11
|
+
passengers[name]
|
12
|
+
end
|
13
|
+
|
14
|
+
def register_as(name)
|
15
|
+
Passenger.passengers = {} if Passenger.passengers.nil?
|
16
|
+
Passenger.passengers[name.to_s] = self
|
17
|
+
end
|
18
|
+
|
19
|
+
def needs(config_name)
|
20
|
+
def_delegator :@config, config_name
|
21
|
+
end
|
22
|
+
|
23
|
+
def with_emoji(emoji_name)
|
24
|
+
@emoji_name = emoji_name
|
25
|
+
end
|
26
|
+
|
27
|
+
def emoji_name
|
28
|
+
@emoji_name || :alien
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def_delegators :@executor,
|
33
|
+
:start_section,
|
34
|
+
:start_task,
|
35
|
+
:task_succeeded,
|
36
|
+
:task_failed,
|
37
|
+
:skip_task,
|
38
|
+
:info,
|
39
|
+
:mkpath,
|
40
|
+
:get_path_in_home,
|
41
|
+
:get_path_in_working_directory,
|
42
|
+
:get_path_for,
|
43
|
+
:execute,
|
44
|
+
:execute_interactive,
|
45
|
+
:silent_execute,
|
46
|
+
:ln_s,
|
47
|
+
:rm_rf,
|
48
|
+
:ask?,
|
49
|
+
:clone_repo,
|
50
|
+
:pull_repo,
|
51
|
+
:command_exists?
|
52
|
+
|
53
|
+
def initialize(config, executor = Executor.instance)
|
54
|
+
@config = config
|
55
|
+
@executor = executor
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'exogenesis/support/spacesuit'
|
4
|
+
|
5
|
+
class Ship
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def initialize(raw_config)
|
9
|
+
config = OpenStruct.new(raw_config)
|
10
|
+
@package_managers = []
|
11
|
+
config.passengers.each do |passenger_name|
|
12
|
+
passenger = Passenger.by_name(passenger_name).new(config)
|
13
|
+
@package_managers << Spacesuit.new(passenger)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def_delegator :@package_managers, :each
|
18
|
+
|
19
|
+
def clean
|
20
|
+
each(&:clean)
|
21
|
+
end
|
22
|
+
|
23
|
+
def up
|
24
|
+
each(&:up)
|
25
|
+
end
|
26
|
+
|
27
|
+
def down
|
28
|
+
each(&:down)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# A wrapper around a passenger
|
2
|
+
class Spacesuit
|
3
|
+
def initialize(passenger)
|
4
|
+
@passenger = passenger
|
5
|
+
end
|
6
|
+
|
7
|
+
# * Installs the package manager itself
|
8
|
+
# * Installs all packages (the list has to be provided in the initialize method)
|
9
|
+
# * Updates the package manager itself and all packages
|
10
|
+
def up
|
11
|
+
wrap :up
|
12
|
+
end
|
13
|
+
|
14
|
+
# Starts a clean-up process
|
15
|
+
def clean
|
16
|
+
wrap :clean
|
17
|
+
end
|
18
|
+
|
19
|
+
# Uninstalls all packages and the package manager itself
|
20
|
+
def down
|
21
|
+
wrap :down
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def wrap(task_name)
|
27
|
+
return unless @passenger.respond_to? task_name
|
28
|
+
@passenger.start_section(@passenger.class.to_s, @passenger.class.emoji_name)
|
29
|
+
@passenger.public_send(task_name)
|
30
|
+
end
|
31
|
+
end
|
data/lib/exogenesis/version.rb
CHANGED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# require spec support files and shared behavior
|
4
|
+
Dir[File.expand_path('../support/**/*.rb', __FILE__)].each do |file|
|
5
|
+
require file
|
6
|
+
end
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.include ExecutorDouble
|
10
|
+
|
11
|
+
config.expect_with :rspec do |c|
|
12
|
+
# Allow both for now
|
13
|
+
c.syntax = [:should, :expect]
|
14
|
+
end
|
15
|
+
|
16
|
+
config.mock_with :rspec do |c|
|
17
|
+
# Allow both for now
|
18
|
+
c.syntax = [:should, :expect]
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rspec/mocks'
|
2
|
+
|
3
|
+
# This is a stub you can use in your tests that has the
|
4
|
+
# same interface as the original executor
|
5
|
+
module ExecutorDouble
|
6
|
+
def executor_double
|
7
|
+
executor = instance_double('Executor')
|
8
|
+
executor.stub(:start_section)
|
9
|
+
executor.stub(:start_task)
|
10
|
+
executor.stub(:task_succeeded)
|
11
|
+
executor.stub(:task_failed)
|
12
|
+
executor.stub(:skip_task)
|
13
|
+
executor.stub(:info)
|
14
|
+
executor.stub(:get_path_in_home)
|
15
|
+
executor.stub(:get_path_in_working_directory)
|
16
|
+
executor.stub(:get_path_for)
|
17
|
+
executor.stub(:execute)
|
18
|
+
executor.stub(:execute_interactive)
|
19
|
+
executor.stub(:silent_execute)
|
20
|
+
executor.stub(:rm_rf)
|
21
|
+
executor.stub(:ln_s)
|
22
|
+
executor.stub(:ask?)
|
23
|
+
executor.stub(:clone_repo)
|
24
|
+
executor.stub(:pull_repo)
|
25
|
+
executor.stub(:command_exists?)
|
26
|
+
executor
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'exogenesis/passengers/dotfile'
|
3
|
+
|
4
|
+
describe Dotfile do
|
5
|
+
let(:directory_name) { 'tilde' }
|
6
|
+
let(:config) { double }
|
7
|
+
before { allow(config).to receive(:directory_name).and_return(directory_name) }
|
8
|
+
|
9
|
+
let(:executor) { executor_double }
|
10
|
+
|
11
|
+
subject { Dotfile.new(config, executor) }
|
12
|
+
|
13
|
+
let(:source_directory) { double('SourceDirectory', entries: [source_vimrc]) }
|
14
|
+
let(:source_vimrc) { double('SourceVimrc', basename: 'vimrc') }
|
15
|
+
let(:target_vimrc) { double('TargetVimrc') }
|
16
|
+
|
17
|
+
before do
|
18
|
+
allow(executor).to receive(:get_path_in_working_directory)
|
19
|
+
.with(directory_name)
|
20
|
+
.and_return(source_directory)
|
21
|
+
allow(executor).to receive(:get_path_in_home)
|
22
|
+
.with('.vimrc')
|
23
|
+
.and_return(target_vimrc)
|
24
|
+
allow(source_directory).to receive(:each_child).and_yield(source_vimrc)
|
25
|
+
end
|
26
|
+
|
27
|
+
describe :up do
|
28
|
+
it 'should ln_s from the source to the destination' do
|
29
|
+
expect(executor).to receive(:ln_s).with(source_vimrc, target_vimrc)
|
30
|
+
subject.up
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe :down do
|
35
|
+
it 'should rm_rf the destination' do
|
36
|
+
expect(executor).to receive(:rm_rf).with(target_vimrc)
|
37
|
+
subject.down
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'tmpdir'
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'exogenesis/passengers/fonts'
|
4
|
+
|
5
|
+
describe Fonts do
|
6
|
+
|
7
|
+
let(:config) { double }
|
8
|
+
|
9
|
+
before {
|
10
|
+
temp_dir = Dir.mktmpdir
|
11
|
+
FileUtils.touch File.join(temp_dir, 'roboto.ttf')
|
12
|
+
FileUtils.touch File.join(temp_dir, 'bignoodle.otf')
|
13
|
+
allow(config).to receive(:fonts_path).and_return(temp_dir)
|
14
|
+
}
|
15
|
+
|
16
|
+
let(:executor) { executor_double }
|
17
|
+
subject { Fonts.new(config, executor) }
|
18
|
+
|
19
|
+
after do
|
20
|
+
FileUtils.remove_entry_secure config.fonts_path
|
21
|
+
end
|
22
|
+
|
23
|
+
describe :up do
|
24
|
+
|
25
|
+
it 'should copy fonts if they are not found' do
|
26
|
+
expect(executor).to receive(:execute)
|
27
|
+
.with('Copying roboto.ttf', "cp #{config.fonts_path}/roboto.ttf #{ENV['HOME']}/Library/Fonts/roboto.ttf")
|
28
|
+
expect(executor).to receive(:execute)
|
29
|
+
.with('Copying bignoodle.otf', "cp #{config.fonts_path}/bignoodle.otf #{ENV['HOME']}/Library/Fonts/bignoodle.otf")
|
30
|
+
subject.up
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should skip fonts if they are found' do
|
34
|
+
expect(File).to receive(:exist?).and_return(true).twice
|
35
|
+
expect(executor).to receive(:skip_task)
|
36
|
+
.with('Copying roboto.ttf', 'Already copied')
|
37
|
+
expect(executor).to receive(:skip_task)
|
38
|
+
.with('Copying bignoodle.otf', 'Already copied')
|
39
|
+
subject.up
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe :down do
|
44
|
+
|
45
|
+
it 'should remove the fonts if they are found' do
|
46
|
+
expect(executor).to receive(:rm_rf)
|
47
|
+
.with("#{ENV['HOME']}/Library/Fonts/roboto.ttf")
|
48
|
+
expect(executor).to receive(:rm_rf)
|
49
|
+
.with("#{ENV['HOME']}/Library/Fonts/bignoodle.otf")
|
50
|
+
subject.down
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'exogenesis/passengers/git_repo'
|
3
|
+
|
4
|
+
describe GitRepo do
|
5
|
+
let(:git_repo) { double('GitRepository') }
|
6
|
+
let(:target) { double('Target') }
|
7
|
+
let(:target_path) { double('TargetPath') }
|
8
|
+
let(:repos) { { git_repo => target } }
|
9
|
+
|
10
|
+
let(:config) { double }
|
11
|
+
before { allow(config).to receive(:repos).and_return(repos) }
|
12
|
+
|
13
|
+
let(:executor) { executor_double }
|
14
|
+
before { allow(executor).to receive(:get_path_for).with(target).and_return(target_path) }
|
15
|
+
|
16
|
+
subject { GitRepo.new(config, executor) }
|
17
|
+
|
18
|
+
describe :up do
|
19
|
+
context 'path exists' do
|
20
|
+
before { allow(target_path).to receive(:exist?).and_return true }
|
21
|
+
|
22
|
+
it 'should pull the repo' do
|
23
|
+
expect(executor).to receive(:pull_repo).with(git_repo, target_path)
|
24
|
+
subject.up
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'path does not exist' do
|
29
|
+
before { allow(target_path).to receive(:exist?).and_return false }
|
30
|
+
|
31
|
+
it 'should clone the repo' do
|
32
|
+
expect(executor).to receive(:clone_repo).with(git_repo, target_path)
|
33
|
+
subject.up
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe :down do
|
39
|
+
it 'should `rm_rf` the repo' do
|
40
|
+
expect(executor).to receive(:rm_rf).with(target_path)
|
41
|
+
subject.down
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'exogenesis/passengers/homebrew_cask'
|
3
|
+
|
4
|
+
describe HomebrewCask do
|
5
|
+
let(:casks) { %w(firefox cow) }
|
6
|
+
|
7
|
+
let(:config) { double }
|
8
|
+
before { allow(config).to receive(:casks).and_return(casks) }
|
9
|
+
|
10
|
+
let(:executor) { executor_double }
|
11
|
+
|
12
|
+
subject { HomebrewCask.new(config, executor) }
|
13
|
+
|
14
|
+
describe :up do
|
15
|
+
before do
|
16
|
+
allow(executor).to receive(:silent_execute)
|
17
|
+
.with('brew tap')
|
18
|
+
.and_return("caskroom/cask\n")
|
19
|
+
allow(executor).to receive(:silent_execute)
|
20
|
+
.with('brew cask list')
|
21
|
+
.and_return("vlc\nfirefox")
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'set up homebrew cask' do
|
25
|
+
context 'cask is tapped' do
|
26
|
+
it 'should not attempt to tap cask' do
|
27
|
+
expect(executor).to_not receive(:execute_interactive)
|
28
|
+
.with('Tap Cask', anything)
|
29
|
+
subject.up
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'cask is not tapped' do
|
34
|
+
before do
|
35
|
+
allow(executor).to receive(:silent_execute)
|
36
|
+
.with('brew tap')
|
37
|
+
.and_return('')
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should tap cask' do
|
41
|
+
expect(executor).to receive(:execute_interactive)
|
42
|
+
.with('Tap Cask', 'brew tap caskroom/cask')
|
43
|
+
subject.up
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'install missing casks' do
|
49
|
+
it 'should install the missing cask' do
|
50
|
+
expect(executor).to receive(:execute)
|
51
|
+
.with('Installing cow', /\Abrew cask install cow\s*\z/)
|
52
|
+
subject.up
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should not install the already installed cask' do
|
56
|
+
expect(executor).to_not receive(:execute)
|
57
|
+
.with('Installing firefox', anything)
|
58
|
+
subject.up
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
describe :clean do
|
65
|
+
it 'should cleanup the cask cache' do
|
66
|
+
expect(executor).to receive(:execute)
|
67
|
+
.with('Clean Up', 'brew cask cleanup')
|
68
|
+
subject.clean
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe :down do
|
73
|
+
before do
|
74
|
+
allow(executor).to receive(:silent_execute)
|
75
|
+
.with('brew tap')
|
76
|
+
.and_return("caskroom/cask\n")
|
77
|
+
allow(executor).to receive(:silent_execute)
|
78
|
+
.with('brew cask list')
|
79
|
+
.and_return("vlc\nfirefox")
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should uninstall installed casks' do
|
83
|
+
expect(executor).to receive(:execute)
|
84
|
+
.with('Uninstalling firefox', 'brew cask uninstall firefox')
|
85
|
+
expect(executor).to receive(:execute)
|
86
|
+
.with('Uninstalling vlc', 'brew cask uninstall vlc')
|
87
|
+
subject.down
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should untap cask' do
|
91
|
+
expect(executor).to receive(:execute)
|
92
|
+
.with('Untap Cask', 'brew untap caskroom/cask')
|
93
|
+
subject.down
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|