; QUALPATH.ASM	--  Include-able library function file

; QualPathspec:  Convert user-supplied string to fully qualified pathspec

; K. Heidenstrom  (kheidens@actrix.gen.nz)

; Modified:
;
; KH.19950219.001  Split from FIN.ASM
; KH.19950604.002  Removed shorthand macro usage, return wildcard flag,
;		   require QualPathDTA specifically for this function

; Notes
;
; This function produces a fully qualified search specification from a user
; supplied string such as a command line parameter.  The specification is
; stored into a buffer area specified by DI, and starts with a drive letter,
; colon, and leading backslash, then contains zero or more directory names
; separated by backslashes, followed by a filespec, which may be a filename,
; e.g. file.ext, a user-provided filespec, e.g. *.bak, or the default wildcard
; spec of '*.*' if the user specified a directory name or typed a trailing
; backslash.
;
; This function starts by determining the drive letter, which is always placed
; at the start of the buffer (followed by a colon).  If a drive was not
; specified in the command line parameter, the default drive is used.
; The function then checks whether a leading backslash was supplied.  If not,
; it requests the default directory on the specified (or defaulted) drive and
; inserts this, with leading and trailing backslashes, into the buffer at the
; appropriate point, handling the special case for the root directory which is
; reported as nothing and only requires one backslash.
; It then copies the remainder of the input text, converting it to upper case
; in the process of copying, and resolving any occurrences of '.' and '..'.
; See the notes near this section for details.	Then it checks for the '*'
; and '?' wildcard characters in the final part of the resulting specification,
; and sets a flag if either are found.
; Next it determines whether the default '*.*' filespec should be appended.
; If the user-supplied text ended with a backslash, it assumes that this
; specifies a directory, and simply appends the '*.*'.  If not, it checks the
; wildcard-found flag - if set, it does not append '*.*' at all.  If clear,
; it determines whether the currently existing specification is a directory
; or not, using a DOS find-first call, and if so, it appends '\*.*' to the
; specification.  That's all.
;
; Note that this function does NOT support the forward slash '/' as a
; directory separator.
;
; Some examples, assuming the following:
;
;	Default drive is C
;	Default directory on drive C is \WORK
;	Default directory on drive A is the root
;	There is a directory \TEMP on drive C
;	There is a directory \WORK\MISC on drive C
;	There is no directory \NOT_DIR on drive C
;
; User string			Buffer contains
;
; *				C:\WORK\*
; *.*				C:\WORK\*.*
; A:				A:\*.*
; file				C:\WORK\FILE
; misc				C:\WORK\MISC\*.*
; \temp				C:\TEMP\*.*
; \not_dir			C:\NOT_DIR
; \not_dir\			C:\NOT_DIR\*.*
; .				C:\WORK\*.*
; .\				C:\WORK\*.*
; .\.\.\.\.\.			C:\WORK\*.*
; ..				C:\*.*
; ..\				C:\*.*
; misc\..\..			C:\*.*
;
; On calling, SI points to the start of the pathspec, which may be a null-
; terminated string or may point to a string on the command line.
; On return, SI points to the character on which the parsing terminated;
; this will usually be the null terminator, a space, a tab, or a C/R.  DI
; points just past the null-terminator on the end of the fully qualified
; path specification in the buffer.  The carry flag indicates whether any
; wildcards are present; this is set if wildcards were found during parsing
; or if the default '\*.*' ending is present.
;
; Note that this function may potentially make several disk accesses; therefore
; it should be called just before the files are processed.  In other words, do
; not qualify all the pathspecs first, then process all the files, as this
; could cause each drive to be accessed twice if there are several drives in
; the list of specifications provided on the command line.
;
; In:	SI -> Source string (terminated with space, C/R, or null)
;	DI -> Buffer area for resulting string
; Out:	SI -> Terminator character on source string
;	DI -> Just past end of result string
;	CF = Wildcard flag: CY = Wildcard present, NC = No wildcard present
; Lost:	AX = 0, BX, CX, DX, SI, DI, BP (all working registers are modified)
;
; This function requires the function ConvertUC.  This function must convert
; a character in AL to upper case.  It must not modify any other registers.
;
; The user program must contain a storage area called QualPathDTA, reserving
; 44 bytes, for use as a DTA (Disk Transfer Area) by the function.
;
; Note:  This function assumes that ES=DS and DF=0 on calling.

PROC	QualPathspec	near
		mov	bp,di		; Keep pointer to destination
		lodsb			; Get first char of user string
		call	ConvertUC	; Convert possible drive letter to U/C
		cmp	[BYTE si],":"	; Have colon?
		je	HaveDrive	; Check for initial drive spec
		mov	ah,19h		; If none, use default
		int	21h		; Get default drive
		add	al,"A"		; Convert to letter
		dec	si
		dec	si		; Back up
HaveDrive:	inc	si		; Point to start of spec excluding drive
		mov	ah,":"		; Get colon
		stosw			; Store drive letter and colon

; Now check whether a leading backslash was found.  If not, request the default
; directory for the specified drive and insert it into the search specification.

		mov	al,"\"		; Backslash
		cmp	al,[si]		; Leading backslash?
		je	CopyLoop	; If so, continue
		stosb			; If not, store a leading backslash
		xchg	si,di		; Get 'up-to' pointer to SI
		mov	dl,[si-3]	; Get drive letter
		sub	dl,"A"-1	; Convert "A" to 1
		mov	ah,47h
		int	21h		; Get current directory for that drive
		xchg	si,di		; Get 'up-to' pointer back to DI
		xor	al,al		; Null-terminator to scan for
		mov	cx,65		; Maximum length
		repne	scasb		; Scan for null-terminator
		dec	di		; Back up
		mov	al,"\"		; Backslash
		cmp	al,[BYTE di-1]	; Empty directory?
		je	CopyLoop	; If so
		stosb			; If not, add trailing backslash

