file.ss
;;;
;;; Time-stamp: <06/02/05 20:40:10 noel>
;;;
;;; Copyright (C) 2005 by Noel Welsh.
;;;

;;; This library is free software; you can redistribute it
;;; and/or modify it under the terms of the GNU Lesser
;;; General Public License as published by the Free Software
;;; Foundation; either version 2.1 of the License, or (at
;;; your option) any later version.

;;; This library is distributed in the hope that it will be
;;; useful, but WITHOUT ANY WARRANTY; without even the
;;; implied warranty of MERCHANTABILITY or FITNESS FOR A
;;; PARTICULAR PURPOSE.  See the GNU Lesser General Public
;;; License for more details.

;;; You should have received a copy of the GNU Lesser
;;; General Public License along with this library; if not,
;;; write to the Free Software Foundation, Inc., 59 Temple
;;; Place, Suite 330, Boston, MA 02111-1307 USA

;;; Author: Noel Welsh <noelwelsh@yahoo.com>
;;
;;
;; Commentary:
(module file mzscheme

  (require
   (lib "file.ss")
   (lib "plt-match.ss")
   (lib "string.ss" "srfi" "13"))

  (provide 
   (all-defined)
   )
  
  ;; make-directory-tree : (tree-of string) -> void
  (define (make-directory-tree tree)
    (define (tree-fold seed tree)
      (define (list->path head rest)
        (apply build-path (reverse (cons head rest))))
      (match tree
        [(? string? here)
         (make-directory* (list->path here seed))]
        [(list) (void)]
        [`(,(? string? head) (,children ...) . ,rest)
         (make-directory* (list->path head seed))
         (tree-fold (cons head seed) children)
         (tree-fold seed rest)]
        [`(,(? string? here) . ,rest)
         (make-directory* (list->path here seed))
         (tree-fold seed rest)]))
    (tree-fold null tree))
  
  ;; make-non-conflicting-path : path string -> path
  ;;
  ;; The path version of make-non-conflicting-filename, explained
  ;; below:
  (define (make-non-conflicting-path path filename)
    (build-path path (make-non-conflicting-filename path filename)))
  
  ;; make-non-conflicting-path : path string -> string
  ;;
  ;; Searches the specified path for any files whose name might conflict
  ;; with the suggested filename. If conflicting files are found, a
  ;; non-conflicting variant of filename is returned.
  ;;
  ;; If no conflicting files are found, the filename is returned untouched.
  ;;
  ;; Non-conflicting names are generated in a nice, friendly, Windows-esque
  ;; kind of way, where a digit is appended to the end of the stem of the
  ;; file. There are a few subtleties to this: examples follow:
  ;;
  ;;   my-file.txt becomes:
  ;;
  ;;     my-file1.txt if my-file.txt exists
  ;;     my-file2.txt if my-file.txt and my-file1.txt exist
  ;;
  ;;   test-file becomes:
  ;;
  ;;     test-file1 if test-file exists
  ;;
  ;;   my-file5.txt becomes:
  ;;
  ;;     my-file6.txt if my-file5.txt exists
  ;;
  ;;   my-file1.blah.txt becomes:
  ;;
  ;;     my-file1.blah1.txt if my-file1.blah.txt exists
  (define (make-non-conflicting-filename path filename)
    ;; stem->stem-and-index : string -> (values string integer)
    ;;
    ;; Strips trailing digits off a string and returns them as a number.
    ;;
    ;; For example:
    ;;     "abc123" => (values "abc" 123)
    ;;     "abc" =? (values "abc" 1)
    (define (stem->stem-and-index stem)
      (let loop ([stem stem] [index-string ""])
        (if (char-numeric? (string-ref stem (sub1 (string-length stem))))
            (loop (string-drop-right stem 1) 
                  (string-append index-string (string-take-right stem 1)))
            (values 
             stem
             (if (= (string-length index-string) 0)
                 1
                 (string->number index-string))))))
    (if (file-exists? (build-path path filename))
        ; Split the filename into a stem and an extension
        (let* ([pos (string-index-right filename #\.)]
               [stem (if pos (string-take filename pos) filename)]
               [extension (if pos (string-drop filename pos) "")])
          ; Find a non-conflicting filename and return it
          (let-values ([(stem index) 
                        (stem->stem-and-index stem)])
            (let loop ([index index])
              (let ([filename 
                     (string-append 
                      stem
                      (number->string index) 
                      extension)])
                (if (file-exists? (build-path path filename))
                    (loop (add1 index))
                    filename)))))
        filename))

  )