Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
122 lines (92 sloc) 4.73 KB

Flue

Build Status

Flue is a Swift library. It extracts, converts and validates values from user input. It produces help strings describing those operations.

Flue supports both unnamed values and named values source like the environment.

Requirements

Flue is written in Swift 3.0. It requires Foundation. It has been tested on a Mac with Xcode 8.0.

Example

This code is from Example.playground:

import Cocoa
import Flue

struct Settings {
    var debug: Bool
    var port: Int
    var config: Any
    var key: String
    var path: [String]
}

enum SettingsOrError {
    case success(Settings)
    case error(String)
}

func readSettings(env: [String: String]) -> (SettingsOrError, String) {
    let vp = Flue.ValueParser()
    let dp = Flue.DictParser(dict: env, valueParser: vp)

    let conversions = Flue.Conversions()

    let debugExtract = conversions.add(dp.extract("DEBUG").asBool())
    let portExtract = conversions.add(dp.extract("PORT").asInt())
    let configExtract = conversions.add(dp.extract("CONFIG").asJSON())
    let keyExtract = conversions.add(dp.extract("KEY").minLength(6).addHelp("Encryption key.", prefix: false))
    let pathExtract = conversions.add(dp.extract("PATH").asType({ val, ctx in val.components(separatedBy: ":") }, help: "String with components separated by :"))

    let helps = conversions.usage()
    let maxNameWidth = helps.reduce(0) { max($0, $1[0].characters.count) }
    let help = helps.map({ formatHelp(parts: $0, nameWidth: maxNameWidth) }).joined(separator: "\n")

    do {
        return (.success(Settings(
            debug: try debugExtract.required(),
            port: try portExtract.required(),
            config: try configExtract.required(),
            key: try keyExtract.required(),
            path: try pathExtract.required()
        )), help)
    } catch {
        return (.error(conversions.errors().map { String(describing: $0) }.joined(separator: ", ")), help)
    }
}

func readEnv(env: [String: String]) {
    let (res, help) = readSettings(env: env)
    print("Read from environment:")
    print("help: \(help)")
    switch res {
    case let .success(s): print("Settings: \(s)")
    case let .error(e): print("Error: \(e)")
    }
}

func readExample() {
    readEnv(env: ["DEBUG": "yes", "PORT": "42158", "CONFIG": "{\"asdf\": \"bar\"}", "KEY": "qwertyui", "PATH": ProcessInfo.processInfo.environment["PATH"]!])
    readEnv(env: [:])
}

func formatHelp(parts: [String], nameWidth: Int) -> String {
    return String(format: "%@ -- %@", parts[0].padding(toLength: nameWidth, withPad: " ", startingAt: 0), parts[1..<parts.count].joined(separator: ". "))
}

Calling readExample() will get you output like this:

Read from environment:
help: DEBUG  -- Boolean: true if string starts with [YyTt1-9]
PORT   -- Integer
CONFIG -- JSON Data
KEY    -- Minimum length: 6. Encryption key.
PATH   -- String with components separated by :
Settings: Settings(debug: true, port: 42158, config: {
    asdf = bar;
}, key: "qwertyui", path: ["/usr/bin", "/bin", "/usr/sbin", "/sbin"])
Read from environment:
help: DEBUG  -- Boolean: true if string starts with [YyTt1-9]
PORT   -- Integer
CONFIG -- JSON Data
KEY    -- Minimum length: 6. Encryption key.
PATH   -- String with components separated by :
Error: Required value DEBUG wasn't found, Required value PORT wasn't found, Required value CONFIG wasn't found, Required value KEY wasn't found, Required value PATH wasn't found

Localization

Flue contains user-visible messages. By default they are loaded with the standard Foundation localization APIs from the bundle containing the ValueParser class, but the system can be overridden by specifying a custom StringLoader to the ValueParser instance.

If you wish to use Foundation NSBundle-based string loading mechanism, as Flue does by default, but want to load the strings from another bundle, you can use the stringBundleLoader helper function to construct a loader that uses NSLocalizedString with your desired bundle.

If you don't want to use NSBundle, like when you're building a standalone CLI executable, you can construct an arbitrary StringLoader function. One approach is to embed the localizations in the binary with the linker; see Examples/FlueCLIExample for a sample. See "Other Linker Flags" in the project and the loading code in main.swift. Basically you'll need to specify extra linker flags, like:

-Wl,-sectcreate,__LOCALIZATIONS,__base,../../Flue/en.lproj/Localizable.strings,-segprot,__LOCALIZATIONS,r,r

And then you can load them with getsectdata and construct a dictionary with propertyListFromStringsFileFormat.

License

Flue is released under the MIT license. See LICENSE for details.