as-extensions 0.1.0

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/ext/object.rb ADDED
@@ -0,0 +1,38 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 Moodstocks SAS
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # or in the LICENSE file which you should have received as part of
11
+ # this distribution.
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #++
19
+
20
+ Object.class_eval do
21
+
22
+ # Completely clone the object.
23
+ # No reference will be shared with the original.
24
+ def deepcopy
25
+ Marshal.load(Marshal.dump(self))
26
+ end
27
+
28
+ # Return true if the object is a boolean, false otherwise.
29
+ def boolean?
30
+ is_a?(::TrueClass) || is_a?(::FalseClass)
31
+ end
32
+
33
+ # Force boolean type
34
+ def to_b
35
+ self ? true : false
36
+ end
37
+
38
+ end
data/ext/set.rb ADDED
@@ -0,0 +1,27 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 Moodstocks SAS
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # or in the LICENSE file which you should have received as part of
11
+ # this distribution.
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #++
19
+
20
+ Set.class_eval do
21
+
22
+ # If method doesn't exist, consider as an Array.
23
+ def method_missing(s, *args, &blk)
24
+ self.to_a.send(s, *args, &blk)
25
+ end
26
+
27
+ end
data/ext/socket.rb ADDED
@@ -0,0 +1,40 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 Moodstocks SAS
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # or in the LICENSE file which you should have received as part of
11
+ # this distribution.
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #++
19
+
20
+ Socket.class_eval do
21
+
22
+ class << self
23
+ # Thanks to 'coderrr'
24
+ def local_ip(force_reload=false)
25
+ @ase_local_ip = nil if force_reload
26
+ @ase_local_ip ||= begin
27
+ # turn off reverse DNS resolution temporarily
28
+ orig, do_not_reverse_lookup = do_not_reverse_lookup, true
29
+ UDPSocket.open do |s|
30
+ s.connect('184.106.196.237', 1)
31
+ s.addr.last
32
+ end
33
+ ensure
34
+ do_not_reverse_lookup = orig
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ end
data/ext/string.rb ADDED
@@ -0,0 +1,82 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 Moodstocks SAS
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # or in the LICENSE file which you should have received as part of
11
+ # this distribution.
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #++
19
+
20
+ String.class_eval do
21
+
22
+ ASE_ARRAY_METHODS = %w{first first= head head= init last last= tail}.map_m(:to_sym)
23
+
24
+ class << self
25
+
26
+ attr_accessor :ase_array_methods
27
+
28
+ # Load a string from a file or a URL.
29
+ def from(file_or_url)
30
+ ASE::String::from(file_or_url)
31
+ end
32
+
33
+ # Return an alphanumeric string.
34
+ def rand_alphanum(n=1, secure=false)
35
+ @ase_alphanum ||= [('a'..'z'), ('A'..'Z'), ('0'..'9')].map{ |x| x.to_a }.flatten
36
+ Array.new(n){ @ase_alphanum.pick(secure) }.join
37
+ end
38
+
39
+ # Return an hexadecimal string.
40
+ def rand_hex(n=1, secure=false)
41
+ @ase_hex ||= "0123456789abcdef".chars
42
+ Array.new(n){ @ase_hex.pick(secure) }.join
43
+ end
44
+
45
+ end # class << self
46
+
47
+ alias :camelcase_noase :camelcase
48
+ # Same as camelcase from ActiveSupport
49
+ # but dashes are considered as underscores.
50
+ def camelcase
51
+ underscore.camelcase_noase
52
+ end
53
+
54
+ alias :dasherize_noase :dasherize
55
+ # Same as underscore from ActiveSupport
56
+ # with dashes instead of underscores.
57
+ def dasherize
58
+ underscore.dasherize_noase
59
+ end
60
+
61
+ # Consider the String as an Array of chars for a few methods
62
+ if (RUBY_VERSION < "1.9")
63
+ def method_missing(n,*a,&b)
64
+ if ASE_ARRAY_METHODS.include?(n) then chars.to_a.send(n,*a,&b).join else super end
65
+ end
66
+ else
67
+ ASE_ARRAY_METHODS.each{|n| define_method(n){|*a,&b| chars.to_a.send(n,*a,&b).join}}
68
+ end
69
+
70
+ # Helper to convert a raw string to a sane identifier with dashes and ASCII letters.
71
+ # Interpolation is here to force String type, to_s won't always work.
72
+ def sanitize_dashes
73
+ "#{self.to_slug.approximate_ascii.to_ascii.normalize.to_s}"
74
+ end
75
+
76
+ # Sometimes calling puts as a method can be useful
77
+ def puts
78
+ Kernel::puts self
79
+ end
80
+ alias :p :puts
81
+
82
+ end
data/ext/symbol.rb ADDED
@@ -0,0 +1,28 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 Moodstocks SAS
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # or in the LICENSE file which you should have received as part of
11
+ # this distribution.
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #++
19
+
20
+ Symbol.class_eval do
21
+
22
+ # When a method doesn't exist, consider the Symbol as a String
23
+ # Warning: this will *not* convert back to Symbol
24
+ def method_missing(method_sym, *arguments, &block)
25
+ to_s.send(method_sym, *arguments, &block)
26
+ end
27
+
28
+ end
data/ext/time.rb ADDED
@@ -0,0 +1,34 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 Moodstocks SAS
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # or in the LICENSE file which you should have received as part of
11
+ # this distribution.
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #++
19
+
20
+ Time.class_eval do
21
+
22
+ alias :to_s_noase :to_s
23
+ # Convert to the standard ASE time string format
24
+ def to_s(format=nil)
25
+ if format.nil? then ASE::Time::to_string(self)
26
+ else to_s_noase(format) end
27
+ end
28
+
29
+ # Get the number of seconds since Epoch
30
+ def to_epoch
31
+ ASE::Time::epoch(self)
32
+ end
33
+
34
+ end
data/ext/uri.rb ADDED
@@ -0,0 +1,61 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 Moodstocks SAS
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # or in the LICENSE file which you should have received as part of
11
+ # this distribution.
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #++
19
+
20
+ URI.class_eval do
21
+
22
+ ASE_UNSAFE_CHARS = /\~|\[|\]/
23
+
24
+ class << self
25
+
26
+ alias :parse_noase :parse
27
+ def parse(uri)
28
+ begin
29
+ parse_noase(uri)
30
+ rescue
31
+ safe_parse(uri)
32
+ end
33
+ end
34
+
35
+ # Encode accented and unsafe characters in URI strings before parsing
36
+ def safe_parse(uri)
37
+ if uri.is_a?(String)
38
+ uri = encode(uri)
39
+ if uri.match(ASE_UNSAFE_CHARS) && ( (s1 = uri.split('://')).length == 2 )
40
+ if s1.last.chars.first == '[' # IPv6 (RFC 2732)
41
+ if (s2 = s1.last.split(']')).length > 1
42
+ s2 = [s2.head, s2.tail.join(']')]
43
+ if s2.last.match(ASE_UNSAFE_CHARS)
44
+ '~[]'.chars.each { |c| s2.last.gsub!(c, "%#{c.unpack("H2").first.upcase}") }
45
+ uri = "#{s1.first}://#{s2.first}]#{s2.last}"
46
+ end
47
+ end
48
+ else # IPv4 or DN
49
+ if s1.last.match(ASE_UNSAFE_CHARS)
50
+ '~[]'.chars.each { |c| s1.last.gsub!(c, "%#{c.unpack("H2").first.upcase}") }
51
+ uri = s1.join('://')
52
+ end
53
+ end
54
+ end
55
+ end
56
+ parse_noase(uri)
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,38 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 Moodstocks SAS
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # or in the LICENSE file which you should have received as part of
11
+ # this distribution.
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #++
19
+
20
+ module ASE; class << self
21
+
22
+ # Remove nil values in a recursive data structure composed
23
+ # of hashes and arrays.
24
+ def deepcompact(x)
25
+ if x.is_a?(::Array)
26
+ x.reject{ |x| x.nil? }.map{ |x| deepcompact(x) }
27
+ elsif x.is_a?(::Hash)
28
+ ret = {}
29
+ x.each_pair do |k,v|
30
+ ret[k] = deepcompact(v) if !v.nil?
31
+ end
32
+ ret
33
+ else
34
+ x
35
+ end
36
+ end
37
+
38
+ end end # class << self
@@ -0,0 +1,64 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 Moodstocks SAS
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # or in the LICENSE file which you should have received as part of
11
+ # this distribution.
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #++
19
+
20
+ module ASE
21
+
22
+ class Enum
23
+ class << self
24
+
25
+ def const_missing(key)
26
+ raise NameError "#{key} not in enum" unless @hash.has_key?(key)
27
+ @hash[key]
28
+ end
29
+
30
+ # Iterate over the key/value pairs in the Enum
31
+ def each_pair(&blk)
32
+ @hash.each_pair(&blk)
33
+ end
34
+
35
+ def init(h)
36
+ @hash = {}
37
+ h.each_pair do |k,v|
38
+ @hash[k] = v
39
+ end
40
+ self
41
+ end
42
+
43
+ # Check if a value is part of the Enum
44
+ def include?(x)
45
+ @hash.has_value?(x)
46
+ end
47
+
48
+ end # class << self
49
+ end # class Enum
50
+
51
+ class << self
52
+
53
+ # Instantiate a new Enum from a Hash.
54
+ # Usage example:
55
+ # Numbers = enum( :ONE => 1, :TWO => 2, :THREE => 3 )
56
+ # assert (1 == Numbers::ONE)
57
+ def enum(hash)
58
+ k = Class.new(Enum)
59
+ k.init(hash)
60
+ end
61
+
62
+ end # class << self
63
+
64
+ end
@@ -0,0 +1,34 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 Moodstocks SAS
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # or in the LICENSE file which you should have received as part of
11
+ # this distribution.
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #++
19
+
20
+ module ASE module IO
21
+ class << self
22
+
23
+ # Read from an IO stream that can be compressed
24
+ def to_string(io, gzipped=false)
25
+ if gzipped
26
+ ASE::need 'zlib'
27
+ Zlib::GzipReader.new(io).read
28
+ else
29
+ io.read
30
+ end
31
+ end
32
+
33
+ end # class << self
34
+ end end
@@ -0,0 +1,42 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 Moodstocks SAS
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # or in the LICENSE file which you should have received as part of
11
+ # this distribution.
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #++
19
+
20
+ module ASE module String
21
+ class << self
22
+
23
+ # Load a string from a file or a URL.
24
+ def from(where)
25
+ where.strip!
26
+ uri = URI.parse(where)
27
+ is_gz = (where.split('.').last == 'gz')
28
+ case uri.scheme
29
+ when nil
30
+ unless File.file?( where = File.expand_path(where) )
31
+ return ASE::log("#{where} is not a file.", :error)
32
+ end
33
+ open(where) do |f| return IO::to_string(f, is_gz) end
34
+ else
35
+ ASE::need 'open-uri'
36
+ args = uri.userinfo ? { :http_basic_authentication => uri.userinfo.split(':') } : {}
37
+ uri.open(args) do |f| return IO::to_string(f, is_gz) end
38
+ end # case
39
+ end
40
+
41
+ end # class << self
42
+ end end
@@ -0,0 +1,20 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 Moodstocks SAS
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # or in the LICENSE file which you should have received as part of
11
+ # this distribution.
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #++
19
+
20
+ ASE::require_part %w{ io string }
@@ -0,0 +1,60 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 Moodstocks SAS
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # or in the LICENSE file which you should have received as part of
11
+ # this distribution.
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #++
19
+
20
+ # Patch the LibXML::XML::Reader class to add useful methods.
21
+
22
+ ::LibXML::XML::Reader.class_eval do
23
+
24
+ # Test if the Reader is at the beginning of a tag.
25
+ # If a string argument is passed, it has to be the name of the tag.
26
+ def at_start?(tag = nil)
27
+ ( (self.node_type == self.class::TYPE_ELEMENT) &&
28
+ [self.name, nil].include?(tag) )
29
+ end
30
+
31
+ # Test if the Reader is at the end of a tag.
32
+ # If a string argument is passed, it has to be the name of the tag.
33
+ def at_end?(tag = nil)
34
+ ( (self.node_type == self.class::TYPE_END_ELEMENT) &&
35
+ [self.name, nil].include?(tag) )
36
+ end
37
+
38
+ # Test if the Reader is at a whitespace text element.
39
+ def at_whitespace?()
40
+ [ self.class::TYPE_WHITESPACE,
41
+ self.class::TYPE_SIGNIFICANT_WHITESPACE ].include?(self.node_type)
42
+ end
43
+
44
+ # Test if the Reader is at a non-whitespace text element.
45
+ def at_text?()
46
+ [ self.class::TYPE_TEXT,
47
+ self.class::TYPE_CDATA ].include?(self.node_type)
48
+ end
49
+
50
+ # Move the Reader to the next text element and return its value.
51
+ def get_text!(tag_only = false, mover_symbol = :read)
52
+ mover = self.method(mover_symbol)
53
+ loop do
54
+ if tag_only && self.at_end? then return nil
55
+ elsif self.at_text? then return self.value.strip
56
+ elsif mover.call == 0 then return nil end
57
+ end
58
+ end
59
+
60
+ end
@@ -0,0 +1,32 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 Moodstocks SAS
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # or in the LICENSE file which you should have received as part of
11
+ # this distribution.
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #++
19
+
20
+ module ASE
21
+
22
+ EXTRA_EXT = %w{ libxml }
23
+
24
+ class << self
25
+
26
+ def load_extra_ext(ext)
27
+ require_part(ext) if EXTRA_EXT.include?(ext)
28
+ end
29
+
30
+ end # class << self
31
+
32
+ end
@@ -0,0 +1,42 @@
1
+ #--
2
+ # Copyright (c) 2010-2011 Moodstocks SAS
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # or in the LICENSE file which you should have received as part of
11
+ # this distribution.
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #++
19
+
20
+ module ASE
21
+
22
+ class << self
23
+
24
+ # Make sure a directory exists;
25
+ # create it if it is not the case.
26
+ def ensure_dir_exists(dir, *a)
27
+ dir = File.join(File.expand_path(dir), *a)
28
+ FileUtils.mkdir_p dir unless File.exist?(dir)
29
+ dir
30
+ end
31
+
32
+ # Return the first file that exists in a list of files
33
+ def select_file(*files)
34
+ files.each do |file|
35
+ if File.exists?(file) then return file end
36
+ end
37
+ return nil
38
+ end
39
+
40
+ end # class << self
41
+
42
+ end