user=> (map str (fs/which "java" {:all true}))
("/Users/borkdude/.jenv/versions/11.0/bin/java" "/Users/borkdude/.jenv/shims/java" "/usr/bin/java" "/Users/borkdude/.jenv/shims/java")
😃Oh, this is also funny:
user=> (map str (fs/real-path "."))
("Users" "borkdude" "Dropbox" "dev" "clojure" "babashka" "fs")
Path implements iterable so it works out of the box with map, etc.Alrighty, babashka.fs alpha 4: https://babashka.org/fs/babashka.fs.html Please gloss over the docs and try it out from clojars if you have some time. I think this is the first release that will be in babashka soon. Not planning any changes unless feedback.
I think it might be better to remove the follow-links behavior in delete-tree as this seems uncommon. Not even rm -rf supports it.
Or I don't know. I'll ask a second opinion on this. There might be use cases, but it seems dangerous
https://clojurians.slack.com/archives/C03RZGPG3/p1612689372122100
fwiw, I have a bb script that does recursive delete, and I have it check for symlinks first and fail if it finds any. This might have been before I realized a delete only deletes the link... just the same I was obviously feeling very cautious about the danger accidentally over-deleting something.
right. so same as rm -rf
not including the option anymore
cool, good thinking
Very cool, I will certainly make use of this!
Minor: the docstring for directory-stream
states that it “Returns a stream of all files in dir” - maybe make it clearer whether or not it includes files in subdirs too?
Yeah, I might just make this function private since I don't think it will be useful to anyone really. It's more like an impl detail
I think most people want list-dir
It's not recursive. For recursive you want glob
I'll add that to the docstring
"Returns all paths in dir as vector. For descending into subdirectories use glob."
If find docstring for file-name
a bit confusing “Returns farthest component from the root as string, if any.” The name of the fn tells me the intent but the docstring kind of makes me scratch my head. The javadoc for .getName
might be closer “Returns the name of the file or directory denoted by this abstract pathname.” Oh, does that mean file-name
might not be a great name for the fn?
glob
looks handy!
Do you have a different suggestion for the name?
or docstring
it's basically foo/bar/baz -> baz
Maybe just name
? Because it can be a directory name too.
And that matches Java naming so would be perhaps familiar.
ok. I was hoping to avoid clashes with clojure fn names, but yeah, name might be the most clear
Oh… right.
note that clj-commons/fs has "base-name" which I also found confusing
Yeah. To avoid a clash maybe name-from
? Or is that too contrived?
I'll just make it name.
For docstring, maybe more like Java: “Returns the name of the file or directory for x”
Oh btw, no, I named it after this one: https://docs.oracle.com/javase/7/docs/api/java/nio/file/Path.html#getFileName() Note that this API is mostly based on Path, not http://java.io.File
so actually this name is correct
> Returns the name of the file or directory denoted by this path as a Path object. The file name is the farthest element from the root in the directory hierarchy.
Ah… I see where your docstring inspiration came from!
I'll just add an example
Yeah, so a directory has a “file name”, I guess.
Still this “The file name is the farthest element from the root in the directory hierarchy.” is hard to parse, at least for my little brain.
> "Returns the name of the file or directory. E.g. (file-name \"foo/bar/baz\") returns \"baz\". "
Mucho better!
altered.
which
will be handy in scripts! Should docstring explain what {:keys [:all]}
does?
Probably yes
If you were hosting docs on cljdoc I would suggest wikilinks when referring to other fns in docstrings.
I'm a bit tired of including cljdoc into the equation because of broken analysis and not being able to update for a given version. I haven't looked at it for a while.
After rewrite-clj v1 is out, I’m probably going to head back to cljdoc to help out with issues.
Updated to: "Locates a program in (exec-paths) similar to the which Unix command. The :all option will return a vec of results instead of a single result."
Should it be 'similarly' ?
Maybe, but either reads fine to me.
codox updated
I like how you handle delete for files. A clearer design, in my opinion than <http://clojure.java.io|clojure.java.io>
(delete-file f & [silently])
.
the impl is basically dictated by java.nio
Alternative implementation of which
using already existing ingredients of fs:
(str (first (filter fs/executable? (fs/list-dirs (filter fs/exists? (fs/exec-paths)) "java"))))
"/Users/borkdude/.jenv/versions/11.0/bin/java"
"java"
can be a glob like "*.{.clj, cljs}"
too
I like that delete-tree
has an option to not follow symlinks. Nice for safer deletes. Should following links be off by default and with an option to turn it :follow-links
on? Or are you just matching a Java API?
Just matching the API
ok
I tried to make up as little as possible myself
I think you put it together very nicely, though!
thanks
and thanks for the review too!
my pleasure!
The hardest function by far was glob
which is not a standard function in the Java API
Docstring for delete
might need an update?
So I first made a function walk-file-tree
which basically mirrors Files/walkFileTree
and built it on top of that
why update, what's wrong with it?
(defn delete
"Deletes f via Path#delete. Returns nil if the delete was succesful,
throws otherwise."
[dir]
(Files/delete (as-path dir)))
Doesn’t delete via Path#delete?
Oh and small typo in succesful
Same idea for delete-if-exists
docstring.
Both updated now, thanks
If I use :nofolow-links
on delete-tree
and there is a symlink found will it just skip the symlink or throw?
It will just delete the symlink itself, I think, but that would be good to check ...
Oh right… that makes sense.
I think I must make a unit test for this...
Also, do you think a delete-tree-if-exists
would be useful? To match delete-if-exists
?
Also no mention of what delete-tree
returns in docstring.
It always returns nil
right now
And will throw if it can’t delete something, I assume.
The root is deleted using fs/delete
and this will throw indeed
Makes sense
I’m probably getting too nit picky but (exec-paths _)`` shows in API docs, which is minor but a bit weird.
hmm I think we can make delete-tree take follow-links instead of no-follow-links
since there is no Files thing for this
if it matches existing APIs enough, it would be a safer default choice.
I think we can base it on walk-file-tree
instead of what we do now
and that has follow-links
Docstring, I think is incorrect here:
(defn file-time->millis
"Converts a java.nio.file.attribute.FileTime to a java.time.Instant."
[^FileTime ft]
(.toMillis ft))
Probably belongs on?
(defn file-time->instant [^FileTime ft]
(.toInstant ft))
Please continue. I'll first fix the delete-tree and then go through the others
That’s all I got for now! Looking awesome!
afk for dinner now, back in a bit
lunchtime for me!
Is babashka.fs usable in normal java clojure? It seems like it’d be useful in the general case
@max.r.rothman yes
Oh… one other small thought… should side-effecting fns have a !
?
I think in the context of immutable data structures that might make sense, but in the context of files, the function names are pretty explanatory about their side effects? E.g. the functions in http://clojure.java.io don't have !s either
Strangely enough I can't make walk-file-tree follow links...
o wait, it's just my test
so the file gets visited, but delete needs to both delete symlink and the target dir.... hmm
@lee I hope this makes sense:
(testing "symlinks"
(let [tmp-dir1 (temp-dir)
tmp-dir2 (temp-dir)
tmp-file (fs/path tmp-dir2 "foo")
_ (fs/create-file tmp-file)
link-path (fs/path tmp-dir1 "link-to-tmp-dir2")
link (fs/create-sym-link link-path tmp-dir2)]
(is (fs/exists? tmp-file))
(fs/delete-tree tmp-dir1 {:follow-links true})
(is (not (fs/exists? link)))
(is (not (fs/exists? tmp-file)))
(is (not (fs/exists? tmp-dir2)))
(is (not (fs/exists? tmp-dir1))))
(let [tmp-dir1 (temp-dir)
tmp-dir2 (temp-dir)
tmp-file (fs/path tmp-dir2 "foo")
_ (fs/create-file tmp-file)
link-path (fs/path tmp-dir1 "link-to-tmp-dir2")
link (fs/create-sym-link link-path tmp-dir2)]
(is (fs/exists? tmp-file))
(fs/delete-tree tmp-dir1 {:follow-links false})
(is (not (fs/exists? link)))
(is (fs/exists? tmp-file))
(is (fs/exists? tmp-dir2))
(is (not (fs/exists? tmp-dir1)))))
The first case tests that the link + the target gets deleted
The second case tests that only the links get deleted
We could do this similarly for delete
: if you provide :follow-links
there as well, both the link and the target gets deleted....
but one other possibility could be that only the target gets deleted... :/
but if you delete the target, then what is a symlink without a target. that seems not right ;)
so yeah, let's include that option there too
yeah, agreed, deleting target but not link is wrong
I wonder why Files doesn't provide this behavior of follow-link for delete. We might just support it in delete-tree. If people want to delete both, they can use that, while delete follows the normal java behavior
ah, in delete I think you will have a conflicting behavior when you say: delete this link, but also follow the link. If the link then is a dir, you are not delete-tree-ing, so this can't remove the entire dir
so what's now on master probably makes most sense. also updated codox with your other suggestions
Good observation, yeah. So delete
will delete a symlink but never follow it, right?
yes
Got it
I'll also doc that