Files
swift-mirror/utils/swift-xcodegen/Sources/Xcodeproj/PropertyList.swift
Hamish Knight 1667925c9b [xcodegen] Avoid an intermediate String
We don't need to create an intermediate String
when escaping characters in the plist.
2024-12-19 12:35:12 +00:00

153 lines
4.1 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Foundation
/// A enum representing data types for legacy PropertyList type.
/// Note that the `identifier` enum is not strictly necessary,
/// but useful to semantically distinguish the strings that
/// represents object identifiers from those that are just data.
/// see: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html
public enum PropertyList {
case identifier(String)
case string(String)
case array([PropertyList])
case dictionary([String: PropertyList])
var string: String? {
if case .string(let string) = self {
return string
}
return nil
}
var array: [PropertyList]? {
if case .array(let array) = self {
return array
}
return nil
}
}
extension PropertyList: CustomStringConvertible {
public var description: String {
String(decoding: serialize(), as: UTF8.self)
}
}
extension PropertyList {
/// Serializes the Plist enum.
public func serialize() -> Data {
var writer = UTF8Writer()
writePlistRepresentation(to: &writer)
return Data(writer.bytes)
}
}
fileprivate extension PropertyList {
struct UTF8Writer {
private(set) var level: Int = 0
private(set) var bytes: [UInt8] = []
init() {
self += "// !$*UTF8*$!\n"
}
mutating func withIndent(body: (inout Self) -> Void) {
level += 1
body(&self)
level -= 1
}
mutating func append(_ byte: UInt8) {
bytes.append(byte)
}
static func += (writer: inout UTF8Writer, str: StaticString) {
str.withUTF8Buffer { utf8 in
writer.bytes += utf8
}
}
@_disfavoredOverload
static func += (writer: inout UTF8Writer, str: String) {
writer.bytes += str.utf8
}
mutating func indent() {
for _ in 0 ..< level {
self += " "
}
}
/// Appends the given string, with instances of quote (") and backward slash
/// (\) characters escaped with a backslash.
mutating func appendEscaped(_ string: String) {
for char in string.utf8 {
if char == UInt8(ascii: "\\") || char == UInt8(ascii: "\"") {
append(UInt8(ascii: "\\"))
}
append(char)
}
}
}
/// Private function to generate OPENSTEP-style plist representation.
func writePlistRepresentation(to writer: inout UTF8Writer) {
// Do the appropriate thing for each type of plist node.
switch self {
case .identifier(let ident):
// FIXME: we should assert that the identifier doesn't need quoting
writer += ident
case .string(let string):
writer += "\""
writer.appendEscaped(string)
writer += "\""
case .array(let array):
writer += "(\n"
writer.withIndent { writer in
for (i, item) in array.enumerated() {
writer.indent()
item.writePlistRepresentation(to: &writer)
writer += (i != array.count - 1) ? ",\n" : "\n"
}
}
writer.indent()
writer += ")"
case .dictionary(let dict):
let dict = dict.sorted(by: {
// Make `isa` sort first (just for readability purposes).
switch ($0.key, $1.key) {
case ("isa", "isa"): return false
case ("isa", _): return true
case (_, "isa"): return false
default: return $0.key < $1.key
}
})
writer += "{\n"
writer.withIndent { writer in
for (key, value) in dict {
writer.indent()
writer += key
writer += " = "
value.writePlistRepresentation(to: &writer)
writer += ";\n"
}
}
writer.indent()
writer += "}"
}
}
}