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

################################################################################################################################################################
##
# @file      debCmpPackages.rb
# @author    Mitch Richling <https://www.mitchr.me>
# @Copyright Copyright 2015 by Mitch Richling.  All rights reserved.
# @brief     Print reports about package diffrences.@EOL
# @Keywords  ubuntu debian apt apt-get packages differences compare
# @Std       Ruby 2.0
# @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
#
# This script is intended to make it easy to spot the differences between the packages installed on one or more systems.
#
# Usage: # debCmpPackages.rb file_with_package_names ...
#
#    A file name of '.' means to use the packages on the current system for that file -- requires sudo access
#    
#    Each file should contain a list of packages.  A file containing a suitable list of packages with dpkg-query or even this script:
#      * sudo dpkg-query -f='${Package}\n' -W  > file_with_package_names
#      * debCmpPackages.rb . | awk '{print $5}'   > file_with_package_names
#    
#    A report is printed on STDOUT showing each package and which files it appears in.  The report is sorted so that common inclusion patterns are
#    adjacent. Here is an example report (with two files -- just like the two examples above):
#              
#              H   #   1   2 
#              S ============
#              D 002 YES YES : zsh
#              D 002 YES YES : zlib1g-dev
#              ...
#              D 002 YES YES : zeitgeist-core
#              S ============
#              D 001 YES --- : zlib-bin
#              ...
#              D 001 YES --- : z88-doc
#              S ============
#              D 001 --- YES : zsh-dev
#              ...
#
# Some example applications:
#    * Get a report showing the packages on the current system:
#        ./debCmpPackages.rb .
#    * Get a report regarding the package differences between two systems:
#        ./debCmpPackages.rb debian_7.8 debian_8.0
#    * Get a report regarding the package differences between another system (debian_7.8) and the current system:
#        ./debCmpPackages.rb debian_7.8 .
#
# Note that the format is intended to be machine readable by line oriented UNIX tools, and this opens up lots of new application possibilities:
#    * Get a list of package names on the current system:
#         ./debCmpPackages.rb . | awk '{print $5}'
#    * Get a list of packages in the file 'debin_7.8' that are NOT on the current system:
#        ./debCmpPackages.rb debian_7.8 . | awk '$1=="D" && $3=="YES" && $4!="YES" {print $6}'
#    * Get a list of packages in the file 'debin_7.8' that are on the current system:
#        ./debCmpPackages.rb debian_7.8 . | awk '$1=="D" && $2=="002" {print $0}' | wc -l
#                          or -- if you just must do it all with awk. ;)
#        ./debCmpPackages.rb debian_7.8 . | awk 'BEGIN { c=1}; $1=="D" && $2=="002" {c++}; END {print c};'
#    * Count the number of packages in common between two systems:
#        ./debCmpPackages.rb debian_7.8 debian_8.0 | awk '$1=="D" && $2=="002" {print $0}' | wc -l
#    * Count the number of packages only on one system:
#        ./debCmpPackages.rb debian_7.8 debian_8.0 | awk '$1=="D" && $2=="001" {print $0}' | wc -l
#
# The report format
#    * First char of each line:
#       * 'H' -- header line    -- Only the first line
#       * 'D' -- data line      -- one for each package.
#       * 'S' -- separator line -- printed when the inclusion pattern changes
#    * Each 'D' line contains four space separated fields after the 'D ':
#       * Zero padded, three digit number indicating the number of files the package was contained in
#       * 'YES' or '---' for each file name given to indicate if the package was in the file
#       * ':'
#       * package name
#
################################################################################################################################################################

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
require 'set'

#---------------------------------------------------------------------------------------------------------------------------------------------------------------
pkgListAll = Set.new
pkgLists = Array.new
if(ARGV.length >= 1) then
  ARGV.each do |fileName|
    curSet = Set.new
    if (fileName == '.') then
      IO.popen('sudo dpkg-query -f="\${Package}\n" -W', 'r') do |pipe|
        pipe.each_line do |line|
          curSet.add(line.chomp)
          pkgListAll.add(line.chomp)
        end
      end
      pkgLists.push(curSet)
    else
      curSet = Set.new
      open(fileName, "r:binary") do |file|  
        file.each_line do |line|
          cline = line.chomp
          curSet.add(cline)
          pkgListAll.add(cline)
        end
      end
      pkgLists.push(curSet)
    end
  end

  # Create report data
  repLines = Array.new
  pkgListAll.each do |pkg|
    numIn = 0;
    repSt = ''
    pkgLists.each do |curSet|
      if(curSet.member?(pkg)) then
        numIn += 1
        repSt += "YES "
      else
        repSt += "--- "
      end
    end
    repLines.push(sprintf("D %03d %s: %s\n", numIn, repSt, pkg))
  end

  # Print report
  printf("H %3s ", '#')
  1.upto(pkgLists.length) do |i|
    printf("%3d ", i)
  end
  puts("")
  lastHead = ''
  repLines.sort! { |a,b| b<=>a }
  repLines.each do |line|
    lineHead = line[0..((1+pkgLists.length)*4)]
    if(lineHead != lastHead) then
      puts('S ' + ('=' * (1+pkgLists.length) * 4))
    end
      puts(line)
    lastHead = lineHead
  end
end