exogenesis 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.hound.yml +117 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +16 -0
  5. data/CHANGELOG.md +6 -0
  6. data/README.md +70 -8
  7. data/Rakefile +6 -1
  8. data/exogenesis.gemspec +10 -8
  9. data/lib/exogenesis.rb +14 -6
  10. data/lib/exogenesis/passengers/dotfile.rb +28 -0
  11. data/lib/exogenesis/passengers/fonts.rb +44 -0
  12. data/lib/exogenesis/passengers/git_repo.rb +36 -0
  13. data/lib/exogenesis/passengers/homebrew.rb +78 -0
  14. data/lib/exogenesis/passengers/homebrew_cask.rb +70 -0
  15. data/lib/exogenesis/passengers/npm.rb +43 -0
  16. data/lib/exogenesis/passengers/python.rb +37 -0
  17. data/lib/exogenesis/passengers/rbenv.rb +51 -0
  18. data/lib/exogenesis/passengers/rvm.rb +55 -0
  19. data/lib/exogenesis/passengers/vundle.rb +37 -0
  20. data/lib/exogenesis/support/executor.rb +195 -0
  21. data/lib/exogenesis/support/output.rb +93 -0
  22. data/lib/exogenesis/support/passenger.rb +57 -0
  23. data/lib/exogenesis/support/ship.rb +30 -0
  24. data/lib/exogenesis/support/spacesuit.rb +31 -0
  25. data/lib/exogenesis/support/task_skipped.rb +9 -0
  26. data/lib/exogenesis/version.rb +1 -1
  27. data/spec/spec_helper.rb +20 -0
  28. data/spec/support/executor_double.rb +28 -0
  29. data/spec/unit/dotfile_spec.rb +40 -0
  30. data/spec/unit/fonts_spec.rb +54 -0
  31. data/spec/unit/git_repo_spec.rb +44 -0
  32. data/spec/unit/homebrew_cask_spec.rb +96 -0
  33. data/spec/unit/homebrew_spec.rb +107 -0
  34. data/spec/unit/npm_spec.rb +58 -0
  35. data/spec/unit/python_spec.rb +64 -0
  36. data/spec/unit/rbenv_spec.rb +52 -0
  37. data/spec/unit/rvm_spec.rb +79 -0
  38. data/spec/unit/vundle_spec.rb +62 -0
  39. metadata +89 -30
  40. data/lib/exogenesis/abstract_package_manager.rb +0 -25
  41. data/lib/exogenesis/dotfile.rb +0 -45
  42. data/lib/exogenesis/homebrew.rb +0 -45
  43. data/lib/exogenesis/oh-my-zsh.rb +0 -28
  44. data/lib/exogenesis/rvm.rb +0 -32
  45. 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
@@ -0,0 +1,9 @@
1
+ class TaskSkipped < RuntimeError
2
+ def initialize(additional_information)
3
+ @additional_information = additional_information
4
+ end
5
+
6
+ def to_s
7
+ @additional_information
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
1
  module Exogenesis
2
- VERSION = "0.0.1"
2
+ VERSION = '0.2.0'
3
3
  end
@@ -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