require 'erb' module MetricFu # The Template class is intended as an abstract class for concrete # template classes to subclass. It provides a variety of utility # methods to make templating a bit easier. However, classes do not # have to inherit from here in order to provide a template. The only # requirement for a template class is that it provides a #write method # to actually write out the template. See AwesomeTemplate for an # example. class Template attr_accessor :result, :per_file_data, :formatter, :output_directory def output_directory @output_directory || MetricFu::Io::FileSystem.directory('output_directory') end # Renders a partial and add optional instance variables to the template def render_partial(name, instance_variables = {}) create_instance_vars(instance_variables) erbify("_#{name}") end private # Creates a new erb evaluated result from the passed in section. # # @param section String # The section name of # # @return String # The erb evaluated string def erbify(section) template_file = template(section) erb = erb_template_source(template_file) erb.result(binding) rescue => e message = "Error: #{e.class}; message #{e.message}. " message << "Failed evaluating erb template " message << "for section #{section} and template #{template_file}." raise message end def erb_template_source(template_file) erb_doc = File.read(template_file) erb = ERB.new(erb_doc) erb.filename = template_file erb end # Copies an instance variable mimicing the name of the section # we are trying to render, with a value equal to the passed in # constant. Allows the concrete template classes to refer to # that instance variable from their ERB rendering # # @param section String # The name of the instance variable to create # # @param contents Object # The value to set as the value of the created instance # variable def create_instance_var(section, contents) instance_variable_set("@#{section}", contents) end def create_instance_vars(variables) variables.each { |variable| create_instance_var(*variable) } end # Generates the filename of the template file to load and # evaluate. In this case, the path to the template directory + # the section name + .html.erb # # @param section String # A section of the template to render # # @return String # A file path def template(section) # TODO: each MetricFu::Metric should know about its templates # This class knows too much about the filesystem structure if MetricFu::Metric.enabled_metrics.map(&:name).include?(section) # expects a symbol File.join(template_dir(section.to_s), "#{section}.html.erb") else File.join(template_directory, section.to_s + ".html.erb") end end def template_dir(metric) File.join(MetricFu.metrics_dir, metric, metric_template_dir) end # e.g. template_awesome, template_standard def metric_template_dir template_name = self.class.name.sub('Template', '')[/^([A-Z][a-z]+)+/].downcase "template_#{template_name}" end # Determines whether a template file exists for a given section # of the full template. # # @param section String # The section of the template to check against # # @return Boolean # Does a template file exist for this section or not? def template_exists?(section) File.exist?(template(section)) end # Returns the filename that the template will render into for # a given section. In this case, the section name + '.html' # # @param section String # A section of the template to render # # @return String # The output filename def output_filename(section) section.to_s + ".html" end # Returns the contents of a given css file in order to # render it inline into a template. # # @param css String # The name of a css file to open # # @return String # The contents of the css file def inline_css(css) css_file = File.join(MetricFu.lib_dir,'templates', css) MetricFu::Utility.binread(css_file) end # Provides a link to open a file through the textmate protocol # on Darwin, or otherwise, a simple file link. # # @param name String # # @param line Integer # The line number to link to, if textmate is available. Defaults # to nil # # @return String # An anchor link to a textmate reference or a file reference def link_to_filename(name, line = nil, link_content = nil) href = file_url(name, line) link_text = link_content(name, line, link_content) "#{link_text}" end def round_to_tenths(decimal) decimal = 0.0 if decimal.to_s.eql?('NaN') (decimal * 10).round / 10.0 end def link_content(name, line=nil, link_content=nil) # :nodoc: if link_content link_content elsif line "#{name}:#{line}" else name end end def display_location(location) class_name, method_name = location.fetch('class_name'), location.fetch('method_name') str = "" str += link_to_filename(location.fetch('file_name'), location.fetch('line_number')) str += " : " if method_name || class_name if(method_name) str += "#{method_name}" else #TODO HOTSPOTS BUG ONLY exists on move over to metric_fu if class_name.is_a?(String) str+= "#{class_name}" end end str end def file_url(name, line) # :nodoc: return '' unless name filename = complete_file_path(name) if render_as_txmt_protocol? "txmt://open/?url=file://#{filename}" << (line ? "&line=#{line}" : "") else link_prefix = MetricFu.configuration.templates_option('link_prefix') if link_prefix == MetricFu::Templates::Configuration::FILE_PREFIX path = filename else path = name.gsub(/:.*$/, '') end "#{link_prefix}/#{path}" end end def complete_file_path(filename) File.expand_path(remove_leading_slash(filename)) end def remove_leading_slash(filename) filename.gsub(/^\//, '') end def render_as_txmt_protocol? # :nodoc: if MetricFu.configuration.osx? !MetricFu.configuration.templates_option('darwin_txmt_protocol_no_thanks') else false end end # Provides a brain dead way to cycle between two values during # an iteration of some sort. Pass in the first_value, the second_value, # and the cardinality of the iteration. # # @param first_value Object # # @param second_value Object # # @param iteration Integer # The number of times through the iteration. # # @return Object # The first_value if iteration is even. The second_value if # iteration is odd. def cycle(first_value, second_value, iteration) return first_value if iteration % 2 == 0 return second_value end # available in the erb template # as it's processed in the context of # the binding of this class def metric_links @metrics.keys.map {|metric| metric_link(metric.to_s) } end def metric_link(metric) <<-LINK #{snake_case_to_title_case(metric)} LINK end def snake_case_to_title_case(string) string.split('_').collect{|word| word[0] = word[0..0].upcase; word}.join(" ") end # belive me, I tried to meta program this with an inherited hook # I couldn't get it working def template_directory raise "you need to define this method in each subclass with File.dirname(__FILE__)" # def template_directory # File.dirname(__FILE__) # end end end end