Bee C0 Coverage Information - RCov

lib/bee_util.rb

Name Total Lines Lines of Code Total Coverage Code Coverage
lib/bee_util.rb 421 286
94.54%
91.96%

Key

Code reported as executed by Ruby looks like this...and this: this line is also marked as covered.Lines considered as run by rcov, but not reported by Ruby, look like this,and this: these lines were inferred by rcov (using simple heuristics).Finally, here's a line marked as not executed.

Coverage Details

1 # Copyright 2006-2012 Michel Casabianca <michel.casabianca@gmail.com>
2 #           2006 Avi Bryant
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 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 require 'rubygems'
17 require 'net/http'
18 require 'bee_version_dependant'
19 
20 module Bee
21   
22   module Util
23 
24     # Limit of number of HTTP redirections to follow.
25     HTTP_REDIRECTIONS_LIMIT = 10
26     # Default package name.
27     DEFAULT_PACKAGE = 'default'    
28     # Compact pattern for resource (':gem.file[version]')
29     COMPACT_PATTERN  = /^:(.*?):(.*?)(\[(.*)\])?$/
30     # Expanded pattern for resource ('ruby://gem:version/file')
31     EXPANDED_PATTERN = /^ruby:\/\/(.*?)(:(.*?))?\/(.*)$/
32     # Default terminal width
33     DEFAULT_TERM_WIDTH = (RUBY_PLATFORM =~ /win32/ ? 79 : 80)
34 
35     # Get line length calling IOCTL. Return DEFAULT_TERM_WIDTH if call failed.
36     def self.term_width
37       begin
38         tiocgwinsz = RUBY_PLATFORM =~ /darwin/ ? 0x40087468 : 0x5413
39         string = [0, 0, 0, 0].pack('SSSS')
40         if $stdin.ioctl(tiocgwinsz, string) >= 0 then
41           rows, cols, xpixels, ypixels = string.unpack('SSSS')
42           cols = DEFAULT_TERM_WIDTH if cols <= 0
43           return cols
44         else
45           return DEFAULT_TERM_WIDTH
46         end
47       rescue
48         return DEFAULT_TERM_WIDTH
49       end
50     end
51 
52     # Tells if we are running under Windows.
53     def self.windows?
54       return RUBY_PLATFORM =~ /(mswin|ming)/
55     end
56 
57     # Parse packaged name and return package and name.
58     # - packaged: packaged name (such as 'foo.bar').
59     # Return: package ('foo') and name ('bar').
60     def self.get_package_name(packaged)
61       if packaged =~ /\./
62         package, name = packaged.split('.')
63       else
64         package, name = DEFAULT_PACKAGE, packaged
65       end
66       return package, name
67     end
68 
69     # Get a given file or URL. Manages HTTP redirections.
70     # - location: file path, resource or URL of the file to get.
71     # - base: base for relative files (defaults to nil, which is current dir).
72     def self.get_file(location, base=nil)
73       base = base || Dir.pwd
74       abs = absolute_path(location, base)
75       if abs =~ /^http:/
76         # this is HTTP
77         return fetch(abs)
78       else
79         # this is a file
80         return File.read(abs)
81       end  
82     end
83 
84     private
85 
86     # Looks recursively up in file system for a file.
87     # - file: file name to look for.
88     # Return: found file or raises an exception if file was not found.
89     def self.find(file)
90       return file if File.exists?(file)
91       raise "File not found" if File.identical?(File.dirname(file), '/')
92       file = File.join('..', file)
93       find(file)
94     end
95 
96     # Tells is a given location is a URL (starting with 'http://').
97     # - location: location to consider as a string.
98     def self.url?(location)
99       return false if not location.kind_of?(String)
100       return location =~ /^http:\/\//
101     end
102 
103     # Tells is a given location is a resource (starting with 'ruby://' or ':').
104     # - location: location to consider as a string.
105     def self.resource?(location)
106       return false if not location.kind_of?(String)
107       return location =~ /^ruby:\/\// || location =~ /^:/
108     end
109 
110     # Tells if a given path is absolute.
111     # - path: path to consider.
112     def self.absolute_path?(path)
113       if url?(path) or resource?(path)
114         return true
115       else
116         if windows?
117           return path =~ /^(([a-zA-Z]):)?\//
118         else
119           return path =~ /^\//
120         end
121       end
122     end
123 
124     # Return absolute path for a given path and optional base:
125     # - path: relative path to get absolute path for.
126     # - base: optional base for path (defaults to current directory).
127     def self.absolute_path(path, base=nil)
128       path = File.expand_path(path) if path =~ /~.*/
129       if absolute_path?(path)
130         if resource?(path)
131           path = resource_path(path)
132         end
133         return path
134       else
135         base = Dir.pwd if not base
136         return File.join(base, path)
137       end
138     end
139 
140     # Get a given URL.
141     # - url: URL to get.
142     # - limit: redirectrion limit (defaults to HTTP_REDIRECTIONS_LIMIT).
143     def self.fetch(url, limit=HTTP_REDIRECTIONS_LIMIT,
144                    username=nil, password=nil)
145       raise 'HTTP redirect too deep' if limit == 0
146       response = Net::HTTP.get_response(URI.parse(url))
147       case response
148       when Net::HTTPSuccess
149         response.body
150       when Net::HTTPRedirection
151         fetch(response['location'], limit-1)
152       when Net::HTTPUnauthorized
153         uri = URI.parse(url)
154         Net::HTTP.start(uri.host, uri.port) do |http|
155           request = Net::HTTP::Get.new("#{uri.path}?#{uri.query}")
156           request.basic_auth(username, password)
157           response = http.request(request)
158           return response.body
159         end
160       else
161         response.error!
162       end
163     end
164 
165     # Return absolute path to a given resoure:
166     # - resource: the resource (expanded patterns are like ':gem.file[version]'
167     # and compact ones like 'ruby://gem:version/file').
168     def self.resource_path(resource)
169       # get gem, version and path from resource or interrupt build with an error
170       case resource
171       when COMPACT_PATTERN
172         gem, version, path = $1, $4, $2
173         gem = "bee_#{gem}" if gem != 'bee'
174       when EXPANDED_PATTERN
175         gem, version, path = $1, $3, $4
176       else
177         raise "'#{resource}' is not a valid resource"
178       end
179       # get gem descriptor
180       if version
181         if Gem::Specification.respond_to?(:find_by_name)
182           begin
183             gem_descriptor = Gem::Specification.find_by_name(gem, version)
184           rescue Exception
185             gem_descriptor = nil
186           end
187         else
188           gem_descriptor = Gem.source_index.find_name(gem, version)[0]
189         end
190         raise "Gem '#{gem}' was not found in version '#{version}'" if
191           not gem_descriptor
192       else
193         if Gem::Specification.respond_to?(:find_by_name)
194           begin
195             gem_descriptor = Gem::Specification::find_by_name(gem)
196           rescue Exception
197             gem_descriptor = nil
198           end
199         else
200           gem_descriptor = Gem.source_index.find_name(gem)[0]
201         end
202         raise "Gem '#{gem}' was not found" if not gem_descriptor
203       end
204       # get resource path
205       gem_path = gem_descriptor.full_gem_path
206       file_path = File.join(gem_path, path)
207       return file_path
208     end
209 
210     # Find a given template and return associated file.
211     # - template: template to look for (like 'foo.bar').
212     # return: found associated file.
213     def self.find_template(template)
214       raise Bee::Util::BuildError.new("Invalid template name '#{template}'") if
215         not template =~ /^([^.]+\.)?[^.]+$/
216       package, egg = template.split('.')
217       if not egg
218         egg = package
219         package = 'bee'
220       end
221       resource = ":#{package}:egg/#{egg}.yml"
222       begin
223         file = absolute_path(resource, Dir.pwd)
224       rescue Exception
225         raise BuildError.new("Template '#{template}' not found")
226       end
227       raise BuildError.new("Template '#{template}' not found") if
228         not File.exists?(file)
229       return file
230     end
231 
232     # Search files for a given templates that might contain a joker (*).
233     # - template: template to look for ('foo.bar' or 'foo.*' or '*.bar').
234     # return: a hash associating template and corresponding file.
235     def self.search_templates(template)
236       raise Bee::Util::BuildError.
237         new("Invalid template name '#{template}'") if
238           not template =~ /^([^.]+\.)?[^.]+$/
239       package, egg = template.split('.')
240       if not egg
241         egg = package
242         package = 'bee'
243       end
244       egg = '*' if egg == '?'
245       resource = ":#{package}:egg/#{egg}.yml"
246       begin
247         glob = absolute_path(resource, nil)
248       rescue
249         if egg == '*'
250           raise BuildError.new("Template package '#{package}' not found")
251         else
252           raise BuildError.new("Template '#{template}' not found")
253         end
254       end
255       files = Dir.glob(glob)
256       hash = {}
257       for file in files
258         egg = file.match(/.*?([^\/]+)\.yml/)[1]
259         name = "#{package}.#{egg}"
260         hash[name] = file
261       end
262       return hash
263     end
264 
265     # Aliases
266 
267     def self.gem_available?(gem)
268       return Bee::VersionDependant::gem_available?(gem)
269     end
270 
271     def self.find_gems(*patterns)
272       return Bee::VersionDependant::find_gems(*patterns)
273     end
274 
275     # Class that holds information about a given method.
276     class MethodInfo
277       
278       attr_accessor :source, :comment, :defn, :params
279 
280       # Constructor taking file name and line number.
281       # - file: file name of the method.
282       # - lineno: line number of the method.
283       def initialize(file, lineno)
284         lines = file_cache(file)
285         @source = match_tabs(lines, lineno, "def")
286         @comment = preceding_comment(lines, lineno)
287         @defn = lines[lineno].strip.gsub(/^def\W+(.*)/){$1}
288         if @defn =~ /.*?\(.*?\)/
289           @params = @defn.gsub(/.*?\((.*?)\)/){$1}.split(',').map{|p| p.strip}
290         else
291           @params = []
292         end
293       end
294       
295       private
296 
297       @@file_cache = {}
298       
299       def file_cache(file)
300         unless lines = @@file_cache[file]
301           @@file_cache[file] = lines = File.new(file).readlines
302         end
303         lines
304       end	
305       
306       def match_tabs(lines, i, keyword)
307         lines[i] =~ /(\W*)((#{keyword}(.*;\W*end)?)|(.*))/
308         return $2 if $4 or $5
309         tabs = $1
310         result = ""
311         lines[i..-1].each do |line|
312           result << line.gsub(/^#{tabs}(.*)/) { $1}
313           return result if $1 =~ /^end/
314         end
315       end
316       
317       def preceding_comment(lines, i)
318         result = []
319         i = i-1
320         i = i-1 while lines[i] =~ /^\W*$/
321         if lines[i] =~ /^=end/
322           i = i-1
323           until lines[i] =~ /^=begin/
324             result.unshift lines[i]
325             i = i-1
326           end
327         else
328           while lines[i] =~ /^\W*#(.*)/
329             result.unshift $1[1..-1]
330             i = i-1
331           end
332         end
333         result.join("\n")
334       end
335       
336     end
337     
338     # This abstract class provides information about its methods.
339     class MethodInfoBase
340       
341       @@minfo = {}
342 
343       # Return comment for a given method.
344       # - method: the method name to get info for.
345       def self.method_info(method)
346         @@minfo[method.to_s]
347       end
348       
349       private
350 
351       # Called when a method is added.
352       # - method: added method.
353       def self.method_added(method)
354         super if defined? super
355         last = caller[0]
356         file, lineno = last.match(/(.+?):(\d+)/)[1, 2]
357         @@minfo[method.to_s] = MethodInfo.new(file, lineno.to_i - 1)
358       end
359       
360     end
361 
362     # Error raised on a user error. This error should be raised to interrupt
363     # the build with a message on console but with no stack trace (that should
364     # be displayed on an internal error only). Include BuildErrorMixin to get
365     # a convenient way to raise such an error.
366     class BuildError < RuntimeError
367       
368       # Last met target.
369       attr_accessor :target
370       # Last met task.
371       attr_accessor :task
372 
373     end
374     
375     # Build error mixin provides error() function to raise a BuildError.
376     # Use this function to interrupt the build on a user error (bad YAML
377     # syntax, error running a task and so on). This will result in an
378     # error message on the console, with no stack trace.
379     module BuildErrorMixin
380       
381       # Convenient method to raise a BuildError.
382       # - message: error message.
383       def error(message)
384         Kernel.raise BuildError.new(message)
385       end
386       
387     end
388     
389     # Mixin that provides a way to check a hash entries using a description
390     # that associates hash keys with a :mandatory or :optional symbol. Other
391     # keys are not allowed.
392     module HashCheckerMixin
393 
394       include BuildErrorMixin
395       
396       # Check that all mandatory keys are in the hash and all keys in the
397       # hash are in description.
398       # - hash: hash to check.
399       # - description: hash keys description.
400       def check_hash(hash, description)
401         # check for mandatory keys
402         for key in description.keys
403           case description[key]
404           when :mandatory
405             error "Missing mandatory key '#{key}'" if not hash.has_key?(key)
406           when :optional
407           else
408             error "Unknown symbol '#{description[key]}'"
409           end
410         end
411         # look for unknown keys in hash
412         for key in hash.keys
413           error "Unknown key '#{key}'" if not description.keys.member?(key)
414         end
415       end
416       
417     end
418 
419   end
420   
421 end

Generated on Fri Oct 09 02:07:49 +0200 2015 with rcov 1.0.0