exogenesis 0.0.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,70 @@
|
|
1
|
+
require 'exogenesis/support/passenger'
|
2
|
+
|
3
|
+
class HomebrewCask < Passenger
|
4
|
+
CASKROOM = 'caskroom/cask'
|
5
|
+
|
6
|
+
register_as :homebrew_cask
|
7
|
+
needs :casks
|
8
|
+
with_emoji :beer
|
9
|
+
|
10
|
+
def up
|
11
|
+
tap_cask
|
12
|
+
install_missing_casks
|
13
|
+
end
|
14
|
+
|
15
|
+
def clean
|
16
|
+
execute 'Clean Up', 'brew cask cleanup'
|
17
|
+
end
|
18
|
+
|
19
|
+
def down
|
20
|
+
uninstall_installed_casks
|
21
|
+
untap_cask
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def install_missing_casks
|
27
|
+
(casks || []).each do |cask|
|
28
|
+
next if installed_casks.include?(cask)
|
29
|
+
install_package cask
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def uninstall_installed_casks
|
34
|
+
installed_casks.each do |cask|
|
35
|
+
uninstall_package cask
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def install_package(name)
|
40
|
+
execute "Installing #{name}", "brew cask install #{name}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def uninstall_package(name)
|
44
|
+
execute "Uninstalling #{name}", "brew cask uninstall #{name}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def tap_cask
|
48
|
+
if cask_tapped?
|
49
|
+
skip_task 'Tap Cask'
|
50
|
+
else
|
51
|
+
execute_interactive 'Tap Cask', "brew tap #{CASKROOM}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def untap_cask
|
56
|
+
execute 'Untap Cask', "brew untap #{CASKROOM}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def cask_tapped?
|
60
|
+
installed_taps.include?(CASKROOM)
|
61
|
+
end
|
62
|
+
|
63
|
+
def installed_casks
|
64
|
+
@installed_casks ||= silent_execute('brew cask list').split(/\s/)
|
65
|
+
end
|
66
|
+
|
67
|
+
def installed_taps
|
68
|
+
@installed_taps ||= silent_execute('brew tap').split(/\n/)
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'exogenesis/support/passenger'
|
2
|
+
|
3
|
+
# Install NPM and NPM packages
|
4
|
+
# REQUIRES: Homebrew (so put it after your homebrew task)
|
5
|
+
class Npm < Passenger
|
6
|
+
register_as :npm
|
7
|
+
needs :npms
|
8
|
+
with_emoji :cyclone
|
9
|
+
|
10
|
+
def up
|
11
|
+
install_node
|
12
|
+
|
13
|
+
npms.each do |package|
|
14
|
+
if installed.include? package
|
15
|
+
update_package(package)
|
16
|
+
else
|
17
|
+
install_package(package)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def install_node
|
25
|
+
if command_exists? 'npm'
|
26
|
+
skip_task 'Install Node'
|
27
|
+
else
|
28
|
+
execute 'Install Node', 'brew install node'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def installed
|
33
|
+
@installed ||= silent_execute('npm ls -g --depth=0').scan(/(\S+)@[\d.]+/).flatten
|
34
|
+
end
|
35
|
+
|
36
|
+
def update_package(package)
|
37
|
+
execute "Update #{package}", "npm update -g #{package}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def install_package(package)
|
41
|
+
execute "Install #{package}", "npm install -g #{package}"
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'exogenesis/support/passenger'
|
2
|
+
|
3
|
+
# Install Python and pip packages
|
4
|
+
# REQUIRES: Homebrew (so put it after your homebrew task)
|
5
|
+
class Python < Passenger
|
6
|
+
register_as :python
|
7
|
+
needs :pips
|
8
|
+
with_emoji :snake
|
9
|
+
|
10
|
+
def up
|
11
|
+
if command_exists? 'pip'
|
12
|
+
skip_task 'Install Python'
|
13
|
+
else
|
14
|
+
execute 'Install Python', 'brew install python'
|
15
|
+
end
|
16
|
+
|
17
|
+
execute 'Link Python', 'brew link --overwrite python' do |output|
|
18
|
+
raise TaskSkipped, 'Already linked' if output.include? 'Already linked'
|
19
|
+
end
|
20
|
+
|
21
|
+
(['pip'] + pips).each do |package|
|
22
|
+
if installed_pips.include? package
|
23
|
+
execute "Upgrade #{package}", "pip install --user --upgrade #{package}" do |output|
|
24
|
+
raise TaskSkipped, 'Already up to date' if output.include? 'already up-to-date'
|
25
|
+
end
|
26
|
+
else
|
27
|
+
execute "Install #{package}", "pip install --user #{package}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def installed_pips
|
35
|
+
@installed_pips ||= silent_execute('pip list').scan(/(\S+) \([\d.]+\)/).flatten
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'exogenesis/support/passenger'
|
2
|
+
|
3
|
+
# Manages the Ruby Version Manager Rbenv and the ruby installer ruby-build
|
4
|
+
class Rbenv < Passenger
|
5
|
+
register_as :rbenv
|
6
|
+
needs :rubies
|
7
|
+
with_emoji :cyclone
|
8
|
+
|
9
|
+
def up
|
10
|
+
if command_exists? 'rbenv'
|
11
|
+
update_rbenv
|
12
|
+
else
|
13
|
+
install_rbenv
|
14
|
+
end
|
15
|
+
rubies.each { |ruby| install_ruby ruby }
|
16
|
+
execute 'Rehash', 'rbenv rehash'
|
17
|
+
end
|
18
|
+
|
19
|
+
def down
|
20
|
+
execute_interactive 'Teardown', 'rm -r ~/.rbenv'
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def install_rbenv
|
26
|
+
execute 'Install rbenv', 'git clone https://github.com/sstephenson/rbenv.git ~/.rbenv'
|
27
|
+
execute 'Install ruby-build plugin', 'git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build'
|
28
|
+
end
|
29
|
+
|
30
|
+
def update_rbenv
|
31
|
+
execute 'Update rbenv', 'cd ~/.rbenv && git pull'
|
32
|
+
execute 'Update ruby-build', 'cd ~/.rbenv/plugins/ruby-build && git pull'
|
33
|
+
end
|
34
|
+
|
35
|
+
def install_ruby(ruby)
|
36
|
+
return if installed_versions.include? ruby
|
37
|
+
execute "Installing #{ruby}", "rbenv install #{ruby}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def installed_versions
|
41
|
+
return @installed_versions if @installed_versions
|
42
|
+
|
43
|
+
@installed_versions = []
|
44
|
+
execute 'Getting Installed Versions', 'rbenv versions' do |output|
|
45
|
+
output.scan(/^[\*]?\s*([\S]*).*$/).each do |ruby|
|
46
|
+
@installed_versions << ruby[0]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
@installed_versions
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'exogenesis/support/passenger'
|
2
|
+
|
3
|
+
# Manages the Ruby Version Manager RVM
|
4
|
+
class Rvm < Passenger
|
5
|
+
register_as :rvm
|
6
|
+
needs :rubies
|
7
|
+
with_emoji :cyclone
|
8
|
+
|
9
|
+
def up
|
10
|
+
if command_exists? 'rvm'
|
11
|
+
skip_task 'Setup'
|
12
|
+
else
|
13
|
+
execute_interactive 'Setup', '\\curl -L https://get.rvm.io | bash -s'
|
14
|
+
end
|
15
|
+
execute 'Update', 'rvm get head'
|
16
|
+
rubies.each { |ruby| install_or_update_ruby ruby }
|
17
|
+
execute 'Reload', 'rvm reload'
|
18
|
+
end
|
19
|
+
|
20
|
+
def down
|
21
|
+
execute_interactive 'Teardown', 'rvm implode'
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def install_or_update_ruby(ruby)
|
27
|
+
if installed_versions[ruby].nil?
|
28
|
+
install_ruby ruby
|
29
|
+
else
|
30
|
+
update_ruby installed_versions[ruby], ruby
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def install_ruby(ruby)
|
35
|
+
execute "Installing #{ruby}", "rvm install #{ruby} --with-gcc=gcc-4.2"
|
36
|
+
end
|
37
|
+
|
38
|
+
def update_ruby(old_ruby, new_ruby)
|
39
|
+
execute "Upgrading #{new_ruby}", "rvm upgrade #{old_ruby} #{new_ruby} --force --with-gcc=gcc-4.2" do |_output, error_output|
|
40
|
+
raise TaskSkipped, 'Already Up to Date' if error_output.include? 'are the same'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def installed_versions
|
45
|
+
return @installed_versions if @installed_versions
|
46
|
+
|
47
|
+
@installed_versions = {}
|
48
|
+
execute 'Getting Installed Versions', 'rvm list' do |output|
|
49
|
+
output.scan(/((\w+-[\w\.]+)(-(p\d+))?)/).each do |ruby|
|
50
|
+
@installed_versions[ruby[1]] = ruby[0]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@installed_versions
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'exogenesis/support/passenger'
|
2
|
+
|
3
|
+
# Manages the Vim Package Manager Vundle
|
4
|
+
class Vundle < Passenger
|
5
|
+
VUNDLE_REPO = 'git://github.com/gmarik/vundle.git'
|
6
|
+
|
7
|
+
register_as :vundle
|
8
|
+
with_emoji :gift
|
9
|
+
|
10
|
+
def up
|
11
|
+
if vundle_folder.exist?
|
12
|
+
skip_task 'Cloning Vundle'
|
13
|
+
else
|
14
|
+
mkpath(vundle_folder)
|
15
|
+
execute 'Cloning Vundle', "git clone #{VUNDLE_REPO} #{vundle_folder}"
|
16
|
+
end
|
17
|
+
execute_interactive 'Installing and Updating Vim Bundles', 'vim +BundleInstall\! +qall'
|
18
|
+
end
|
19
|
+
|
20
|
+
def down
|
21
|
+
rm_rf vim_folder
|
22
|
+
end
|
23
|
+
|
24
|
+
def clean
|
25
|
+
execute_interactive 'Cleaning', 'vim +BundleClean\! +qall'
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def vim_folder
|
31
|
+
@vim_folder ||= get_path_in_home '.vim'
|
32
|
+
end
|
33
|
+
|
34
|
+
def vundle_folder
|
35
|
+
@vundle_folder ||= get_path_in_home '.vim', 'bundle', 'vundle'
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'singleton'
|
3
|
+
require 'open3'
|
4
|
+
require 'bundler'
|
5
|
+
require 'exogenesis/support/output'
|
6
|
+
require 'exogenesis/support/task_skipped'
|
7
|
+
|
8
|
+
# Executor is a Singleton. Get the instance
|
9
|
+
# via `Executor.instance`
|
10
|
+
# If you add a public method here, please also
|
11
|
+
# add it to the `executor_double`. Same goes for
|
12
|
+
# removing public methods ;)
|
13
|
+
class Executor
|
14
|
+
include Singleton
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@output = Output.instance
|
18
|
+
end
|
19
|
+
|
20
|
+
# Start a new output section with a given
|
21
|
+
# text
|
22
|
+
def start_section(text, emoji_name)
|
23
|
+
@output.decorated_header(text, emoji_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Notify the user that you started with a
|
27
|
+
# task like 'Configure Homebrew'
|
28
|
+
def start_task(text)
|
29
|
+
@output.left(text)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Notify the user that the started task
|
33
|
+
# was successful.
|
34
|
+
def task_succeeded(further_information = '')
|
35
|
+
@output.success(further_information)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Notify the user that the started task
|
39
|
+
# failed.
|
40
|
+
def task_failed(further_information, exit_status = 1)
|
41
|
+
@output.failure(further_information)
|
42
|
+
exit(exit_status)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Notify the user that you have skipped a task
|
46
|
+
def skip_task(description, further_information = '')
|
47
|
+
@output.left(description)
|
48
|
+
@output.skipped(further_information)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Notify the user about something
|
52
|
+
# TODO: It has to be possible to give an info for a started task
|
53
|
+
def info(description, information)
|
54
|
+
@output.left(description)
|
55
|
+
@output.info(information)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Create a path
|
59
|
+
# Expects a PathName
|
60
|
+
def mkpath(path)
|
61
|
+
FileUtils.mkpath(path)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Get a path starting from the home dir
|
65
|
+
def get_path_in_home(*path)
|
66
|
+
Pathname.new(File.join(Dir.home, *path))
|
67
|
+
end
|
68
|
+
|
69
|
+
# Get a path starting from the current working directory
|
70
|
+
def get_path_in_working_directory(*path)
|
71
|
+
Pathname.pwd.join(*path)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Get an expanded PathName for a String
|
75
|
+
def get_path_for(path_as_string)
|
76
|
+
Pathname.new(File.expand_path(path_as_string))
|
77
|
+
end
|
78
|
+
|
79
|
+
# Execute a shell script. The description will
|
80
|
+
# be printed before the execution. This method
|
81
|
+
# will handle failures of the executed script.
|
82
|
+
# If you provide a block, execute will pass the output
|
83
|
+
# of the execution to it. If you raise a TaskSkipped
|
84
|
+
# Exception, it will skip the task instead of marking
|
85
|
+
# it as done (and give the text you provided to the
|
86
|
+
# exception as additional information if verbose is
|
87
|
+
# active)
|
88
|
+
def execute(description, script)
|
89
|
+
start_task description
|
90
|
+
|
91
|
+
output, error_output, exit_status = nil
|
92
|
+
Bundler.with_clean_env do
|
93
|
+
Open3.popen3(script) do |_stdin, stdout, stderr, process|
|
94
|
+
output = stdout.read
|
95
|
+
error_output = stderr.read
|
96
|
+
exit_status = process.value.exitstatus
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
yield(output, error_output) if block_given?
|
101
|
+
|
102
|
+
if exit_status > 0
|
103
|
+
task_failed("An Error occured while executing `#{script}`: \n#{output} #{error_output}", exit_status)
|
104
|
+
else
|
105
|
+
task_succeeded(output)
|
106
|
+
end
|
107
|
+
rescue TaskSkipped => e
|
108
|
+
@output.skipped(e)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Executes the script via system, so the user
|
112
|
+
# can interact with the output. Instead of
|
113
|
+
# the usual output of the other commands, it
|
114
|
+
# will draw a border around the interactive section
|
115
|
+
def execute_interactive(description, script)
|
116
|
+
@output.start_border description
|
117
|
+
system script
|
118
|
+
@output.end_border
|
119
|
+
end
|
120
|
+
|
121
|
+
# Execute without printing anything, return the result
|
122
|
+
# (Use instead of `command` to mock it)
|
123
|
+
def silent_execute(command)
|
124
|
+
Bundler.with_clean_env do
|
125
|
+
`#{command}`
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Wrapper around FileUtils' `rm_rf`
|
130
|
+
# First check if the path exists, info if it doesn't exist
|
131
|
+
# If it exists, ask if the user really wants to delete it
|
132
|
+
# path: Needs to be a PathName
|
133
|
+
def rm_rf(path)
|
134
|
+
if path.exist?
|
135
|
+
FileUtils.rm_rf(path) if ask?("Do you really want to `rm -rf #{path}?`")
|
136
|
+
else
|
137
|
+
info("Delete `#{path}`", 'Already deleted')
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Wrapper around FileUtils' `ln_s`
|
142
|
+
def ln_s(src, dest)
|
143
|
+
if dest.symlink? && dest.readlink == src
|
144
|
+
skip_task "Linking #{src}", 'Already linked'
|
145
|
+
else
|
146
|
+
start_task "Linking #{src}"
|
147
|
+
if dest.exist? || dest.symlink?
|
148
|
+
task_failed 'Target already exists'
|
149
|
+
else
|
150
|
+
FileUtils.ln_s(src, dest)
|
151
|
+
task_succeeded
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Ask the user a yes/no question
|
157
|
+
def ask?(question)
|
158
|
+
start_task("#{question} (y for yes, everything else aborts)")
|
159
|
+
STDIN.gets == "y\n"
|
160
|
+
end
|
161
|
+
|
162
|
+
# Clone a git repository
|
163
|
+
# TODO: Currently only supports Github Repos, but should work in hub-style in the future
|
164
|
+
# git_repo: A Github repo name
|
165
|
+
# target: A path object where you want to clone to
|
166
|
+
def clone_repo(git_repo, target)
|
167
|
+
execute "Cloning #{git_repo}", "git clone git@github.com:#{git_repo}.git #{target}"
|
168
|
+
end
|
169
|
+
|
170
|
+
# Pull a git repository
|
171
|
+
# git_repo: Is just used for the description
|
172
|
+
# git_repo: A Github repo name (only used for task description)
|
173
|
+
# target: A path object where you want to pull
|
174
|
+
def pull_repo(git_repo, target)
|
175
|
+
check_if_git_repo! target
|
176
|
+
execute "Pulling #{git_repo}", "cd #{target} && git pull"
|
177
|
+
end
|
178
|
+
|
179
|
+
# Check if a command exists
|
180
|
+
def command_exists?(command)
|
181
|
+
system("which #{command} &>/dev/null;")
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
# Path: A pathname object where you want to pull
|
187
|
+
def check_if_git_repo!(path)
|
188
|
+
start_task "Checking #{path.basename}"
|
189
|
+
if path.children.one? { |child| child.basename.to_s == '.git' }
|
190
|
+
task_succeeded
|
191
|
+
else
|
192
|
+
task_failed 'Not a git repository'
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|