rubycut-babushka 0.10.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (171) hide show
  1. data/Gemfile +8 -0
  2. data/Gemfile.lock +31 -0
  3. data/README.markdown +246 -0
  4. data/Rakefile +26 -0
  5. data/bin/babushka +11 -0
  6. data/deps/babushka.rb +101 -0
  7. data/deps/dev.rb +12 -0
  8. data/deps/fhs.rb +31 -0
  9. data/deps/git.rb +29 -0
  10. data/deps/homebrew.rb +30 -0
  11. data/deps/os_x.rb +33 -0
  12. data/deps/packages.rb +22 -0
  13. data/deps/pkg_managers.rb +110 -0
  14. data/deps/ruby.rb +23 -0
  15. data/deps/rubygems.rb +24 -0
  16. data/deps/system.rb +10 -0
  17. data/deps/templates/app.rb +68 -0
  18. data/deps/templates/external.rb +12 -0
  19. data/deps/templates/installer.rb +31 -0
  20. data/deps/templates/managed.rb +105 -0
  21. data/deps/templates/ppa.rb +24 -0
  22. data/deps/templates/src.rb +42 -0
  23. data/deps/templates/tmbundle.rb +15 -0
  24. data/lib/babushka.rb +28 -0
  25. data/lib/babushka/accepts_block_for.rb +72 -0
  26. data/lib/babushka/accepts_list_for.rb +49 -0
  27. data/lib/babushka/accepts_value_for.rb +24 -0
  28. data/lib/babushka/base.rb +78 -0
  29. data/lib/babushka/bug_reporter.rb +55 -0
  30. data/lib/babushka/cmdline.rb +133 -0
  31. data/lib/babushka/cmdline/handler.rb +41 -0
  32. data/lib/babushka/cmdline/helpers.rb +127 -0
  33. data/lib/babushka/cmdline/parser.rb +69 -0
  34. data/lib/babushka/colorizer.rb +59 -0
  35. data/lib/babushka/core_patches/array.rb +171 -0
  36. data/lib/babushka/core_patches/blank.rb +22 -0
  37. data/lib/babushka/core_patches/bytes.rb +52 -0
  38. data/lib/babushka/core_patches/hash.rb +107 -0
  39. data/lib/babushka/core_patches/hashish.rb +14 -0
  40. data/lib/babushka/core_patches/integer.rb +25 -0
  41. data/lib/babushka/core_patches/io.rb +8 -0
  42. data/lib/babushka/core_patches/numeric.rb +16 -0
  43. data/lib/babushka/core_patches/object.rb +27 -0
  44. data/lib/babushka/core_patches/string.rb +116 -0
  45. data/lib/babushka/core_patches/symbol.rb +12 -0
  46. data/lib/babushka/core_patches/try.rb +15 -0
  47. data/lib/babushka/core_patches/uri.rb +24 -0
  48. data/lib/babushka/dep.rb +470 -0
  49. data/lib/babushka/dep_context.rb +18 -0
  50. data/lib/babushka/dep_definer.rb +115 -0
  51. data/lib/babushka/dep_pool.rb +49 -0
  52. data/lib/babushka/dep_runner.rb +85 -0
  53. data/lib/babushka/dsl.rb +26 -0
  54. data/lib/babushka/git_repo.rb +185 -0
  55. data/lib/babushka/helpers/git_helpers.rb +32 -0
  56. data/lib/babushka/helpers/log_helpers.rb +176 -0
  57. data/lib/babushka/helpers/path_helpers.rb +34 -0
  58. data/lib/babushka/helpers/run_helpers.rb +145 -0
  59. data/lib/babushka/helpers/shell_helpers.rb +229 -0
  60. data/lib/babushka/helpers/suggest_helpers.rb +16 -0
  61. data/lib/babushka/helpers/uri_helpers.rb +36 -0
  62. data/lib/babushka/ip.rb +160 -0
  63. data/lib/babushka/lambda_chooser.rb +40 -0
  64. data/lib/babushka/levenshtein.rb +125 -0
  65. data/lib/babushka/meta_dep.rb +65 -0
  66. data/lib/babushka/meta_dep_context.rb +15 -0
  67. data/lib/babushka/parameter.rb +143 -0
  68. data/lib/babushka/pkg_helper.rb +81 -0
  69. data/lib/babushka/pkg_helpers/apt_helper.rb +61 -0
  70. data/lib/babushka/pkg_helpers/base_helper.rb +19 -0
  71. data/lib/babushka/pkg_helpers/binpkgsrc_helper.rb +48 -0
  72. data/lib/babushka/pkg_helpers/binports_helper.rb +34 -0
  73. data/lib/babushka/pkg_helpers/brew_helper.rb +110 -0
  74. data/lib/babushka/pkg_helpers/gem_helper.rb +120 -0
  75. data/lib/babushka/pkg_helpers/macports_helper.rb +22 -0
  76. data/lib/babushka/pkg_helpers/npm_helper.rb +45 -0
  77. data/lib/babushka/pkg_helpers/pacman_helper.rb +27 -0
  78. data/lib/babushka/pkg_helpers/pip_helper.rb +45 -0
  79. data/lib/babushka/pkg_helpers/src_helper.rb +16 -0
  80. data/lib/babushka/pkg_helpers/yum_helper.rb +25 -0
  81. data/lib/babushka/popen.rb +40 -0
  82. data/lib/babushka/prompt.rb +176 -0
  83. data/lib/babushka/renderable.rb +67 -0
  84. data/lib/babushka/resource.rb +215 -0
  85. data/lib/babushka/run_reporter.rb +60 -0
  86. data/lib/babushka/shell.rb +108 -0
  87. data/lib/babushka/source.rb +216 -0
  88. data/lib/babushka/source_pool.rb +146 -0
  89. data/lib/babushka/system_definitions.rb +97 -0
  90. data/lib/babushka/system_profile.rb +210 -0
  91. data/lib/babushka/task.rb +142 -0
  92. data/lib/babushka/vars.rb +108 -0
  93. data/lib/babushka/version_of.rb +65 -0
  94. data/lib/babushka/version_str.rb +57 -0
  95. data/lib/babushka/xml_string.rb +28 -0
  96. data/lib/components.rb +82 -0
  97. data/lib/fancypath/fancypath.rb +200 -0
  98. data/lib/inkan/inkan.rb +76 -0
  99. data/spec/acceptance/acceptance.rb +43 -0
  100. data/spec/acceptance_helper.rb +113 -0
  101. data/spec/archives/Blah.app.zip +0 -0
  102. data/spec/archives/archive.tar +0 -0
  103. data/spec/archives/archive.tar.bz2 +0 -0
  104. data/spec/archives/archive.tar.gz +0 -0
  105. data/spec/archives/archive.tbz2 +0 -0
  106. data/spec/archives/archive.tgz +0 -0
  107. data/spec/archives/archive.zip +0 -0
  108. data/spec/archives/content.txt +5 -0
  109. data/spec/archives/invalid_archive +5 -0
  110. data/spec/archives/nested archive/content.txt +5 -0
  111. data/spec/archives/nested_archive.tar +0 -0
  112. data/spec/archives/really_a_gzip.zip +0 -0
  113. data/spec/archives/test-0.3.1.tgz +0 -0
  114. data/spec/archives/tgz_archive +0 -0
  115. data/spec/archives/zip_without_extension +0 -0
  116. data/spec/babushka/accepts_for_spec.rb +174 -0
  117. data/spec/babushka/accepts_for_support.rb +72 -0
  118. data/spec/babushka/cmdline/console_spec.rb +11 -0
  119. data/spec/babushka/cmdline/help_spec.rb +61 -0
  120. data/spec/babushka/cmdline/version_spec.rb +10 -0
  121. data/spec/babushka/core_patches_spec.rb +171 -0
  122. data/spec/babushka/dep_context_spec.rb +58 -0
  123. data/spec/babushka/dep_definer_spec.rb +152 -0
  124. data/spec/babushka/dep_definer_support.rb +36 -0
  125. data/spec/babushka/dep_spec.rb +567 -0
  126. data/spec/babushka/dep_support.rb +29 -0
  127. data/spec/babushka/deps_spec.rb +113 -0
  128. data/spec/babushka/gem_helper_spec.rb +90 -0
  129. data/spec/babushka/git_repo_spec.rb +396 -0
  130. data/spec/babushka/ip_spec.rb +131 -0
  131. data/spec/babushka/lambda_chooser_spec.rb +115 -0
  132. data/spec/babushka/meta_dep_definer_spec.rb +127 -0
  133. data/spec/babushka/meta_dep_wrapper_spec.rb +32 -0
  134. data/spec/babushka/parameter_spec.rb +135 -0
  135. data/spec/babushka/path_helpers_spec.rb +102 -0
  136. data/spec/babushka/prompt_spec.rb +188 -0
  137. data/spec/babushka/renderable_spec.rb +100 -0
  138. data/spec/babushka/resource_spec.rb +141 -0
  139. data/spec/babushka/run_helpers_spec.rb +26 -0
  140. data/spec/babushka/shell_helpers_spec.rb +244 -0
  141. data/spec/babushka/shell_spec.rb +19 -0
  142. data/spec/babushka/source_pool_spec.rb +320 -0
  143. data/spec/babushka/source_pool_support.rb +31 -0
  144. data/spec/babushka/source_spec.rb +382 -0
  145. data/spec/babushka/source_support.rb +17 -0
  146. data/spec/babushka/system_profile_spec.rb +61 -0
  147. data/spec/babushka/task_spec.rb +141 -0
  148. data/spec/babushka/uri_spec.rb +13 -0
  149. data/spec/babushka/vars_spec.rb +59 -0
  150. data/spec/babushka/version_of_spec.rb +110 -0
  151. data/spec/babushka/version_str_spec.rb +130 -0
  152. data/spec/babushka/version_str_support.rb +37 -0
  153. data/spec/babushka/xml_string_spec.rb +98 -0
  154. data/spec/deps/bad/broken.rb +7 -0
  155. data/spec/deps/bad/working.rb +3 -0
  156. data/spec/deps/good/meta.rb +14 -0
  157. data/spec/deps/good/test.rb +11 -0
  158. data/spec/deps/outer/deps.rb +19 -0
  159. data/spec/deps/outer/more deps.rb +11 -0
  160. data/spec/deps/params/params.rb +10 -0
  161. data/spec/fancypath/fancypath_spec.rb +272 -0
  162. data/spec/fancypath_support.rb +10 -0
  163. data/spec/inkan/inkan_spec.rb +217 -0
  164. data/spec/renderable/different_example.conf.erb +4 -0
  165. data/spec/renderable/example.conf.erb +3 -0
  166. data/spec/renderable/example.sh +6 -0
  167. data/spec/renderable/with_binding.conf.erb +4 -0
  168. data/spec/renderable/xml_example.conf.erb +8 -0
  169. data/spec/repos/remote.git.tgz +0 -0
  170. data/spec/spec_helper.rb +87 -0
  171. metadata +238 -0
