How to copy a directory in Clojure? - clojure

Is there a library which allows me to copy a directory and all subdirectories in Clojure? Something like:
(copy "source-dir" "destination-dir")

You could use the following code for copy-dir:
copy-dir (copy-dir from to)
Copy a directory from from to to. If
to already exists, copy the directory to a directory with the same
name as from within the to directory.
(defn copy-dir
"Copy a directory from `from` to `to`. If `to` already exists, copy the directory
to a directory with the same name as `from` within the `to` directory."
[from to]
(when (exists? from)
(if (file? to)
(throw (IllegalArgumentException. (str to " is a file")))
(let [from (file from)
to (if (exists? to)
(file to (base-name from))
(file to))
trim-size (-> from str count inc)
dest #(file to (subs (str %) trim-size))]
(mkdirs to)
(dorun
(walk (fn [root dirs files]
(doseq [dir dirs]
(when-not (directory? dir)
(-> root (file dir) dest mkdirs)))
(doseq [f files]
(copy+ (file root f) (dest (file root f)))))
from))
to))))
Or directly use the fs library available on Github.

You can use copy-dir of the fs library as already answered.
One issue with copy-dir is that it'll put the source directory under the target directory if the target directory already exists behaving like:
# if target already exists
cp -r source/ target/
# target/source/...
To avoid this, you can use the copy-dir-into function. It will behave something like this:
cp -r source/* target/
It's not in the document but it's a public function so it should be safe to use.

Related

How to substitute path to home for "~"?

If I pass in a path from the command line, "~" expands to my home directory:
(defn -main
"I don't do a whole lot ... yet."
[& args]
(doseq [arg args]
(println arg)))
britannia:uberjar srseverance$ java -jar args-0.1.0-SNAPSHOT-standalone.jar ~/158.clj
/Volumes/Macintosh HD/Users/srseverance/158.clj
But if I try to use a path-file containing ~, I can't find the file.
user> (with-open [r (clojure.java.io/reader "~/158.clj")]
(doall (line-seq r)))
FileNotFoundException ~/158.clj (No such file or directory) java.io.FileInputStream.open0 (FileInputStream.java:-2)
How do I take a string like, "~/158.clj" and get back something clojure.java.io/reader can use, such as "/Volumes/Macintosh HD/Users/srseverance/158.clj"?
You can define
(defn expand-home [s]
(if (.startsWith s "~")
(clojure.string/replace-first s "~" (System/getProperty "user.home"))
s))
and use it to resolve home directory:
(clojure.java.io/reader (expand-home "~/158.clj"))]
You could also look into fs library definition of expand-home, which solves the ~foo problem outlined in bfontaine's comment below:
(let [homedir (io/file (System/getProperty "user.home"))
usersdir (.getParent homedir)]
(defn home
"With no arguments, returns the current value of the `user.home` system
property. If a `user` is passed, returns that user's home directory. It
is naively assumed to be a directory with the same name as the `user`
located relative to the parent of the current value of `user.home`."
([] homedir)
([user] (if (empty? user) homedir (io/file usersdir user)))))
(defn expand-home
"If `path` begins with a tilde (`~`), expand the tilde to the value
of the `user.home` system property. If the `path` begins with a
tilde immediately followed by some characters, they are assumed to
be a username. This is expanded to the path to that user's home
directory. This is (naively) assumed to be a directory with the same
name as the user relative to the parent of the current value of
`user.home`."
[path]
(let [path (str path)]
(if (.startsWith path "~")
(let [sep (.indexOf path File/separator)]
(if (neg? sep)
(home (subs path 1))
(io/file (home (subs path 1 sep)) (subs path (inc sep)))))
path)))
Addressing bfontaine's comment, we can get correct results for ~user and ~root by asking the system instead:
(require '[clojure.java.shell :refer [sh]])
(defn bash [command]
(sh "bash" "-c" command))
(defn expand [path]
(-> (str "echo -n " path)
bash
:out))
(expand "~")
;; => /home/teodorlu
(expand "~teodorlu")
;; => /home/teodorlu
(expand "~root")
;; => /root
Though, just use this for trusted code!
(expand "`cat ~/.passwords`")
;; => All my passwords!

Clojure, console: println output not always visible

Subj. There's a working program, which basically copies filesystem trees recursively. Somehow println from inside the recursive function won't show any output.
build-album calls traverse-dir; I can see the "10" in the console, but never any "11"s -- should be a lot of them. (println "11") can't possibly miss the path of execution, since files get really copied (the line above). This is not quite nice, since the project is meant as a console application, reporting to the user each copied file, lest he should suspect freezing. This is no joke, because the app is intended to upload albums to mobile phones.
(defn traverse-dir
"Traverses the (source) directory, preorder"
[src-dir dst-step]
(let [{:keys [options arguments]} *parsed-args*
dst-root (arguments 1)
[dirs files] (list-dir-groomed (fs/list-dir src-dir))
dir-handler (fn [dir-obj]
"Processes the current directory, source side;
creates properly named directory destination side, if necessary"
(let [dir (.getPath dir-obj)
step (str dst-step *nix-sep* (fs/base-name dir-obj))]
(fs/mkdir (str dst-root step))
(traverse-dir dir step)))
file-handler (fn [file-obj]
"Copies the current file, properly named and tagged"
(let [dst-path (str dst-root dst-step *nix-sep* (.getName file-obj))]
(fs/copy file-obj (fs/file dst-path))
(println "11")
dst-path))]
(concat (map dir-handler dirs) (map file-handler files))))
(defn build-album
"Copy source files to destination according
to command line options"
[]
(let [{:keys [options arguments]} *parsed-args*
output (traverse-dir (arguments 0) "")]
(println "10")
output))
Might be the problem with lazy sequences: you build a lazy seq which is never realized and thus the code never executes. Try calling doall on the result of traverse-dir:
(doall (concat (map dir-handler dirs) (map file-handler files))))

emacs function for switching between header and implementation for non-standard project structure

I'm working on a C++ project with a bit uncommon layout of source and include files (well, at least it's uncommon for what I've seen so far) and am trying to come up with a helper emacs function for switching between .cpp and respective .h file (for this particular case only, so it doesn't need to be super flexible), because ff-find-other-file fails on this setup. This is also an elisp learning experience for me.
The project structure is set up as follows:
source files fall within projectname/src/namespacepath/*.cpp
respective include files fall withing projectname/include/namespacepath/*.h
In addition I may have an additional checkout of that project (projectname2/....) and switching between cpp and h should happen within project boundaries.
In other words, for a source file Foo.cpp of class Foo in namespace a::b::c I have:
project/src/a/b/c/Foo.cpp
project/include/a/b/c/Foo.h
The "project" itself is kept in a "src" directory where I keep all my sources (so the full path is something like ~/src/project/src/....), which means the function should only replace "src" with "include" for the last "src" occurence in the path.
I came up with the below elisp function; it substitues the last occurence of "src" with "include" in the current file path and "cpp" extension with "h" (or vice versa) and tries to visit the resulting file.
Since I'm new to lisp, I'd be interested in knowing if it can be made any simpler? Or perhaps ff-find-other-file can be customized to do exactly this? (and yeah, I've seen ff-search-directories, but that wouldn't help when working on multiple checkouts of the same project).
(defun alternate-include-or-src()
(interactive)
(let (
(name)
(newname "")
(repl t)
)
(setq name (nreverse (split-string (buffer-file-name) "/")))
(setq filename (car name))
(dolist (p (cdr name)) ;; iterate over reversed list of path components
(if repl ;; do the src <-> substitution only once
(if (string= p "src")
(progn
(setq p "include"
repl nil)
(setq filename (concat (file-name-sans-extension filename) ".h"))
)
(if (string= p "include")
(progn
(setq p "src"
repl nil)
(setq filename (concat (file-name-sans-extension filename) ".cpp"))
)
)
)
)
(setq newname (concat p "/" newname))
)
(setq newname (concat newname filename))
(if (file-exists-p newname)
(find-file newname)
)
)
)
I suggest you to have a look into cc-other-file-alist, for its use with ff-find-other-file. It allows custom function calls and you can save some coding:
Example:
(setq cc-other-file-alist
`(
("\\.cxx$" ,(my-look-for-other-file-1))
("\\.hxx$" ,(my-look-for-other-file-2))))

Clojure create directory hierarchy - but not in a procedural way

Let's say I need to create the following directory structure in Clojure:
a
\--b
| \--b1
| \--b2
\--c
\-c1
Instead of doing procedural things like the following:
(def a (File. "a"))
(.mkdir a)
(def b (File. a "b"))
(.mkdir b)
;; ...
... is there a clever way to somehow represent the above actions as data, declaratively, and then create the hierarchy in one fell swoop?
a quick and simple approach would be to make a vector of dirs to create and map mkdir on to it:
user> (map #(.mkdir (java.io.File. %)) ["a", "a/b" "a/b/c"])
(true true true)
or you can specify your dir structure as a tree and use zippers to walk it making the dirs on the way:
(def dirs ["a" ["b" ["b1" "b2"]] ["c" ["c1"]]])
(defn make-dir-tree [original]
(loop [loc (zip/vector-zip original)]
(if (zip/end? loc)
(zip/root loc)
(recur (zip/next
(do (if (not (vector? (zip/node loc)))
(let [path (apply str (interpose "/" (butlast (map first (zip/path loc)))))
name (zip/node loc)]
(if (empty? path)
(.mkdir (java.io.File. name))
(.mkdir (java.io.File. (str path "/" name))))))
loc))))))
(make-dir-tree dirs)
.
arthur#a:~/hello$ find a
a
a/c
a/c/c1
a/b
a/b/c
a/b/b2
a/b/b1
If you are doing a lot of general systems administration then something heavier may be in order. The pallet project is a library for doing system administration of all sorts on physical and cloud hosted systems (though it tends to lean towards the cloudy stuff). Specifically the directory
Another option if you want to easily handle creating recursive directories is to use .mkdirs
user> (require '[clojure.java.io :as io]')
user> (.mkdirs (io/file "a/b/c/d"))
You can use absolute path eg. /a/b/c/d or else it will be created relative to the path you initiated the repl from.
Also handy to check if given path is not an existing directory
user> (.isDirectory (io/file "a/b/c/d"))

how to load resources from a specific .jar file using clojure.java.io

In clojure.java.io, there is a io/resource function but I think it just loads the resource of the current jar that is running. Is there a way to specify the .jar file that the resource is in?
For example:
I have a jar file: /path/to/abc.jar
abc.jar when unzipped contains some/text/output.txt in the root of the unzipped directory
output.txt contains the string "The required text that I want."
I need functions that can do these operations:
(list-jar "/path/to/abc.jar" "some/text/")
;; => "output.txt"
(read-from-jar "/path/to/abc.jar" "some/text/output.txt")
;; => "The required text that I want"
Thanks in advance!
From Ankur's comments, I managed to piece together the functions that I needed:
The java.util.jar.JarFile object does the job.
you can call the method (.entries (Jarfile. a-path)) to give the list of files but instead of returning a tree structure:
i.e:
/dir-1
/file-1
/file-2
/dir-2
/file-3
/dir-3
/file-4
it returns an enumeration of filenames:
/dir-1/file-1, /dir-1/file-2, /dir-1/dir-2/file-3, /dir-1/dir-3/file-4
The following functions I needed are defined below:
(import java.util.jar.JarFile)
(defn list-jar [jar-path inner-dir]
(if-let [jar (JarFile. jar-path)]
(let [inner-dir (if (and (not= "" inner-dir) (not= "/" (last inner-dir)))
(str inner-dir "/")
inner-dir)
entries (enumeration-seq (.entries jar))
names (map (fn [x] (.getName x)) entries)
snames (filter (fn [x] (= 0 (.indexOf x inner-dir))) names)
fsnames (map #(subs % (count inner-dir)) snames)]
fsnames)))
(defn read-from-jar [jar-path inner-path]
(if-let [jar (JarFile. jar-path)]
(if-let [entry (.getJarEntry jar inner-path)]
(slurp (.getInputStream jar entry)))))
Usage:
(read-from-jar "/Users/Chris/.m2/repository/lein-newnew/lein-newnew/0.3.5/lein-newnew-0.3.5.jar"
"leiningen/new.clj")
;=> "The list of built-in templates can be shown with `lein help new`....."
(list-jar "/Users/Chris/.m2/repository/lein-newnew/lein-newnew/0.3.5/lein-newnew-0.3.5.jar" "leiningen")
;; => (new/app/core.clj new/app/project.clj .....)