Company logo with the letters 'NotTooBad Software' TextSmith Blog

Moderator: parsing commandline arguments in Swift

Swift  

There are a lot of command line argument parsers written in Swift available on Github, so when I needed one for a client project I was surprised I could not find any which fulfilled these requirements:


Nor could I find any where it would be relatively easy to add these features, since most just loop through the arguments from first to last and have one big function trying to find out what to do with each of them. That becomes messy quickly.

I think it’s much simpler to create multiple small argument parsers, where each one in turn takes in the list of arguments, removes the ones it wants to handle and passes the rest on to the next one. Any arguments left over are then unrecognised and can be handled accordingly. This way the end user does not need to worry about the order of the arguments. The developer however needs to be careful about the order of the parsers.

So I created Moderator with these goals:

And as always:

And this is what it looks like (from linuxmain-generator):

import Moderator
import FileSmith

let arguments = Moderator(description: "Automatically add code to Swift Package Manager projects to run unit tests on Linux.")
let overwrite = arguments.add(.option("o","overwrite", description: "Replace <test directory>/LinuxMain.swift if it already exists."))
let testdirarg = arguments.add(Argument<String?>
    .optionWithValue("testdir", name: "test directory", description: "The path to the directory with the unit tests.")
    .default("Tests"))
_ = arguments.add(Argument<String?>
    .singleArgument(name: "directory", description: "The project root directory.")
    .default("./")
    .map { (projectpath: String) in
        let projectdir = try Directory(open: projectpath)
        try projectdir.verifyContains("Package.swift")
        Directory.current = projectdir
    })

do {
    try arguments.parse()

    let testdir = try Directory(open: testdirarg.value)
    if !overwrite.value && testdir.contains("LinuxMain.swift") {
        throw ArgumentError(errormessage: "\(testdir.path)/LinuxMain.swift already exists. Use -o/--overwrite to replace it.")
    }
    ...
} catch {
    WritableFile.stderror.print(error)
    exit(Int32(error._code))
}

For more, see the project homepage.

Suggest changes to post.

Comments

Want to hear about new posts?