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