Shannon Severance
Shannon Severance

Reputation: 18410

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"?

Upvotes: 4

Views: 2558

Answers (2)

Shlomi
Shlomi

Reputation: 4748

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)))

Upvotes: 11

Teodor
Teodor

Reputation: 759

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!

Upvotes: 3

Related Questions