#!/usr/bin/regina
/*
 vim:ts=4:noet:wrap:
 $Id: diffdirs,v 1.19 2001/09/06 02:59:55 rick Exp $
 * Rick Younie <younie@debian.org>
-+-
USAGE: diffdirs [-sq] arg1 arg2

  -s  - diff on size as well
  -q  - ignore any CVS/ directories

   the arguments can be directories, tarballs, debs, zip files or
   and combination of these.  If one is a text file (a text file is
   assumed if it isn't a recognized archive type) they both must be.

 DIFFDIRS - if this environment variable is set, use it for diffing
      text files
-*-
 * 
 * 
 */
	TRACE OFF
	SIGNAL ON HALT
	SIGNAL OFF ERROR
	SIGNAL ON FAILURE
	SIGNAL ON NOVALUE
	SIGNAL ON SYNTAX

/* -------------------------------------------------------------------
 *	  constants
 */
	g.!lf		= '0a'x

/* -------------------------------------------------------------------
 *
 */
MAIN:

	opt. = 0
	if \abbrev(arg(1), '-') then parse arg arg1 arg2
	else do
		parse arg '-' opts arg1 arg2
		do while opts <> ''
			parse var opts opt +1 opts
			select
				when opt = 'h' | opt = '-' then call Usage
				when opt = 's' then opt.!dosize = 1
				when opt = 'q' then opt.!quiet = 1
				otherwise call ERX '..unknown option "'opt'"'
			end
		end
	end

	/* which pager? */
	pager = value('PAGER',,'SYSTEM')
	if pager = '' then pager = 'sensible-pager'

	call popen 'ls 2>/dev/null -1d "'arg1'" "'arg2'"', 'fullname.'
	if fullname.0 <> 2
		then call ERX '..there must be exactly 2 file/dir arguments'

	/* get file name or dir name for filelist & display name */
	do i = 1 to fullname.0
		fullname.i = strip(fullname.i,'T','/')
		tmp = translate(fullname.i,' ','/')
		displayname.i = word(tmp, words(tmp))
	end i

	/* create 2 sub dirs (1/,2/) inside a temp dir in /tmp */
	/* so it is easy to cleanup afterwards */
	tempdir_base = '/tmp/diffdirs.'getpid()
	'mkdir -m 700' tempdir_base
	if RC <> 0 then call ERX '..could not create temp dir "'tempdir_base'"'
	do i = 1 to 2
		tempdir.i = tempdir_base'/'i
		'mkdir' tempdir.i
		if RC <> 0 then call ERX '..could not make temp dir "'tempdir.i'"'

		diff_file.i = tempdir.i'/'displayname.i
	end i

	is_text. = 0
	do fnidx = 1 to 2

		is_uncompressed_text = 0
		filelist = MAKE_FILE_LIST(fullname.fnidx)

		/* if it was a text file, the file will be diffed in place */
		if is_uncompressed_text then diff_file.fnidx = fullname.fnidx
		/* ..if gzip/bzip2, it was already uncompressed to diff_file.i */
		else if \is_text.fnidx then call charout diff_file.fnidx, filelist, 1

	end fnidx

	totaltext = is_text.1 + is_text.2
	select
		/* dir. lists or archive files lists - diff them */
		when totaltext = 0 then call DIFF_WORKDIRS

		/* both are assumed to be text */
		when totaltext = 2 then do
			/* use diff program if set.. */
			env_diff = value('DIFFDIRS','','SYSTEM')
			if value(env_diff) <> ''
				then env_diff diff_file.1 diff_file.2 '2>/dev/null'
			/* ..else do normal diff */
			else do
				opt.!text = 1
				call DIFF_WORKDIRS
			end
		end

		otherwise do
			say '..one of the args is text or compressed text --'g.!lf,
				'at least it was not a known archive type.'g.!lf,
				'Both must be (optionally compressed) text or neither.'
		end
	end

	/* lose the temp dir */
	signal QUIT


/* ---------------------------------------------------------------------
 * 
 */
