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.
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