class YARD::CLI::Diff

@since 0.6.0
of a project (library, gem, working copy).
CLI command to return the objects that were added/removed from 2 versions

def added_objects(registry1, registry2)

def added_objects(registry1, registry2)
  registry2.reject {|o| registry1.find {|o2| o2.path == o.path } }
end

def all_objects

def all_objects
  return Registry.all if @verifier.expressions.empty?
  @verifier.run(Registry.all)
end

def cleanup(gemfile)

def cleanup(gemfile)
  dir = File.join(Dir.tmpdir, gemfile)
  log.info "Cleaning up #{dir}..."
  FileUtils.rm_rf(dir)
end

def description

def description
  'Returns the object diff of two gems or .yardoc files'
end

def expand_and_parse(gemfile, io)

def expand_and_parse(gemfile, io)
  dir = expand_gem(gemfile, io)
  generate_yardoc(dir)
  cleanup(gemfile)
end

def expand_gem(gemfile, io)

def expand_gem(gemfile, io)
  tmpdir = File.join(Dir.tmpdir, gemfile)
  FileUtils.mkdir_p(tmpdir)
  log.info "Expanding #{gemfile} to #{tmpdir}..."
  if Gem::VERSION >= '2.0.0'
    require 'rubygems/package/tar_reader'
    reader = Gem::Package::TarReader.new(io)
    reader.each do |pkg|
      next unless pkg.full_name == 'data.tar.gz'
      Zlib::GzipReader.wrap(pkg) do |gzio|
        tar = Gem::Package::TarReader.new(gzio)
        tar.each do |entry|
          file = File.join(tmpdir, entry.full_name)
          FileUtils.mkdir_p(File.dirname(file))
          File.open(file, 'wb') do |out|
            out.write(entry.read)
            begin
              out.fsync
            rescue NotImplementedError
              nil # noop
            end
          end
        end
      end
      break
    end
  else
    Gem::Package.open(io) do |pkg|
      pkg.each do |entry|
        pkg.extract_entry(tmpdir, entry)
      end
    end
  end
  tmpdir
end

def generate_yardoc(dir)

def generate_yardoc(dir)
  Dir.chdir(dir) do
    log.enter_level(Logger::ERROR) { Yardoc.run('-n', '--no-save') }
  end
end

def initialize

def initialize
  super
  @list_all = false
  @use_git = false
  @compact = false
  @modified = true
  @verifier = Verifier.new
  @old_git_commit = nil
  @old_path = Dir.pwd
  log.show_backtraces = true
end

def load_gem_data(gemfile)

def load_gem_data(gemfile)
  require_rubygems
  Registry.clear
  # First check for argument as .yardoc file
  [File.join(gemfile, '.yardoc'), gemfile].each do |yardoc|
    log.info "Searching for .yardoc db at #{yardoc}"
    next unless File.directory?(yardoc)
    Registry.load_yardoc(yardoc)
    Registry.load_all
    return true
  end
  # Next check installed RubyGems
  gemfile_without_ext = gemfile.sub(/\.gem$/, '')
  log.info "Searching for installed gem #{gemfile_without_ext}"
  YARD::GemIndex.each.find do |spec|
    next unless spec.full_name == gemfile_without_ext
    yardoc = Registry.yardoc_file_for_gem(spec.name, "= #{spec.version}")
    if yardoc
      Registry.load_yardoc(yardoc)
      Registry.load_all
    else
      log.enter_level(Logger::ERROR) do
        olddir = Dir.pwd
        Gems.run(spec.name, spec.version.to_s)
        Dir.chdir(olddir)
      end
    end
    return true
  end
  # Look for local .gem file
  gemfile += '.gem' unless gemfile =~ /\.gem$/
  log.info "Searching for local gem file #{gemfile}"
  if File.exist?(gemfile)
    File.open(gemfile, 'rb') do |io|
      expand_and_parse(gemfile, io)
    end
    return true
  end
  # Remote gemfile from rubygems.org
  url = "http://rubygems.org/downloads/#{gemfile}"
  log.info "Searching for remote gem file #{url}"
  begin
    # Note: In Ruby 2.4.x, URI.open is a private method. After
    # 2.5, URI.open behaves much like Kernel#open once you've
    # required 'open-uri'
    OpenURI.open_uri(url) {|io| expand_and_parse(gemfile, io) }
    return true
  rescue OpenURI::HTTPError
    nil # noop
  end
  false
