623 lines
22 KiB
JavaScript
Executable File
623 lines
22 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
import {readFileSync, writeFileSync} from 'fs';
|
|
import {toFormattedSwitchCase} from './commonUtility.mjs';
|
|
import {mediaDocumentations, toDocumentation} from './MediaDocumentations.mjs';
|
|
|
|
String.prototype.snakeToCamel = function () {
|
|
return this.replace(/-(.)/g, (_, m) => m.toUpperCase())
|
|
};
|
|
String.prototype.firstNumberTreated = function () {
|
|
return this.replace(/^\d/, (m) => '_'.concat(m))
|
|
}
|
|
|
|
/** @typedef RawMedia {[key:string]:Array<string>} */
|
|
/**
|
|
* @typedef RawMediaRecord {{
|
|
* type : string,
|
|
* tree : Array<string>,
|
|
* }}
|
|
*/
|
|
/**
|
|
* @typedef SwiftEnumRecord ({
|
|
* name : string,
|
|
* cases : Array<{
|
|
* case: string,
|
|
* value: string
|
|
* }>
|
|
* })
|
|
*/
|
|
|
|
/** @type {RawMedia}*/
|
|
const medias = JSON.parse(readFileSync('./Medias.json').toString());
|
|
|
|
/** @type {function({type:string, subtypes:string}): RawMediaRecord} */
|
|
const toMediaRecord = ({type, subtype}) => ({
|
|
type,
|
|
tree: subtype.split(';')[0].split('+')[0].split('.')
|
|
});
|
|
|
|
const mediaRecords = Object.freeze(
|
|
Object
|
|
.keys(medias)
|
|
.map(key => medias[key].map(subtype => ({type: key, subtype: subtype})))
|
|
.reduce((l, r) => l.concat(r), [])
|
|
.map(toMediaRecord)
|
|
.filter(r => r.tree.length === 1) // TODO: Rework when support for tree subtypes is added.
|
|
);
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @returns {{subtype: string, caseName: string}}
|
|
*/
|
|
function toSubtype(name) {
|
|
return {
|
|
subtype: name,
|
|
caseName: name.snakeToCamel().firstNumberTreated()
|
|
}
|
|
}
|
|
|
|
/** @type {Array<{pascalCase:string,lowerCase:string}>} */
|
|
const types = mediaRecords
|
|
.filter((r, _, l) => l.find(_r => _r.type === r.type) === r)
|
|
.map(r => r.type)
|
|
.map(type => ({lowerCase: type, pascalCase: type.replace(/^(.)/, (_, m) => m.toUpperCase())}));
|
|
|
|
types
|
|
.map(({pascalCase, lowerCase}) => ({
|
|
fileName: pascalCase.concat('.swift'),
|
|
code: `import Foundation
|
|
|
|
${toDocumentation(mediaDocumentations[lowerCase].typeDoc.description, 0)}
|
|
public enum ${pascalCase} {
|
|
${
|
|
mediaRecords
|
|
.filter(r => r.type === lowerCase)
|
|
.map(r => toSubtype(r.tree[0]))
|
|
.filter((c, i, l) => l.findIndex(x => x.caseName === c.caseName) === i)
|
|
.map(({subtype, caseName}) => ` /// Represents the \`${subtype}\` subtype.
|
|
case ${caseName}(Suffix? = nil, Parameters? = nil)`)
|
|
.slice()
|
|
.sort((a, b) => a.charCodeAt(0) - b.charCodeAt(0))
|
|
.concat(toDocumentation(
|
|
mediaDocumentations["generic"].typeDoc.otherCase
|
|
.concat([""])
|
|
.concat(mediaDocumentations[lowerCase].typeDoc.otherCase),
|
|
2)
|
|
)
|
|
.concat(' case other(String, Suffix? = nil, Parameters? = nil)')
|
|
.concat(' case anything(Suffix? = nil, Parameters? = nil)')
|
|
.join('\n')
|
|
}
|
|
}
|
|
|
|
extension ${pascalCase}: CustomStringConvertible {
|
|
public var description: String { rawValue }
|
|
}
|
|
|
|
extension ${pascalCase}: RawRepresentable {
|
|
|
|
public init(rawValue: String) {
|
|
let (subtype, suffix, parameters) = convert(string: rawValue)
|
|
switch subtype {
|
|
${
|
|
mediaRecords
|
|
.filter(r => r.type === lowerCase)
|
|
.map(r => ({
|
|
caseName: r.tree[0].snakeToCamel().firstNumberTreated(),
|
|
value: r.tree[0]
|
|
}))
|
|
.filter((r, i, l) => i === l.findIndex(_r => _r.caseName === r.caseName))
|
|
.map(({caseName, value}) => [
|
|
` case "${value}": `,
|
|
`self = .${caseName}(suffix, parameters)`
|
|
])
|
|
.slice()
|
|
.sort(([a], [b]) => a.charCodeAt(0) - b.charCodeAt(0))
|
|
.concat([[
|
|
` case "*": `,
|
|
`self = .anything(suffix, parameters)`
|
|
]])
|
|
.concat([[
|
|
` default: `,
|
|
`self = .other(subtype, suffix, parameters)`
|
|
]])
|
|
.map(toFormattedSwitchCase)
|
|
.join('\n')
|
|
}
|
|
}
|
|
}
|
|
|
|
public var rawValue: String {
|
|
switch self {
|
|
${
|
|
mediaRecords
|
|
.filter(r => r.type === lowerCase)
|
|
.map(r => ({
|
|
caseName: r.tree[0].snakeToCamel().firstNumberTreated(),
|
|
value: r.tree[0]
|
|
}))
|
|
.filter((r, i, l) => i === l.findIndex(_r => _r.caseName === r.caseName))
|
|
.map(({caseName, value}) => [
|
|
` case .${caseName}(let suffix, let parameters): `,
|
|
`return "${value}\\(suffix)\\(parameters)"`
|
|
])
|
|
.slice()
|
|
.sort(([a], [b]) => a.charCodeAt(0) - b.charCodeAt(0))
|
|
.concat([[
|
|
' case .other(let value, let suffix, let parameters): ',
|
|
'return "\\(value)\\(suffix)\\(parameters)"'
|
|
]])
|
|
.concat([[
|
|
' case .anything(let suffix, let parameters): ',
|
|
'return "*\\(suffix)\\(parameters)"'
|
|
]])
|
|
.map(toFormattedSwitchCase)
|
|
.join('\n')
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
extension ${pascalCase}: MediaSubtype { public var type: MediaType { .${lowerCase}(self) } }
|
|
|
|
extension ${pascalCase}: Hashable {
|
|
public static func ==(lhs: Self, rhs: Self) -> Bool {
|
|
switch lhs {
|
|
${
|
|
mediaRecords
|
|
.filter(r => r.type === lowerCase)
|
|
.map(r => ({
|
|
caseName: r.tree[0].snakeToCamel().firstNumberTreated(),
|
|
value: r.tree[0]
|
|
}))
|
|
.filter((r, i, l) => i === l.findIndex(_r => _r.caseName === r.caseName))
|
|
.slice()
|
|
.sort((a, b) => a.caseName.charCodeAt(0) - b.caseName.charCodeAt(0))
|
|
.map(({caseName, value}) => [
|
|
` case .${caseName}(let lhsSuffix, let lhsParameters):`,
|
|
` guard case let .${caseName}(rhsSuffix, rhsParameters) = rhs else { return false }`,
|
|
` if lhsSuffix != rhsSuffix { return false }`,
|
|
` return lhsParameters == rhsParameters`
|
|
].join('\n'))
|
|
.concat([
|
|
' case .other(let lhsSubtype, let lhsSuffix, let lhsParameters):',
|
|
' guard case let .other(rhsSubtype, rhsSuffix, rhsParameters) = rhs else { return false }',
|
|
' if lhsSubtype.description != rhsSubtype.description { return false }',
|
|
' if lhsSuffix != rhsSuffix { return false }',
|
|
' return lhsParameters == rhsParameters'
|
|
].join('\n'))
|
|
.concat([
|
|
' case .anything(let lhsSuffix, let lhsParameters):',
|
|
' guard case let .anything(rhsSuffix, rhsParameters) = rhs else { return false }',
|
|
' if lhsSuffix != rhsSuffix { return false }',
|
|
' return lhsParameters == rhsParameters'
|
|
].join('\n'))
|
|
.join('\n')
|
|
}
|
|
}
|
|
}
|
|
|
|
public func hash(into hasher: inout Hasher) {
|
|
switch self {
|
|
${
|
|
mediaRecords
|
|
.filter(r => r.type === lowerCase)
|
|
.map(r => ({
|
|
caseName: r.tree[0].snakeToCamel().firstNumberTreated(),
|
|
value: r.tree[0]
|
|
}))
|
|
.filter((r, i, l) => i === l.findIndex(_r => _r.caseName === r.caseName))
|
|
.slice()
|
|
.sort((a, b) => a.caseName.charCodeAt(0) - b.caseName.charCodeAt(0))
|
|
.map(({caseName}, i) => [
|
|
` case .${caseName}(let suffix, let parameters):`,
|
|
` hasher.combine(${i})`,
|
|
` hasher.combine(suffix)`,
|
|
` hasher.combine(parameters)`
|
|
].join('\n'))
|
|
.concat([
|
|
' case .other(let subtype, let suffix, let parameters):',
|
|
` hasher.combine(-1)`,
|
|
` hasher.combine(subtype.description)`,
|
|
` hasher.combine(suffix)`,
|
|
` hasher.combine(parameters)`
|
|
].join('\n'))
|
|
.concat([
|
|
' case .anything(let suffix, let parameters):',
|
|
` hasher.combine(-2)`,
|
|
` hasher.combine(suffix)`,
|
|
` hasher.combine(parameters)`
|
|
].join('\n'))
|
|
.join('\n')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`
|
|
}))
|
|
.forEach(r => writeFileSync(r.fileName, r.code))
|
|
|
|
writeFileSync(
|
|
'MediaType.swift',
|
|
`import Foundation
|
|
|
|
/// A type-safe representation of [Media Type](https://www.iana.org/assignments/media-types/media-types.xhtml)s
|
|
/// (or formerly known as MIME types).
|
|
///
|
|
/// You can create a media type in a type-safe manner using one of the possible cases. You can also create
|
|
/// media type instances simply using string literals.
|
|
///
|
|
/// \`\`\`swift
|
|
/// let mediaType: MediaType = "application/json" // is equivalent to
|
|
/// MediaType.application(.json())
|
|
/// \`\`\`
|
|
///
|
|
/// Media type suffixes and parameters are supported both via string literals and \`\`MediaType\`\` cases.
|
|
///
|
|
/// \`\`\`swift
|
|
/// let mediaType: MediaType = "application/atom; charset=utf-8" // is equivalent to
|
|
/// MediaType.application(.atom(nil, ["charset": "utf-8"]))
|
|
///
|
|
/// let mediaType: MediaType = "application/atom+xml" // is equivalent to
|
|
/// MediaType.application(.atom(.xml))
|
|
///
|
|
/// let mediaType: MediaType = "application/atom+xml; charset=utf-8" // is equivalent to
|
|
/// MediaType.application(.atom(.xml, ["charset": "utf-8"]))
|
|
/// \`\`\`
|
|
///
|
|
/// You can create media type trees using either the string literal syntax, or using the \`other\` case of a particular
|
|
/// media type.
|
|
///
|
|
/// \`\`\`swift
|
|
/// "application/vnd.efi.img" // is equivalent to
|
|
/// MediaType.application(.other("vnd.efi.img"))
|
|
/// \`\`\`
|
|
public enum MediaType {
|
|
${
|
|
types
|
|
.map(({lowerCase, pascalCase}) => `${toDocumentation(mediaDocumentations[lowerCase].caseDoc, 2)}
|
|
case ${lowerCase}(${pascalCase})`)
|
|
.join('\n')
|
|
}
|
|
/// Represents a custom media type that is currently not officially defined.
|
|
///
|
|
/// Represents a custom media type with the given \`type\` and \`subtype\`. Optionally, you can specify a \`\`Suffix\`\` and
|
|
/// \`\`Parameters\`\`.
|
|
case other(type: CustomStringConvertible, subtype: CustomStringConvertible, Suffix? = nil, Parameters? = nil)
|
|
/// Represents a wildcard media type.
|
|
///
|
|
/// A wildcard media type has a type of \`*\`. A few examples:
|
|
///
|
|
/// \`\`\`swift
|
|
/// MediaType.anything(.anything()) // Creates: */*
|
|
/// MediaType.anything(.other("dialog")) // Creates: */dialog
|
|
/// MediaType.anything(.other("response", .xml)) // Creates: */response+xml
|
|
/// \`\`\`
|
|
case anything(Anything)
|
|
}
|
|
|
|
extension MediaType: CustomStringConvertible {
|
|
/// The textual representation of the media type.
|
|
///
|
|
/// The string form of a media type follows the pattern: \`type/subtype[+suffix][;parameters]\`. A few examples:
|
|
///
|
|
/// \`\`\`swift
|
|
/// MediaType.text(.css()).description // Outputs: text/css
|
|
/// MediaType.audio(.ac3(nil, ["rate": 32000])).description // Outputs: audio/ac3;rate=32000
|
|
/// MediaType.application(.atom(.xml, ["charset": "utf-8"])).description // Outputs: application/atom+xml;charset=utf-8
|
|
/// \`\`\`
|
|
public var description: String { rawValue }
|
|
}
|
|
|
|
extension MediaType: RawRepresentable {
|
|
/// Creates a media type from its raw string value.
|
|
///
|
|
/// - Parameter rawValue: The raw string value.
|
|
public init(rawValue: String) {
|
|
let chunks = rawValue.split(separator: "/", maxSplits: 1)
|
|
let rawType = String(chunks.first ?? "*")
|
|
let rawSubtype = String(chunks.count > 1 ? chunks[1] : "")
|
|
let (subtype, suffix, parameters) = convert(string: rawSubtype)
|
|
switch rawType {
|
|
${
|
|
types
|
|
.map(({lowerCase, pascalCase}) => [
|
|
` case "${lowerCase}": `,
|
|
`self = .${lowerCase}(${pascalCase}(rawValue: rawSubtype))`
|
|
])
|
|
.concat([[
|
|
' case "*": ',
|
|
'self = .anything(Anything(rawValue: rawSubtype))'
|
|
]])
|
|
.concat([[
|
|
' default: ',
|
|
'self = .other(type: rawType, subtype: subtype, suffix, parameters)'
|
|
]])
|
|
.slice()
|
|
.sort(([a], [b]) => a.charCodeAt(0) - b.charCodeAt(0))
|
|
.map(toFormattedSwitchCase)
|
|
.join('\n')
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The raw string value of the media type.
|
|
public var rawValue: String {
|
|
switch self {
|
|
${
|
|
types
|
|
.map(({lowerCase}) => [
|
|
` case .${lowerCase}(let subtype): `,
|
|
`return "${lowerCase}/\\(subtype)"`
|
|
])
|
|
.concat([[
|
|
' case .other(let type, let subtype, let suffix, let parameters):',
|
|
'return "\\(type)/\\(subtype)\\(suffix)\\(parameters)"'
|
|
]])
|
|
.concat([[
|
|
' case .anything(let anything):',
|
|
'return "*/\\(anything)"'
|
|
]])
|
|
.slice()
|
|
.sort(([a], [b]) => a.charCodeAt(0) - b.charCodeAt(0))
|
|
.map(toFormattedSwitchCase)
|
|
.join('\n')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension MediaType: ExpressibleByStringLiteral {
|
|
/// Creates a media type from a string literal.
|
|
///
|
|
/// Do not call this initializer directly. This rather allows you to use a string literal where you have to provide
|
|
/// a \`\`MediaType\`\` node.
|
|
public init(stringLiteral value: String) { self.init(rawValue: value) }
|
|
}
|
|
|
|
extension MediaType: Hashable {
|
|
public static func ==(lhs: Self, rhs: Self) -> Bool {
|
|
switch lhs {
|
|
${
|
|
types
|
|
.slice()
|
|
.sort((a, b) => a.lowerCase.charCodeAt(0) - b.lowerCase.charCodeAt(0))
|
|
.map(({lowerCase}) => [
|
|
` case .${lowerCase}(let lhs): `,
|
|
`if case let .${lowerCase}(rhs) = rhs { return lhs == rhs } else { return false }`
|
|
])
|
|
.concat([[
|
|
' case .anything(let lhs): ',
|
|
`if case let .anything(rhs) = rhs { return lhs == rhs } else { return false }`
|
|
]])
|
|
.map(toFormattedSwitchCase)
|
|
.concat([
|
|
' case .other(let lhsType, let lhsSubtype, let lhsSuffix, let lhsParameters):',
|
|
' guard case let .other(rhsType, rhsSubtype, rhsSuffix, rhsParameters) = rhs else { return false }',
|
|
' if (lhsType.description != rhsType.description) { return false }',
|
|
' if (lhsSubtype.description != rhsSubtype.description) { return false }',
|
|
' if (lhsSuffix != rhsSuffix) { return false }',
|
|
' if (lhsParameters != rhsParameters) { return false }',
|
|
' return true'
|
|
].join('\n'))
|
|
.join('\n')
|
|
}
|
|
}
|
|
}
|
|
|
|
public func hash(into hasher: inout Hasher) {
|
|
switch self {
|
|
${
|
|
types
|
|
.slice()
|
|
.sort((a, b) => a.lowerCase.charCodeAt(0) - b.lowerCase.charCodeAt(0))
|
|
.map(({lowerCase}, i) => [
|
|
` case .${lowerCase}(let subtype):`,
|
|
` hasher.combine(${i})`,
|
|
' hasher.combine(subtype)'
|
|
].join('\n'))
|
|
.concat([
|
|
' case .anything(let subtype):',
|
|
` hasher.combine(-1)`,
|
|
' hasher.combine(subtype)'
|
|
].join('\n'))
|
|
.concat([
|
|
' case .other(let type, let subtype, let suffix, let parameters):',
|
|
' hasher.combine(-2)',
|
|
' hasher.combine(type.description)',
|
|
' hasher.combine(subtype.description)',
|
|
' hasher.combine(suffix)',
|
|
' hasher.combine(parameters)'
|
|
].join('\n'))
|
|
.join('\n')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`);
|
|
|
|
writeFileSync(
|
|
'Anything.swift',
|
|
`import Foundation
|
|
|
|
public enum Anything {
|
|
case other(CustomStringConvertible, Suffix? = nil, Parameters? = nil)
|
|
case anything(Suffix? = nil, Parameters? = nil)
|
|
}
|
|
|
|
extension Anything: CustomStringConvertible {
|
|
public var description: String { rawValue }
|
|
}
|
|
|
|
extension Anything: MediaSubtype { public var type: MediaType { .anything(self) } }
|
|
|
|
extension Anything: RawRepresentable {
|
|
|
|
public init(rawValue: String) {
|
|
let (subtype, suffix, parameters) = convert(string: rawValue)
|
|
switch subtype {
|
|
case "*" : self = .anything(suffix, parameters)
|
|
default : self = .other(subtype, suffix, parameters)
|
|
}
|
|
}
|
|
|
|
public var rawValue: String {
|
|
switch self {
|
|
case .other(let subtype, let suffix, let params): return "\\(subtype)\\(suffix)\\(params)"
|
|
case .anything(let suffix, let params): return "*\\(suffix)\\(params)"
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
extension Anything: Hashable {
|
|
public static func ==(lhs: Self, rhs: Self) -> Bool {
|
|
switch lhs {
|
|
case .other(let lhsSubtype, let lhsSuffix, let lhsParameters):
|
|
guard case let .other(rhsSubtype, rhsSuffix, rhsParameters) = rhs else { return false }
|
|
if lhsSubtype.description != rhsSubtype.description { return false }
|
|
if lhsSuffix != rhsSuffix { return false }
|
|
return lhsParameters == rhsParameters
|
|
case .anything(let lhsSuffix, let lhsParameters):
|
|
guard case let .anything(rhsSuffix, rhsParameters) = rhs else { return false }
|
|
if lhsSuffix != rhsSuffix { return false }
|
|
return lhsParameters == rhsParameters
|
|
}
|
|
}
|
|
|
|
public func hash(into hasher: inout Hasher) {
|
|
switch self {
|
|
case .other(let subtype, let suffix, let parameters):
|
|
hasher.combine(-1)
|
|
hasher.combine(subtype.description)
|
|
hasher.combine(suffix.description)
|
|
hasher.combine(parameters)
|
|
case .anything(let suffix, let parameters):
|
|
hasher.combine(-2)
|
|
hasher.combine(suffix)
|
|
hasher.combine(parameters)
|
|
}
|
|
}
|
|
}
|
|
`);
|
|
|
|
writeFileSync(
|
|
'Parameters.swift',
|
|
`import Foundation
|
|
|
|
/// Represents parameters of \`\`MediaType\`\`s.
|
|
///
|
|
/// A media type may have parameters. For example \`text/html;charset=utf-8\` defines a media type with UTF-8 charset
|
|
/// instead of the default ASCII.
|
|
///
|
|
/// You can specify arbitrary parameters to *any* of the \`\`MediaType\`\`s using a Swift
|
|
/// [dictionary](https://developer.apple.com/documentation/swift/dictionary). Keep in mind though, that not all such
|
|
/// parameter values are registered (see the
|
|
/// [official site](https://www.iana.org/assignments/media-types/media-types.xhtml) for details).
|
|
///
|
|
/// You can specify parameters by using either the Swift DSL or string literal syntax. Parameters in string variables
|
|
/// are also supported. The following examples are equivalent:
|
|
///
|
|
/// \`\`\`swift
|
|
/// let mediaType: MediaType = "audio/ac3;rate=32000" // is equivalent to
|
|
///
|
|
/// let mediaType = MediaType.audio(.ac3(nil, ["rate": 32_000])) // is equivalent to
|
|
///
|
|
/// let rawMediaType = "audio/ac3;rate=32000"
|
|
/// let mediaType = MediaType(rawValue: rawMediaType)
|
|
/// \`\`\`
|
|
public typealias Parameters = [String: CustomStringConvertible?]
|
|
|
|
internal extension DefaultStringInterpolation {
|
|
@inlinable mutating func appendInterpolation(_ value: Parameters?) {
|
|
guard let parameters = value else { return }
|
|
if parameters.isEmpty { return }
|
|
for (key, value) in parameters {
|
|
appendLiteral(";")
|
|
appendLiteral(key)
|
|
guard let value = value else { continue }
|
|
appendLiteral("=")
|
|
appendLiteral(value.description)
|
|
}
|
|
}
|
|
}
|
|
`);
|
|
|
|
writeFileSync(
|
|
'_Utility.swift',
|
|
`import Foundation
|
|
|
|
internal typealias RawSubtype = (
|
|
subtype: String,
|
|
suffix: Suffix?,
|
|
parameters: Parameters?
|
|
)
|
|
internal func convert(string rawValue: String) -> RawSubtype {
|
|
let chunks = rawValue.split(separator: ";")
|
|
let parameterChunks = chunks.count > 1 ? chunks[1...] : []
|
|
let parameters: Parameters? = parameterChunks.isEmpty
|
|
? nil
|
|
: parameterChunks
|
|
.map { $0.split(separator: "=") }
|
|
.reduce(into: [:]) { (result, parameterChunk) in
|
|
if parameterChunk.isEmpty { return }
|
|
result?[String(parameterChunk.first!).trimmingCharacters(in: .whitespacesAndNewlines)] = parameterChunk.indices.contains(1)
|
|
? parameterChunk[1].trimmingCharacters(in: .whitespacesAndNewlines)
|
|
: nil
|
|
}
|
|
let suffixedChunks = chunks.first?.split(separator: "+")
|
|
let subType = suffixedChunks?.first ?? "*"
|
|
let suffix = (suffixedChunks?.count ?? 0) > 1
|
|
? suffixedChunks?[1] == nil
|
|
? nil
|
|
: Suffix(rawValue: String(suffixedChunks![1]))
|
|
: nil
|
|
return (
|
|
subtype: String(subType),
|
|
suffix: suffix,
|
|
parameters: parameters
|
|
)
|
|
}
|
|
|
|
func ==(lhs: Parameters, rhs: Parameters) -> Bool {
|
|
if lhs.keys != rhs.keys { return false }
|
|
for (lhsKey, lhsValue) in lhs {
|
|
guard let rhsValue = rhs[lhsKey] else {
|
|
if lhsValue != nil {
|
|
return false
|
|
} else {
|
|
continue
|
|
}
|
|
}
|
|
guard let lhsValue = lhsValue else { return false }
|
|
if lhsValue.description != rhsValue!.description { return false }
|
|
}
|
|
return true
|
|
}
|
|
|
|
func !=(lhs: Parameters, rhs: Parameters) -> Bool { !(lhs == rhs) }
|
|
|
|
func ==(lhs: Parameters?, rhs: Parameters?) -> Bool {
|
|
var leftIsNil = false
|
|
var rightIsNil = false
|
|
if case .none = lhs { leftIsNil = true }
|
|
if case .none = rhs { rightIsNil = true }
|
|
if leftIsNil || rightIsNil { return leftIsNil == rightIsNil }
|
|
return lhs! == rhs!
|
|
}
|
|
|
|
func !=(lhs: Parameters?, rhs: Parameters?) -> Bool { !(lhs == rhs) }
|
|
|
|
extension Hasher {
|
|
@inlinable mutating func combine(_ value: Parameters?) {
|
|
guard let value = value else { return }
|
|
for (key, value) in value {
|
|
combine(key)
|
|
combine(value?.description)
|
|
}
|
|
}
|
|
}
|
|
`);
|