#!/bin/sh
# tla-abbrev-merge-log -- Abbreviate redundant tla log-for-merge output
#
#  Copyright (C) 2004, 2005  Miles Bader <miles@gnu.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Written by Miles Bader <miles@gnu.org>
#
#-
#   -A, --archive       Override `my-default-archive'
#
# This filter makes the "Patches applied:" section of `tla log-for-merge'
# output more concise by coalescing identical summary lines, using range
# notation for multiple revisions, etc.  Only the format of the applied
# patches list in the body is changed, the rest of the log file is passed
# though unchanged.

# (---- beginning of hdr.shpp ----)
# hdr.shpp

me=`basename $0`

bindir='/usr/bin'
AWK='/usr/bin/nawk'; export AWK
TLA='tla'; export TLA
SED='/bin/sed'; export SED
UUIDGEN='uuidgen'; export UUIDGEN

# (---- TLA_TOOLS_VERSION defined from ,tla-tools-version ----)
TLA_TOOLS_VERSION='unknown-version
'
# (---- end of TLA_TOOLS_VERSION defined from ,tla-tools-version ----)

TLA_TOOL_PFX="${bindir+$bindir/}"
export TLA_TOOL_PFX

TLA_ESCAPE='yes'

if test "$TLA_ESCAPE" = yes; then
  TLA_UNESCAPED_OPT='--unescaped'
else
  TLA_UNESCAPED_OPT=''
fi

# Some tools get completely confused in stupid ways by non-default
# settings of LANG (like gawk, which fucks up regexp character ranges).
LANG=C; export LANG

# (---- end of hdr.shpp ----)
# (---- beginning of cmd-line.shpp ----)
# cmd-line.shpp -- Command-line helper functions for shell scripts

script="$0"
case "$script" in
  */*) ;;
  *)   script="${TLA_TOOL_PFX}$script";;
esac

usage ()
{
  $SED -n -e '/^\([^#]\|#-* *$\)/{s@.*@Usage: '"$me"' [--help|--version]@p;q;}'	\
         -e '/^# *Usage:/,/^# *$/{s/^# //p;q;}'				\
     < "$script"
}

short_help ()
{
  $SED -n -e '/^\([^#]\|-*# *$\|# *Usage:\)/q'				\
	 -e '/^#!/d;s/^.*-- */# /;s/^#[ 	]*//p'			\
     < "$script" | fmt
}

help_body ()
{
  $SED -n '/^ *$/q;/^#-/,/^[^#]/s/^#\( \|$\)//p' < "$script"
}

help ()
{
  usage
  short_help
  echo ''
  help_body
}

version ()
{
  local no_nl_vers=`echo "$TLA_TOOLS_VERSION"`
  echo "$me (tla-tools) $no_nl_vers"
  $SED -n '/^[^#]/q;/^#-/q;s/^# *\(Written by\)/\
\1/p' < "$script"
  $SED -n '/^[^#]/q;/^#-/q;s/^# *\(Copyright\)/\
\1/p' < "$script"
}

