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