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,41 @@
1
+ require 'abbrev'
2
+
3
+ module Babushka
4
+ module Cmdline
5
+
6
+ def handle name, description, &blk
7
+ Handler.add name, description, blk
8
+ end
9
+ module_function :handle
10
+
11
+ class Handler
12
+ def self.add name, description, opt_definer
13
+ Handler.new(name, description, opt_definer).tap {|handler|
14
+ (@handlers ||= []).push handler
15
+ }
16
+ end
17
+
18
+ def self.all
19
+ @handlers.reject {|h| h.name == 'global' }
20
+ end
21
+
22
+ def self.abbrev
23
+ all.map(&:name).abbrev
24
+ end
25
+
26
+ def self.for name
27
+ @handlers.detect {|h| h.name == name }
28
+ end
29
+
30
+ attr_reader :name, :description, :opt_definer, :handler
31
+
32
+ def initialize name, description, opt_definer
33
+ @name, @description, @opt_definer = name, description, (opt_definer || L{})
34
+ end
35
+
36
+ def run &handler
37
+ @handler = handler
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,127 @@
1
+ # coding: utf-8
2
+
3
+ module Babushka
4
+ module Cmdline
5
+ module_function
6
+
7
+ def fail_with message
8
+ log message if message.is_a? String
9
+ exit 1
10
+ end
11
+
12
+ module Helpers
13
+ extend LogHelpers
14
+ module_function
15
+
16
+ def print_version opts = {}
17
+ if opts[:full]
18
+ log "Babushka v#{VERSION}, (c) 2011 Ben Hoskings <ben@hoskings.net>"
19
+ else
20
+ log VERSION
21
+ end
22
+ end
23
+
24
+ def print_usage
25
+ log "\nThe gist:"
26
+ log " #{Base.program_name} <command> [options]"
27
+ log "\nAlso:"
28
+ log " #{Base.program_name} help <command> # Print command-specific usage info"
29
+ log " #{Base.program_name} <dep name> # A shortcut for 'babushka meet <dep name>'"
30
+ log " #{Base.program_name} babushka # Update babushka itself (what babushka.me/up does)"
31
+ end
32
+
33
+ def print_handlers
34
+ log "\nCommands:"
35
+ Handler.all.each {|handler|
36
+ log " #{handler.name.ljust(10)} #{handler.description}"
37
+ }
38
+ end
39
+
40
+ def print_examples
41
+ log "\nExamples:"
42
+ log " # Inspect the 'system' dep (and all its sub-deps) without touching the system.".colorize('grey')
43
+ log " #{Base.program_name} system --dry-run"
44
+ log "\n"
45
+ log " # Meet the 'fish' dep (i.e. install fish and all its dependencies).".colorize('grey')
46
+ log " #{Base.program_name} fish"
47
+ log "\n"
48
+ log " # Meet the 'user setup' dep, printing lots of debugging (including realtime".colorize('grey')
49
+ log " # shell command output).".colorize('grey')
50
+ log " #{Base.program_name} 'user setup' --debug"
51
+ end
52
+
53
+ def print_notes
54
+ log "\nCommands can be abbrev'ed, as long as they remain unique."
55
+ log " e.g. '#{Base.program_name} l' is short for '#{Base.program_name} list'."
56
+ end
57
+
58
+ def search_results_for q
59
+ YAML.load(search_webservice_for(q).body).sort_by {|i|
60
+ -i[:runs_this_week]
61
+ }.map {|i|
62
+ [
63
+ i[:name],
64
+ i[:source_uri],
65
+ ((i[:runs_this_week] && i[:runs_this_week] > 0) ? "#{i[:runs_this_week]} this week" : "#{i[:total_runs]} ever"),
66
+ ((i[:runs_this_week] && i[:runs_this_week] > 0) ? "#{(i[:success_rate_this_week] * 100).round}%" : ((i[:total_runs] && i[:total_runs] > 0) ? "#{(i[:total_success_rate] * 100).round}%" : '')),
67
+ (i[:source_uri][github_autosource_regex] ? "#{Base.program_name} #{$1}:#{"'" if i[:name][/\s/]}#{i[:name]}#{"'" if i[:name][/\s/]}" : '✣')
68
+ ]
69
+ }
70
+ end
71
+
72
+ def print_search_results search_term, results
73
+ log "The webservice knows about #{results.length} dep#{'s' unless results.length == 1} that match#{'es' if results.length == 1} '#{search_term}':"
74
+ log ""
75
+ Logging.log_table(
76
+ ['Name', 'Source', 'Runs', ' ✓', 'Command'],
77
+ results
78
+ )
79
+ if (custom_sources = results.select {|r| r[1][github_autosource_regex].nil? }.length) > 0
80
+ log ""
81
+ log "✣ #{custom_sources == 1 ? 'This source has a custom URI' : 'These sources have custom URIs'}, so babushka can't discover #{custom_sources == 1 ? 'it' : 'them'} automatically."
82
+ log " You can run #{custom_sources == 1 ? 'its' : 'their'} deps in the same way, though, once you add #{custom_sources == 1 ? 'it' : 'them'} manually:"
83
+ log " $ #{Base.program_name} sources -a <alias> <uri>"
84
+ log " $ #{Base.program_name} <alias>:<dep>"
85
+ end
86
+ end
87
+
88
+ def github_autosource_regex
89
+ /^git\:\/\/github\.com\/(.*)\/babushka-deps(\.git)?/
90
+ end
91
+
92
+ def search_webservice_for q
93
+ Net::HTTP.start('babushka.me') {|http|
94
+ http.get URI.escape("/deps/search.yaml/#{q}")
95
+ }
96
+ end
97
+
98
+ def generate_list_for to_list, filter_str
99
+ context = to_list == :deps ? Base.program_name : ':template =>'
100
+ match_str = filter_str.try(:downcase)
101
+ Base.sources.all_present.each {|source|
102
+ source.load!
103
+ }.map {|source|
104
+ [source, source.send(to_list).items]
105
+ }.map {|(source,items)|
106
+ if match_str.nil? || source.name.downcase[match_str]
107
+ [source, items]
108
+ else
109
+ [source, items.select {|item| item.name.downcase[match_str] }]
110
+ end
111
+ }.select {|(_,items)|
112
+ !items.empty?
113
+ }.sort_by {|(source,_)|
114
+ source.name
115
+ }.each {|(source,items)|
116
+ indent = (items.map {|item| "#{source.name}:#{item.name}".length }.max || 0) + 3
117
+ log ""
118
+ log "# #{source.name} (#{source.type})#{" - #{source.uri}" unless source.implicit?}"
119
+ log "# #{items.length} #{to_list.to_s.chomp(items.length == 1 ? 's' : '')}#{" matching '#{filter_str}'" unless filter_str.nil?}:"
120
+ items.each {|dep|
121
+ log "#{context} #{"'#{source.name}:#{dep.name}'".ljust(indent)}"
122
+ }
123
+ }
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,69 @@
1
+ require 'optparse'
2
+
3
+ module Babushka
4
+ module Cmdline
5
+ class Parser
6
+ include LogHelpers
7
+
8
+ attr_reader :verb, :argv, :opts
9
+
10
+ def self.for argv
11
+ if argv.empty? || (%w[-h --help] & argv).any?
12
+ new 'help', argv
13
+ elsif !Handler.abbrev.has_key?(argv.first.sub(/^--/, ''))
14
+ new 'meet', argv, :implicit_verb => true
15
+ else
16
+ new Handler.abbrev[argv.shift.sub(/^--/, '')], argv
17
+ end
18
+ end
19
+
20
+ def initialize verb, argv, parse_opts = {}
21
+ @verb, @argv, @opts, @implicit_verb = verb, argv, default_opts, parse_opts[:implicit_verb]
22
+ parse(&Handler.for('global').opt_definer)
23
+ parse(&Handler.for(verb).opt_definer)
24
+ end
25
+
26
+ def run
27
+ parser.parse! argv
28
+ Handler.for(verb).handler.call self
29
+ rescue OptionParser::ParseError => e
30
+ log_error "The #{e.args.first} option #{error_reason(e)}. #{hint}"
31
+ end
32
+
33
+ def print_usage
34
+ log parser.to_s.sub(/^Usage:.*$/, '')
35
+ end
36
+
37
+ def parse &blk
38
+ instance_eval(&blk)
39
+ end
40
+
41
+ private
42
+
43
+ def default_opts
44
+ {
45
+ :"[no_]color" => $stdout.tty?
46
+ }
47
+ end
48
+
49
+ def hint
50
+ "`babushka#{" #{verb}" unless @implicit_verb} --help` for more info."
51
+ end
52
+
53
+ def error_reason e
54
+ {
55
+ OptionParser::MissingArgument => "requires an argument"
56
+ }[e.class] || "isn't valid"
57
+ end
58
+
59
+ def opt *args, &block
60
+ opt_name = args.collapse(/^--/).first.gsub(/\s.*$/, '').gsub('-', '_').to_sym
61
+ parser.on(*args) {|arg| opts[opt_name] = arg }
62
+ end
63
+
64
+ def parser
65
+ @parser ||= OptionParser.new
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,59 @@
1
+ class String
2
+
3
+ private
4
+
5
+ class Colorizer
6
+ include Singleton
7
+
8
+ HomeOffset = 29
9
+ LightOffset = 60
10
+ BGOffset = 10
11
+ LightRegex = /^light_/
12
+ ColorRegex = /^(light_)?none|gr[ae]y|red|green|yellow|blue|pink|cyan|white$/
13
+ CtrlRegex = /^bold|underlined?|blink(ing)?|reversed?$/
14
+ ColorOffsets = {
15
+ 'none' => 0,
16
+ 'gray' => 61, 'grey' => 61,
17
+ 'red' => 2,
18
+ 'green' => 3,
19
+ 'yellow' => 4,
20
+ 'blue' => 5,
21
+ 'pink' => 6,
22
+ 'cyan' => 7,
23
+ 'white' => 8
24
+ }
25
+ CtrlOffsets = {
26
+ 'bold' => 1,
27
+ 'underline' => 4, 'underlined' => 4,
28
+ 'blink' => 5, 'blinking' => 5,
29
+ 'reverse' => 7, 'reversed' => 7
30
+ }
31
+
32
+ def colorize text, description
33
+ return text if Babushka::Base.task.opts[:"[no_]color"] == false
34
+
35
+ terms = " #{description} ".gsub(' light ', ' light_').gsub(' on ', ' on_').strip.split(/\s+/)
36
+ bg = terms.detect {|i| /on_#{ColorRegex}/ =~ i }
37
+ fg = terms.detect {|i| ColorRegex =~ i }
38
+ ctrl = terms.detect {|i| CtrlRegex =~ i }
39
+
40
+ "\e[#{"0;#{fg_for(fg)};#{bg_for(bg) || ctrl_for(ctrl)}"}m#{text}\e[0m"
41
+ end
42
+
43
+ def fg_for name
44
+ light = name.gsub!(LightRegex, '') unless name.nil?
45
+ (ColorOffsets[name] || 0) + HomeOffset + (light ? LightOffset : 0)
46
+ end
47
+
48
+ def bg_for name
49
+ # There's a hole in the table on bg=none, so we use BGOffset to the left
50
+ offset = fg_for((name || '').sub(/^on_/, ''))
51
+ offset + BGOffset unless offset == HomeOffset
52
+ end
53
+
54
+ def ctrl_for name
55
+ CtrlOffsets[name] || HomeOffset
56
+ end
57
+ end
58
+
59
+ end
@@ -0,0 +1,171 @@
1
+ class Array
2
+ # Returns true iff +other+ appears exactly at the start of +self+.
3
+ def starts_with? first, *rest
4
+ other = first.is_a?(Array) ? first : [first].concat(rest)
5
+ self[0, other.length] == other
6
+ end
7
+
8
+ # Returns true iff +other+ appears exactly at the end of +self+.
9
+ def ends_with? first, *rest
10
+ other = first.is_a?(Array) ? first : [first].concat(rest)
11
+ self[-other.length, other.length] == other
12
+ end
13
+
14
+ # Like #detect, but return the result of the block instead of the element.
15
+ def pick &block
16
+ value = nil
17
+ detect {|i| value = yield(i) }
18
+ value
19
+ end
20
+
21
+ # This is defined separately, and then aliased into place if required, so we
22
+ # can run specs against it no matter which ruby we're running against.
23
+ def local_group_by &block
24
+ inject({}) {|hsh,i|
25
+ (hsh[yield(i)] ||= []).push i
26
+ hsh
27
+ }
28
+ end
29
+ alias_method :group_by, :local_group_by unless [].respond_to?(:group_by)
30
+
31
+ # Return two arrays, the first being the portion of this array preceding the
32
+ # first element for which the block returs true, and the second being the
33
+ # remainder (or +nil+ if the block didn't return true for any elements).
34
+ def cut &block
35
+ if (cut_at = index {|i| yield i }).nil?
36
+ [self, nil]
37
+ else
38
+ [self[0...cut_at], self[cut_at..-1]]
39
+ end
40
+ end
41
+ # Return two arrays in the same manner as +cut+, but check for element
42
+ # equality against +value+ to find the point at which to cut the array.
43
+ def cut_at value
44
+ cut {|i| i == value }
45
+ end
46
+ # Return a new array containing every element from this array for which
47
+ # the block returns true.
48
+ def extract &block
49
+ dup.extract!(&block)
50
+ end
51
+ # Like +extract+, but remove the extracted values in-place before
52
+ # returning them.
53
+ def extract! &block
54
+ dup.inject [] do |extracted,i|
55
+ extracted << delete(i) if yield i
56
+ extracted
57
+ end
58
+ end
59
+ # Return a new array containing all the elements from this array that
60
+ # are neither +#nil?+ nor +#blank?+.
61
+ def squash
62
+ dup.squash!
63
+ end
64
+ # Like +squash+, but remove the +#nil?+ and +#blank?+ entries in-place.
65
+ def squash!
66
+ delete_if(&:blank?)
67
+ end
68
+
69
+ # Return a new array containing the elements that match +pattern+, with
70
+ # +pattern+ removed (or replaced via Array#sub, if +replacement+ is
71
+ # supplied).
72
+ #
73
+ # This is useful for selecting items from a list based on some label,
74
+ # removing the label at the same time. A good example is finding the current
75
+ # git branch. Given this repository:
76
+ # $ git branch
77
+ # master
78
+ # * next
79
+ # topic
80
+ # You can use +#collapse+ to retrieve the current branch like this:
81
+ # shell('git branch').split("\n").collapse(/\* /) #=> ["next"]
82
+ def collapse pattern, replacement = ''
83
+ grep(pattern).map {|i| i.sub pattern, replacement }
84
+ end
85
+
86
+ # Return a new array by converting each element in this array to a VersionOf.
87
+ def versions
88
+ map {|i| Babushka::VersionOf::Helpers.VersionOf i }
89
+ end
90
+
91
+ # Return a string describing this array as an English list. The final two
92
+ # elements are separated with 'and', and all the other elements are separated
93
+ # with commas.
94
+ #
95
+ # %w[John Paul Ringo George].to_list #=> "John, Paul, Ringo and George"
96
+ #
97
+ # A custom conjugation can be specified by passing +:conj+; if present, it
98
+ # will be used instead of 'and'.
99
+ #
100
+ # %[rain hail shine].to_list(:conj => 'or') #=> "rain, hail or shine"
101
+ #
102
+ # To add an oxford comma before the conjugation, pass +:oxford => true+.
103
+ #
104
+ # %w[hook line sinker].to_list(:oxford => true) #=> "hook, line, and sinker"
105
+ #
106
+ # If +:suffix+ is set, it will be appended along with the correct linking verb,
107
+ # i.e. 'is' for single-item lists and 'are' otherwise.
108
+ #
109
+ # %w[coffee].to_list(:suffix => 'great') => "coffee is great"
110
+ # %w[Cîroc Żubrówka].to_list(:suffix => 'vodkas') #=> "Cîroc and Żubrówka are vodkas"
111
+ #
112
+ # If +:limit+ is set, only the first +:limit+ items will be included in the
113
+ # output. If any elements were ommitted as a result, the suffix 'et al' will
114
+ # be appended to indicate there are missing elements.
115
+ #
116
+ # %w[latte espresso ristretto].to_list(:suffix => 'coffees', :limit => 2) #=> "latte, espresso et al are coffees"
117
+ #
118
+ # If +:noun+ is set in addition to +:limit+, it will be used to describe the
119
+ # length of the list after 'et al' if any items were ommitted as a result of
120
+ # the +:limit+ setting.
121
+ #
122
+ # %w[latte espresso ristretto].to_list(:limit => 2, :noun => 'coffees') #=> "latte, espresso et al - 3 coffees"
123
+ def to_list(opts = {})
124
+ items = map(&:to_s)
125
+ if opts[:limit].nil? || (length <= opts[:limit])
126
+ [
127
+ items[0..-2].squash.join(', '),
128
+ items.last
129
+ ].squash.join("#{',' if opts[:oxford]} #{opts[:conj] || 'and'} ")
130
+ else
131
+ items[0..(opts[:limit] - 1)].squash.join(', ') + ' et al' + (opts[:noun].nil? ? '' : " - #{length} #{opts[:noun]}")
132
+ end +
133
+ (opts[:suffix] ? " #{length > 1 ? 'are' : 'is'} #{opts[:suffix].strip}" : '')
134
+ end
135
+
136
+ # If the final element of the array is a +Hash+, it's removed from this array
137
+ # and returned. Otherwise, an empty hash is returned.
138
+ def extract_options!
139
+ last.is_a?(::Hash) ? pop : {}
140
+ end
141
+
142
+ # As above, without modifying the receiving object.
143
+ def extract_options
144
+ dup.extract_options!
145
+ end
146
+
147
+ # Return a new array containing the terms from this array that were
148
+ # determined to be 'similar to' +string+. A string is considered to
149
+ # be similar to another if its Levenshtein distance is less than
150
+ # either the string's length minus one, or one fifth is length plus
151
+ # two, whichever is less.
152
+ #
153
+ # word length 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 …
154
+ # typos allowed 0 0 1 2 3 3 3 3 3 4 4 4 4 4 5 …
155
+ #
156
+ # This means that:
157
+ # - a little over one fifth of strings longer than 4 characters can be misspelt;
158
+ # - strings 3 or 4 characters long can have 1 or 2 misspelt characters respectively;
159
+ # - strings 1 or 2 characters long must be spelt correctly.
160
+ def similar_to string
161
+ map {|term|
162
+ [term, Babushka::Levenshtein.distance(term, string)]
163
+ }.select {|(i, similarity)|
164
+ similarity <= [i.length - 2, (i.length / 5) + 2].min
165
+ }.sort_by {|(_, similarity)|
166
+ similarity
167
+ }.map {|(i, _)|
168
+ i
169
+ }
170
+ end
171
+ end