class Flay
Public Class Methods
default_options()
click to toggle source
Returns the default options.
# File lib/flay.rb, line 36 def self.default_options { :diff => false, :mass => 16, :summary => false, :verbose => false, :number => true, :timeout => 10, :liberal => false, :fuzzy => false, :only => nil, :filters => [], } end
load_plugins()
click to toggle source
Loads all flay plugins. Files must be named “flay_*.rb”.
# File lib/flay.rb, line 135 def self.load_plugins unless defined? @@plugins then @@plugins = [] plugins = Gem.find_files("flay_*.rb").reject { |p| p =~ /flay_task/ } plugins.each do |plugin| plugin_name = File.basename(plugin, ".rb").sub(/^flay_/, "") next if @@plugins.include? plugin_name begin load plugin @@plugins << plugin_name rescue LoadError => e warn "error loading #{plugin.inspect}: #{e.message}. skipping..." end end end @@plugins rescue => e warn "Error loading plugins: #{e}" if option[:verbose] end
new(option = {})
click to toggle source
Create a new instance of Flay
with +option+s.
# File lib/flay.rb, line 165 def initialize option = {} @option = Flay.default_options.merge option @hashes = Hash.new { |h,k| h[k] = [] } self.identical = {} self.masses = {} self.total = 0 self.mass_threshold = @option[:mass] end
parse_options(args = ARGV)
click to toggle source
Process options in args
, defaulting to ARGV
.
# File lib/flay.rb, line 54 def self.parse_options args = ARGV options = self.default_options OptionParser.new do |opts| opts.banner = "flay [options] files_or_dirs" opts.version = Flay::VERSION opts.separator "" opts.separator "Specific options:" opts.separator "" opts.on("-h", "--help", "Display this help.") do puts opts exit end opts.on("-f", "--fuzzy [DIFF]", Integer, "Detect fuzzy (copy & paste) duplication (default 1).") do |n| options[:fuzzy] = n || 1 end opts.on("-l", "--liberal", "Use a more liberal detection method.") do options[:liberal] = true end opts.on("-m", "--mass MASS", Integer, "Sets mass threshold (default = #{options[:mass]})") do |m| options[:mass] = m.to_i end opts.on("-#", "Don't number output (helps with diffs)") do |m| options[:number] = false end opts.on("-v", "--verbose", "Verbose. Show progress processing files.") do options[:verbose] = true end opts.on("-o", "--only NODE", String, "Only show matches on NODE type.") do |s| options[:only] = s.to_sym end opts.on("-d", "--diff", "Diff Mode. Display N-Way diff for ruby.") do options[:diff] = true end opts.on("-s", "--summary", "Summarize. Show flay score per file only.") do options[:summary] = true end opts.on("-t", "--timeout TIME", Integer, "Set the timeout. (default = #{options[:timeout]})") do |t| options[:timeout] = t.to_i end extensions = ["rb"] + Flay.load_plugins opts.separator "" opts.separator "Known extensions: #{extensions.join(", ")}" extensions.each do |meth| msg = "options_#{meth}" send msg, opts, options if self.respond_to?(msg) end begin opts.parse! args rescue => e abort "#{e}\n\n#{opts}" end end options end
run(args = ARGV)
click to toggle source
# File lib/flay.rb, line 21 def self.run args = ARGV extensions = ["rb"] + Flay.load_plugins glob = "**/*.{#{extensions.join ","}}" expander = PathExpander.new args, glob files = expander.filter_files expander.process, DEFAULT_IGNORE flay = Flay.new Flay.parse_options args flay.process(*files.sort) flay end
Public Instance Methods
analyze(filter = nil)
click to toggle source
Prune, find identical nodes, and update masses.
# File lib/flay.rb, line 212 def analyze filter = nil self.prune self.hashes.each do |hash,nodes| identical[hash] = nodes[1..-1].all? { |n| n == nodes.first } end update_masses sorted = masses.sort_by { |h,m| exp = hashes[h].first [-m, exp.file, exp.line, exp.sexp_type.to_s] } sorted.map { |hash, mass| nodes = hashes[hash] next unless nodes.first.first == filter if filter same = identical[hash] node = nodes.first n = nodes.size bonus = "*#{n}" if same locs = nodes.sort_by { |x| [x.file, x.line] }.each_with_index.map { |x, i| extra = :fuzzy if x.modified? Location[x.file, x.line, extra] } Item[hash, node.sexp_type, bonus, mass, locs] }.compact end
filter_sexp(exp)
click to toggle source
Before processing, filter any sexp’s that match against filters specified in option[:filters]
. This changes the sexp itself.
# File lib/flay.rb, line 279 def filter_sexp exp exp.delete_if { |sexp| if Sexp === sexp then del = option[:filters].any? { |pattern| pattern.satisfy? sexp } del or (filter_sexp(sexp); false) end } end
process(*files)
click to toggle source
Process any number of files.
# File lib/flay.rb, line 178 def process(*files) # TODO: rename from process - should act as SexpProcessor files.each do |file| warn "Processing #{file}" if option[:verbose] ext = File.extname(file).sub(/^\./, "") ext = "rb" if ext.nil? || ext.empty? msg = "process_#{ext}" unless respond_to? msg then warn " Unknown file type: #{ext}, defaulting to ruby" msg = "process_rb" end begin sexp = begin send msg, file rescue => e warn " #{e.message.strip}" warn " skipping #{file}" nil end next unless sexp process_sexp sexp rescue SyntaxError => e warn " skipping #{file}: #{e.message}" end end end
process_erb(file)
click to toggle source
Process erb and parse the result. Returns the sexp of the parsed ruby.
# File lib/flay_erb.rb, line 10 def process_erb file erb = File.read file ruby = Erubi.new(erb).src begin RubyParser.new.process(ruby, file) rescue => e warn ruby if option[:verbose] raise e end end
process_rb(file)
click to toggle source
Parse a ruby file
and return the sexp.
– TODO: change the system and rename this to parse_rb.
# File lib/flay.rb, line 267 def process_rb file begin RubyParser.new.process(File.binread(file), file, option[:timeout]) rescue Timeout::Error warn "TIMEOUT parsing #{file}. Skipping." end end
process_sexp(pt)
click to toggle source
Process a sexp pt
.
# File lib/flay.rb, line 291 def process_sexp pt filter_sexp(pt).deep_each do |node| next :skip if node.none? { |sub| Sexp === sub } next :skip if node.mass < self.mass_threshold self.hashes[node.structural_hash] << node process_fuzzy node, option[:fuzzy] if option[:fuzzy] end end
update_masses()
click to toggle source
Reset total and recalculate the masses for all nodes in hashes
.
# File lib/flay.rb, line 251 def update_masses self.total = 0 masses.clear self.hashes.each do |hash, nodes| masses[hash] = nodes.first.mass * nodes.size masses[hash] *= (nodes.size) if identical[hash] self.total += masses[hash] end end