rubycut-babushka 0.10.6

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