A function that recursively copies files and directories

I know there is the function copy-directory/files, but I want a function that while descending directories performs some tasks before copying (see actions-on-dir). Here is the code I have written so far.

#lang racket

(provide (prefix-out charon: recursive-copy))

;; Isn't there a builtin function for this?
(define path-basename
  (compose last explode-path))

;; Try to perform an action. If any failure occurs, just print the
;; error and go ahead as if nothing happened.
(define-syntax-rule (try-and-go-on action)
  (with-handlers ([exn:fail?
                   (λ (e) (printf "ERROR: ~a\n" (exn-message e)))])
    action))

;; Copy a file into a given directory. Modification times are
;; considered to avoid copying when the copy is already up to date.
(define (copy-file-to-directory src-file dest)
  (try-and-go-on
   (let* ([name (path-basename src-file)]
          [copy (build-path dest name)])
     (if (file-exists? copy)
         (let ([mt1 (file-or-directory-modify-seconds src-file)]
               [mt2 (file-or-directory-modify-seconds copy)])
           (when (> mt1 mt2)
             (copy-file src-file copy #:exists-ok? #t)))
         (copy-file src-file copy #:exists-ok? #t)))))

(define (ls dir)
  (map path->string (directory-list dir)))

(define (actions-on dir)
  (current-directory dir)
  (let* ([dir-cont (ls dir)]
         [exts (map path-get-extension dir-cont)])
    (cond
      ;; project managed with `make`
      [(member "Makefile" dir-cont)
       (printf "INFO: running \"make clean\" in ~a...\n" dir)
       (system "make clean")]
      ;; Haskell projects
      [(member ".stack-work" dir-cont)
       (printf "INFO: running \"stack purge\" in ~a...\n" dir)
       (system "stack purge")]
      [(member #".cabal" exts)
       (printf "INFO: running \"cabal clean\" in ~a...\n" dir)
       (system "cabal clean")])))

(define (recursive-copy src dest)
  (if (file-exists? src)
      (copy-file-to-directory src dest)
      (begin
        (actions-on src)
        (let* ([name (path-basename src)]
               [new-dest (build-path dest name)])
          (make-directory* new-dest)
          (for-each
           (λ (e) (recursive-copy (build-path src e) new-dest))
           (directory-list src))))))

I'm relatively new to Racket: I'm exploring it as I come up with small programs I need for some routine work. Is this decent, or can be made more simple? In particular, there is a function I don't get how it works but I think it might be useful here: the function fold-files.

1 Like

I think you want in-directory.

(Its doc examples use it with for/list to gather results. But using it just for effect, you could just use for.)

;; Isn't there a builtin function for this?
(define path-basename
  (compose last explode-path))

See file-name-from-path.

1 Like

A tip: parameterize the current-directory so it will be restored when your functions return

1 Like

I know path-basename is an unhappy choice... Maybe path-last should fit better. The function I want just splits a path and takes the last element. On the other hand, file-name-from-path gives #f on path that are not syntactically directories.

You two are suggesting to write something like

(for ([f (in-directory dir)])
  (do-something-with f))

with a make-parameter somewhere within (do-something f). What would you do?