Contents

jaywcjlove/swiftini

中文InstallationUsagePublic API

Features

  • Parse INI text or files.
  • Serialize INIObject values back to INI text.
  • Supports ; and # comments.
  • Supports [section] and [parent.child] nested sections.
  • Supports key = value arrays.
  • Supports duplicate-key arrays.
  • Supports escaped \; and \# so values are not treated as comments.
  • Supports quoted keys and values.
  • Supports typed true, false, and null values.
  • Keeps numbers as strings by default, matching npm ini.
  • Skips proto keys to avoid prototype-pollution style issues.

Installation

Add the package to Package.swift:

dependencies: [
    .package(url: "https://github.com/jaywcjlove/SwiftINI.git", from: "1.0.0")
]

Then add SwiftINI to your target dependencies:

targets: [
    .target(
        name: "YourTarget",
        dependencies: ["SwiftINI"]
    )
]

For local development, use a path dependency:

.package(path: "/Users/wong/git/packages/SwiftINI")

Usage

import SwiftINI

let text = """
; This comment is ignored
scope = global

[database]
user = dbuser
password = dbpassword
database = use_this_database

[paths.default]
datadir = /var/lib/data
array[] = first value
array[] = second value
array[] = third value
"""

let config = INI.parse(text)

print(config["scope"]?.string ?? "")
print(config["database"]?["user"]?.string ?? "")
print(config["paths"]?["default"]?["datadir"]?.string ?? "")
print(config["paths"]?["default"]?["array"]?.array ?? [])

Reading Files

let config = try INI.parse(contentsOfFile: "path/to/config.ini")
print(config["Header"]?["Key"]?.string ?? "")

You can also read from a URL:

let url = URL(fileURLWithPath: "path/to/config.ini")
let config = try INI.parse(contentsOf: url)

Serializing

let object: INIObject = [
    "scope": "global",
    "database": [
        "user": "dbuser",
        "password": "dbpassword",
        "database": "use_this_database",
    ],
    "paths": [
        "default": [
            "datadir": "/var/lib/data",
            "array": [
                "first value",
                "second value",
                "third value",
            ],
        ],
    ],
]

let output = INI.stringify(object)
print(output)

Output:

scope=global

[database]
user=dbuser
password=dbpassword
database=use_this_database

[paths.default]
datadir=/var/lib/data
array[]=first value
array[]=second value
array[]=third value

Public API

INI.parse(_ text: String) -> INIObject
INI.parse(contentsOfFile path: String) throws -> INIObject
INI.parse(contentsOf url: URL) throws -> INIObject

INI.decode(_ text: String) -> INIObject

INI.stringify(_ object: INIObject) -> String
INI.encode(_ object: INIObject) -> String

Recommended semantics:

  • parse: parse INI text or files.
  • decode: alias of parse(_:), matching npm ini.
  • stringify: serialize an INIObject to INI text.
  • encode: alias of stringify(_:), matching npm ini.

Accessing Values

INIObject subscripting returns INIValue?, and object values can be chained:

let value = config["database"]?["user"]?.string

Common value accessors:

value.string   // String?
value.bool     // Bool?
value.array    // [INIValue]?
value.object   // INIObject?

INIValue supports:

case string(String)
case bool(Bool)
case null
case array([INIValue])
case object(INIObject)

Decode Options

let config = INI.parse(text, options: .init(bracketedArray: true))

INI.DecodeOptions:

| Option | Default | Description | | --- | --- | --- | | bracketedArray | true | When true, key = value is parsed as an array. When false, repeated keys are parsed as arrays. |

Example:

array[] = first
array[] = second

Default result:

["array": ["first", "second"]]

Duplicate-key mode:

let config = INI.parse(text, options: .init(bracketedArray: false))
array = first
array = second

Result:

["array": ["first", "second"]]

Encode Options

let output = INI.stringify(
    object,
    options: .init(
        align: true,
        sort: true,
        whitespace: true
    )
)

INI.EncodeOptions:

| Option | Default | Description | | --- | --- | --- | | section | nil | Wraps output in a section prefix. | | align | false | Aligns = and implies whitespace. | | newline | false | Adds a blank line after section headers. | | sort | false | Sorts keys before encoding. | | whitespace | false | Uses key = value; otherwise outputs key=value. | | platform | nil | Use "win32" for CRLF output. | | bracketedArray | true | When true, emits key=value; when false, emits repeated key=value lines. |

Section Prefix

let output = INI.stringify(
    ["type": "file"],
    options: .init(section: "log")
)

Output:

[log]
type=file

Array Format

Arrays are emitted as key by default:

let output = INI.stringify([
    "items": ["first", "second"]
])
items[]=first
items[]=second

With bracketedArray: false:

let output = INI.stringify(
    ["items": ["first", "second"]],
    options: .init(bracketedArray: false)
)
items=first
items=second

Format Behavior

Comments

Lines starting with ; or # are ignored:

; comment

name = SwiftINI

Inline comments are cut off at unescaped ; or #:

name = value ; comment
hash = value # comment

Escape ; or # when the character is part of the value:

semicolon = this\; is not a comment
hash = this\# is not a comment

Quotes

Quoted values are parsed as JSON strings when possible, preserving leading/trailing spaces and escaped characters:

value = " a "

Parsed result:

" a "

Type Conversion

Only these literal values are converted automatically:

enabled = true
disabled = false
empty = null
count = 10

Parsed result:

enabled  // .bool(true)
disabled // .bool(false)
empty    // .null
count    // .string("10")

10 remains a string.

Testing

swift test

The tests cover parsing, serializing, arrays, duplicate keys, nested sections, CRLF input/output, escaped comments, proto protection, and the main fixture behaviors from npm ini.

License

Licensed under the MIT License.

Package Metadata

Repository: jaywcjlove/swiftini

Default branch: main

README: README.md