#!/usr/bin/env bash
#
# usage: inclosure [ -I dir ] ... [ -G header-name ] ... header-name ...
#
# Locates each standard header and argument header-name in the
# specified "-I" include path (default is /usr/include) and parses 
# any header names out of its #include directives.  These names are 
# treated recursively to identify a _transitive_closure_ of standard
# header names, which is sorted and sent to standard output.  Headers 
# not specified with -G, and included somewhere but not located are 
# reported.
#
# Each header reported by this program must be "shadowed" by a 
# file of the same name in a C++ header.  See 
#   http://www.cantrip.org/cheaders.html
#
# BUGS: 
#  - Cannot cope with header file names that contain spaces
#  - Ignores comment-block delimiters
#  - Ignores sub-includes under #include_next headers.

OLDH=/tmp/old$$
echo "this-compensates-for-a-stupid-bug-in-GNU-fgrep." >$OLDH
HDRS=/tmp/hdrs$$
>$HDRS
NEW=/tmp/new$$
>$NEW
IGNORES=/tmp/ignores$$
echo "this-compensates-for-a-stupid-bug-in-GNU-fgrep.">$IGNORES

trap "rm -f $NEW $HDRS $OLDH $IGNORES" 0

# process arguments
unset INCPATH
while [ $# != 0 -a "$1" != "${1#-}" ]; do
  FLAG="${1%%${1##-?}}"
  case "$FLAG" in -I|-G)
    ARG="${1##${FLAG}}"
    if [ "$ARG" = "" ]; then
      if [ $# != 0 ]; then 
        shift;
        ARG="$1"
      else
        echo "$0: $FLAG needs an argument."
	exit
      fi
    fi ;;
  esac
  shift
  case "$FLAG" in 
    -I) INCPATH="$INCPATH $ARG" ;;
    -G) echo " $ARG " >>$IGNORES ;;
  esac
done
INCPATH=${INCPATH-"/usr/include"}  

# identify headers

STDHDRS="assert.h ctype.h errno.h float.h limits.h \
 locale.h math.h setjmp.h signal.h stdarg.h stddef.h \
 stdio.h stdlib.h string.h time.h wchar.h wctype.h "
OTHERS="$*"

for file in $STDHDRS $OTHERS; do
  echo "$file" 
done >$HDRS

until cmp -s $OLDH $HDRS; do #  (until no new headers found)

  fgrep -v -f $OLDH $HDRS \
  | while read file; do
    found=no
    for dir in $INCPATH; do 
      name="$dir/$file"
      if [ -f "$name" ]; then 
        cat "$name"
	found=yes
        break;
      fi
    done
    if [ "$found" = no ]; then # && echo " $file " | fgrep -v -q -f $IGNORES 
      echo "$0: warning: header $file not found in include path." $1>&2
    fi
  done \
  | sed -n -e \
'/^[ 	]*#[ 	]*include[ 	]*<[^>]*>/s/^[^<]*<\([^>]*\)>.*/\1/p' \
  | while read file; do
     drop=no
     for ignore in `cat $IGNORES`; do
       if [ "$ignore" = "$file" ]; then drop=yes; fi
     done
     case "$file" in /*) drop=yes;; esac   # no absolute paths
     case $drop in no) echo "$file";; esac
  done >$NEW
  mv $HDRS $OLDH
  cat $OLDH $NEW | sort -u -o $HDRS

done
cat $HDRS