end

def load_git_commit(commit)

def load_git_commit(commit)
  Registry.clear
  commit_path = 'git_commit' + commit.gsub(/\W/, '_')
  tmpdir = File.join(Dir.tmpdir, commit_path)
  log.info "Expanding #{commit} to #{tmpdir}..."
  Dir.chdir(@old_path)
  FileUtils.mkdir_p(tmpdir)
  FileUtils.cp_r('.', tmpdir)
  Dir.chdir(tmpdir)
  log.info("git says: " + `git reset --hard #{commit}`.chomp)
  generate_yardoc(tmpdir)
ensure
  Dir.chdir(@old_path)
  cleanup(commit_path)
end

def modified_objects(registry1, registry2)

def modified_objects(registry1, registry2)
  registry1.select do |obj|
    case obj
    when CodeObjects::MethodObject
      registry2.find {|o| obj == o && o.source != obj.source }
    when CodeObjects::ConstantObject
      registry2.find {|o| obj == o && o.value != obj.value }
    end
  end.compact
end

def optparse(*args)

def optparse(*args)
  opts = OptionParser.new
  opts.banner = "Usage: yard diff [options] oldgem newgem"
  opts.separator ""
  opts.separator "Example: yard diff yard-0.5.6 yard-0.5.8"
  opts.separator ""
  opts.separator "If the files don't exist locally, they will be grabbed using the `gem fetch`"
  opts.separator "command. If the gem is a .yardoc directory, it will be used. Finally, if the"
  opts.separator "gem name matches an installed gem (full name-version syntax), that gem will be used."
  opts.on('-a', '--all', 'List all objects, even if they are inside added/removed module/class') do
    @list_all = true
  end
  opts.on('--compact', 'Show compact results') { @compact = true }
  opts.on('--git', 'Compare versions from two git commit/branches') do
    @use_git = true
  end
  opts.on('--query QUERY', 'Only diff filtered objects') do |query|
    @verifier.add_expressions(query)
  end
  opts.on('--no-modified', 'Ignore modified objects') do
    @modified = false
  end
  common_options(opts)
  parse_options(opts, args)
  unless args.size == 2
    log.puts opts.banner
    exit(0)
  end
  args
end

def removed_objects(registry1, registry2)

def removed_objects(registry1, registry2)
  registry1.reject {|o| registry2.find {|o2| o2.path == o.path } }
end

def require_rubygems

def require_rubygems
  require 'rubygems'
  require 'rubygems/package'
rescue LoadError => e
  log.error "Missing RubyGems, cannot run this command."
  raise(e)
end

def run(*args)

def run(*args)
  registry = optparse(*args).map do |gemfile|
    if @use_git
      load_git_commit(gemfile)
      all_objects
    elsif load_gem_data(gemfile)
      log.info "Found #{gemfile}"
      all_objects
    else
      log.error "Cannot find gem #{gemfile}"
      nil
    end
  end.compact
  return if registry.size != 2
  first_object = nil
  [["Added objects", "A", added_objects(*registry)],
      ["Modified objects", "M", modified_objects(*registry)],
      ["Removed objects", "D", removed_objects(*registry)]].each do |name, short, objects|
    next if short == "M" && @modified == false
    next if objects.empty?
    last_object = nil
    all_objects_notice = false
    log.puts name + ":" unless @compact
    objects.sort_by(&:path).each do |object|
      if !@list_all && last_object && object.parent == last_object
        log.print " (...)" unless all_objects_notice
        all_objects_notice = true
        next
      elsif @compact
        log.puts if first_object
      else
        log.puts
      end
      all_objects_notice = false
      log.print "" + (@compact ? "#{short} " : "  ") +
                object.path + " (#{object.file}:#{object.line})"
      last_object = object
      first_object = true
    end
    unless @compact
      log.puts; log.puts
    end
  end
  log.puts if @compact
end