class DerivingLicense

Attributes

license_aliases[R]
license_details[R]

Public Class Methods

add_to_detected_licenses(licenses,detected_licenses,dep_name) click to toggle source
# File lib/deriving_license.rb, line 147
def self.add_to_detected_licenses(licenses,detected_licenses,dep_name)
  licenses.each do |l| # add each detected license to the results
    if l == "custom"
      detected_licenses["custom"] << "#{dep_name}" # the 'custom' key is
        # special and holds an array of the deps with custom licenses.
    else
      detected_licenses[l]+=1
    end
  end
end
describe(licenses) click to toggle source
# File lib/deriving_license.rb, line 74
def self.describe(licenses)
  # Print link to description of each license type, then attempt to determine
  # whether any notable restrictions apply (e.g. you can't sell this project,
  # you must include a copy of the GPL, etc)
  unrecognized = []
  output = []
  licenses.each do |l|
    unless l.first == "custom"
      instances = "(#{l.last} instance#{l.last == 1 ? "" : "s"})"
      key = @license_aliases[l.first]
      key ||= l.first
      if @license_details[key]
        output << "#{key}: #{@license_details[key][:name]} #{instances} [#{@license_details[key][:link]}]"
      else
        unrecognized << key
      end
    end
  end
  unless output.empty?
    puts "Detected #{output.count} known license#{output.count==1 ? "" : "s"}:"
    output.each{|o| puts o}
  end
  unless unrecognized.empty?
    puts "There #{unrecognized.count==1 ? "is" : "are"} also #{unrecognized.count} unrecognized license#{unrecognized.count==1 ? "" : "s"}: #{unrecognized.join(', ')}"
  end
  if licenses.has_key?("custom") and !licenses["custom"].empty?
    puts "The following dependencies have custom licenses: #{licenses["custom"].join(', ')}"
  end
end
determine_licenses(deps,detected_licenses,available_strategies) click to toggle source
# File lib/deriving_license.rb, line 128
def self.determine_licenses(deps,detected_licenses,available_strategies)
  licenses = []
  deps.each do |d|
    print "Determining license for #{d.name}:\n"
    # Try each license finding strategy...
    available_strategies.each do |s|
      print "\tTrying #{s} strategy..."
      licenses = eval("#{s}(\"#{d.name}\")")
      unless licenses.empty? # and break out of the search if successful
        print "#{licenses.count == 1 and licenses[0] == "custom" ? "CUSTOM\n" : "SUCCESS\n"}"
        break
      end
      print "FAILED\n"
    end
    add_to_detected_licenses(licenses,detected_licenses,d.name)
  end
  detected_licenses.delete("custom") if detected_licenses["custom"].empty?
end
find_deps(path, content) click to toggle source
# File lib/deriving_license.rb, line 104
def self.find_deps(path, content)
  deps = []
  if /gemspec/.match(path.downcase) # Gemspecs...
    deps = Gemnasium::Parser::Gemspec.new(content).dependencies
  else # Gemfiles...
    content = Gemnasium::Parser::Gemfile.new(content)
    gemspec_content = ""
    begin
      gemspec_content = File.open(content.gemspec, "r").read
    rescue # If we fail to read the gemspec, search current directory
      possible_gemspec_files = Find.find( File.dirname(content.gemspec) )
      raise "Gemfile calls out to unnamed gemspec, but multiple gemspec files found in gemfile's directory. Tell the developer to name their sources!" unless possible_gemspec_files.count == 1
      begin
        gemspec_content = File.open(possible_gemspec_files[0], "r").read
      rescue
        raise "Could not open gemspec file \"#{possible_gemspec_files[0]}\" that was called by gemfile."
      end
    end if content.gemspec? # If the gemfile sources a gemspec.
    deps = content.dependencies
    deps += Gemnasium::Parser::Gemspec.new(gemspec_content).dependencies unless gemspec_content.empty?
  end
  deps
end
from_gem_specification(dep) click to toggle source

STRATEGIES #

# File lib/deriving_license.rb, line 224
def self.from_gem_specification(dep)
  spec = get_gem_spec(dep)
  spec["licenses"]
end
from_license_file(dep) click to toggle source
# File lib/deriving_license.rb, line 229
def self.from_license_file(dep)
  licenses = []

  yield_gem_source_directory(dep) do |gem_source_directory|

    license_file_paths = []
    Find.find(gem_source_directory) do |path|
      license_file_paths << path if path =~ /(license|LICENSE)$/
    end
    break unless license_file_paths
  
    # Found filename with the word "license", so now look for known license
    # names in the rest of this filename.
    license_file_paths.each do |p|
      licenses = search_in_content(p)
      break unless licenses.empty?
    end
  
    if licenses.empty?
      # Failing that, open each file and check the content in a similar manner.
      licenses = search_in_paths(license_file_paths)
    end
    
    # Finally, if we couldn't find a known license, but a license file
    # exists, report it as custom.
    if licenses.empty?
      licenses << "custom"
    end
    
  end # end of call to yield_gem_source_directory
  
  return licenses

