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