@@ -0,0 +1,32 @@
1
+ module Babushka
2
+ module GitHelpers
3
+ def git uri, opts = {}, &block
4
+ repo = GitRepo.new(opts[:to] || (BuildPrefix / File.basename(uri.to_s).chomp('.git')))
5
+
6
+ if git_update(uri, repo)
7
+ repo.root.touch # so we can tell when it was last updated
8
+ block.nil? || cd(repo.path, &block)
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def git_update uri, repo
15
+ if !repo.exists?
16
+ repo.clone! uri
17
+ else
18
+ log_block "Updating #{uri}" do
19
+ if repo.repo_shell('git fetch origin').nil?
20
+ log_error " Couldn't fetch,", :newline => false
21
+ elsif !repo.behind?
22
+ log " Already up-to-date at #{repo.current_head.colorize('yellow')},", :newline => false
23
+ true
24
+ else
25
+ log " #{repo.current_head.colorize('yellow')}..#{repo.repo_shell("git rev-parse --short origin/#{repo.current_branch}").colorize('yellow')} (#{repo.repo_shell("git log -1 --pretty=format:%s origin/#{repo.current_branch}").chomp('.')}),", :newline => false
26
+ repo.reset_hard! "origin/#{repo.current_branch}"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,176 @@
1
+ # coding: utf-8
2
+
3
+ module Babushka
4
+ module LogHelpers
5
+ # Log +message+ as an error. This is a shortcut for
6
+ # log(message, :as => :error)
7
+ def log_error message, opts = {}, &block
8
+ log message, opts.merge(:as => :error), &block
9
+ end
10
+
11
+ # Log +message+ as a warning. This is a shortcut for
12
+ # log(message, :as => :warning)
13
+ def log_warn message, opts = {}, &block
14
+ log message, opts.merge(:as => :warning), &block
15
+ end
16
+
17
+ def log_verbose message, opts = {}, &block
18
+ log_error "#{caller.first}: #log_verbose has been deprecated. Instead, just use #log." # deprecated
19
+ log message, opts, &block
20
+ end
21
+
22
+ # Yield the block, writing a note to the log about it beforehand and
23
+ # afterwards.
24
+ #
25
+ # As an example, suppose we called #log_block as follows:
26
+ # log_block('Sleeping for a bit') { sleep 10 }
27
+ #
28
+ # While the block yields, the log would show
29
+ # Sleeping for a bit... (without a newline)
30
+ #
31
+ # Once the block returns, the log would be completed to show
32
+ # Sleeping for a bit... done.
33
+ def log_block message, opts = {}, &block
34
+ log "#{message}...", :newline => false
35
+ block.call.tap {|result|
36
+ log result ? ' done.' : ' failed', :as => (result ? nil : :error), :indentation => false
37
+ }
38
+ end
39
+
40
+ # Write +message+ to the debug log, prefixed with +TickChar+, returning +true+.
41
+ #
42
+ # This is used to report events that have succeeded, or items that are
43
+ # already working. For example, when the package manager reports that a
44
+ # package is already installed, that's an 'OK' that babushka can move on to
45
+ # the next job, and so +log_ok+ is used to report that fact.
46
+ def log_ok message, opts = {}, &block
47
+ log message.end_with('.'), opts.merge(:as => :ok), &block
48
+ true
49
+ end
50
+
51
+ # Write +message+ to the debug log.
52
+ #
53
+ # The message will be written to the log in the normal style, but will only
54
+ # appear on STDOUT if debug logging is enabled.
55
+ def debug message, opts = {}, &block
56
+ log message, opts.merge(:debug => !opts[:log]), &block
57
+ end
58
+
59
+ # Write +message+ to the log.
60
+ #
61
+ # By default, the log is written to STDOUT, and to ~/.babushka/logs/<dep_name>.
62
+ # The log in ~/.babushka/logs is always a full debugging log, but STDOUT
63
+ # only includes debug logging if +--debug+ was supplied on the command
64
+ # line.
65
+ #
66
+ # By default, the message is ended with a newline. You can pass
67
+ # :newline => false to prevent the newline character being added.
68
+ #
69
+ # To specify the message type, you can use :as. There are four custom
70
+ # types supported:
71
+ # :ok The message is printed in grey with +TickChar+ prepended, as
72
+ # used by +log_ok+.
73
+ # :warning The message is printed in yellow, as used by +log_warn+.
74
+ # :error The message is printed in red, as used by +log_error+.
75
+ # :stderr The message (representing STDERR output) is printed in bold,
76
+ # as used by +Shell+ for debug logging.
77
+ #
78
+ # If a block is given, the block is yielded with the indentation level
79
+ # incremented. Opening and closing braces are printed to the log to represent
80
+ # the nesting. (This is the logging style used to show the nesting during dep
81
+ # runs - so please consider other logging styles before using this one, so as
82
+ # not to visually confuse dep runs with other operations.)
83
+ def log message, opts = {}, &block
84
+ # now = Time.now
85
+ # print "#{now.to_i}.#{now.usec}: ".ljust(20) unless opts[:debug]
86
+ printable = !opts[:debug] || Base.task.opt(:debug)
87
+ Logging.print_log Logging.indentation, printable unless opts[:indentation] == false
88
+ if block_given?
89
+ Logging.print_log "#{message} {\n".colorize('grey'), printable
90
+ Logging.indent! if printable
91
+ yield.tap {|result|
92
+ Logging.undent! if printable
93
+ log Logging.closing_log_message(message, result, opts), opts
94
+ }
95
+ else
96
+ message = message.to_s.rstrip.gsub "\n", "\n#{Logging.indentation}"
97
+ message = "#{Logging::TickChar.colorize('grey')} #{message}" if opts[:as] == :ok
98
+ message = message.colorize 'red' if opts[:as] == :error
99
+ message = message.colorize 'yellow' if opts[:as] == :warning
100
+ message = message.colorize 'bold' if opts[:as] == :stderr
101
+ message = message.end_with "\n" unless opts[:newline] == false
102
+ Logging.print_log message, printable
103
+ $stdout.flush
104
+ nil
105
+ end
106
+ end
107
+ end
108
+
109
+ class Logging
110
+ extend LogHelpers
111
+
112
+ TickChar = '✓'
113
+ CrossChar = '✗'
114
+
115
+ def self.closing_log_message message, result = true, opts = {}
116
+ message = opts[:closing_status] if opts[:closing_status].is_a?(String)
117
+
118
+ if opts[:closing_status] == :status_only
119
+ '}'.colorize('grey') + ' ' + "#{result ? TickChar : CrossChar}".colorize(result ? 'green' : 'red')
120
+ elsif opts[:closing_status] == :dry_run
121
+ '}'.colorize('grey') + ' ' + "#{result ? TickChar : '~'} #{message}".colorize(result ? 'green' : 'blue')
122
+ elsif opts[:closing_status]
123
+ '}'.colorize('grey') + ' ' + "#{result ? TickChar : CrossChar} #{message}".colorize(result ? 'green' : 'red')
124
+ else
125
+ "}".colorize('grey')
126
+ end
127
+ end
128
+
129
+ def self.log_table headings, rows
130
+ all_rows = rows.map {|row|
131
+ row.map(&:to_s)
132
+ }.unshift(
133
+ headings
134
+ ).transpose.map {|col|
135
+ max_length = col.map(&:length).max
136
+ col.map {|cell| cell.ljust(max_length) }
137
+ }.transpose
138
+
139
+ [
140
+ all_rows.first.join(' | '),
141
+ all_rows.first.map {|i| '-' * i.length }.join('-+-')
142
+ ].concat(
143
+ all_rows[1..-1].map {|row| row.join(' | ') }
144
+ ).each {|row|
145
+ log row
146
+ }
147
+ end
148
+
149
+
150
+ private
151
+
152
+ def self.print_log message, printable
153
+ print message if printable
154
+ write_to_persistent_log message
155
+ end
156
+
157
+ def self.write_to_persistent_log message
158
+ Base.task.persistent_log.write message unless Base.task.persistent_log.nil?
159
+ end
160
+
161
+ def self.indentation
162
+ ' ' * indentation_level * 2
163
+ end
164
+ def self.indentation_level
165
+ @indentation_level ||= 0
166
+ end
167
+ def self.indent!
168
+ @indentation_level ||= 0
169
+ @indentation_level += 1
170
+ end
171
+ def self.undent!
172
+ @indentation_level ||= 0
173
+ @indentation_level -= 1
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,34 @@
1
+ module Babushka
2
+ module PathHelpers
3
+ def cd dir, opts = {}, &block
4
+ if dir.nil?
5
+ yield Dir.pwd.p
6
+ else
7
+ path = dir.p
8
+ shell("mkdir -p '#{path}'", :sudo => opts[:sudo]) if opts[:create] unless path.exists?
9
+ if Dir.pwd == path
10
+ yield path
11
+ else
12
+ Dir.chdir path do
13
+ debug "in dir #{dir} (#{path})" do
14
+ yield path
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ def in_dir dir, opts = {}, &block
22
+ log_error "#{caller.first}: #in_dir has been renamed to #cd." # deprecated
23
+ cd dir, opts, &block
24
+ end
25
+
26
+ def in_build_dir path = '', &block
27
+ cd Babushka::BuildPrefix / path, :create => true, &block
28
+ end
29
+
30
+ def in_download_dir path = '', &block
31
+ cd Babushka::DownloadPrefix / path, :create => true, &block
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,145 @@
1
+ module Babushka
2
+ module RunHelpers
3
+ include LogHelpers
4
+ include ShellHelpers
5
+ include PathHelpers
6
+
7
+ def hostname
8
+ shell 'hostname -f'
9
+ end
10
+
11
+ def rake cmd, &block
12
+ sudo "rake #{cmd} RAILS_ENV=#{var :app_env}", :as => var(:username), &block
13
+ end
14
+
15
+ def bundle_rake cmd, &block
16
+ cd var(:rails_root) do
17
+ shell "bundle exec rake #{cmd} --trace RAILS_ENV=#{var :app_env}", :as => var(:username), :log => true, &block
18
+ end
19
+ end
20
+
21
+ def check_file file_name, method_name
22
+ File.send(method_name, file_name).tap {|result|
23
+ log_error "#{file_name} failed #{method_name.to_s.sub(/[?!]$/, '')} check." unless result
24
+ }
25
+ end
26
+
27
+ def grep pattern, file
28
+ if (path = file.p).exists?
29
+ output = if pattern.is_a? String
30
+ path.readlines.select {|l| l[pattern] }
31
+ elsif pattern.is_a? Regexp
32
+ path.readlines.grep pattern
33
+ end
34
+ output unless output.blank?
35
+ end
36
+ end
37
+
38
+ def change_line line, replacement, filename
39
+ path = filename.p
40
+
41
+ log "Patching #{path}"
42
+ shell "cat > #{path}", :as => path.owner, :input => path.readlines.map {|l|
43
+ l.gsub(/^(\s*)(#{Regexp.escape(line)})/, "\\1# #{edited_by_babushka}\n\\1# was: \\2\n\\1#{replacement}")
44
+ }.join("")
45
+ end
46
+
47
+ def insert_into_file insert_before, path, lines, opts = {}
48
+ opts.defaults! :comment_char => '#', :insert_after => nil
49
+ nlines = lines.split("\n").length
50
+ before, after = path.p.readlines.cut {|l| l.strip == insert_before.strip }
51
+
52
+ log "Patching #{path}"
53
+ if after.empty? || (opts[:insert_after] && before.last.strip != opts[:insert_after].strip)
54
+ log_error "Couldn't find the spot to write to in #{path}."
55
+ else
56
+ shell "cat > #{path}", :as => path.owner, :sudo => !File.writable?(path), :input => [
57
+ before,
58
+ added_by_babushka(nlines).start_with(opts[:comment_char] + ' ').end_with("\n"),
59
+ lines.end_with("\n"),
60
+ after
61
+ ].join
62
+ end
63
+ end
64
+
65
+ def change_with_sed keyword, from, to, file
66
+ # Remove the incorrect setting if it's there
67
+ shell("#{sed} -ri 's/^#{keyword}\s+#{from}//' #{file}", :sudo => !File.writable?(file))
68
+ # Add the correct setting unless it's already there
69
+ grep(/^#{keyword}\s+#{to}/, file) or shell("echo '#{keyword} #{to}' >> #{file}", :sudo => !File.writable?(file))
70
+ end
71
+
72
+ def sed
73
+ Base.host.linux? ? 'sed' : 'gsed'
74
+ end
75
+
76
+ def append_to_file text, file, opts = {}
77
+ text = text.to_s
78
+ shell %Q{echo "\n# #{added_by_babushka(text.split("\n").length)}\n#{text.gsub('"', '\"')}" >> #{file}}, opts
79
+ end
80
+
81
+ def _by_babushka
82
+ "by babushka-#{VERSION} at #{Time.now}"
83
+ end
84
+ def edited_by_babushka
85
+ "This line edited #{_by_babushka}"
86
+ end
87
+ def added_by_babushka nlines
88
+ if nlines == 1
89
+ "This line added #{_by_babushka}"
90
+ else
91
+ "These #{nlines} lines added #{_by_babushka}"
92
+ end
93
+ end
94
+
95
+ def babushka_config? path
96
+ if !path.p.exists?
97
+ unmet "the config hasn't been generated yet"
98
+ elsif !grep(/Generated by babushka/, path)
99
+ unmet "the config needs to be regenerated"
100
+ else
101
+ true
102
+ end
103
+ end
104
+
105
+ def yaml path
106
+ require 'yaml'
107
+ YAML.load_file path.p
108
+ end
109
+
110
+ def render_erb erb, opts = {}
111
+ if (path = erb_path_for(erb)).nil?
112
+ log_error "If you use #render_erb within a dynamically defined dep, you have to give the full path to the erb template."
113
+ elsif !File.exists?(path) && !opts[:optional]
114
+ log_error "Couldn't find erb to render at #{path}."
115
+ elsif File.exists?(path)
116
+ Renderable.new(opts[:to]).render(path, opts.merge(:context => self)).tap {|result|
117
+ if result
118
+ log "Rendered #{opts[:to]}."
119
+ else
120
+ log_error "Couldn't render #{opts[:to]}."
121
+ end
122
+ }
123
+ end
124
+ end
125
+
126
+ def erb_path_for erb
127
+ if erb.to_s.starts_with? '/'
128
+ erb # absolute path
129
+ elsif load_path
130
+ File.dirname(load_path) / erb # directory this dep is in, plus relative path
131
+ end
132
+ end
133
+
134
+ def log_and_open message, url
135
+ log "#{message} Hit Enter to open the download page.", :newline => false
136
+ read_from_prompt ' '
137
+ shell "open #{url}"
138
+ end
139
+
140
+ def mysql cmd, username = 'root', include_password = true
141
+ password_segment = "--password='#{var :db_password}'" if include_password
142
+ shell "echo \"#{cmd.gsub('"', '\"').end_with(';')}\" | mysql -u #{username} #{password_segment}"
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,229 @@
1
+ module Babushka
2
+ module ShellHelpers
3
+ include LogHelpers
4
+
5
+ # Run +cmd+.
6
+ #
7
+ # If the command succeeds (i.e. returns 0), its output will be returned
8
+ # with a trailing newline stripped, if there was one. If the command fails
9
+ # (i.e. returns a non-zero value), nil will be returned.
10
+ #
11
+ # If a block is given, it will be yielded once the command has run, with a
12
+ # Babushka::Shell object as its sole argument. Details of the shell command
13
+ # are contained in this object - see the methods +cmd+, +ok?+, +result+,
14
+ # +stdout+, and +stderr+.
15
+ #
16
+ # Several options can be provided to alter #shell's behaviour.
17
+ # <tt>:sudo => true</tt> runs the the command as root. If the command
18
+ # contains piping or redirection, a 'sudo su' variant will be used
19
+ # instead so that the pipe receiver or redirect targets are also
20
+ # included in the sudo.
21
+ # <tt>:as => 'user'</tt> causes sudo to run as the specified user instead
22
+ # of root.
23
+ # <tt>:sudo => 'user'</tt> is a shortcut that has the same effect as
24
+ # <tt>:sudo => true, :as => 'user'</tt>
25
+ # <tt>:cd</tt> specifies the directory in which the command should run.
26
+ # If the path doesn't exist or isn't a directory, an error is raised
27
+ # unless the <tt>:create</tt> option is also set.
28
+ # To achieve the directory change, the command is rewritten to change
29
+ # directory first: `cd #{dir} && #{cmd}`.
30
+ # <tt>:create</tt> causes the directory specified by the <tt>:cd</tt>
31
+ # option to be created if it doesn't already exist.
32
+ # <tt>:input</tt> can be used to supply input for the shell command. It
33
+ # be any object that can be written to an IO with <tt>io << obj</tt>.
34
+ # When passed, it will be written to the command's stdin pipe before
35
+ # any output is read.
36
+ # <tt>:spinner => true</tt> When this option is passed, a /-\| spinner
37
+ # is printed to stdout, and advanced whenever a line is read on the
38
+ # command's stdout or stderr pipes. This is useful for monitoring the
39
+ # progress of a long-running command, like a build or an installer.
40
+ def shell *cmd, &block
41
+ shell!(*cmd, &block)
42
+ rescue Shell::ShellCommandFailed => e
43
+ if cmd.extract_options[:log]
44
+ # Don't log the error if the command already logged
45
+ elsif e.stdout.empty? && e.stderr.empty?
46
+ log "$ #{e.cmd.join(' ')}".colorize('grey') + ' ' + "#{Logging::CrossChar} shell command failed".colorize('red')
47
+ else
48
+ log "$ #{e.cmd.join(' ')}", :closing_status => 'shell command failed' do
49
+ log_error(e.stderr.empty? ? e.stdout : e.stderr)
50
+ end
51
+ end
52
+ end
53
+
54
+ # Run +cmd+, returning true if its exit code was 0.
55
+ #
56
+ # This is useful to run shell commands whose output isn't important,
57
+ # but whose exit code is. Unlike +#shell+, which logs the output of shell
58
+ # commands that exit with non-zero status, +#shell?+ runs silently.
59
+ #
60
+ # The idea is that +#shell+ is for when you're interested in the command's
61
+ # output, and +#shell?+ is for when you're interested in the exit status.
62
+ def shell? *cmd
63
+ shell(*cmd) {|s| s.stdout.chomp if s.ok? }
64
+ end
65
+
66
+ # Run +cmd+ via #shell, raising an exception if it doesn't exit
67
+ # with success.
68
+ def shell! *cmd, &block
69
+ opts = cmd.extract_options!
70
+ cmd = cmd.first if cmd.map(&:class) == [Array]
71
+
72
+ if opts[:dir] # deprecated
73
+ log_error "#{caller.first}: #shell's :dir option has been renamed to :cd."
74
+ opts[:cd] = opts[:dir]
75
+ end
76
+ if opts[:cd]
77
+ if !opts[:cd].p.exists?
78
+ if opts[:create]
79
+ opts[:cd].p.mkdir
80
+ else
81
+ raise Errno::ENOENT, opts[:cd]
82
+ end
83
+ end
84
+ end
85
+ shell_method = (opts[:as] || opts[:sudo]) ? :sudo : :shell_cmd
86
+ send shell_method, *cmd.dup.push(opts), &block
87
+ end
88
+
89
+ # This method is a shortcut for accessing the results of a shell command
90
+ # without using a block. The method itself returns the shell object that
91
+ # is yielded to the block by +#shell+.
92
+ # As an example, this shell command:
93
+ # shell('grep rails Gemfile') {|shell| shell.stdout }.empty?
94
+ # can be simplified to this:
95
+ # raw_shell('grep rails Gemfile').stdout.empty?
96
+ def raw_shell *cmd
97
+ shell(*cmd) {|s| s }
98
+ end
99
+
100
+ def failable_shell *cmd
101
+ log_error "#failable_shell has been renamed to #raw_shell." # deprecated
102
+ raw_shell(*cmd)
103
+ end
104
+
105
+ # Run +cmd+ in a separate interactive shell. This is useful for running
106
+ # commands that depend on something shell-related that was changed during
107
+ # this run, like changing the user's shell. It's also useful for running
108
+ # commands that are only valid on an interactive shell, like rvm-related
109
+ # commands.
110
+ # TODO: specs.
111
+ def login_shell cmd, opts = {}, &block
112
+ if shell('echo $SHELL').p.basename == 'zsh'
113
+ shell %Q{zsh -i -c "#{cmd.gsub('"', '\"')}"}, opts, &block
114
+ else
115
+ shell %Q{bash -l -c "#{cmd.gsub('"', '\"')}"}, opts, &block
116
+ end
117
+ end
118
+
119
+ # Run +cmd+ via `sudo`, bypassing it if possible (i.e. if we're running as
120
+ # root already, or as the user that was requested).
121
+ #
122
+ # The return behaviour and block handling of +#sudo+ are identical to that
123
+ # of +#shell+. In fact, +#sudo+ constructs a sudo command, and then uses
124
+ # +#shell+ internally to run the command.
125
+ #
126
+ # All the options that can be passed to +#shell+ are valid for +#sudo+ as
127
+ # well. The :sudo and :as options can be ommitted, though, which will cause
128
+ # the command to be run as root. Hence, this sudo call:
129
+ # sudo('ls')
130
+ # is equivalent to these two shell calls:
131
+ # shell('ls', :sudo => true)
132
+ # shell('ls', :as => 'root')
133
+ #
134
+ # In the same manner, this sudo call:
135
+ # sudo('ls', :as => 'ben')
136
+ # is equivalent to these two shell calls:
137
+ # shell('ls', :sudo => 'ben')
138
+ # shell('ls', :as => 'ben')
139
+ def sudo *cmd, &block
140
+ opts = cmd.extract_options!
141
+ env = cmd.first.is_a?(Hash) ? cmd.shift : {}
142
+
143
+ if cmd.map(&:class) != [String]
144
+ raise ArgumentError, "#sudo commands have to be passed as a single string, not splatted strings or an array, since the `sudo` is composed from strings."
145
+ end
146
+
147
+ as = opts[:as] || (opts[:sudo].is_a?(String) ? opts[:sudo] : 'root')
148
+ cmd = cmd.last
149
+
150
+ sudo_cmd = if current_username == as
151
+ cmd # Don't sudo if we're already running as the specified user.
152
+ else
153
+ if opts[:su] || cmd[' |'] || cmd[' >']
154
+ "sudo su - #{as} -c \"#{cmd.gsub('"', '\"')}\""
155
+ else
156
+ "sudo -u #{as} #{cmd}"
157
+ end
158
+ end
159
+
160
+ shell [env, sudo_cmd], opts.discard(:as, :sudo, :su), &block
161
+ end
162
+
163
+ # This method returns the full path to the specified command in the PATH,
164
+ # if that command appears anywhere in the PATH. If it doesn't, nil is
165
+ # returned.
166
+ #
167
+ # For example, on a stock OS X machine:
168
+ # which('ruby') #=> "/usr/bin/ruby"
169
+ # which('babushka') #=> nil
170
+ #
171
+ # This is roughly equivalent to using `which` or `type` on the shell.
172
+ # However, because those commands' behaviour and ouptut vary across
173
+ # platforms and shells, we instead use the logic in #cmd_dir.
174
+ def which cmd_name
175
+ matching_dir = cmd_dir(cmd_name)
176
+ File.join(matching_dir, cmd_name.to_s) unless matching_dir.nil?
177
+ end
178
+
179
+ # Return the directory from which the specified command would run if
180
+ # invoked via the PATH. If the command doesn't appear in the PATH, nil is
181
+ # returned.
182
+ #
183
+ # For example, on a stock OS X machine:
184
+ # cmd_dir('ruby') #=> "/usr/bin"
185
+ # cmd_dir('babushka') #=> nil
186
+ #
187
+ # This is a direct implementation because the behaviour and output of
188
+ # `which` and `type` vary across different platforms and shells. It's
189
+ # also faster to not shell out.
190
+ def cmd_dir cmd_name
191
+ ENV['PATH'].split(':').detect {|path|
192
+ File.executable? File.join(path, cmd_name.to_s)
193
+ }
194
+ end
195
+
196
+ # Run a shell command, logging before and after using #log_block, and using
197
+ # a spinner while the command runs.
198
+ # The first argument, +message+, is the message to print before running the
199
+ # command, and the remaining arguments are identical to those of #shell.
200
+ #
201
+ # As an example, suppose we called #log_shell as follows:
202
+ # log_shell('Sleeping for a bit', 'sleep 10')
203
+ #
204
+ # While the command runs, the log would show
205
+ # Sleeping for a bit... (without a newline)
206
+ #
207
+ # The command runs with a /-\| spinner that animates each time a line of
208
+ # output is emitted by the command. Once the command terminates, the log
209
+ # would be completed to show
210
+ # Sleeping for a bit... done.
211
+ def log_shell message, *cmd, &block
212
+ opts = cmd.extract_options!
213
+ log_block message do
214
+ shell *cmd.dup.push(opts.merge(:spinner => true)), &block
215
+ end
216
+ end
217
+
218
+ private
219
+
220
+ def shell_cmd *cmd, &block
221
+ Shell.new(*cmd).run(&block)
222
+ end
223
+
224
+ def current_username
225
+ require 'etc'
226
+ Etc.getpwuid(Process.euid).name
227
+ end
228
+ end
229
+ end