Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/portupgrade
Path: blob/master/bin/portcvsweb
102 views
#!/usr/bin/env ruby
# -*- ruby -*-
# vim: set sts=2 sw=2 ts=8 et:
#
# Copyright (c) 2000-2004 Akinori MUSHA
# Copyright (c) 2005,2006 KOMATSU Shinichiro
# Copyright (c) 2006-2008 Sergey Matveychuk <[email protected]>
# Copyright (c) 2009-2012 Stanislav Sedov <[email protected]>
# Copyright (c) 2012 Bryan Drewery <[email protected]>
#
# 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.
#

MYNAME = File.basename($0)

require "optparse"
require 'pkgtools'

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

OS_DEFAULT = 'FreeBSD'

MAKEFILE = 'Makefile'

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

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

  PathInfo = Struct.new(:dir, :file, :path)
  UriInfo = Struct.new(:uri, :base, :path, :os, :anchor)

  def get_pathinfo(path)
    if File.directory?(path)
      path = File.expand_path(path)
      return PathInfo.new(path, nil, path)
    elsif File.exist?(path)
      path = File.expand_path(path)
      dir, file = File.split(File.expand_path(path))
      return PathInfo.new(dir, file, path)
    else
      return nil
    end
  end
  module_function :get_pathinfo

  module GetCvsInfo
    module_function
    def get_cvsinfo(file)
      uri_info = check_cvs(file)

      if file
	if !uri_info
	  (uri_info = check_file(file, true)) \
	  || (uri_info = check_makefile)
	end
      else
	uri_info = check_makefile
      end

      if !uri_info
	dir, rel = File.split(Dir.pwd)

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

	  dir, last = File.split(dir)

	  rel = File.join(last, rel)
	end

	if !uri_info
	  uri_info = UriInfo.new
	  uri_info.path = file ? file.sub(/^\//, '') : ''
	end
      end

      uri_info
    end

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

      uri_info = UriInfo.new

      pwd = Dir.pwd if $verbose

      open('CVS/Repository') do |f|
	puts "Reading #{pwd}/CVS/Repository..." if $verbose
	uri_info.path = File.join(f.gets.chomp, file || '')
      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
	      uri_info.anchor = '#rev' + ary[2]
	      break
	    end
	  end
	end rescue nil
      end

      uri_info
    end

    def check_makefile(file = nil, subdir = nil)
      uri_info = check_file(MAKEFILE)

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

	uri_info.anchor = nil if file.nil?
      end

      uri_info
    end

    def check_file(file, set_anchor = false)
      uri_info = nil

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

	if CVSTAG_RE =~ line
	  uri_info = UriInfo.new
	  uri_info.os = $1
	  uri_info.anchor = '#rev' + $3 if set_anchor

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

      uri_info
    rescue
      return false
    end
  end

  module SearchDB
    module_function
    def search_pkgdb(file)
      ret = nil

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

	  puts "Searching pkgdb..." if $verbose

	  if pkg = $pkgdb.glob(pattern)[0]
	    #puts "Inquiring the CVSweb site (ports)..." if $verbose
	    ret = pkg.origin
	  end
	rescue RegexpError => e
	  # warning_message e.message.capitalize
	end
      end

      ret
    end

    def search_portsdb(file)
      ret = nil

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

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

      puts "Searching portsdb..." if $verbose

      if port = $portsdb.glob(pattern)[0]
	ret = port.origin
      end

      ret
    end
  end

  class Uri_Base
    include GetCvsInfo

    public
    def Uri_Base.path2uri(arg, check_cvsinfo)
      uri_info = nil

      if check_cvsinfo
	savedir = Dir.pwd
	Dir.chdir(arg.dir)
	begin
	  uri_info = GetCvsInfo::get_cvsinfo(arg.file)
	  if !uri_info.os
	    uri_info.os = guess_os(arg.file) || OS_DEFAULT
	  end
	ensure
	  Dir.chdir(savedir)
	end
      else
	uri_info = UriInfo.new
	uri_info.os = guess_os(arg.file) || OS_DEFAULT
	uri_info.path = arg
      end

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

      nil
    end

    def Uri_Base.guess_os(hint)
      (hint ? guess_tag(hint) : nil) || guess_tag(RUBY_PLATFORM) || nil
    end
  end

  class Uri_CvsWeb < Uri_Base
    URI_BASE = {
      'FreeBSD' => 'http://svnweb.FreeBSD.ORG/',
      'NetBSD' => 'http://cvsweb.netbsd.org/bsdweb.cgi/',
      'OpenBSD' => 'http://www.openbsd.org/cgi-bin/cvsweb/',
    }

    def Uri_CvsWeb.path2uri(arg, check_cvsinfo)
      uri_info = Uri_Base::path2uri(arg, check_cvsinfo)
      uri_info.base = URI_BASE[uri_info.os]
      uri_info.uri = uri_info.base + uri_info.path + (uri_info.anchor || '')
      uri_info
    end

    def Uri_CvsWeb.search_pkgdb(file)
      if origin = SearchDB::search_pkgdb(file)
	uri_info = UriInfo.new
	uri_info.os = Uri_Base.guess_os(nil) || OS_DEFAULT
	uri_info.base = URI_BASE[uri_info.os]
        if uri_info.os == "FreeBSD"
          uri_info.path = File.join('ports/head', origin)
        else
          uri_info.path = File.join('ports', origin)  # XXX
        end
	uri_info.uri = uri_info.base + uri_info.path
	uri_info
      else
	nil
      end
    end

    def Uri_CvsWeb.search_portsdb(file)
      ret = nil
      if origin = SearchDB::search_portsdb(file)
	if PortCvsWeb::get_pathinfo(File.join($ports_dir, origin))
	  uri_info = UriInfo.new
	  uri_info.os = Uri_Base.guess_os(nil) || OS_DEFAULT
	  uri_info.base = URI_BASE[uri_info.os]
          if uri_info.os == "FreeBSD"
            uri_info.path = File.join('ports/head', origin)
          else
            uri_info.path = File.join('ports', origin)  # XXX
          end
	  uri_info.uri = uri_info.base + uri_info.path
	  ret = uri_info
	end
      end
      ret
    end
  end

  class Uri_FreshPorts < Uri_Base
    URI_BASE = "http://www.FreshPorts.org/"
    DATE_PATH = 'date.php?date='
    DATE_FMT = '%Y/%m/%d'

    TZ_FRESHPORTS = 'GMT'

    DATESPEC_DAYBACK_RE = /^-\d+$/
    DATESPEC_DATE_RE = %r|^\d[\d/-]*$|

    def Uri_FreshPorts.path2uri(arg, check_cvsinfo)
      uri_info = Uri_Base::path2uri(arg, check_cvsinfo)
      uri_info.base = URI_BASE
      uri_info.uri = uri_info.base + uri_info.path 
      uri_info
    end

    def Uri_FreshPorts.date2uri
      if (DATESPEC_DAYBACK_RE =~ $datespec) || (DATESPEC_DATE_RE =~ $datespec)
	uri_info = UriInfo.new
	uri_info.base = URI_BASE
	uri_info.os = 'FreeBSD'
	uri_info.path = DATE_PATH + gettime.strftime(DATE_FMT)
	uri_info.uri = uri_info.base + uri_info.path
	uri_info
      else
	nil
      end
    end

    def Uri_FreshPorts.search_pkgdb(file)
      if origin = SearchDB::search_pkgdb(file)
	uri_info = UriInfo.new
	uri_info.os = Uri_Base.guess_os(nil) || OS_DEFAULT
	uri_info.base = URI_BASE
	uri_info.path = origin
	uri_info.uri = uri_info.base + uri_info.path
	uri_info
      else
	nil
      end

    def Uri_FreshPorts.search_portsdb(file)
      ret = nil
      if origin = SearchDB::search_portsdb(file)
	if PortCvsWeb::get_pathinfo(File.join($ports_dir, origin))
	  uri_info = UriInfo.new
	  uri_info.os = Uri_Base.guess_os(nil) || OS_DEFAULT
	  uri_info.base = URI_BASE
	  uri_info.path = origin
	  uri_info.uri = uri_info.base + uri_info.path
	  ret = uri_info
	end
      end
      ret
    end
    end

    private
    def Uri_FreshPorts.gettime
      time = nil

      tz_back = ENV['TZ']
      ENV['TZ'] = TZ_FRESHPORTS
      if DATESPEC_DAYBACK_RE =~ $datespec
	time = Time.at(Time.now + $datespec.to_i*24*60*60)
      else   # DATESPEC_DATE_RE =~ datespec
	time = Time.parse($datespec)
      end
      ENV['TZ'] = tz_back

      time
    end
  end
end

def init_global
  $tempdir = ""
end

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

  banner = <<-"EOF"
#{MYNAME} #{Version} (#{PkgTools::DATE})

#{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") { |v|
      $noconfig = v
    }

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

    opts.def_option("-F", "--freshports", 
                    "View FreshPorts.org instead of cvsweb") { |v|
      $freshports = v
    }

    opts.def_option("-D [DATE]", "--datespec [DATE]",
                    "View the FreshPorts.org of DAYBACK days ago.") { |v|
      $datespec = v
    }

    begin
      init_global
      init_pkgtools_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 = argv[0] || '.'

  if $freshports
    if $datespec
      uri = PortCvsWeb::Uri_FreshPorts.date2uri.uri
    else
      uri = resolve_uri(file, PortCvsWeb::Uri_FreshPorts)
    end
    open_browser!(uri)
  else
    if uri = resolve_uri(file, PortCvsWeb::Uri_CvsWeb)
      open_browser!(uri)
    else
      STDERR.puts "#{MYNAME}: #{file}: not found."
      exit 1
    end
  end
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 resolve_uri(file, uri_class)
  uri = nil

  if pathinfo = PortCvsWeb.get_pathinfo(file)
    return uri_class.path2uri(pathinfo, true).uri
  else
    # search pkgdb
    if (uri_info = uri_class.search_pkgdb(file)) && uri_exist?(uri_info.uri)
      return uri_info.uri
    end

    # search portsdb
    if uri_info = uri_class.search_portsdb(file)
      puts "search_portsdb"
      uri = uri_info.uri
    else
      puts "search cvsweb"
      uri = search_cvsweb(file)
    end
    return uri
  end
end

def search_cvsweb(file)
  puts "Inquiring the CVSweb site..." if $verbose
  uri = PortCvsWeb::Uri_CvsWeb::URI_BASE[OS_DEFAULT] + file.sub(/^\//, '')
  if uri_exist?(uri)
    return uri
  end

  puts "Inquiring the CVSweb site (ports)..." if $verbose
  uri = PortCvsWeb::Uri_CvsWeb::URI_BASE[OS_DEFAULT] + File.join('ports/head', file)
  if uri_exist?(uri)
    return uri
  end

  nil
end

def uri_exist?(uri)
  if system('/usr/bin/fetch', '-qs', uri)
    return true
  else
    return false
  end
end

main(ARGV)