end
from_parsing_readme(dep) click to toggle source
# File lib/deriving_license.rb, line 284
def self.from_parsing_readme(dep)
  licenses = []

  yield_gem_source_directory(dep) do |gem_source_directory|

    readme_file_paths = []
    Find.find(gem_source_directory) do |path|
      if path =~ /(read\.me|readme)/i
        readme_file_paths << path 
      end
    end
    break if readme_file_paths.empty?
  
    # Open each readme file and check the content.
    licenses = search_in_paths(readme_file_paths)
  
  end # end of call to yield_gem_source_directory
  return licenses
end
from_scraping_homepage(dep) click to toggle source
# File lib/deriving_license.rb, line 264
def self.from_scraping_homepage(dep)
  spec = get_gem_spec(dep)
  licenses = []
  unless spec["homepage"] and !spec["homepage"].empty?
    return []
  end
  begin
    content = Curl::Easy.perform(spec["homepage"]){|easy| easy.follow_location = true; easy.max_redirects=nil}.body_str
  rescue
    return []
  end
  content.split('\n').each do |l|
    if /license/.match(l)
      licenses = search_in_content(l)
      break unless licenses.empty?
    end
  end
  return licenses # If we didn't return early, there's no match.
end
get_gem_spec(dep) click to toggle source
# File lib/deriving_license.rb, line 158
def self.get_gem_spec(dep)
  # Check spec cache first.
  spec = @specs_cache[dep]
  return spec if spec
  # See if the gem is installed locally, and if not add -r to call
  Bundler.with_clean_env do # This gets out of the bundler context.
    remote = /#{dep}/.match( `gem list #{dep}` ) ? "" : "-r "      
    yaml = `gem specification #{remote}#{dep} --yaml`
    spec = YAML.load(yaml, :safe => true)
  end
  @specs_cache[dep] = spec # Cache it.
  spec
end
run(path=nil, strategies=[]) click to toggle source
# File lib/deriving_license.rb, line 45
def self.run(path=nil, strategies=[])
  unless path
    raise ArgumentError.new("Path to Gemfile or Gemspec required")
  end
  
  unless /(gemfile|gemspec)+/.match(path.downcase)
    raise ArgumentError.new("Argument must be a path to Gemfile or Gemspec")
  end
  
  begin
    content = File.open(path, "r").read
  rescue
    raise "Invalid path to gemfile or gemspec."
  end
  
  available_strategies = strategies.select{|s| @strategies.include?(s)}
  raise "Supplied strategies must be from the following list: [#{@strategies.join(', ')}]" if available_strategies.count != strategies.count
  available_strategies = @strategies if strategies.empty?
  
  deps = find_deps(path, content)
  detected_licenses = Hash.new(0)
  detected_licenses["custom"]=[]
  
  # For each dependency specified...
  determine_licenses(deps,detected_licenses,available_strategies)
  
  detected_licenses
end
search_in_content(content) click to toggle source
# File lib/deriving_license.rb, line 210
def self.search_in_content(content)
  licenses = []
  (@license_details.keys + @license_aliases.keys).each do |n|
    if /#{n}/.match(content)
      licenses << n
      return licenses
    end
  end
  licenses
end
search_in_paths(paths) click to toggle source
# File lib/deriving_license.rb, line 190
def self.search_in_paths(paths)
  licenses = []
  paths.each do |p|
    if File.exist?(p)
      File.open(p).each_line do |l|
        if /license/i.match(l)
          # Found the word "license", so now look for known license names.
          (@license_details.keys + @license_aliases.keys).each do |n|
            if /#{n}/.match(l)
              licenses << n
              break
            end
          end
        end
      end
    end
  end
  licenses
end
yield_gem_source_directory(dep) { |unpack_dir, gem_filename| ... } click to toggle source
# File lib/deriving_license.rb, line 172
def self.yield_gem_source_directory(dep)
  # Yields a path to a temporary directory containing the gem source, then
  # cleans up after itself. Supply a block taking the gem_dir_path and
  # interrogate the directory as required.
  Bundler.with_clean_env do # This gets out of the bundler context.
    @fetch_output = `gem fetch #{dep}`
    @unpack_output = `gem unpack #{dep} --target=./deriving_license_tmp`
  end
  unpack_dir = /'([\/a-zA-Z0-9._\-]*)'/.match(@unpack_output)[1]
  gem_filename = /Downloaded\ ([\/a-zA-Z0-9._\-]*)$/.match(@fetch_output)[1]
  gem_filename += ".gem"
  
  yield unpack_dir, gem_filename if block_given?
  
  `rm -rf ./deriving_license_tmp` # Clean up tmp dir. Don't fuck this up.
  `rm #{gem_filename}` # Remove fetched gem.
end