unrec_opt ()
{
  echo 1>&2 "$me: unrecognized option "\`"$1'"
  echo 1>&2 "Try "\`"$me --help' for more information."
}

cmd_line_err ()
{
  usage 1>&2
  echo 1>&2 "Try "\`"$me --help' for more information."
}

long_opt_val ()
{
  echo "$1" | $SED 's/^[^=]*=//'
}

short_opt_val ()
{
  echo "$1" | $SED 's/^-.//'
}

# (---- end of cmd-line.shpp ----)

ARCHIVE=`$TLA my-default-archive`

# Parse command-line options
while :; do
  case "$1" in
    -A|--archive)
      ARCHIVE="$2"; shift 2;;
    -A*)
      ARCHIVE=`short_opt_val "$1"`; shift;;
    --archive=*)
      ARCHIVE=`long_opt_val "$1"`; shift;;
    --help|-h|-H)
      help; exit 0;;
    --version|-V)
      version; exit 0;;
    -[!-]?*)
      # split concatenated single-letter options apart
      FIRST="$1"; shift
      set -- `echo $FIRST | $SED 's/-\(.\)\(.*\)/-\1 -\2/'` "$@"
      ;;
    -*)
      unrec_opt "$1"; exit 1;;
    *)
      break;
  esac
done

test $# -eq 0 || { cmd_line_err; exit 1; }

$AWK '
function revision_num(rev)
{
  if (match (rev, /[0-9]+$/))
    return substr (rev, RSTART, RLENGTH) + 0
  else
    return "<?>"
}

function revision_kind(rev)
{
  sub (/-[0-9]+$/, "", rev)
  sub (/^.*--/, "", rev)
  return rev
}

# return true if REV2 follows REV1
function consecutive_revisions (rev1, rev2  ,pfx1,pfx2,num1,num2)
{
  if (match (rev1, /[0-9]+$/)) {
    pfx1 = substr (rev1, 1, RSTART - 1)
    num1 = substr (rev1, RSTART, RLENGTH) + 0
  } else
    return 0

  if (match (rev2, /[0-9]+$/)) {
    pfx2 = substr (rev2, 1, RSTART - 1)
    num2 = substr (rev2, RSTART, RLENGTH) + 0
  } else
    return 0

  return pfx1 == pfx2 && num1 + 1 == num2
}

function version_rev_kind_range_desc(version, kind   ,first_rev,last_rev,desc)
{
  first_rev = version_rev_kind_first_rev[version, kind]
  last_rev = version_rev_kind_last_rev[version, kind]

  desc = revision_num(first_rev)

  if (kind == "base" && desc == "0" && first_rev == last_rev)
    desc = ""
  else {
    desc = " " desc
    if (first_rev != last_rev)
      desc = desc "-" revision_num(last_rev)
  }

  return desc
}

function fill(str, trigger_width, wrap_width, wrap_marker   ,out,line,spc_pos,spc_len)
{
  sub (/^[ ]+/, "", str)
  sub (/[ ]+$/, "", str)

  if (length (str) > trigger_width) {
    out = 0
    line = 0

    while (length (str) > 0) {
      if (match (str, /[ ]+/)) {
	spc_pos = RSTART
	spc_len = RLENGTH
      } else {
	spc_pos = length (str) + 1
	spc_len = 0
      }

      if (line && length (line) + spc_pos > wrap_width) {
	if (out)
	  out = out wrap_marker line
	else
	  out = line
	line = 0
      }

      line = line ? line " " : ""
      line = line substr (str, 1, spc_pos - 1)

      str = substr (str, spc_pos + spc_len)
    }
    
    if (! out)
      out = line
    else if (line)
      out = out wrap_marker line

    str = out
  }
  return str
}

# Add REV & SUM_LINE to the current set of entries
function add_entry(rev, sum_line   ,version,kind,num,revs_desc,prev_kind_first_rev,last_rev)
{
  sub (/^[ \t]*/, "", sum_line)

  version = rev
  sub (/(--patch-[0-9]*|--base-0|--versionfix-[0-9]*|-version-0)$/, "", version)
  
  if (sum_line != "<no summary provided>") {
    if (! ((version, sum_line) in version_sums)) {
      version_sums[version, sum_line] = sum_line
      version_sums[version] \
	= version_sums[version] "\n" fill(sum_line, 75, 70, "\r")
    }
  }

  kind = revision_kind(rev)
  num = revision_num(rev)
  last_rev = version_last_rev[version]

  if (! (version in version_last_rev_kind)) {
    revs_desc = kind
    ordered_versions[num_versions++] = version
    version_rev_kind_first_rev[version, kind] = rev
  } else {
    revs_desc = version_revs_desc[version]

    if (kind != version_last_rev_kind[version]) {
      revs_desc = revs_desc version_rev_kind_range_desc(version, version_last_rev_kind[version]) ", " kind
      version_rev_kind_first_rev[version, kind] = rev
    } else if (! consecutive_revisions(last_rev, rev)) {
      revs_desc = revs_desc version_rev_kind_range_desc(version, kind) ","
      version_rev_kind_first_rev[version, kind] = rev
    }
  }

  version_last_rev[version] = rev
  version_last_rev_kind[version] = kind
  version_revs_desc[version] = revs_desc
  version_rev_kind_last_rev[version, kind] = rev
}

function flush_entries(   head_ver_num,head_version,ver_num,version,revs_desc,sums,key)
{
  for (head_ver_num = 0; head_ver_num < num_versions; head_ver_num++) {
    head_version = ordered_versions[head_ver_num]

    if (head_version in version_sums) {
      sums = version_sums[head_version]

      print ""

      for (ver_num = head_ver_num; ver_num < num_versions; ver_num++) {
	version = ordered_versions[ver_num]

	if (version_sums[version] == sums) {
	  revs_desc = version_revs_desc[version]

	  revs_desc = revs_desc \
	    version_rev_kind_range_desc(version, version_last_rev_kind[version])

	  delete version_sums[version]
	  delete version_revs_desc[version]
	  delete version_last_rev[version]
	  delete version_last_rev_kind[version]

	  if (substr (version, 1, length (archive) + 1) == archive "/")
	    version = substr (version, length (archive) + 2)

	  print " * " version "  (" revs_desc ")"
	}
      }

      gsub (/\n/, "\n   - ", sums)
      gsub (/\r/,  "\n     ", sums)

      print sums
    }
  }

  for (key in version_sums)
    delete version_sums[key]

  num_versions = 0;
}

BEGIN {
  archive = "'"$ARCHIVE"'"

  body = 0
  squash = 0
  pending_rev = 0
  num_versions = 0
}

!body && /^Archive:/ { archive = $2; next }

# Beginning of patch-summary list
/^$/ { body = 1 }

!body { print; next }

# Revision line
/^[ ][*][ ][^@ \/]+@[^@ \/]+(--[^@ \/]+)?\/[^@ \/]+--[^@ \/]+--[^@ \/]+--[^@ \/]+$/ {
  pending_rev = $2
  pending_rev_line = $0
  squash = 1
  blank = 0
  next
}
# Summary line
squash && pending_rev && /^   [^ ]/ {
  add_entry(pending_rev, $0);
  pending_rev = 0;
  next
}
# Something odd where a summary line should have been
squash && pending_rev {
  flush_entries()
  print pending_rev_line
  pending_rev = 0
  next
}

# Blank separator line
squash && /^ *$/ { blank++; next }

/^ *$/ { blank++; next }

# Something we do not understand
{
  flush_entries()
  pending_rev = pending_rev_line = 0
  if (blank)
    do
      print ""
    while (--blank)
  print
  squash = 0
}

END { flush_entries() }
'

