#!/usr/local/bin/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/bin/portcvsweb,v 1.14 2002/02/18 01:34:31 knu Exp $
RCS_REVISION = RCS_ID.split[2]
MYNAME = File.basename($0)

require "optparse"
require 'pkgtools'

BROWSER = ENV['BROWSER'] || 'w3m:lynx:links:mozilla:netscape'

CVSWEB_URI = {
  'FreeBSD' => 'http://www.FreeBSD.org/cgi/cvsweb.cgi/',
  'NetBSD' => 'http://cvsweb.netbsd.org/bsdweb.cgi/',
  'OpenBSD' => 'http://www.openbsd.org/cgi-bin/cvsweb/',
}

CVSTAGS = CVSWEB_URI.keys

CVSTAG_RE = /\$(#{CVSTAGS.join('|')}):\s+(\S+),v\s*(\d[\d.]*)/

MAKEFILE = 'Makefile'

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

def init_global
  $tag = nil
  $path = nil
  $anchor = nil
  $file = nil
end

def main(argv)
  usage = <<-"EOF"
usage: #{MYNAME} [-hqv] {pkgname_glob|portorigin_glob|file|directory}
    EOF

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

#{usage}
  EOF

  dry_parse = true

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

    opts.def_option("-q", "--noconfig", "Do not read pkgtools.conf") {
      |$noconfig|
    }

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

    begin
      init_global

      rest = opts.order(*argv)

      unless $noconfig
	init_global
	load_config
      else
	argv = rest
      end

      dry_parse = false

      opts.order!(argv)
    rescue OptionParser::ParseError => e
      STDERR.puts "#{MYNAME}: #{e}", usage
      exit 64
    end
  end

  file = $file = argv[0] || '.'

  retried = false

  loop do
    if File.directory?($file)
      $file = File.expand_path($file)

      Dir.chdir($file)
      $file = nil

      break
    elsif File.exist?($file)
      $file = File.expand_path($file)

      dir, $file = File.split(File.expand_path($file))
      Dir.chdir(dir)

      break
    elsif retried
      puts "Inquiring the CVSweb site..." if $verbose

      uri = CVSWEB_URI['FreeBSD'] + file.sub(/^\//, '')

      open_browser(uri)

      puts "Inquiring the CVSweb site (ports)..." if $verbose

      uri = CVSWEB_URI['FreeBSD'] + File.join('ports', file)

      open_browser(uri)

      break
    else
      retried = true

      # search pkgdb
      if pattern = $pkgdb.strip($file)
	begin
	  pattern = parse_pattern($file)

	  puts "Searching pkgdb..." if $verbose

	  if pkg = $pkgdb.glob(pattern)[0]
	    $file = File.join($ports_dir, pkg.origin)
	    next
	  end
	rescue RegexpError => e
	  # warning_message e.message.capitalize
	end
      end

      # search portsdb
      pattern = $portsdb.strip($file) || $file    # allow pkgname_glob

      begin
	pattern = parse_pattern(pattern)
      rescue RegexpError => e
	# warning_message e.message.capitalize
	next
      end

      puts "Searching portsdb..." if $verbose

      if port = $portsdb.glob(pattern)[0]
	$file = File.join($ports_dir, port.origin)
	next
      end
    end
  end

  if $file && !File.file?($file)
    STDERR.puts "#{MYNAME}: #{file}: not found."
    exit 1
  end

  check_cvs()

  if $file
    if $tag.nil? || $path.nil?
      check_file($file, true) || check_makefile
    end
  else
    check_makefile
  end

  if $path.nil?
    dir, rel = File.split(Dir.pwd)

    until dir == '/'
      Dir.chdir(dir)
      check_makefile(rel) and break

      dir, last = File.split(dir)

      rel = File.join(last, rel)
    end

    if $path.nil?
      open_browser!(CVSWEB_URI['FreeBSD'] + file.sub(/^\//, ''))
    end
  end

  if $tag.nil?
    $tag = guess_tag(Dir.pwd) || guess_tag(RUBY_PLATFORM) || 'FreeBSD'
  end

  uri = CVSWEB_URI[$tag] + $path + ($anchor || '')

  open_browser!(uri)
rescue => e
  STDERR.puts e.message
  exit 1
end

def open_browser(uri)
  if uri_exist?(uri)
    open_browser!(uri)
  end
end

def open_browser!(uri)
  BROWSER.split(':').each do |s|
    s.strip!

    next if s.empty?

    s << ' %s' if not s.include?('%s')

    system('/bin/sh', '-c', format(s, uri)) and exit 0
  end

  STDERR.puts 'No browser is available - please define BROWSER.'
  exit 1
end

def check_cvs
  File.directory?('CVS') or return

  pwd = Dir.pwd if $verbose

  open('CVS/Repository') do |f|
    puts "Reading #{pwd}/CVS/Repository..." if $verbose
    $path = File.join(f.gets.chomp, $file || '')
  end rescue nil

  open('CVS/Root') do |f|
    puts "Reading #{pwd}/CVS/Root..." if $verbose
    $tag = guess_tag(f.gets)
  end rescue nil

  if $file
    open('CVS/Entries') do |f|
      puts "Reading #{pwd}/CVS/Entries..." if $verbose
      f.each do |line|
	ary = line.split('/')

	if ary[1] == $file
	  $anchor = '#rev' + ary[2]
	  break
	end
      end
    end rescue nil
  end
end

def check_makefile(subdir = nil)
  ret = check_file(MAKEFILE)

  if $path
    if subdir
      $path = File.join(File.dirname($path), subdir, $file || '')
    else
      $path = File.join(File.dirname($path), $file || '')
    end

    $anchor = nil if $file.nil?
  end

  ret
end

def check_file(file, set_anchor = false)
  IO::popen("/usr/bin/ident #{file} 2>/dev/null").each do |line|
    puts "Checking ident of #{file}..." if $verbose

    if CVSTAG_RE =~ line
      $tag = $1
      $anchor = '#rev' + $3 if set_anchor

      if $path.nil?
	case $tag
	when 'NetBSD'
	  if %r:/([^/]+/[^/]+)$: =~ Dir.pwd
	    $path = File.join('pkgsrc', $1, file)
	  end
	when 'OpenBSD'
	  category = `make -V CATEGORIES`.split[0]
	  if %r"/(#{Regexp.quote(category)}(?:/[^/]+){1,3})$" =~ Dir.pwd
	    $path = File.join('ports', $1, file)
	  end
	else
	  $path = $2
	end
	break
      end
    end
  end

  true
rescue
  return false
end

def guess_tag(hint)
  CVSTAGS.each do |name|
    if /#{name}/i =~ hint
      return name
    end
  end

  nil
end

def uri_exist?(uri)
  if system("/usr/bin/fetch -s '#{uri}' >/dev/null")
    return true
  else
    return false
  end
end

main(ARGV)
