        FUNCTION FIND_ALL_DIR, PATH, PATH_FORMAT=PATH_FORMAT,   $
                PLUS_REQUIRED=PLUS_REQUIRED, RESET=RESET
;+
; NAME:
;       FIND_ALL_DIR()
; PURPOSE:
;       Finds all directories under a specified directory.
; EXPLANATION:
;       This routine finds all the directories in a directory tree when the
;       root of the tree is specified.  This provides the same functionality as
;       having a directory with a plus in front of it in the environment
;       variable IDL_PATH.
;
; CALLING SEQUENCE:
;       Result = FIND_ALL_DIR( PATH )
;
;               PATHS = FIND_ALL_DIR('+mypath', /PATH_FORMAT)
;               PATHS = FIND_ALL_DIR('+mypath1:+mypath2')
;
; INPUTS:
;       PATH    = The path specification for the top directory in the tree.
;               Optionally this may begin with the '+' character but the action
;               is the same unless the PLUS_REQUIRED keyword is set.
;
;               One can also path a series of directories separated
;               by the correct character ("," for VMS, ":" for Unix)
;
; OUTPUTS:
;       The result of the function is a list of directories starting from the
;       top directory passed and working downward from there.   Normally, this
;       will be a string array with one directory per array element, but if
;       the PATH_FORMAT keyword is set, then a single string will be returned,
;       in the correct format to be incorporated into !PATH.
;
; OPTIONAL INPUT KEYWORDS:
;       PATH_FORMAT     = If set, then a single string is returned, in
;                                 the format of !PATH.
;
;       PLUS_REQUIRED   = If set, then a leading plus sign is required
;                       in order to expand out a directory tree.
;                       This is especially useful if the input is a
;                       series of directories, where some components
;                       should be expanded, but others shouldn't.
;
;       RESET   = Often FIND_ALL_DIR is used with logical names.  It
;               can be rather slow to search through these subdirectories.
;               The /RESET keyword can be used to redefine an environment
;               variable so that subsequent calls don't need to look for the
;               subdirectories.
;
;               To use /RESET, the PATH parameter must contain the name of a
;               *single* environment variable.  For example
;
;                               setenv,'FITS_DATA=+/datadisk/fits'
;                               dir = find_all_dir('FITS_DATA',/reset,/plus)
;
;               The /RESET keyword is usually combined with /PLUS_REQUIRED.
;
; PROCEDURE CALLS:
;       DEF_DIRLIST, FIND_WITH_DEF(), BREAK_PATH()
;
; RESTRICTIONS:
;       PATH must point to a directory that actually exists.
;
; REVISION HISTORY:
;               Version 11, Zarro (SM&A/GSFC), 23-March-00
;                       Removed all calls to IS_DIR
;               Version 12, William Thompson, GSFC, 02-Feb-2001
;                       In Windows, use built-in expand_path if able.
;               Version 13, William Thompson, GSFC, 23-Apr-2002
;                       Follow logical links in Unix
;                       (Suggested by Pascal Saint-Hilaire)
;               Version 14, Zarro (EER/GSFC), 26-Oct-2002
;                       Saved/restored current directory to protect against
;                       often mysterious directory changes caused by 
;                       spawning FIND in Unix
;               Version 15, William Thompson, GSFC, 9-Feb-2004
;                       Resolve environment variables in Windows.
;
; Version     : Version 16 W. Landsman GSFC Sep 2006
;                        Remove VMS support
;-
;
        ON_ERROR, 2
        compile_opt idl2
;
        IF N_PARAMS() NE 1 THEN MESSAGE,        $
                'Syntax:  Result = FIND_ALL_DIR( PATH )'

;-- save current directory

   cd,current=current

;
;  If more than one directory was passed, then call this routine reiteratively.
;  Then skip directly to the test for the PATH_FORMAT keyword.
;
        PATHS = BREAK_PATH(PATH, /NOCURRENT)
        IF N_ELEMENTS(PATHS) GT 1 THEN BEGIN
                DIRECTORIES = FIND_ALL_DIR(PATHS[0],    $
                        PLUS_REQUIRED=PLUS_REQUIRED)
                FOR I = 1,N_ELEMENTS(PATHS)-1 DO DIRECTORIES =  $
                        [DIRECTORIES, FIND_ALL_DIR(PATHS[I],    $
                                PLUS_REQUIRED=PLUS_REQUIRED)]
                GOTO, TEST_FORMAT
        ENDIF
