#!/usr/bin/env ruby
# -*- ruby -*-
#
# Copyright (c) 2000-2001 Akinori MUSHA
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#

RCS_ID = %q$Idaemons: /home/cvs/pkgtools/portversion,v 1.38 2001/08/25 10:29:59 knu Exp $
RCS_REVISION = RCS_ID.split[2]
MYNAME = File.basename($0)

require "optparse"
require "pkgtools"

$command_output = false
$command = 'portupgrade'
$regex = false
$inv_limit_chars = nil
$limit_chars = nil
$test_version = false
$recursive = false
$upward_recursive = false
$exclude_packages = []

COLUMNSIZE = 24
NEXTLINE = "\n%*s" % [5 + COLUMNSIZE, '']

INEQ_SIGNS = [
  '<',
  '=',
  '>',
  '?',
]

SUGGESTIONS = [
  'needs updating (port has %s)',
  'up-to-date with port',
  'succeeds port (port has %s)',
  'origin unknown',
]

def main(argv)
  usage = "usage: #{MYNAME} [-hEv] [-l limit_chars] [-L inv_limit_chars]\n" +
    "         [pkgname_glob ...]\n" +
    "       #{MYNAME} [-v] -t ver0 ver1 [ver2 ...]"

  banner = <<-"EOF"
#{MYNAME} rev.#{RCS_REVISION}

#{usage}
    EOF

  OptionParser.new(banner, COLUMNSIZE) do |opts|
    opts.def_option("-h", "--help", "Show this message") {
      print opts
      return 0
    }

    opts.def_option("-c", "--command-output", "Enable command output") {
      |$command_output|
    }

    opts.def_option("-C", "--command-args=ARGS", "Specify the arguments for portupgrade in command#{NEXTLINE}output (default: none)") {
      |command_args|
      $command = 'portupgrade ' + command_args
    }

    opts.def_option("-E", "--regex", "Use extended regular expression for pkg_glob") {
      |$regex|
    }

    opts.def_option("-l", "--limit=CHARS", "Only include the packages with the specified#{NEXTLINE}status flags") {
      |$limit_chars|
    }

    opts.def_option("-L", "--inv-limit=CHARS", "Exclude the packages with the specified#{NEXTLINE}status flags") {
      |$inv_limit_chars|
    }

    opts.def_option("-r", "--recursive", "Check for all those depending on the given#{NEXTLINE}packages as well") {
      |$recursive|
    }

    opts.def_option("-R", "--upward-recursive", "Check for all those required by the given#{NEXTLINE}packages as well") {
      |$upward_recursive|
    }

    opts.def_option("-t", "--test=VERSION", "Compare the version with the following one(s),#{NEXTLINE}and print the result(s)") {
      |$test_version|
      $test_version = PkgVersion.new($test_version)
    }

    opts.def_option("-v", "--verbose", "Be verbose") {
      |$verbose|
    }

    opts.def_option("-x", "--exclude=GLOB", "Exclude packages matching the specified glob#{NEXTLINE}pattern") {
      |arg|
      begin
	pattern = $regex ? Regexp.new(arg) : arg
      rescue RegexpError => e
	warning_message e.message.capitalize
	break
      end

      $exclude_packages |= $pkgdb.glob(pattern, false)
    }

    opts.def_tail_option '
pkgname_glob is one of these: a full pkgname, a pkgname w/o version,
a shell glob pattern in which you can use the wildcards *, ?, and [..],
or an extended regular expression if -E/--regex is specified.
If none is given, all the installed packages are checked.

