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,120 @@
1
+ module Babushka
2
+ class GemHelper < PkgHelper
3
+ class << self
4
+ def pkg_type; :gem end
5
+ def pkg_cmd; 'gem' end
6
+ def manager_key; :gem end
7
+ def manager_dep; 'rubygems' end
8
+
9
+ def _install! pkgs, opts
10
+ pkgs.each {|pkg|
11
+ log_shell "Installing #{pkg} via #{manager_key}",
12
+ "#{pkg_cmd} install #{cmdline_spec_for pkg} #{opts}",
13
+ :sudo => should_sudo?
14
+ }
15
+ end
16
+
17
+ def gem_path_for gem_name, version = nil
18
+ unless (detected_version = has?(VersionOf(gem_name, version), :log => false)).nil?
19
+ gem_root / VersionOf(gem_name, detected_version)
20
+ end
21
+ end
22
+
23
+ def bin_path
24
+ # The directory in which the binaries from gems are found. This is
25
+ # sometimes different to where `gem` itself is running from.
26
+ env_info.val_for('EXECUTABLE DIRECTORY').p
27
+ end
28
+
29
+ def gem_root
30
+ gemdir / 'gems'
31
+ end
32
+
33
+ def gemspec_dir
34
+ gemdir / 'specifications'
35
+ end
36
+
37
+ def gemdir
38
+ env_info.val_for('INSTALLATION DIRECTORY')
39
+ end
40
+
41
+ def ruby_path
42
+ env_info.val_for('RUBY EXECUTABLE').p
43
+ end
44
+
45
+ def ruby_wrapper_path
46
+ if ruby_path.to_s['/.rvm/rubies/'].nil?
47
+ ruby_path
48
+ else
49
+ ruby_path.sub(
50
+ # /Users/ben/.rvm/rubies/ruby-1.9.2-p0/bin/ruby
51
+ /^(.*)\/\.rvm\/rubies\/([^\/]+)\/bin\/ruby/
52
+ ) {
53
+ # /Users/ben/.rvm/wrappers/ruby-1.9.2-p0/ruby
54
+ "#{$1}/.rvm/wrappers/#{$2}/ruby"
55
+ }
56
+ end
57
+ end
58
+
59
+ def ruby_arch
60
+ if RUBY_PLATFORM =~ /universal/
61
+ "universal"
62
+ elsif RUBY_PLATFORM == "java"
63
+ "java"
64
+ elsif RUBY_PLATFORM =~ /darwin/
65
+ # e.g. "/opt/ruby-enterprise/bin/ruby: Mach-O 64-bit executable x86_64"
66
+ shell("file -L '#{ruby_path}'").sub(/.* /, '')
67
+ else
68
+ Base.host.cpu_type
69
+ end
70
+ end
71
+
72
+ def ruby_binary_slug
73
+ [
74
+ (defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'),
75
+ RUBY_VERSION,
76
+ ruby_arch,
77
+ (RUBY_PLATFORM['darwin'] ? 'macosx' : RUBY_PLATFORM.sub(/^.*?-/, ''))
78
+ ].join('-')
79
+ end
80
+
81
+ def slug_for ruby
82
+ shell %Q{#{ruby} -e "require '#{Babushka::Path.lib / 'babushka'}'; puts Babushka::GemHelper.ruby_binary_slug"}
83
+ end
84
+
85
+ def should_sudo?
86
+ super || (gem_root.exists? && !gem_root.writable?)
87
+ end
88
+
89
+ def version
90
+ env_info.val_for('RUBYGEMS VERSION').to_version
91
+ end
92
+
93
+
94
+ private
95
+
96
+ def _has? pkg
97
+ versions_of(pkg).sort.reverse.detect {|version| pkg.matches? version }
98
+ end
99
+
100
+ def versions_of pkg
101
+ pkg_name = pkg.respond_to?(:name) ? pkg.name : pkg
102
+ gemspecs_for(pkg_name).select {|i|
103
+ i.p.read.val_for('s.name')[/^[\'\"\%qQ\{]*#{pkg_name}[\'\"\}]*$/]
104
+ }.map {|i|
105
+ File.basename(i).scan(/^#{pkg_name}-(.*).gemspec$/).flatten.first
106
+ }.map {|i|
107
+ i.to_version
108
+ }.sort
109
+ end
110
+
111
+ def gemspecs_for pkg_name
112
+ gemspec_dir.glob("#{pkg_name}-*.gemspec")
113
+ end
114
+
115
+ def env_info
116
+ @_cached_env_info ||= shell('gem env')
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,22 @@
1
+ module Babushka
2
+ class MacportsHelper < PkgHelper
3
+ class << self
4
+ def existing_packages
5
+ Dir.glob(prefix / "var/macports/software/*").map {|i| File.basename i }
6
+ end
7
+ def pkg_type; :port end
8
+ def pkg_cmd; 'port' end
9
+ def manager_key; :macports end
10
+
11
+ def _install! pkgs, opts
12
+ log_shell "Fetching #{pkgs.join(', ')}", "#{pkg_cmd} fetch #{pkgs.join(' ')}", :sudo => should_sudo?
13
+ super
14
+ end
15
+
16
+ private
17
+ def _has? pkg
18
+ existing_packages.include? pkg.name.split(/\s+/, 2).first
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,45 @@
1
+ module Babushka
2
+ class NpmHelper < PkgHelper
3
+ class << self
4
+ def pkg_type; :npm end
5
+ def pkg_cmd; "#{pkg_binary} --color false" end
6
+ def pkg_binary; "npm" end
7
+ def manager_key; :npm end
8
+
9
+ private
10
+
11
+ def _install! pkgs, opts
12
+ pkgs.each {|pkg|
13
+ log_shell "Installing #{pkg} via #{manager_key}",
14
+ "#{pkg_cmd} install -g #{cmdline_spec_for pkg} #{opts}",
15
+ :sudo => should_sudo?
16
+ }
17
+ end
18
+
19
+ def _has? pkg
20
+ # Some example output:
21
+ # socket.io@0.6.15 =rauchg active installed remote
22
+ shell("#{pkg_cmd} ls -g").split("\n").grep(
23
+ /^\W*#{Regexp.escape(pkg.name)}\@/
24
+ ).any? {|match|
25
+ pkg.matches? match.scan(/\@(.*)$/).flatten.first
26
+ }
27
+ end
28
+
29
+ def cmdline_spec_for pkg
30
+ if pkg.version.nil?
31
+ # e.g. 'socket.io'
32
+ "'#{pkg.name}'"
33
+ else
34
+ # e.g. 'socket.io@==0.12.0'
35
+ "'#{pkg.name}@#{pkg.version.operator}#{pkg.version.version}'"
36
+ end
37
+ end
38
+
39
+ def should_sudo?
40
+ !File.writable?(bin_path / pkg_binary)
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,27 @@
1
+ module Babushka
2
+ class PacmanHelper < PkgHelper
3
+ class << self
4
+ def pkg_type; :package end # a description of the type of package, that makes sense in the context of, e.g. "the zsh deb"
5
+ def pkg_cmd; "#{pkg_binary}" end # The full command to run the package manager
6
+ def pkg_binary; "pacman" end # The name of the binary itself
7
+ def manager_key; :pacman end # How packages should be specified in deps, e.g. "via :apt, 'zsh'"
8
+
9
+ private
10
+
11
+ # NOTE By default, Arch has sudo's `tty_tickets` option enabled. This will
12
+ # result in sudo asking for your password every single time it's run from
13
+ # Babushka. If you find this annoying, please refer to
14
+ # https://wiki.archlinux.org/index.php/Sudo#Disable_per-terminal_sudo
15
+ #
16
+ # tl;dr - Add the following line to /etc/sudoers:
17
+ # Defaults !tty_tickets
18
+ def _install! pkgs, opts
19
+ log_shell "Downloading", "#{pkg_cmd} -S --noconfirm #{pkgs.join(' ')}", :sudo => should_sudo?
20
+ end
21
+
22
+ def _has? pkg_name
23
+ raw_shell("pacman -Q #{pkg_name}").stderr !~ /not found$/
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,45 @@
1
+ module Babushka
2
+ class PipHelper < PkgHelper
3
+ class << self
4
+ def pkg_type; :pip end
5
+ def pkg_cmd; pkg_binary end
6
+ def pkg_binary; "pip" end
7
+ def manager_key; :pip end
8
+
9
+ private
10
+
11
+ def _install! pkgs, opts
12
+ pkgs.each {|pkg|
13
+ log_shell "Installing #{pkg} via #{manager_key}",
14
+ "#{pkg_cmd} install #{cmdline_spec_for pkg} #{opts}",
15
+ :sudo => should_sudo?
16
+ }
17
+ end
18
+
19
+ def _has? pkg
20
+ # Some example output:
21
+ # gunicorn==0.12.0
22
+ raw_shell("pip freeze '#{pkg.name}'").stdout.split("\n").select {|line|
23
+ line[/^#{Regexp.escape(pkg.name)}\=\=/]
24
+ }.any? {|match|
25
+ pkg.matches? match.scan(/\=\=(.*)$/).flatten.first
26
+ }
27
+ end
28
+
29
+ def cmdline_spec_for pkg
30
+ if pkg.version.nil?
31
+ # e.g. 'gunicorn'
32
+ "'#{pkg.name}'"
33
+ else
34
+ # e.g. 'gunicorn==0.12.0'
35
+ "'#{pkg.name}#{pkg.version.operator}#{pkg.version.version}'"
36
+ end
37
+ end
38
+
39
+ def should_sudo?
40
+ !File.writable?(bin_path / pkg_binary)
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,16 @@
1
+ module Babushka
2
+ class SrcHelper < PkgHelper
3
+ class << self
4
+ def pkg_type; :src end
5
+
6
+ def install_src! cmd, opts = {}
7
+ log_shell "install", cmd, :sudo => (opts[:sudo] || should_sudo?)
8
+ end
9
+
10
+ def prefix
11
+ '/usr/local'
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,25 @@
1
+ module Babushka
2
+ class YumHelper < PkgHelper
3
+ class << self
4
+ def pkg_type; :rpm end
5
+ def pkg_cmd; pkg_binary end
6
+ def pkg_binary; "yum" end
7
+ def manager_key; :yum end
8
+
9
+ private
10
+
11
+ def _has? pkg_name
12
+ # Some example output, with 'wget' installed:
13
+ # fedora-13: 'wget.x86_64 1.12-2.fc13 @fedora'
14
+ # centos-5.5: 'wget.x86_64 1.11.4-2.el5_4.1 installed'
15
+ raw_shell("#{pkg_binary} list -q '#{pkg_name}'").stdout.split("\n").select {|line|
16
+ line[/^#{Regexp.escape(pkg_name.to_s)}\.\w+\b/] # e.g. wget.x86_64
17
+ }.any? {|match|
18
+ final_word = match[/[^\s]+$/] || ''
19
+ (final_word == 'installed') || final_word.starts_with?('@')
20
+ }
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,40 @@
1
+ module Babushka
2
+ class Open3
3
+ def self.popen3 cmd, opts = {}, &block
4
+ pipes = { :in => IO::pipe, :out => IO::pipe, :err => IO::pipe }
5
+ near = { :in => pipes[:in][1], :out => pipes[:out][0], :err => pipes[:err][0] }
6
+ far = { :in => pipes[:in][0], :out => pipes[:out][1], :err => pipes[:err][1] }
7
+
8
+ pid = fork {
9
+ reopen_pipe_for :read, pipes[:in], STDIN
10
+ reopen_pipe_for :write, pipes[:out], STDOUT
11
+ reopen_pipe_for :write, pipes[:err], STDERR
12
+
13
+ Dir.chdir opts[:chdir] if opts[:chdir]
14
+ ENV.update opts[:env] if opts[:env]
15
+
16
+ exec(*cmd)
17
+ }
18
+
19
+ far.values.each(&:close)
20
+ near.values.each {|p| p.sync = true }
21
+
22
+ begin
23
+ yield near[:in], near[:out], near[:err]
24
+ Process.waitpid2(pid).last.exitstatus
25
+ ensure
26
+ near.values.each {|p| p.close unless p.closed? }
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def self.reopen_pipe_for task, pipe, io
33
+ to_close = pipe[task == :read ? 1 : 0]
34
+ to_reopen = pipe[task == :read ? 0 : 1]
35
+ to_close.close
36
+ io.reopen to_reopen
37
+ to_reopen.close
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,176 @@
1
+ module Babushka
2
+
3
+ class DefaultUnavailable < RuntimeError
4
+ def initialize message
5
+ super %{Not prompting for "#{message}" because we're running with '--defaults'.}
6
+ end
7
+ end
8
+
9
+ class PromptUnavailable < RuntimeError
10
+ def initialize message
11
+ super %{Can't prompt for "#{message}" because STDIN isn't a terminal.}
12
+ end
13
+ end
14
+
15
+ class Prompt
16
+ extend LogHelpers
17
+
18
+ module Helpers
19
+ def confirm message, opts = {}, &block
20
+ Prompt.confirm message, opts, &block
21
+ end
22
+ end
23
+
24
+ class << self
25
+ def confirm message, opts = {}, &block
26
+ prompter = (!opts[:always_ask] && respond_to?(:var)) ? :var : :get_value
27
+ answer = send(prompter, message,
28
+ :message => message,
29
+ :confirmation => true,
30
+ :default => (opts[:default] || 'y')
31
+ ).starts_with?('y')
32
+
33
+ if block.nil?
34
+ answer
35
+ elsif answer
36
+ block.call
37
+ elsif opts[:otherwise]
38
+ log opts[:otherwise]
39
+ end
40
+ end
41
+
42
+ def get_ip message, opts = {}
43
+ get_value(message, opts.merge(
44
+ :retry => "That's not an IP, like '10.0.1.1'."
45
+ )) {|value|
46
+ IP.new(value).valid?
47
+ }
48
+ end
49
+
50
+ def get_ip_range message, opts = {}
51
+ get_value(message, opts.merge(
52
+ :retry => "That's not an IP range, like '10.0.1.x'."
53
+ )) {|value|
54
+ IPRange.new(value).valid?
55
+ }
56
+ end
57
+
58
+ def get_path message, opts = {}
59
+ get_value(message, opts.merge(
60
+ :retry => "Doesn't exist, or not a directory."
61
+ )) {|value|
62
+ (value || '').p.dir?
63
+ }
64
+ end
65
+
66
+ def get_value message, opts = {}, &block
67
+ if opts[:choices] && opts[:choice_descriptions]
68
+ raise ArgumentError, "You can't use the :choices and :choice_descriptions options together."
69
+ elsif opts[:choice_descriptions]
70
+ opts[:choices] = opts[:choice_descriptions].keys
71
+ end
72
+ if opts[:choices] && opts[:choices].any? {|c| !c.is_a?(String) }
73
+ raise ArgumentError, "Choices must be passed as strings."
74
+ end
75
+ opts.defaults! :prompt => '? '
76
+ prompt_and_read_value prompt_message(message, opts), opts.merge(:ask => !Base.task.opt(:defaults)), &block
77
+ end
78
+
79
+
80
+ private
81
+
82
+ def prompt_message message, opts
83
+ if opts[:choices] && opts[:choice_descriptions].nil?
84
+ "#{message.chomp '?'} (#{opts[:choices] * ','})"
85
+ else
86
+ message.chomp '?'
87
+ end + "#{" #{opts[:dynamic] ? '{' : '['}#{opts[:default]}#{opts[:dynamic] ? '}' : ']'}" if opts[:default]}"
88
+ end
89
+
90
+ def log_choice_descriptions descriptions
91
+ unless descriptions.nil?
92
+ max_length = descriptions.keys.map(&:length).max
93
+ log "There are #{descriptions.length} choices:"
94
+ descriptions.each_pair {|choice,description|
95
+ log "#{choice.ljust(max_length)} - #{description}"
96
+ }
97
+ end
98
+ end
99
+
100
+ def prompt_and_read_value message, opts, &block
101
+ if !opts[:default] && !opts[:ask]
102
+ raise DefaultUnavailable.new(message)
103
+ elsif opts[:ask] && !$stdin.tty?
104
+ raise PromptUnavailable.new(message)
105
+ else
106
+ log_choice_descriptions opts[:choice_descriptions]
107
+ log message, :newline => false
108
+
109
+ if opts[:default] && !opts[:ask]
110
+ puts '.'
111
+ opts[:default]
112
+ else
113
+ read_value_from_prompt message, opts, &block
114
+ end
115
+ end
116
+ end
117
+
118
+ def read_value_from_prompt message, opts, &block
119
+ value = nil
120
+
121
+ value = read_from_prompt(opts[:prompt].end_with(' '), opts[:choices]).try(:chomp)
122
+ value = opts[:default].to_s if value.blank? && !(opts[:default] && opts[:default].to_s.empty?)
123
+
124
+ error = if opts[:choices] && !opts[:choices].include?(value)
125
+ "That's not a valid choice"
126
+ elsif block_given? && !yield(value)
127
+ opts[:retry] || "That wasn't valid"
128
+ elsif value.blank? && !(opts[:default] && opts[:default].empty?)
129
+ "That was blank"
130
+ elsif !opts[:confirmation] && value == 'y' && !confirm("Wait, do you mean the literal value 'y'?", :default => 'n', :always_ask => true)
131
+ "Thought so :) Hit enter for the [default]"
132
+ end
133
+
134
+ if error
135
+ log "#{error.end_with('.')} #{message}", :newline => false
136
+ read_value_from_prompt message, opts, &block
137
+ else
138
+ value
139
+ end
140
+ end
141
+
142
+ begin
143
+ require 'readline'
144
+ def read_from_prompt prompt, choices = nil
145
+ using_libedit = !Readline.respond_to?(:vi_editing_mode)
146
+ Readline.completion_append_character = nil
147
+
148
+ Readline.completion_proc = if !choices.nil?
149
+ L{|str| choices.select {|i| i.starts_with? choice } }
150
+ else
151
+ L{|str|
152
+ Dir["#{str}*"].map {|path|
153
+ path.end_with(if File.directory?(path)
154
+ using_libedit ? '' : '/' # libedit adds its own trailing slash to dirs
155
+ else
156
+ ' ' # Add a trailing space to files
157
+ end)
158
+ }
159
+ }
160
+ end
161
+
162
+ # This is required in addition to the call in bin/babushka.rb for
163
+ # interrupts to work during Readline calls.
164
+ Base.exit_on_interrupt!
165
+
166
+ Readline.readline(prompt, true).try(:strip)
167
+ end
168
+ rescue LoadError
169
+ def read_from_prompt prompt, choices = nil
170
+ print prompt
171
+ $stdin.gets.try(:strip)
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end