;
;  Test to see if the first character is a plus sign.  If it is, then remove
;  it.  If it isn't, and PLUS_REQUIRED is set, then remove any trailing '/'
;  character and skip to the end.
;
        DIR = PATHS[0]
        IF STRMID(DIR,0,1) EQ '+' THEN BEGIN
                DIR = STRMID(DIR,1,STRLEN(DIR)-1)
        END ELSE IF KEYWORD_SET(PLUS_REQUIRED) THEN BEGIN
                DIRECTORIES = PATH
                IF STRMID(PATH,STRLEN(PATH)-1,1) EQ '/' THEN    $
                        DIRECTORIES = STRMID(PATH,0,STRLEN(PATH)-1)
                GOTO, TEST_FORMAT
        ENDIF
;
;  For windows,  use the built-in EXPAND_PATH program.   However, first 
;  resolve any environment variables.
;
        IF !VERSION.OS_FAMILY EQ 'Windows' THEN BEGIN
                WHILE STRMID(DIR,0,1) EQ '$' DO BEGIN
                    FSLASH = STRPOS(DIR,'/')
                    IF FSLASH LT 1 THEN FSLASH = STRLEN(DIR)
                    BSLASH = STRPOS(DIR,'/')
                    IF BSLASH LT 1 THEN BSLASH = STRLEN(DIR)
                    SLASH = FSLASH < BSLASH
                    TEST = STRMID(DIR,1,SLASH-1)
                    DIR = GETENV(TEST) + STRMID(DIR,SLASH,STRLEN(DIR)-SLASH)
                ENDWHILE
                TEMP = DIR
                TEST = STRMID(TEMP, STRLEN(TEMP)-1, 1)
                IF (TEST EQ '/') OR (TEST EQ '\') THEN  $
                      TEMP = STRMID(TEMP,0,STRLEN(TEMP)-1)
                DIRECTORIES = EXPAND_PATH('+' + TEMP, /ALL, /ARRAY)
;
;  On Unix machines spawn the Bourne shell command 'find'.  First, if the
;  directory name starts with a dollar sign, then try to interpret the
;  following environment variable.  If the result is the null string, then
;  signal an error.
;
        END ELSE BEGIN
                IF STRMID(DIR,0,1) EQ '$' THEN BEGIN
                    SLASH = STRPOS(DIR,'/')
                    IF SLASH LT 0 THEN SLASH = STRLEN(DIR)
                    EVAR = GETENV(STRMID(DIR,1,SLASH-1))
                    IF SLASH EQ STRLEN(DIR) THEN DIR = EVAR ELSE        $
                            DIR = EVAR + STRMID(DIR,SLASH,STRLEN(DIR)-SLASH)
                ENDIF
;               IF IS_DIR(DIR) NE 1 THEN MESSAGE,       $
;                       'A valid directory must be passed'
                IF STRMID(DIR,STRLEN(DIR)-1,1) NE '/' THEN DIR = DIR + '/'
                SPAWN,'find ' + DIR + ' -follow -type d -print | sort -', $
                        DIRECTORIES, /SH
;
;  Remove any trailing slash character from the first directory.
;
                TEMP = DIRECTORIES[0]
                IF STRMID(TEMP,STRLEN(TEMP)-1,1) EQ '/' THEN    $
                        DIRECTORIES[0] = STRMID(TEMP,0,STRLEN(TEMP)-1)
        ENDELSE
;
;  Reformat the string array into a single string, with the correct separator.
;  If the PATH_FORMAT keyword was set, then this string will be used.  Also use
;  it when the RESET keyword was passed.
;
TEST_FORMAT:
        DIR = DIRECTORIES[0]
        CASE !VERSION.OS_FAMILY OF
                'Windows':  SEP = ';'
                'MacOS': Sep = ','
                ELSE:  SEP = ':'
        ENDCASE
        FOR I = 1,N_ELEMENTS(DIRECTORIES)-1 DO DIR = DIR + SEP + DIRECTORIES[I]
;
;  If the RESET keyword is set, and the PATH variable contains a *single*
;  environment variable, then call SETENV to redefine the environment variable.
;  If the string starts with a $, then try it both with and without the $.
;
        IF KEYWORD_SET(RESET) THEN BEGIN
                EVAR = PATH
                TEST = GETENV(EVAR)
                IF TEST EQ '' THEN IF STRMID(EVAR,0,1) EQ '$' THEN BEGIN
                        EVAR = STRMID(EVAR,1,STRLEN(EVAR)-1)
                        TEST = GETENV(EVAR)
                ENDIF
                IF (TEST NE '') AND (TEST NE PATH) AND (DIR NE PATH) THEN $
                        SETENV, STRTRIM(EVAR,2) + '=' + $
			STRTRIM(STRJOIN(DIR,':'),2)
        ENDIF
;
;-- restore current directory

        cd,current

        IF KEYWORD_SET(PATH_FORMAT) THEN RETURN, DIR ELSE RETURN, DIRECTORIES
;
        END