DIFF_WORKDIRS: PROCEDURE EXPOSE g. displayname. diff_file. opt.

	number_width = 10
	offset = copies(' ', 15)
	if opt.!text then opt.!dosize = 0 /* don't allow -s if text */
	grep = '|egrep ^[+-]'
	if opt.!quiet then grep = grep '|grep -v CVS/'

	call popen 'diff -u "'diff_file.1'" "'diff_file.2'"' grep, 'diff.'

	/* write the heading */
	if opt.!dosize then call charout 'stdout', copies(' ',number_width)
	say displayname.1
	second_head = offset || displayname.2
	if opt.!dosize then call charout 'stdout', copies(' ',number_width)
	say second_head
	say copies('=',length(second_head)+number_width)

	wasdiff = 0
	fsize = ''
	do i = 3 to diff.0
		if opt.!dosize then parse var diff.i mark +1 fsize fname
		else parse var diff.i mark +1 fname

		select
			when mark = '+' then do
				wasdiff = 1
				call charout 'stdout', offset
				if opt.!dosize
					then call charout 'stdout', right(fsize,number_width-1)' ' 
				say fname
			end
			when mark = '-' then do
				wasdiff = 1
				if opt.!dosize
					then call charout 'stdout', right(fsize,number_width-1)' '
				say fname
			end
			otherwise nop
		end
	end i
	if \wasdiff then say '..no difference'

	return

/* ---------------------------------------------------------------------
 * make a file list of the dir, tarball, deb  for diffing
 *
 */
MAKE_FILE_LIST: PROCEDURE EXPOSE g. opt. is_text. is_uncompressed_text,
	diff_file. fnidx

	parse arg fullname

	filelist = ''/* keep return happy; isn't used if it's [compressed] text */
	flist.0 = 0
	select
		/* directory? */
		when stream(fullname'/','C','QUERY EXISTS') <> '' then do
			call DODIR fullname
		end
		when right(fullname,7)='.tar.gz' | right(fullname,4)='.tgz' then do
			call DOTAR 'z',fullname
		end
		when right(fullname,3)='.gz' then do
			'gzip -dc' fullname '>'diff_file.fnidx
			is_text.fnidx = 1
		end
		when right(fullname,8)='.tar.bz2' then do
			call DOTAR 'j',fullname
		end
		when right(fullname,4)='.bz2' then do
			'bzip2 -dc' fullname '>'diff_file.fnidx
			is_text.fnidx = 1
		end
		when right(fullname,6)='.tar.z' | right(fullname,6)='.tar.Z' then do
			call DOTAR 'Z',fullname
		end
		when translate(right(fullname,4)) = '.ZIP' then do
			call DOZIP fullname
		end
		when right(fullname,4) = '.deb' then do
			call DODEB fullname
		end
		/* if not a known archive type, assume text (need 2 to diff) */
		otherwise do
			is_text.fnidx = 1
			is_uncompressed_text = 1
		end
	end

	/* if not a text file, sort, strip any common top-level directories */
	if \is_text.fnidx then do

		call SORT

		if flist.0 > 0 then do
			common = flist.1'/'
			if right(common,2) <> '//' then common = common'/'
			do dummy = 1
				/* lastpos will barf on 0 start - possible? */
				if common = '/' then leave dummy
				lastslash = lastpos('/',common,length(common)-1)
				common = substr(common,1,lastslash)
				do i = 2 to flist.0
					if \abbrev(flist.i,common) then leave i
				end i
				if i > flist.0 then do	/* common to all lines */
					do i = 1 to flist.0
						parsepos = length(common) + 1
						parse var flist.i . =(parsepos) flist.i
					end
					leave dummy
				end
			end dummy
		end

		filelist = ''
		do i = 1 to flist.0
			if flist.i = '' | flist.i = 0 then iterate
			parse var flist.i fname fsize
			if opt.!dosize then filelist = filelist || fsize fname || g.!lf
			else filelist = filelist || fname || g.!lf
		end

	end

	return filelist

