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