Class: CodeDuplicationAction

Inherits:
BuildAction show all
Defined in:
lib/kwala/actions/code_duplication.rb

Instance Method Summary collapse

Methods inherited from BuildAction

command_line_action_name, command_line_action_names, create_action_from_command_line_name, detailed_template_file, summary_template_file

Methods included from InheritanceTracker

#get_implementors

Constructor Details

#initializeCodeDuplicationAction

Returns a new instance of CodeDuplicationAction.



46
47
48
49
# File 'lib/kwala/actions/code_duplication.rb', line 46

def initialize
  @duplicates = nil
  @file_count = 0
end

Instance Method Details

#build_action(context) ⇒ Object



51
52
53
54
55
56
57
# File 'lib/kwala/actions/code_duplication.rb', line 51

def build_action(context)
  temporarily_remove_symlinks(context.project_directory) do |dir|
    @duplicates = find_duplicate_code(dir)
  end
  # Store file count for scoring
  @file_count = context.ruby_files.size + context.test_files.size
end

#cpd_command(dir) ⇒ Object



153
154
155
156
157
# File 'lib/kwala/actions/code_duplication.rb', line 153

def cpd_command(dir)
  "java -Xmx256m -cp #{cpd_jar} net.sourceforge.pmd.cpd.CPD" +
    " --minimum-tokens #{minimum_tokens} --files #{dir}" +
    " --language ruby --format net.sourceforge.pmd.cpd.XMLRenderer"
end

#cpd_error?(stderr) ⇒ Boolean

Returns:

  • (Boolean)


167
168
169
170
171
172
173
174
175
# File 'lib/kwala/actions/code_duplication.rb', line 167

def cpd_error?(stderr)
  lines = stderr.readlines
  if !lines.empty?
    STDERR.puts "code_duplication error with cpd:", lines
    true
  else
    false
  end
end

#cpd_jarObject



159
160
161
# File 'lib/kwala/actions/code_duplication.rb', line 159

def cpd_jar
  EXTERNAL_DIR + "pmd.jar"
end

#detailed_display(context) ⇒ Object



70
71
72
73
74
# File 'lib/kwala/actions/code_duplication.rb', line 70

def detailed_display(context)
  det_res = expand_code_duplication_template(context.amrita_data[:duplicate_code_results])
  det_file = "#{context.output_directory}/#{context.project_name}_code_duplication.html"
  [det_file, det_res]
end

#expand_code_duplication_template(data) ⇒ Object



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/kwala/actions/code_duplication.rb', line 116

def expand_code_duplication_template(data)
  template = TemplateFile.new(self.class.detailed_template_file)
  data[:duplicate_refs] = data[:duplicates].map do |d|
    {
      :entry_num => Amrita::a(:href=>"\##{d[:duplicate][:entry_num]}") {d[:duplicate][:entry_num]}
    }
  end
  max_size = (data[:duplicates].size - 1)
  data[:duplicates].each_with_index do |d, idx|
    d[:duplicate][:entry_num] = Amrita::a(:name=>"#{d[:duplicate][:entry_num]}") {d[:duplicate][:entry_num]}
    if idx > 0
      d[:duplicate][:prev_entry_num] = Amrita::a(:href=>"\##{idx - 1}") { "Prev" }
    end
    if idx < max_size
      d[:duplicate][:next_entry_num] = Amrita::a(:href=>"\##{idx + 1}") { "Next" }
    end
  end
  ProjectBuilderUtils.expand_template(template, data)
end

#find_duplicate_code(dir = Dir.pwd) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/kwala/actions/code_duplication.rb', line 86

def find_duplicate_code(dir=Dir.pwd)
  require "rexml/document"
  results = Array.new
  Open3.popen3( cpd_command(dir) ) do |stdin, stdout, stderr|
    xml = time_out_readlines(stdout)
    if cpd_error?(stderr) || xml.first == "IO readlines timed out."
      return results
    end
    doc = REXML::Document.new(xml.join("\n"))
    doc.elements.each("pmd-cpd/duplication") do |dup|
      results << {
        :duplicate =>  {
          :entry_num => results.size,
          :lines => dup.attribute("lines").to_s,
          :tokens => dup.attribute("tokens").to_s,
          :code_fragement => highlight_code(dup.elements["codefragment"].cdatas.to_s),
          :files => dup.get_elements("file").map { |f|
            {
              :path => f.attribute("path").to_s,
              :line => f.attribute("line").to_s
            }
          },
        }
      }
    end
  end
  results
end

#highlight_code(code) ⇒ Object



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/kwala/actions/code_duplication.rb', line 136

def highlight_code(code)
  # Check if enscript is installed.
  cmd = "/usr/bin/enscript --color --language=html -Eruby --output=-"
  out_data = nil
  Open3.popen3(cmd) do |stdin, stdout, stderr|
    stdin.puts code
    stdin.close
    out_data = time_out_readlines(stdout).join("")
    #time_out_readlines(stderr)
  end
  if (out_data && ( m = /(<pre>.*<\/pre>)/im.match(out_data) ) )
    code = Amrita::SanitizedString.new(m[1])
  end

  code
end

#minimum_tokensObject



163
164
165
# File 'lib/kwala/actions/code_duplication.rb', line 163

def minimum_tokens
  50
end

#scoreObject



76
77
78
79
80
81
82
83
84
# File 'lib/kwala/actions/code_duplication.rb', line 76

def score
  # This should be based on code size ratio, for now using file count, but
  # probably lines of code is better.
  if @duplicates.size > @file_count
    1
  else
    score_scaling(@duplicates.size, @file_count)
  end
end

#summary_display(context) ⇒ Object



59
60
61
62
63
64
65
66
67
68
# File 'lib/kwala/actions/code_duplication.rb', line 59

def summary_display(context)
  template = TemplateFile.new(self.class.summary_template_file)
  context.amrita_data[:duplicate_code_results] = {
    :duplicate_count => @duplicates.size,
    :duplicates => @duplicates
  }
  context.amrita_data[:code_duplication_details] =
   (Amrita::e(:a, :href=>"#{context.project_name}_code_duplication.html") { "Code Duplication Details" })
  summary_expand(template, context)
end


177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/kwala/actions/code_duplication.rb', line 177

def temporarily_remove_symlinks(dir)
  symlinks = Hash.new

  Find.find(dir) do |path|
    if File.symlink?(path)
      lnk = File.expand_path(path)

      # Need to give it relative to lnk
      src = File.expand_path(File.readlink(path), File.dirname(lnk))

      # Only remove symlinks that are relative to dir. i.e. inside
      # project to other parts of the project.

      if src.index(dir) == 0

        symlinks[lnk] = src

        # Store if it is a directory
        dircheck = File.directory?(path)

        # Remove the link
        FileUtils.rm_f(path)

        if dircheck
          # Prune must come after removing the file, because code
          # after prune will not run.
          Find.prune
        end

      end
    end
  end

  yield dir
ensure

  symlinks.each do |sym, source|
    begin
      FileUtils.ln_s(source, sym)
    rescue => err
      STDERR.puts "Cannot restore link:", err
    end
  end
end