Ahoy!
I’m a bit dissatisfied when I see the different ways we print filesystem paths for human consumption to users in OCaml code. I think we should settle once and for all on a method to print paths.
I expect that if a program prints a path, I should be able to copy-paste it as-is, in the general case, and give it to another system command. I may add quoting myself, but would prefer not to.
I often see %S
being used to escape the path, as in:
Printf.printf "My path %S" path
S
: convert a string argument to OCaml syntax (double quotes, escapes).
I think this is incorrect: paths shouldn’t be printed as OCaml strings. This leads to overzealous double-quoting, such as:
# let err = Printf.sprintf "My path %S" {|/home/Antonin/My Pictures|} in
Printf.ksprintf failwith "error: %s" err;;
Exception: Failure "error: My path \"/home/Antonin/My Pictures\""
I’m sure users don’t care about the nested escaped quotes. I’m not sure what %S
is good for unless you’re pretty-printing OCaml code.
I think that Filename.quote
is more adapted, as it:
Returns a quoted version of a file name, suitable for use as one argument in a command line, escaping all meta-characters.
# let err = Printf.sprintf "My path %s" (Filename.quote {|/home/Antonin/My Pictures|}) in
Printf.ksprintf failwith "error: %s" err;;
Exception: Failure "error: My path '/home/Antonin/My Pictures'".
It does suffer from the fact that Filename
is not parametrized by the type of shell, but depends of the platform (Windows, Cygwin, Unix), which makes it impossible out-of-the box to emit bash-compatible paths from a program build for Win32, or emit Windows paths from Linux. See also Filename.{Unix,Win32,Cygwin}
should be exported #11940.
The elegant Fpath
library has conversions to strings and pretty-printers. If I’m not mistaken it doesn’t do anything special with respect to the shell or human readers.
Then there’s the case of ANSI escape sequences embedded in file names, or Unicode characters for that matter. I would expect this ideal function to present escaped paths, such as GNU ls:
$ touch "\e[0;31mred\e[0m"
$ for file in *; do echo "$file"; done
red # actually shown in red
$ ls
\e[0;31mred\e[0m
Neither Filename.quote
nor Fpath.pp
escape ANSI codes:
# print_string (Filename.quote "/october/\027[0;31mred\027[0m/bloup");;
'/october/red/bloup'- : unit = ()
# Fpath.pp Format.std_formatter (Fpath.v "/october/\027[0;31mred\027[0m/bloup");;
/october/red/bloup- : unit = ()
Don’t even get me started on Windows paths! I often see paths containing double backslashes, that I really can’t do anything with unless I painstakingly remove one of each, or change the whole quoting style. Sometimes paths start with backslashes and switch to forward slashes, which the system can deal with, but is not consistent for users.
In conclusion: please stop using %S
for printing paths. A path isn’t an OCaml string. Someone please parametrize and fix the Filename
module. Consider switching to Fpath
.
Is there somewhere a function that can correctly print and escape paths? maybe in compiler-libs? in Dune std? in opam? if not, can we devise one and push it everywhere?