Repeating optional arguments

I am trying to create a utility to create a DMG from a given number of files.

That DMG will contain a number of files and for each file I have a mandatory
name plus an optional position within the DMG window plus an optional type.

So the command line looks like the following:

createDMG [standard options] target [file specifications]
createDMG --verbose example.dmg \
     bin/binary --position=20,20 \
     README.pdf --position 20,80 \
     /Applications --position 60,20 --type=link

In this example --verbose is a "standard option".

As far as I know it is not possibility for the standard argument parser to parse the "file specifications". Am I correct or am I missing something here?

So I ended up by parsing the file specifications manually using custom code:

@Argument(
      parsing: .unconditionalRemaining,
      help: "Specify content in the form path [--position x y] [--type file|link]")
var args: [String]

And then in the run() method I will parse args manually.

One of the unwanted side-effects is that standard options like --verbose must now precede the arguments.

Is there a better way?

The way I’ve seen many other programs (not even necessarily Swift Argument Parser ones) do this is to combine all the information into a custom format in a single argument. For instance : is not generally valid in filenames so you could something like

createDMG --verbose example.dmg \
     bin/binary:20,20 \
     README.pdf:20,80 \
     /Applications:60,20:link

I admit you lose some user experience in having to teach the custom format (especially if someone could specify just the link type and not the position, for instance), but it’s a trade-off between that and being able to specify the other arguments anywhere in the rest of the list.

Assuming any character other than NUL will never exist in a file name can lead to trouble.

The reason you don't normally see colon (:) in macOS file names is because the Hierarchical File System (HFS), the format used by Macs prior HFS+ and prior to APFS, used it as a path separator. For example, what you'd spell as /Users/alex/hello.swift in a Unix-y way would have been represented by HFS as something like Macintosh HD:Users:alex:hello.swift.

You can create a file with a colon in its name in Terminal. I just tested touch a:b now. It's worth noting that Finder shows that file's name as a/b — from a user's perspective, slashes are allowed but not colons, and Finder silently maps between them to actually use a colon in the name stored on disk.

3 Likes

Note that this is why I said generally :wink:

Regardless, even if there are some corner-cases, both macOS and Linux use : as a path separator for things like your PATH, so in general it should be decently okay to use. Obviously not perfect, but it’s a common solution to this problem that other tools I’ve used have implemented successfully. Again, it’s a trade-off between being able to specify your arguments at any point in the command and having to deal with the user experience concerns introduced by the solution.

ArgumentParser doesn’t really support that kind of relationship between positional arguments and options/flags, so I don’t think there’s a much better way to handle what you’re trying to do. One thing I might do is to remove the .unconditionalRemaining parsing strategy, and instead call your command with the file specifications after a double-dash (--):

createDMG [standard options] target -- [file specifications]

That way you’ll be able to be more flexible with the order of the options / target, and still get all your specifications at the end.

1 Like

Thanks All.

Just happy I didn't overlook anything obvious :grinning:

I don't think that the : approach is something I will go for. The -- approach is something I may consider.