#!/home/richmit/bin/verGo.sh ruby
# -*- Mode:Ruby; Coding:us-ascii-unix; fill-column:160 -*-

################################################################################################################################################################
##
# @file      debFindRelatedPackages.rb
# @author    Mitch Richling <https://www.mitchr.me>
# @Copyright Copyright 2015 by Mitch Richling.  All rights reserved.
# @brief     Find related development and documentation packages for installed libaraies.@EOL
# @Keywords  Debian Ubuntu apt packages apt-get
# @Std       Ruby 1.8
# @license
#
# ================================================================================================================================================================
# Copyright (c) 1994-2015, Mitchell Jay Richling <https://www.mitchr.me> 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.
# 
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without
#    specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.
# ================================================================================================================================================================
#
# @Notes
#
# Run time libraries are packaged separately from software development packages for those libraries.  For example, the PNG run time library might be in a
# package called 'libpng', but the files required to compile code using that library might be in a package called 'libpng-dev'.  In a similar manner, extra
# binaries or documentation for a library might be in other packages.  This script helps find packages related to the ones that are already installed by using a
# set of heuristics for finding similarly named packages.  These heuristics are far from perfect, and change as different package maintainers invent new naming
# conventions.  Still, this script can be a great help in locating associated packages.
#
# Note that the package lists generated by this script may contain conflicts, and thus the *entire* list might well not be something that can be installed. For
# example, it is possible to have the run time components for two versions of the same library, but it might be impossible to have both the developedment
# packages installed -- i.e. the header files have conflicting names. For this reason, the following usage methodology is suggested:
#
#   1) Run the script and collect the output into a file.  For example:
#
#          ruby getDev.rb > output.txt
#
#   2 Try to install everything in the file, but pay careful attention the output of the atp-get command!  For example, if it stops with conflicts, then cancel
#     the command, edit the file, and try again.
#
#          sudo apt-get `cat output.txt`
#            
################################################################################################################################################################

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
require 'optparse'
require 'optparse/time'

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Process command line arguments
reqBin = FALSE;
reqDev = FALSE;
reqDoc = FALSE;
opts = OptionParser.new do |opts|
  opts.banner = "Usage: debFindRelatedPackages <options>"
  opts.separator ""
  opts.separator "Options:"
  opts.on("-h",                "--help",    "Show this message")         { puts opts; exit; }
  opts.on(                     "--bin",     "Find BINary packages")      { reqBin = TRUE;   }
  opts.on(                     "--dev",     "Find DEVleopment packages") { reqDev = TRUE;   }
  opts.on(                     "--doc",     "Find DOCument packages")    { reqDoc = TRUE;   }
  opts.separator ""
  opts.separator "By default, all package types are found.  If any of the --bin, --dev, --doc arguments"
  opts.separator "are provided, then just the specified types will be found."
  opts.separator ""
end
opts.parse!(ARGV)

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Various search heuristics
xFormsForDoc = [ [ Regexp.new('-dev$'),          Regexp.new('-dev$'),          '-doc'    ],
                 [ Regexp.new('-dev$'),          Regexp.new('-dev$'),          '-info'   ],
                 [ Regexp.new('-doc$'),          Regexp.new('-doc$'),          '-info'   ],
                 [ Regexp.new('-info$'),         Regexp.new('-info$'),         '-doc'    ],
                 [ Regexp.new('-bin$'),          Regexp.new('-bin$'),          '-doc'    ],
                 [ Regexp.new('-bin$'),          Regexp.new('-bin$'),          '-info'   ],
                 [ Regexp.new('^lib(.+)-dev'),   Regexp.new('^lib(.+)-dev$'),  '\1-doc'  ],
                 [ Regexp.new('^lib(.+)-dev'),   Regexp.new('^lib(.+)-dev$'),  '\1-info' ],
                 [ Regexp.new('^lib(.+)-doc'),   Regexp.new('^lib(.+)-doc$'),  '\1-info' ],
                 [ Regexp.new('^lib(.+)-info'),  Regexp.new('^lib(.+)-info$'), '\1-doc'  ],
                 [ Regexp.new('.*'),             Regexp.new('$'),              '-doc'    ],
                 [ Regexp.new('.*'),             Regexp.new('$'),              '-info'   ] ]

xFormsForDev = [ [ Regexp.new('-doc$'),          Regexp.new('-doc$'),          '-dev'    ],
                 [ Regexp.new('-info$'),         Regexp.new('-info$'),         '-dev'    ],
                 [ Regexp.new('[0-9.]+$'),       Regexp.new('[0-9.]+$'),       '-dev'    ],
                 [ Regexp.new('-[0-9.]+$'),      Regexp.new('-[0-9.]+$'),      '-dev'    ],
                 [ Regexp.new('.*'),             Regexp.new('$'),              '-dev'    ],
                 [ Regexp.new('-bin$'),          Regexp.new('-bin$'),          '-lib'    ],
                 [ Regexp.new('.*'),             Regexp.new('^(.+)$'),         'lib\1'   ] ]

xFormsForBin = [ [ Regexp.new('-doc$'),          Regexp.new('-doc$'),          '-bin'    ],
                 [ Regexp.new('-info$'),         Regexp.new('-info$'),         '-bin'    ],
                 [ Regexp.new('^lib(.+)-dev'),   Regexp.new('^lib(.+)-dev$'),  '\1-bin'  ],
                 [ Regexp.new('^lib(.+)-doc'),   Regexp.new('^lib(.+)-doc$'),  '\1-bin'  ],
                 [ Regexp.new('^lib(.+)-info'),  Regexp.new('^lib(.+)-info$'), '\1-bin'  ] ]

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Figure out what to look for, and populate xforms
xforms = []
if(reqBin || reqDev || reqDoc) then
  xforms = xforms + (reqBin ? xFormsForBin : [])
  xforms = xforms + (reqDoc ? xFormsForDoc : [])
  xforms = xforms + (reqDev ? xFormsForDev : [])
else
  xforms = xFormsForBin + xFormsForDoc + xFormsForDev
end
           
#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Find all installed packages, and populate installedPackages
installedPackages = Hash.new
IO.popen("sudo dpkg-query -f='\${Package}\n' -W", 'r') do |aPipe|
  aPipe.each_line do |line|
    installedPackages[line.chomp] = true
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Find all available packages, and populate availablePackages
availablePackages = Hash.new
IO.popen('apt-cache pkgnames', 'r') do |aPipe|
  aPipe.each_line do |line|
    availablePackages[line.chomp] = true
  end
end

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
# Look for missing packages
installedPackages.keys.each do |pkg|
  xforms.each do |mtchRule, srchRule, replRule|
    if(mtchRule.match(pkg)) then
      npkg = pkg.sub(srchRule, replRule)
      if (availablePackages.member?(npkg)) then
        if ( !(installedPackages.member?(npkg))) then
          puts(npkg)
        end
      end
    end
  end
end