`file.choose()` in `Julia`?

How about readdir()?

2 Likes

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, globbing. 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.

1 Like

This takes it a step further:

using Glob
using REPL.TerminalMenus: request, RadioMenu
files = glob(glob"*.txt", ".")
file = files[request(RadioMenu(files))]
6 Likes

That is a big step @GunnarFarneback ! Thanks a lot! Interactiveness is partly achieved! @rafael.guerra this one is also for you!

1 Like

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)

3 Likes

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.

1 Like

@gustaphe, thanks and nevermind. Will delete post.

JLL done! I’m working on the wrapper. :smiley:

7 Likes

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 GitHub - GunnarFarneback/PackageCompatUI.jl: Terminal UI for [compat] section of Julia Project.toml files..

2 Likes

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.

5 Likes

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

1 Like

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.

1 Like

You do all the time :slightly_smiling_face: 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

7 Likes

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.

2 Likes

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

3 Likes

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.