/* --------------------------------------------------- */
DODEB: PROCEDURE EXPOSE g. flist.

	parse arg fullname

	"dpkg-deb --contents" fullname "2>/dev/null >FIFO"

	pull junk
	i = 0
	do while queued() > 0
		i = i + 1
		parse pull . . fsize . . filename
		flist.i = filename fsize
	end
	flist.0 = i

	return

/* --------------------------------------------------- */
DOZIP: PROCEDURE EXPOSE g. flist.

	parse arg fullname

	call popen 'unzip -v "'fullname'" 2>/dev/null', 'flist.'

	begin_end_marker = '-------'
	j = 0
	do i = 1 to flist.0
		if \abbrev(flist.i, begin_end_marker) then iterate i
		do forever
			i = i + 1
			if i > flist.0 then leave i
			if abbrev(flist.i, begin_end_marker) then leave i
			j = j + 1
			flist.j = subword(flist.i, 8) word(flist.i,1)
		end
	end i
	flist.0 = j

	return

/* --------------------------------------------------- */
DOTAR: PROCEDURE EXPOSE g. flist.

	parse arg arctype,fullname

	'tar tv'arctype'f 2>/dev/null "'fullname'" 2>/dev/null >FIFO'
	if RC <> 0 then call ERX '..error listing "'fullname'"'

	/* zero dir size & add trailing slash */
	i = 0
	do while queued() > 0
		i = i + 1
		parse pull perm . fsize . . filename
		if abbrev(perm, 'd')
			then flist.i = strip(filename,'T','/')'/ 0'
			else flist.i = filename fsize
	end
	flist.0 = i

	return

/* --------------------------------------------------- */
DODIR: PROCEDURE EXPOSE g. flist.

	parse arg fullname

	olddir = value('PWD',,'SYSTEM')
	if chdir(fullname) <> 0 then call ERX '..could not chdir to' fullname

	/* zero dir size & add trailing slash */
	i = 0
	'find . -type d -printf "%p %s\n" 2>/dev/null >FIFO'
	pull junk
	do while queued() > 0
		parse pull dirname .
		i = i + 1
		flist.i = dirname'/ 0'
	end

	/* add the files */
	'find . -type f -printf "%p %s\n" 2>/dev/null >FIFO'
	do while queued() > 0
		i = i + 1
		parse pull flist.i
	end
	flist.0 = i
 
	call chdir olddir

	return

/* -------------------------------------------------------------------
 * RETURNS:
 *	  the sorted flist. stem array
 */
SORT: PROCEDURE EXPOSE g. flist.

	m = 1
	do while (9 * m + 4) < flist.0
		m = m * 3 + 1
	end

	do while m > 0
		k = flist.0 - m
		do j = 1 to k
			q = j
			do while q > 0
				l = q + m
				if flist.q <= flist.l then leave
				tmp = flist.q
				flist.q = flist.l
				flist.l = tmp
				q = q - m
			end
		end
		m = m % 3
	end

	return

/* -------------------------------------------------------------------
 * if the usage line starts with '!' it is interpreted
 *	to allow variable substitution
 */
USAGE: 
	do i = 3 to 10
		line = sourceline(i)
		if left(line,3) <> '-+-' then iterate i
		do j = i + 1 for 50
			line = sourceline(j)
			if left(line,3) = '-*-' then leave i
			if left(line,1) <> '!' then say line
			else interpret substr(line,2)
		end j
	end i
	exit 0

NOVALUE:
FAILURE:
SYNTAX:
ERROR:
HALT:
	call lineout 'stderr',,
		 condition('C') 'error, line' SIGL': "'condition('D')'"'
	exit 1

ERX:
	call lineout 'stderr', arg(1)
	error_return = 1
	exit 1	/* don't rm the temp dir on error */

QUIT:
	error_return = 0
CLEANUP:
	if symbol('tempdir_base') <> 'LIT' then do
		'rm -rf' tempdir_base
	end

	exit error_return
