Extend Arg with Arg.File?

Hello,

is it possible to extend Arg with Arg.File?

My use case

My (imaginary) tool has two options: -o and -v.

-o expects an output filename
-v turns verbose logging on.

So this is the spec:

[
  "-o",
  Arg.String (fun fname -> outfile := fname),
  "output filename";

  "-v",
  Arg.Set verbose,
  "verbose mode";
]

Problem

When a user mistakenly omits the filename after -o and invokes the tool with mytool -o -v the output is written into a file “-v”.
Most likely, this wasn’t the intended behavior.

My current workarround

Whenever a string argument is supposed to be a filename I make sure it dosn’t start with a dash:

  "-o",
  Arg.String (fun fname ->
      match fname with
      | "" ->
	    eprintf "-o: filename cannot be empty string";
		exit 1

      | "-" ->
	    outfile := fname (* stdin or stdout *)

      | s when s.[0] = '-' ->
	    eprintf "-o: filename missing before %S\n" fname;
		exit 1

      | _ ->
	    outfile := fname
    ),
    "outfile filename";

Problem with my workaround

I need this over and over again and would like to handle it with something like

"-o",
Arg.File (fun fname -> outfile := fname),
"output filename";

which implements the workaround internally.

My attempted solution

module Arg = struct
  include Arg

  type spec += File of (string -> unit)
end

Problem with my attempted solution

It doesn’t compile :-/

Error: Type definition spec is not extensible

Questions

  • Is there a better way to handle the situation than the work around described above?
  • Whould it be appropriate to add Arg.File to the Stdlib?

Note that this is the standard command-line behaviour in the Unix world (try it with your favourite C compiler).

Only extensible sum types can be, well, extended. Ordinary sum types cannot be extended. Note that it wouldn’t make sense to extend Arg.spec, as the module Arg would not know what to do with the new constructor.

Best wishes,
Nicolás

Note that this is the standard command-line behaviour in the Unix world (try it with your favourite C compiler).

You’re right.
I thought filenames with leading dashes must be “escaped” with -- in
the Unix world, e.g. gcc -o -- -v file.c. Turns out this is not the
case and thus shouldn’t be “fixed” in the Stdlib.

Only extensible sum types can be, well, extended.
Ordinary sum types cannot be extended.

I skimed over https://sites.google.com/site/ocamlopen/ and
misinterpreted the “…” as arbitrary constructors.
Thus I concluded all types are extensible since 4.02.
I was wrong again.

Note that it wouldn’t make sense to extend Arg.spec, as the module Arg would not know what to do with the new constructor.

Yes, this was just the first step.

Instead of trying to extend the type, you can define a function to share your code:

let arg_file file_ref =
  Arg.String (fun fname ->
      match fname with
      | "" ->
	    eprintf "-o: filename cannot be empty string";
		exit 1

      | "-" ->
	    file_ref := fname (* stdin or stdout *)

      | s when s.[0] = '-' ->
	    eprintf "-o: filename missing before %S\n" fname;
		exit 1

      | _ ->
	    file_ref := fname
    )
in

Arg.spec [
  "-i", arg_file infile, "<FILE> input file";
  "-o", arg_file outfile, "<FILE> output file";
]

Although that would prevent you from passing filenames which start with a dash, so I would not do that.

This isn’t really a solution, but … have you thought about learning to use the excellent, excellent (did I mention it’s excellent) cmdliner package? It was sort of meant for this sort of thing – adding behaviour to the processing of args in a modular way, I mean.

1 Like

Thanks, this is viable, to0.

This should still be possible with --.

Not really - thanks for the suggestion. I usually try to avoid non-stdlib packages if the functionality takes just a couple of lines.