; We have a drive letter, and the default directory if the specification did
; not have a leading backslash.  Now copy the remainder of the specification.
; In the process, resolve any '.' and '..' references, using the following
; algorithm:
; If during copying, a '.' is found, first check that it follows a backslash
; in the destination path.  If not, don't treat it as special.  If so, do not
; copy it yet, and get another character.  This should either be another dot
; (for '..') or a backslash or the end of the input specification.  If it's a
; backslash, ignore it and loop.  This has the effect of converting '\.\' to
; just '\'.  If it's the end of input, just terminate the copy loop; this has
; a similar effect.
; If it's something else, store it and loop; this has the effect of deleting
; bogus dots, e.g. '\.dir\' becomes '\dir\'.  Finally if it is a dot, then
; get another character.  This should be a '\' or the end of the input
; specification.  If it's not end of input and is not a backslash, store it
; and go to loop.  This has a similar effect to above, converting '\..dir\'
; to '\dir\'.  If it was the end of input or a backslash, first check whether
; the destination pointer is pointing just past the first backslash of the
; specification, and if not, decrement it until the next backslash back is
; found, leave it pointing just past this backslash.  Now test the character
; found earlier.  If it was not end of input, go to loop.

CopyLoop:	lodsb			; Char from spec
		call	ConvertUC	; Convert to upper case
		cmp	al," "		; Hit whitespace?
		jbe	CopyFin		; If so, finished
		cmp	al,"."		; Is it a dot?
		jne	StoreLoop	; If not, store it and loop
		cmp	[BYTE di-1],"\"	; Did it follow a backslash?
		jne	StoreLoop	; If not, store to destination and loop

; Now have a backslash with a dot to follow; the backslash is already present
; at the end of the destination spec but the dot is not copied.

		lodsb			; Get another character
		call	ConvertUC	; Convert to upper case
		cmp	al," "		; Hit whitespace?
		jbe	CopyFin		; If so, finished
		cmp	al,"."		; Get a second dot?
		je	SecondDot	; If so
		cmp	al,"\"		; Backslash?
		je	CopyLoop	; If so, don't store, just loop
StoreLoop:	stosb			; Store it
		jmp	SHORT CopyLoop	; Loop

; Now have a backslash with two dots to follow; the backslash is already
; present at the end of the destination spec but the dots are not copied.

SecondDot:	lodsb			; Next char
		call	ConvertUC	; Convert to upper case
		cmp	al," "		; Hit whitespace yet?
		jbe	DotStripLoop	; If so, ended with '..'
		cmp	al,"\"		; Should be a backslash
		jne	StoreLoop	; If not, discard dots
DotStripLoop:	lea	bx,[bp+4]	; Address past "d:\"
		cmp	di,bx		; Too close to start of buffer?
		jb	NoDotStrip	; If so, don't strip one directory back
		dec	di		; Back up
		cmp	[BYTE di-1],"\"	; Looking for previous backslash
		jne	DotStripLoop	; Keep looking
NoDotStrip:	cmp	al," "		; Did we finish parsing?
		ja	CopyLoop	; If not, loop
CopyFin:	dec	si		; Back up to terminator character

; Now scan backwards from end until we hit a backslash, checking for the
; wildcard characters '?' and '*'.  Set a flag if any wildcard char is found.

		xor	ah,ah		; Zero wildcard-found flag
		mov	bx,di		; Point with BX
FindWildLoop:	dec	bx		; Back up
		mov	al,[bx]		; Get char
		cmp	al,"*"		; Wildcard?
		je	IsWild		; If so
		cmp	al,"?"		; Wildcard?
		jne	NotWild		; If not
IsWild:		mov	ah,1		; Flag wildcard found
NotWild:	cmp	bx,bp		; Gone too far?
		jbe	WildDone	; If so
		cmp	al,"\"		; Backslash?
		jne	FindWildLoop	; If not, loop until we meet one

WildDone:	cmp	[BYTE di-1],"\"	; Trailing backslash?
		je	AddStarStar	; If so
		test	ah,ah
		jnz	HadWildcard	; If wildcards already found

; No wildcards specified, and no trailing backslash.  But it could be a
; directory name; find out.  If it's a directory name, append '\*.*'.

		mov	[BYTE di],0	; Temporarily null-terminate

		point	dx,QualPathDTA	; Point to DTA storage
		mov	ah,1Ah
		int	21h		; Set DTA

		mov	dx,bp		; Point to full result specification
		mov	cx,00010000b	; Allow subdirectories
		mov	ah,4Eh
		int	21h		; Find first
		jc	AddNothing	; If nothing found
		cmp	al,18		; No more files to be found error?
		je	AddNothing	; If so
		test	[BYTE QualPathDTA+21],00010000b ; Was it a directory?
		jz	AddNothing	; If not
		mov	al,"\"		; Backslash
		stosb			; Add it
AddStarStar:	mov	ax,".*"		; First two chars of "*.*"
		stosw			; Store "*."
		stosb			; Store "*"
HadWildcard:	stc			; Set wildcard flag
		DB	0B0h		; Skip following CLC instruction
AddNothing:	clc			; Clear wildcard flag
		mov	ax,0		; !! Don't use XOR AX,AX !!
		stosb			; Terminate name
		ret
ENDP	QualPathspec