Environment Variables [default]:
    PORTSDIR                 ports directory [/usr/ports]
    PKG_DBDIR                packages DB directory [/var/db/pkg]
    PORTS_INDEX              ports index file [$PORTSDIR/INDEX]
    PORTS_DBDIR              ports db directory [$PORTSDIR]'

    all = '*'

    argv << all

    tasks = []

    begin
      opts.order(*argv) do |arg|
	next if arg.empty?

	if arg.equal? all
	  pattern = arg

	  $recursive = false
	  $upward_recursive = false
	else
	  all.replace ''

	  pattern = $pkgdb.strip(arg, true)

	  if pattern.nil?
	    pattern = arg

	    if $regex && pattern[-1] == ?/
	      pattern[-1] = ?$
	    end

	    if pattern.include?('/')
	      # `|| arg' just in case
	      pattern = $pkgdb.strip(pattern) || arg
	    end
	  end

	  if $regex
	    begin
	      pattern = Regexp.new(pattern)
	    rescue RegexpError => e
	      warning_message e.message.capitalize
	      next
	    end
	  end

	  if $test_version
	    version = PkgVersion.new(arg)
	    x = $test_version <=> version
	    sign = INEQ_SIGNS[if x < 0 then 0 elsif x == 0 then 1 else 2 end]

	    if $verbose
	      puts [$test_version, sign, version].join(' ')
	    else
	      puts sign
	    end
	    
	    next
	  end
	end

	list = []

	$pkgdb.glob(pattern, false).each do |pkgname|
	  list |= $pkgdb.recurse(pkgname, $recursive, $upward_recursive)
	end

	if list.empty?
	  warning_message "No package matching '#{arg}' was found."
	  next
	end

	list -= $exclude_packages

	if list.empty?
	  warning_message "All the packages matching '#{arg}' were excluded."
	  next
	end

	tasks |= list
      end
    rescue OptionParser::ParseError => e
      STDERR.puts "#{MYNAME}: #{e}", usage
      exit 64
    rescue ArgumentError => e
      STDERR.puts "#{MYNAME}: #{e}", usage
      exit 64
    end

    tasks -= $exclude_packages

    check_pkgs tasks.sort.collect {|pkgname| PkgInfo.new(pkgname) }

    if $command_output
      printf %Q`\nif [ X"$pkgs" != X"" ]; then\n  %s $pkgs\nfi\n`, $command
    end
  end

  0
end

def check_pkg(pkg)
  pkgname = pkg.fullname
  origin = pkg.origin

  if origin.nil?
    return [3, nil]	# ? (origin unknown)
  end

  portinfo = $portsdb[origin]

  if portinfo.nil?
    return [3, nil]	# ? (origin not found)
  end

  newpkg = portinfo.pkgname

  cmp = newpkg.version <=> pkg.version

  if cmp > 0 then
    return [0, newpkg]	# >
  elsif cmp < 0 then
    return [2, newpkg]	# <
  end

  return [1, newpkg]	# =
end

def check_pkgs(pkgs)
  pkgs.each do |pkg|
    ineq, newpkg = check_pkg(pkg)

    next if $command_output && ineq != 0

    sign = INEQ_SIGNS[ineq]

    next if ($limit_chars && !$limit_chars.include?(sign)) ||
      ($inv_limit_chars && $inv_limit_chars.include?(sign))

    if $command_output
      printf %Q`#\n#  %s\n#  %s\n#\npkgs="$pkgs %s"\n\n`,
	pkg.name,
	SUGGESTIONS[ineq] % [ newpkg ? newpkg.version : nil ],
	pkg.fullname
    else
      if $verbose then
	printf "%-26s  %s  %s\n",
	  pkg.fullname,
	  INEQ_SIGNS[ineq],
	  SUGGESTIONS[ineq] % [ newpkg ? newpkg.version : nil ]
      else
	printf "%-26s  %s\n",
	  pkg.name,
	  INEQ_SIGNS[ineq]
      end
    end
  end
rescue PortsDB::IndexFileError
  warning_message "Error reading the ports INDEX."
rescue PortsDB::DBError
  warning_message "Error reading the ports database."
end

def signal_handler(sig)
  puts "\nInterrupted."

  stty_sane

  exit
end

if $0 == __FILE__
  for sig in [2, 3, 15]
    trap(sig) do
      signal_handler(sig)
    end
  end

  exit main(ARGV)
end
