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.
- 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,16 @@
|
|
|
1
|
+
module Babushka
|
|
2
|
+
module SuggestHelpers
|
|
3
|
+
def suggest_value_for typo, choices
|
|
4
|
+
if (possible_matches = choices.similar_to typo.to_s).empty?
|
|
5
|
+
nil # nothing to suggest
|
|
6
|
+
elsif possible_matches.length == 1
|
|
7
|
+
Prompt.confirm "#{"Did you mean".colorize('grey')} '#{possible_matches.first}'#{"?".colorize('grey')}" do
|
|
8
|
+
possible_matches.first
|
|
9
|
+
end or false
|
|
10
|
+
else
|
|
11
|
+
log "Similar: #{possible_matches.map {|d| "'#{d}'" }.join(', ')}"
|
|
12
|
+
Prompt.get_value("Did you mean any of those".colorize('grey'), :default => possible_matches.first)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Babushka
|
|
2
|
+
module UriHelpers
|
|
3
|
+
|
|
4
|
+
def setup_source_uris
|
|
5
|
+
parse_uris
|
|
6
|
+
requires_when_unmet(@uris.map(&:scheme).uniq & %w[ git ])
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def parse_uris
|
|
10
|
+
@uris = source.map(&uri_processor(:escape)).map(&uri_processor(:parse))
|
|
11
|
+
@extra_uris = extra_source.map(&uri_processor(:escape)).map(&uri_processor(:parse)) if respond_to?(:extra_source)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def uri_processor(method_name)
|
|
15
|
+
L{|uri| URI.send(method_name, uri.respond_to?(:call) ? uri.call : uri.to_s) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def process_sources &block
|
|
19
|
+
@extra_uris.each {|uri| handle_source uri } unless @extra_uris.nil?
|
|
20
|
+
@uris.all? {|uri| handle_source uri, &block } unless @uris.nil?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def handle_source uri, &block
|
|
24
|
+
uri = uri_processor(:parse).call(uri) unless uri.is_a?(URI)
|
|
25
|
+
case uri.scheme
|
|
26
|
+
when 'git'
|
|
27
|
+
git uri, &block
|
|
28
|
+
when 'http', 'https', 'ftp', nil # We let `curl` work out the protocol if it's nil.
|
|
29
|
+
Resource.extract uri, &block
|
|
30
|
+
else
|
|
31
|
+
log_error "Babushka can't handle #{uri.scheme}:// URLs yet. But it can if you write a patch! :)"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/babushka/ip.rb
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
module Babushka
|
|
2
|
+
class IP
|
|
3
|
+
attr_reader :bytes
|
|
4
|
+
|
|
5
|
+
def initialize input
|
|
6
|
+
@bytes = sanitize input
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def to_s
|
|
10
|
+
bytes.join '.'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def == other
|
|
14
|
+
bytes == other.bytes
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Returns whether this IP should be considered a valid one for a client to be using.
|
|
18
|
+
def valid?
|
|
19
|
+
[:public, :private, :loopback].include? describe
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def next
|
|
23
|
+
offset_by(1)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def prev
|
|
27
|
+
offset_by(-1)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def sanitize input
|
|
33
|
+
if input.is_a? IP
|
|
34
|
+
input.bytes.dup
|
|
35
|
+
elsif input.is_a? Array
|
|
36
|
+
input.select {|i| (0..255).include? i }
|
|
37
|
+
else
|
|
38
|
+
parse_and_sanitize input do |str,val|
|
|
39
|
+
val if ((1..255) === val) || (val == 0 && str == '0')
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def parse_and_sanitize input, &block
|
|
45
|
+
parts = input.strip.split('.')
|
|
46
|
+
parts.zip(
|
|
47
|
+
parts.map(&:to_i)
|
|
48
|
+
).map {|(str,val)|
|
|
49
|
+
yield str, val
|
|
50
|
+
}.compact
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def offset_by offset
|
|
54
|
+
IP.new bytes.reverse.inject([offset]) {|acc,byte|
|
|
55
|
+
carry, next_byte = (byte + acc.pop).divmod(256)
|
|
56
|
+
acc.push next_byte
|
|
57
|
+
acc.push carry
|
|
58
|
+
}[0...4].reverse.join('.')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Returns a symbol describing the class of IP address +self+ represents, if any.
|
|
62
|
+
#
|
|
63
|
+
# Examples:
|
|
64
|
+
#
|
|
65
|
+
# "Hello world!".valid_ip? #=> false
|
|
66
|
+
# "192.168.".valid_ip? #=> false
|
|
67
|
+
# "127.0.0.1".valid_ip? #=> :loopback
|
|
68
|
+
# "172.24.137.6".valid_ip? #=> :private
|
|
69
|
+
# "169.254.1.142".valid_ip? #=> :self_assigned
|
|
70
|
+
# "72.9.108.122".valid_ip? #=> :public
|
|
71
|
+
def describe
|
|
72
|
+
if bytes.length != 4
|
|
73
|
+
false
|
|
74
|
+
elsif bytes.starts_with? 0 # Source hosts on "this" network
|
|
75
|
+
:reserved
|
|
76
|
+
elsif bytes.starts_with? 127 # Loopback network; RFC1700
|
|
77
|
+
:loopback
|
|
78
|
+
elsif bytes.starts_with? 10 # Class-A private; RFC1918
|
|
79
|
+
:private
|
|
80
|
+
elsif bytes.starts_with?(172) && ((16..31) === bytes[1]) # Class-B private; RFC1918
|
|
81
|
+
:private
|
|
82
|
+
elsif bytes.starts_with? 169, 254 # Link-local range; RFC3330/3927
|
|
83
|
+
[0, 255].include?(bytes[2]) ? :reserved : :self_assigned
|
|
84
|
+
elsif bytes.starts_with? 192, 0, 2 # TEST-NET - used as example.com IP
|
|
85
|
+
:reserved
|
|
86
|
+
elsif bytes.starts_with? 192, 88, 99 # 6-to-4 relay anycast; RFC3068
|
|
87
|
+
:reserved
|
|
88
|
+
elsif bytes.starts_with? 192, 168 # Class-C private; RFC1918
|
|
89
|
+
:private
|
|
90
|
+
elsif bytes.starts_with? 198, 18 # Benchmarking; RFC2544
|
|
91
|
+
:reserved
|
|
92
|
+
else
|
|
93
|
+
:public
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class IPRange < IP
|
|
99
|
+
def valid?
|
|
100
|
+
!bytes.empty?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def padded_bytes
|
|
104
|
+
bytes.concat(['x'] * (4 - bytes.length))
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def ip_for address_part
|
|
108
|
+
IP.new padded_bytes.zip(
|
|
109
|
+
IPTail.new(address_part).padded_bytes
|
|
110
|
+
).map {|(network, address)|
|
|
111
|
+
[network, address, 0].detect {|i| i != 'x' }
|
|
112
|
+
}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def first
|
|
116
|
+
ip_for 'x.0.0.1'
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def last
|
|
120
|
+
ip_for 'x.255.255.255'
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def subnet
|
|
124
|
+
padded_bytes.map {|byte|
|
|
125
|
+
byte == 'x' ? '0' : '255'
|
|
126
|
+
}.join('.')
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def broadcast
|
|
130
|
+
padded_bytes.map {|byte|
|
|
131
|
+
byte == 'x' ? '255' : byte
|
|
132
|
+
}.join('.')
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
def sanitize input
|
|
137
|
+
if /^\d+(\.\d+)*(\.x)+$/ !~ input
|
|
138
|
+
[]
|
|
139
|
+
else
|
|
140
|
+
parse_and_sanitize input.gsub(/x(\.x)*$/, 'x') do |str,val|
|
|
141
|
+
if ((1..255) === val) || (val == 0 && str == '0')
|
|
142
|
+
val
|
|
143
|
+
elsif str == 'x'
|
|
144
|
+
str
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
class IPTail < IPRange
|
|
152
|
+
def padded_bytes
|
|
153
|
+
(['x'] * (4 - bytes.length)).concat bytes
|
|
154
|
+
end
|
|
155
|
+
private
|
|
156
|
+
def sanitize input
|
|
157
|
+
super(input.split('.').reverse.join('.')).reverse
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Babushka
|
|
2
|
+
class LambdaChooser
|
|
3
|
+
|
|
4
|
+
attr_reader :owner
|
|
5
|
+
|
|
6
|
+
def var(name, opts = {}) owner.var(name, opts) end
|
|
7
|
+
|
|
8
|
+
def initialize owner, *possible_choices, &block
|
|
9
|
+
raise ArgumentError, "You can't use :otherwise as a choice name, because it's reserved." if possible_choices.include?(:otherwise)
|
|
10
|
+
@owner = owner
|
|
11
|
+
@possible_choices = possible_choices.push(:otherwise)
|
|
12
|
+
@block = block
|
|
13
|
+
@results = {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def choose choices, method_name = nil
|
|
17
|
+
self.class.send :alias_method, (method_name || :on), :process_choice
|
|
18
|
+
block_result = instance_eval(&@block)
|
|
19
|
+
@results.empty? ? block_result : [*choices].push(:otherwise).pick {|c| @results[c] }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def otherwise first = nil, *rest, &block
|
|
23
|
+
process_choice :otherwise, first, *rest, &block
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def process_choice choice, first = nil, *rest, &block
|
|
27
|
+
raise "You can supply values or a block, but not both." if first && block
|
|
28
|
+
raise "The choice '#{choice}' isn't valid." unless @possible_choices.include?(choice)
|
|
29
|
+
|
|
30
|
+
@results[choice] = if block
|
|
31
|
+
block
|
|
32
|
+
elsif first.is_a? Hash
|
|
33
|
+
first
|
|
34
|
+
else
|
|
35
|
+
[*first].concat(rest)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# This code is licensed under the MIT license with permission
|
|
2
|
+
# from the author. The license follows.
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2008-2009 Paul Battley (pbattley@gmail.com)
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
|
7
|
+
# a copy of this software and associated documentation files (the
|
|
8
|
+
# "Software"), to deal in the Software without restriction, including
|
|
9
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
|
10
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
11
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
|
12
|
+
# the following conditions:
|
|
13
|
+
#
|
|
14
|
+
# The above copyright notice and this permission notice shall be
|
|
15
|
+
# included in all copies or substantial portions of the Software.
|
|
16
|
+
#
|
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
18
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
19
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
20
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
21
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
22
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
23
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
24
|
+
|
|
25
|
+
module Babushka
|
|
26
|
+
# The Levenshtein distance is a metric for measuring the amount
|
|
27
|
+
# of difference between two sequences (i.e., the so called edit
|
|
28
|
+
# distance). The Levenshtein distance between two sequences is
|
|
29
|
+
# given by the minimum number of operations needed to transform
|
|
30
|
+
# one sequence into the other, where an operation is an
|
|
31
|
+
# insertion, deletion, or substitution of a single element.
|
|
32
|
+
#
|
|
33
|
+
# More information about the Levenshtein distance algorithm:
|
|
34
|
+
# http://en.wikipedia.org/wiki/Levenshtein_distance .
|
|
35
|
+
|
|
36
|
+
module Levenshtein
|
|
37
|
+
VERSION = "0.2.0"
|
|
38
|
+
|
|
39
|
+
# Returns the Levenshtein distance as a number between 0.0 and
|
|
40
|
+
# 1.0. It's basically the Levenshtein distance divided by the
|
|
41
|
+
# length of the longest sequence.
|
|
42
|
+
|
|
43
|
+
def self.normalized_distance(s1, s2, threshold=nil)
|
|
44
|
+
s1, s2 = s2, s1 if s1.length > s2.length # s1 is the short one; s2 is the long one.
|
|
45
|
+
|
|
46
|
+
if s2.length == 0
|
|
47
|
+
0.0 # Since s1.length < s2.length, s1 must be empty as well.
|
|
48
|
+
else
|
|
49
|
+
if threshold
|
|
50
|
+
if d = self.distance(s1, s2, (threshold*s2.length+1).to_i)
|
|
51
|
+
d.to_f/s2.length
|
|
52
|
+
else
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
else
|
|
56
|
+
self.distance(s1, s2).to_f/s2.length
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Returns the Levenshtein distance between two sequences.
|
|
62
|
+
#
|
|
63
|
+
# The two sequences can be two strings, two arrays, or two other
|
|
64
|
+
# objects. Strings, arrays and arrays of strings are handled with
|
|
65
|
+
# optimized (very fast) C code. All other sequences are handled
|
|
66
|
+
# with generic (fast) C code.
|
|
67
|
+
#
|
|
68
|
+
# The sequences should respond to :length and :[] and all objects
|
|
69
|
+
# in the sequences (as returned by []) should response to :==.
|
|
70
|
+
|
|
71
|
+
def self.distance(s1, s2, threshold=nil)
|
|
72
|
+
s1, s2 = s2, s1 if s1.length > s2.length # s1 is the short one; s2 is the long one.
|
|
73
|
+
|
|
74
|
+
# Handle some basic circumstances.
|
|
75
|
+
|
|
76
|
+
return 0 if s1 == s2
|
|
77
|
+
return s2.length if s1.length == 0
|
|
78
|
+
|
|
79
|
+
if threshold
|
|
80
|
+
return nil if (s2.length-s1.length) >= threshold
|
|
81
|
+
|
|
82
|
+
a1, a2 = nil, nil
|
|
83
|
+
a1, a2 = s1, s2 if s1.respond_to?(:-) and s2.respond_to?(:-)
|
|
84
|
+
a1, a2 = s1.scan(/./), s2.scan(/./) if s1.respond_to?(:scan) and s2.respond_to?(:scan)
|
|
85
|
+
|
|
86
|
+
if a1 and a2
|
|
87
|
+
return nil if (a1-a2).length >= threshold
|
|
88
|
+
return nil if (a2-a1).length >= threshold
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
distance_fast_or_slow(s1, s2, threshold)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.distance_fast_or_slow(s1, s2, threshold) # :nodoc:
|
|
96
|
+
if respond_to?(:levenshtein_distance_fast)
|
|
97
|
+
levenshtein_distance_fast(s1, s2, threshold) # Implemented in C.
|
|
98
|
+
else
|
|
99
|
+
levenshtein_distance_slow(s1, s2, threshold) # Implemented in Ruby.
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def self.levenshtein_distance_slow(s1, s2, threshold) # :nodoc:
|
|
104
|
+
row = (0..s1.length).to_a
|
|
105
|
+
|
|
106
|
+
1.upto(s2.length) do |y|
|
|
107
|
+
prow = row
|
|
108
|
+
row = [y]
|
|
109
|
+
|
|
110
|
+
1.upto(s1.length) do |x|
|
|
111
|
+
row[x] = [prow[x]+1, row[x-1]+1, prow[x-1]+(s1[x-1]==s2[y-1] ? 0 : 1)].min
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Stop analysing this sequence as soon as the best possible
|
|
115
|
+
# result for this sequence is bigger than the best result so far.
|
|
116
|
+
# (The minimum value in the next row will be equal to or greater
|
|
117
|
+
# than the minimum value in this row.)
|
|
118
|
+
|
|
119
|
+
return nil if threshold and row.min >= threshold
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
row[-1]
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module Babushka
|
|
2
|
+
class MetaDep
|
|
3
|
+
include LogHelpers
|
|
4
|
+
|
|
5
|
+
INVALID_NAMES = %w[base]
|
|
6
|
+
|
|
7
|
+
VALID_NAME_START_CHARS = /[a-z]/
|
|
8
|
+
VALID_NAME_CHARS = /#{VALID_NAME_START_CHARS}[a-z0-9_]*/
|
|
9
|
+
VALID_NAME_START = /^#{VALID_NAME_START_CHARS}/
|
|
10
|
+
VALID_NAME = /\A#{VALID_NAME_CHARS}\z/m
|
|
11
|
+
|
|
12
|
+
TEMPLATE_NAME_MATCH = /\A.+\.(#{VALID_NAME_CHARS})\z/m
|
|
13
|
+
|
|
14
|
+
def self.for supplied_name, source, opts, &block
|
|
15
|
+
name = supplied_name.to_s.downcase
|
|
16
|
+
|
|
17
|
+
if name.empty?
|
|
18
|
+
raise ArgumentError, "You can't define a template with a blank name."
|
|
19
|
+
elsif INVALID_NAMES.include? name
|
|
20
|
+
raise ArgumentError, "You can't use '#{name}' for a template name, because it's reserved."
|
|
21
|
+
elsif name[VALID_NAME_START].nil?
|
|
22
|
+
raise ArgumentError, "You can't use '#{name}' for a template name - it must start with a letter."
|
|
23
|
+
elsif name[VALID_NAME].nil?
|
|
24
|
+
raise ArgumentError, "You can't use '#{name}' for a template name - it can only contain [a-z0-9_]."
|
|
25
|
+
elsif Base.sources.current_load_source.templates.for(name)
|
|
26
|
+
raise ArgumentError, "A template called '#{name}' has already been defined."
|
|
27
|
+
else
|
|
28
|
+
new name, source, opts, &block
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
attr_reader :name, :source, :opts, :context_class
|
|
33
|
+
|
|
34
|
+
def desc; context_class.desc end
|
|
35
|
+
|
|
36
|
+
def initialize name, source, opts, &block
|
|
37
|
+
@name, @source, @opts, @block = name, source, opts, block
|
|
38
|
+
debug "Defining #{source.name}:#{name} template"
|
|
39
|
+
@context_class = build_context block
|
|
40
|
+
source.templates.register self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Returns this template's name, including the source name as a prefix if
|
|
44
|
+
# this template is in a cloneable source.
|
|
45
|
+
#
|
|
46
|
+
# A cloneable source is one that babushka knows how to automatically
|
|
47
|
+
# update; i.e. a source that babushka could have installed itself.
|
|
48
|
+
#
|
|
49
|
+
# In effect, a cloneable source is one whose deps you prefix when you run
|
|
50
|
+
# them, so this method returns the template's name in the same form as you
|
|
51
|
+
# would refer to it when using it from another source.
|
|
52
|
+
def contextual_name
|
|
53
|
+
source.cloneable? ? "#{source.name}:#{name}" : name
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def build_context block
|
|
57
|
+
Class.new(MetaDepContext, &block).tap {|context|
|
|
58
|
+
shadow = self
|
|
59
|
+
context.metaclass.send :define_method, :source_template do
|
|
60
|
+
shadow
|
|
61
|
+
end
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|