I am trying to rename all files and subdirectories in a given directory.
I create vectors of the old and new names, and map a renaming function over the two vectors.
The vectors of file paths are created ok but the mapping
(map #(re-name (as-file %1) (as-file %2)) flist new-flist))) does not seem to call the renaming function at all (I've put a print call in it to test).
After much tweaking and searching I am still perplexed as to what I'm missing.
Also, I would like to know how I could obviate resorting to forums by debugging it by stepping through the code.
Code:
;;;; Use: Replaces whitespace in names of all directories and files in the given directory with the given separator.
(ns file-renamer.core
(:gen-class)
(:require [clojure.string :as str]))
(use '[clojure.java.io])
(use '[clojure.pprint])
; Constants for testing -> params live
(def direc "/home/john/test/")
(def separator "-")
; ====================================
(defn traverse-dir
"Return a seq of pathnames of directories and files in the given directoryt"
[dir-path]
(map #(.getAbsolutePath %) (file-seq (file dir-path))))
(defn replace-spaces
"Return a copy of string s1 with all sequences of spaces replaced with string s2"
[s1 s2]
(str/replace s1 #"\s+" s2))
(defn re-name
"Rename a file"
[old-file new-file]
(pprint (str "Renaming: " old-file " ==>> " new-file)) ; put here for debugging
(.renameTo old-file new-file))
(defn -main
"Map a fn to rename a file over all files and dirs in the given directory"
[& args]
(let [flist (vec (traverse-dir direc))
new-flist (vec (map #(replace-spaces % separator) flist))]
(pprint flist)
(pprint new-flist)
(map #(re-name (as-file %1) (as-file %2)) flist new-flist)))
Output:
17 Mar 20:53 /file-renamer ⋄ lein run
["/home/john/test"
"/home/john/test/test 108"
"/home/john/test/test 108/ baaaa rrrr"
"/home/john/test/test 108/ baaaa rrrr/Open Document Text .... odt"
"/home/john/test/test 108/ baaaa rrrr/ba z z er ."
"/home/john/test/test 108/ baaaa rrrr/ba z z er ./freed.frm"
"/home/john/test/test 108/ baaaa rrrr/ba z z er ./New Folder"
"/home/john/test/test 108/ baaaa rrrr/ba z z er ./New Folder/Plain Text.txt"
"/home/john/test/test 108/ baaaa rrrr/ba z z er ./fr ed.txt"
"/home/john/test/test 108/s p a c e s------S P A C E S "
"/home/john/test/fox"
"/home/john/test/foo"
"/home/john/test/fog"]
["/home/john/test"
"/home/john/test/test-108"
"/home/john/test/test-108/-baaaa-rrrr"
"/home/john/test/test-108/-baaaa-rrrr/Open-Document-Text-....-odt"
"/home/john/test/test-108/-baaaa-rrrr/ba-z-z-er-."
"/home/john/test/test-108/-baaaa-rrrr/ba-z-z-er-./freed.frm"
"/home/john/test/test-108/-baaaa-rrrr/ba-z-z-er-./New-Folder"
"/home/john/test/test-108/-baaaa-rrrr/ba-z-z-er-./New-Folder/Plain-Text.txt"
"/home/john/test/test-108/-baaaa-rrrr/ba-z-z-er-./fr-ed.txt"
"/home/john/test/test-108/s-p-a-c-e-s------S-P-A-C-E-S-"
"/home/john/test/fox"
"/home/john/test/foo"
"/home/john/test/fog"]
17 Mar 20:53 /file-renamer ⋄
There are a couple of problems here. They are not directly your fault, but it's more the REPL that is playing a trick on you and you not fully understanding (probably) the core concepts of Clojure and lazy operations/sequences.
The map function returns a lazy sequence, that need to be realized at some point in time, which you are not doing. On top of that, the re-name function is not a side-effect free function (pure function). The REPL is also playing a trick on you: If you call (-main) on the REPL, it will automatically realize those sequences and can cause a lot of confusion for someone new to Clojure.
The most straightforward solution would be to just use the doall function.
(doall (map #(re-name (as-file %1) (as-file %2)) flist new-flist))
But this is the quick and dirty way and I am going to quote Stuart Sierra here:
You might get the advice that you can “force” a lazy sequence to be evaluated with doall or dorun. There are also snippets floating around that purport to “unchunk” a sequence.
In my opinion, the presence of doall, dorun, or even “unchunk” is almost always a sign that something never should have been a lazy sequence in the first place.
A better solution in this case would be to use the doseq function and write something like this:
(defn -main
"Map a fn to rename a file over all files and dirs in the given directory"
[& args]
(doseq [file-string (traverse-dir direc)]
(let [input-file (as-file file-string)
output-file (as-file (replace-spaces file-string separator))]
(re-name input-file output-file))))
This could also be written way shorter.
A good reading that will help a lot in general is the blog post from Stuart Sierra: Clojure Don’ts: Lazy Effects.
Check the docs on doall.
Here, renaming is a side-effect. The map returns a lazy sequence. You need to force mapping over the whole collections in order for the renaming to happen.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With