How about readdir()
?
Yesterday, I fiddled around with non-interactive alternatives to opening and reading file paths and I found out about readdir()
, which is convenient, indeed, for getting and joining paths+files!
But I also came across a package called Glob
that does, well, glob
bing. I ended up with the following two non-interactive lines of code for getting back, at least, filenames of a specific file type (.txt here) so that the search is limited. Not ideal, but at least saves some time if the directory is not huge. Any input or hint for improvement is more than welcome:
# with the Glob dependency
julia> using Glob
julia> glob(glob"*.txt", ".")
8-element Vector{String}:
"./big_text.txt"
"./exams.txt"
"./intermediate_text.txt"
"./new.txt"
"./newtxt.txt"
"./newtxt2.txt"
"./small_text.txt"
"./test.txt"
# no dependency - base Julia
julia> filter(str -> contains(str, r"\.txt"), readdir(join=true))
8-element Vector{String}:
"/Users/atantos/Desktop/big_text.txt"
"/Users/atantos/Desktop/exams.txt"
"/Users/atantos/Desktop/intermediate_text.txt"
"/Users/atantos/Desktop/new.txt"
"/Users/atantos/Desktop/newtxt.txt"
"/Users/atantos/Desktop/newtxt2.txt"
"/Users/atantos/Desktop/small_text.txt"
"/Users/atantos/Desktop/test.txt"
The benefit of being able to interactively select a directory through a GUI interface to get back the file path is that during daily tasks a (data) scientist-user can very easily browse the filesystem and find a specific file (whose name they donāt recall) that they want to open/read.
This takes it a step further:
using Glob
using REPL.TerminalMenus: request, RadioMenu
files = glob(glob"*.txt", ".")
file = files[request(RadioMenu(files))]
That is a big step @GunnarFarneback ! Thanks a lot! Interactiveness is partly achieved! @rafael.guerra this one is also for you!
If you donāt need the extra functionality of glob,
files = filter(endswith(".txt"), readdir(; join=true))
does the same thing without the dependency. (Possibly worse performance, but you wonāt be bottlenecked on this operation anyway)
Thatās actually the default behaviour, readdir("SubDir")
will return "filename.ext"
, readdir("SubDir"; join=true)
gives "SubDir/filename.ext"
and readdir(abspath("SubDir"); join=true)
gives "/home/path/of/SubDir/filename.ext"
. So using basename
is crossing the river for water.
@gustaphe, thanks and nevermind. Will delete post.
JLL done! Iām working on the wrapper.
May I use this code to do a more advance terminal file picker?
Of course, but you probably want to make a custom menu, so there wouldnāt be much left of these particular code lines. You can get some inspiration of what can be done with REPL.TerminalMenus
from https://github.com/GunnarFarneback/PackageCompatUI.jl.
Looking forward to hearing more news on the wrapper!
I did some very quick experimentation, while waiting for @suavesitoās better wrapper:
using NativeFileDialog_jll
function choose_file()
path = Ref(Ptr{UInt8}())
r = @ccall libnfd.NFD_OpenDialog(C_NULL::Ptr{Cchar}, C_NULL::Ptr{Cchar}, path::Ref{Ptr{UInt8}})::Cint
if r == 2
# User clicked "Cancel"
out = nothing
elseif r == 1
out = unsafe_string(path[])
else
error()
end
return out
end
This works fine on Linux, but it seems to be pretty buggy on macOS (you canāt select the file in the dialog until you press some random keys), I donāt know if itās an upstream problem or what.
@giordano, your magic works wonderfully on Win10.
Thanks for sharing such a useful code.
This is the first time Iāve seen adding a C (?) library directly using the Julia package manager. Could you please comment on this or provide a link for further reading?
You just discovered _jll packages and GitHub - JuliaPackaging/BinaryBuilder.jl: Binary Dependency Builder for Julia . Lotās of packages use C (and others langs such as fortran) dependencies and have these packages as dependencies and julia downloads the binaries automatically through the artifact system.
You do all the time Itās actually nice to see users arenāt even aware of it, I guess it means it works well.
For further information you can read Pkg + BinaryBuilder -- The Next Generation or watch the videos listed in Home Ā· BinaryBuilder.jl. There will be also a talk about this, specifically about the integration of JLL packages with the package manager, in the upcoming Packaging Conference
Unfortunately, on mac it hangsā¦no error or warning or anythingā¦
Hi @giordano, thanks for testing it, and also thanks for that testing code (I was having trouble understanding how to pass that double pointerā¦).
About Macā¦ this issue is fixed here but (alongside other patches) it is in the devel
branch.
If you (or someone else) can try to build that branch and tell me how it goes, the NativeFileDialog_jll.jl can be change to the devel branch if it fixes the issue.
For building the branch on Mac and testing the complete code would be
$ git clone 'https://github.com/mlabbe/nativefiledialog'
$ cd nativefiledialog/src
$ git checout devel
$ cc nfd_cocoa.m nfd_common.c \ # or clang instead of cc
-framework Foundation -framework AppKit -Iinclude \
-O2 -Wall -Wextra -fno-exceptions -fPIC -shared -o libnfd.dylib
$ julia
julia> const libnfd = "./libnfd.dylib"
julia> function choose_file()
path = Ref(Ptr{UInt8}())
r = @ccall libnfd.NFD_OpenDialog(C_NULL::Ptr{Cchar}, C_NULL::Ptr{Cchar}, path::Ref{Ptr{UInt8}})::Cint
if r == 2
# User clicked "Cancel"
out = nothing
elseif r == 1
out = unsafe_string(path[])
else
error()
end
return out
end
julia> choose_file()
Edit: In Linux it works well in the devel
branch.
Just to let you know or any other interested in this, I ran your code above on my mac and when I reached the const
assignment line got:
julia> const libnfd = "./libnfd.dylib"
ERROR: cannot assign a value to variable NativeFileDialog_jll.libnfd from module Main
Stacktrace:
[1] top-level scope
@ REPL[16]:1
Scratch that. Just update your packages, you should get an updated version of NativeFileDialog_jll
which should be working better on macOS
Ok! The result is excellent! I report it here that it works perfectly for macos Catalina. No hang and no unexpected window behaviour.
PS: Added choose_file()
to ~/.julia/config/startup.jl
for now and waiting for the wrapper, I guess.