rubycut-babushka 0.10.6
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +8 -0
- data/Gemfile.lock +31 -0
- data/README.markdown +246 -0
- data/Rakefile +26 -0
- data/bin/babushka +11 -0
- data/deps/babushka.rb +101 -0
- data/deps/dev.rb +12 -0
- data/deps/fhs.rb +31 -0
- data/deps/git.rb +29 -0
- data/deps/homebrew.rb +30 -0
- data/deps/os_x.rb +33 -0
- data/deps/packages.rb +22 -0
- data/deps/pkg_managers.rb +110 -0
- data/deps/ruby.rb +23 -0
- data/deps/rubygems.rb +24 -0
- data/deps/system.rb +10 -0
- data/deps/templates/app.rb +68 -0
- data/deps/templates/external.rb +12 -0
- data/deps/templates/installer.rb +31 -0
- data/deps/templates/managed.rb +105 -0
- data/deps/templates/ppa.rb +24 -0
- data/deps/templates/src.rb +42 -0
- data/deps/templates/tmbundle.rb +15 -0
- data/lib/babushka.rb +28 -0
- data/lib/babushka/accepts_block_for.rb +72 -0
- data/lib/babushka/accepts_list_for.rb +49 -0
- data/lib/babushka/accepts_value_for.rb +24 -0
- data/lib/babushka/base.rb +78 -0
- data/lib/babushka/bug_reporter.rb +55 -0
- data/lib/babushka/cmdline.rb +133 -0
- data/lib/babushka/cmdline/handler.rb +41 -0
- data/lib/babushka/cmdline/helpers.rb +127 -0
- data/lib/babushka/cmdline/parser.rb +69 -0
- data/lib/babushka/colorizer.rb +59 -0
- data/lib/babushka/core_patches/array.rb +171 -0
- data/lib/babushka/core_patches/blank.rb +22 -0
- data/lib/babushka/core_patches/bytes.rb +52 -0
- data/lib/babushka/core_patches/hash.rb +107 -0
- data/lib/babushka/core_patches/hashish.rb +14 -0
- data/lib/babushka/core_patches/integer.rb +25 -0
- data/lib/babushka/core_patches/io.rb +8 -0
- data/lib/babushka/core_patches/numeric.rb +16 -0
- data/lib/babushka/core_patches/object.rb +27 -0
- data/lib/babushka/core_patches/string.rb +116 -0
- data/lib/babushka/core_patches/symbol.rb +12 -0
- data/lib/babushka/core_patches/try.rb +15 -0
- data/lib/babushka/core_patches/uri.rb +24 -0
- data/lib/babushka/dep.rb +470 -0
- data/lib/babushka/dep_context.rb +18 -0
- data/lib/babushka/dep_definer.rb +115 -0
- data/lib/babushka/dep_pool.rb +49 -0
- data/lib/babushka/dep_runner.rb +85 -0
- data/lib/babushka/dsl.rb +26 -0
- data/lib/babushka/git_repo.rb +185 -0
- data/lib/babushka/helpers/git_helpers.rb +32 -0
- data/lib/babushka/helpers/log_helpers.rb +176 -0
- data/lib/babushka/helpers/path_helpers.rb +34 -0
- data/lib/babushka/helpers/run_helpers.rb +145 -0
- data/lib/babushka/helpers/shell_helpers.rb +229 -0
- data/lib/babushka/helpers/suggest_helpers.rb +16 -0
- data/lib/babushka/helpers/uri_helpers.rb +36 -0
- data/lib/babushka/ip.rb +160 -0
- data/lib/babushka/lambda_chooser.rb +40 -0
- data/lib/babushka/levenshtein.rb +125 -0
- data/lib/babushka/meta_dep.rb +65 -0
- data/lib/babushka/meta_dep_context.rb +15 -0
- data/lib/babushka/parameter.rb +143 -0
- data/lib/babushka/pkg_helper.rb +81 -0
- data/lib/babushka/pkg_helpers/apt_helper.rb +61 -0
- data/lib/babushka/pkg_helpers/base_helper.rb +19 -0
- data/lib/babushka/pkg_helpers/binpkgsrc_helper.rb +48 -0
- data/lib/babushka/pkg_helpers/binports_helper.rb +34 -0
- data/lib/babushka/pkg_helpers/brew_helper.rb +110 -0
- data/lib/babushka/pkg_helpers/gem_helper.rb +120 -0
- data/lib/babushka/pkg_helpers/macports_helper.rb +22 -0
- data/lib/babushka/pkg_helpers/npm_helper.rb +45 -0
- data/lib/babushka/pkg_helpers/pacman_helper.rb +27 -0
- data/lib/babushka/pkg_helpers/pip_helper.rb +45 -0
- data/lib/babushka/pkg_helpers/src_helper.rb +16 -0
- data/lib/babushka/pkg_helpers/yum_helper.rb +25 -0
- data/lib/babushka/popen.rb +40 -0
- data/lib/babushka/prompt.rb +176 -0
- data/lib/babushka/renderable.rb +67 -0
- data/lib/babushka/resource.rb +215 -0
- data/lib/babushka/run_reporter.rb +60 -0
- data/lib/babushka/shell.rb +108 -0
- data/lib/babushka/source.rb +216 -0
- data/lib/babushka/source_pool.rb +146 -0
- data/lib/babushka/system_definitions.rb +97 -0
- data/lib/babushka/system_profile.rb +210 -0
- data/lib/babushka/task.rb +142 -0
- data/lib/babushka/vars.rb +108 -0
- data/lib/babushka/version_of.rb +65 -0
- data/lib/babushka/version_str.rb +57 -0
- data/lib/babushka/xml_string.rb +28 -0
- data/lib/components.rb +82 -0
- data/lib/fancypath/fancypath.rb +200 -0
- data/lib/inkan/inkan.rb +76 -0
- data/spec/acceptance/acceptance.rb +43 -0
- data/spec/acceptance_helper.rb +113 -0
- data/spec/archives/Blah.app.zip +0 -0
- data/spec/archives/archive.tar +0 -0
- data/spec/archives/archive.tar.bz2 +0 -0
- data/spec/archives/archive.tar.gz +0 -0
- data/spec/archives/archive.tbz2 +0 -0
- data/spec/archives/archive.tgz +0 -0
- data/spec/archives/archive.zip +0 -0
- data/spec/archives/content.txt +5 -0
- data/spec/archives/invalid_archive +5 -0
- data/spec/archives/nested archive/content.txt +5 -0
- data/spec/archives/nested_archive.tar +0 -0
- data/spec/archives/really_a_gzip.zip +0 -0
- data/spec/archives/test-0.3.1.tgz +0 -0
- data/spec/archives/tgz_archive +0 -0
- data/spec/archives/zip_without_extension +0 -0
- data/spec/babushka/accepts_for_spec.rb +174 -0
- data/spec/babushka/accepts_for_support.rb +72 -0
- data/spec/babushka/cmdline/console_spec.rb +11 -0
- data/spec/babushka/cmdline/help_spec.rb +61 -0
- data/spec/babushka/cmdline/version_spec.rb +10 -0
- data/spec/babushka/core_patches_spec.rb +171 -0
- data/spec/babushka/dep_context_spec.rb +58 -0
- data/spec/babushka/dep_definer_spec.rb +152 -0
- data/spec/babushka/dep_definer_support.rb +36 -0
- data/spec/babushka/dep_spec.rb +567 -0
- data/spec/babushka/dep_support.rb +29 -0
- data/spec/babushka/deps_spec.rb +113 -0
- data/spec/babushka/gem_helper_spec.rb +90 -0
- data/spec/babushka/git_repo_spec.rb +396 -0
- data/spec/babushka/ip_spec.rb +131 -0
- data/spec/babushka/lambda_chooser_spec.rb +115 -0
- data/spec/babushka/meta_dep_definer_spec.rb +127 -0
- data/spec/babushka/meta_dep_wrapper_spec.rb +32 -0
- data/spec/babushka/parameter_spec.rb +135 -0
- data/spec/babushka/path_helpers_spec.rb +102 -0
- data/spec/babushka/prompt_spec.rb +188 -0
- data/spec/babushka/renderable_spec.rb +100 -0
- data/spec/babushka/resource_spec.rb +141 -0
- data/spec/babushka/run_helpers_spec.rb +26 -0
- data/spec/babushka/shell_helpers_spec.rb +244 -0
- data/spec/babushka/shell_spec.rb +19 -0
- data/spec/babushka/source_pool_spec.rb +320 -0
- data/spec/babushka/source_pool_support.rb +31 -0
- data/spec/babushka/source_spec.rb +382 -0
- data/spec/babushka/source_support.rb +17 -0
- data/spec/babushka/system_profile_spec.rb +61 -0
- data/spec/babushka/task_spec.rb +141 -0
- data/spec/babushka/uri_spec.rb +13 -0
- data/spec/babushka/vars_spec.rb +59 -0
- data/spec/babushka/version_of_spec.rb +110 -0
- data/spec/babushka/version_str_spec.rb +130 -0
- data/spec/babushka/version_str_support.rb +37 -0
- data/spec/babushka/xml_string_spec.rb +98 -0
- data/spec/deps/bad/broken.rb +7 -0
- data/spec/deps/bad/working.rb +3 -0
- data/spec/deps/good/meta.rb +14 -0
- data/spec/deps/good/test.rb +11 -0
- data/spec/deps/outer/deps.rb +19 -0
- data/spec/deps/outer/more deps.rb +11 -0
- data/spec/deps/params/params.rb +10 -0
- data/spec/fancypath/fancypath_spec.rb +272 -0
- data/spec/fancypath_support.rb +10 -0
- data/spec/inkan/inkan_spec.rb +217 -0
- data/spec/renderable/different_example.conf.erb +4 -0
- data/spec/renderable/example.conf.erb +3 -0
- data/spec/renderable/example.sh +6 -0
- data/spec/renderable/with_binding.conf.erb +4 -0
- data/spec/renderable/xml_example.conf.erb +8 -0
- data/spec/repos/remote.git.tgz +0 -0
- data/spec/spec_helper.rb +87 -0
- 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
|