commit ad0c82348f519b52959131bd19a83d3a4af81143 Author: fmodf Date: Wed Jun 19 15:06:39 2024 +0000 Initial commit wip wip diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bce6a1b --- /dev/null +++ b/.gitignore @@ -0,0 +1,124 @@ +docs +Frameworks + +######################### +# **.gitignore** file for Xcode4 / OS X Source projects +# +# NB: if you are storing "built" products, this WILL NOT WORK, +# and you should use a different **.gitignore** (or none at all) +# This file is for SOURCE projects, where there are many extra +# files that we want to exclude +# +# For updates, see: http://stackoverflow.com/questions/49478/git-ignore-file-for-xcode-projects +######################### + +##### +# OS X temporary files that should never be committed + +.DS_Store +*.swp +profile + + +#### +# Xcode temporary files that should never be committed +# +# NB: NIB/XIB files still exist even on Storyboard projects, so we want this... + +*~.nib + + +#### +# Xcode build files - +# +# NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "DerivedData" + +DerivedData/ + +# NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "build" + +build/ + + +##### +# Xcode private settings (window sizes, bookmarks, breakpoints, custom executables, smart groups) +# +# This is complicated: +# +# SOMETIMES you need to put this file in version control. +# Apple designed it poorly - if you use "custom executables", they are +# saved in this file. +# 99% of projects do NOT use those, so they do NOT want to version control this file. +# ..but if you're in the 1%, comment out the line "*.pbxuser" + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 +# NB: also, whitelist the default ones, some projects need to use these +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + + +#### +# Xcode 4 - semi-personal settings, often included in workspaces +# +# You can safely ignore the xcuserdata files - but do NOT ignore the files next to them +# + +xcuserdata + +#### +# XCode 4 workspaces - more detailed +# +# Workspaces are important! They are a core feature of Xcode - don't exclude them :) +# +# Workspace layout is quite spammy. For reference: +# +# (root)/ +# (project-name).xcodeproj/ +# project.pbxproj +# project.xcworkspace/ +# contents.xcworkspacedata +# xcuserdata/ +# (your name)/xcuserdatad/ +# xcuserdata/ +# (your name)/xcuserdatad/ +# +# +# +# Xcode 4 workspaces - SHARED +# +# This is UNDOCUMENTED (google: "developer.apple.com xcshareddata" - 0 results +# But if you're going to kill personal workspaces, at least keep the shared ones... +# +# +!xcshareddata + +#### +# XCode 4 build-schemes +# +# PRIVATE ones are stored inside xcuserdata +!xcschemes + +#### +# Xcode 4 - Deprecated classes +# +# Allegedly, if you manually "deprecate" your classes, they get moved here. +# +# We're using source-control, so this is a "feature" that we do not want! + +*.moved-aside +/.idea +/AnotherIM/.idea +/AnotherIM.xcodeproj +/Info.plist +/AnotherIM/AnotherIM.entitlements +/XMPPSwift/Client/VoIP/rickroll.mp4 +/.nvim +/buildServer.json +TODO.txt +PASSWD.txt +/xmls diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Info.plist b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Info.plist new file mode 100644 index 0000000..01d943f --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Info.plist @@ -0,0 +1,42 @@ + + + + + BuildMachineOSBuild + + CFBundleDevelopmentRegion + en + CFBundleExecutable + SwiftGen_SwiftGenCLI + CFBundleIdentifier + SwiftGen.SwiftGenCLI.resources + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + SwiftGen_SwiftGenCLI + CFBundlePackageType + BNDL + CFBundleSupportedPlatforms + + MacOSX + + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 13A233 + DTPlatformName + macosx + DTPlatformVersion + 11.3 + DTSDKBuild + 20E214 + DTSDKName + macosx11.3 + DTXcode + 1300 + DTXcodeBuild + 13A233 + LSMinimumSystemVersion + 10.11 + + diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/literals-swift4.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/literals-swift4.stencil new file mode 100644 index 0000000..af60477 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/literals-swift4.stencil @@ -0,0 +1,43 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if palettes %} +{% set enumName %}{{param.enumName|default:"ColorName"}}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +#if os(macOS) + import AppKit + {% if enumName != 'NSColor' %}{{accessModifier}} enum {{enumName}} { }{% endif %} +#elseif os(iOS) || os(tvOS) || os(watchOS) + import UIKit + {% if enumName != 'UIColor' %}{{accessModifier}} enum {{enumName}} { }{% endif %} +#endif + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - Colors + +// swiftlint:disable identifier_name line_length type_body_length +{{accessModifier}} extension {{enumName}} { +{% macro h2f hex %}{{hex|hexToInt|int255toFloat}}{% endmacro %} +{% macro enumBlock colors accessPrefix %} + {% for color in colors %} + /// 0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}} (r: {{color.red|hexToInt}}, g: {{color.green|hexToInt}}, b: {{color.blue|hexToInt}}, a: {{color.alpha|hexToInt}}) + {{accessPrefix}}static let {{color.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = #colorLiteral(red: {% call h2f color.red %}, green: {% call h2f color.green %}, blue: {% call h2f color.blue %}, alpha: {% call h2f color.alpha %}) + {% endfor %} +{% endmacro %} + {% if palettes.count > 1 or param.forceFileNameEnum %} + {% set accessPrefix %}{{accessModifier}} {% endset %} + {% for palette in palettes %} + enum {{palette.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call enumBlock palette.colors accessPrefix %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call enumBlock palettes.first.colors "" %} + {% endif %} +} +// swiftlint:enable identifier_name line_length type_body_length +{% else %} +// No color found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/literals-swift5.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/literals-swift5.stencil new file mode 100644 index 0000000..af60477 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/literals-swift5.stencil @@ -0,0 +1,43 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if palettes %} +{% set enumName %}{{param.enumName|default:"ColorName"}}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +#if os(macOS) + import AppKit + {% if enumName != 'NSColor' %}{{accessModifier}} enum {{enumName}} { }{% endif %} +#elseif os(iOS) || os(tvOS) || os(watchOS) + import UIKit + {% if enumName != 'UIColor' %}{{accessModifier}} enum {{enumName}} { }{% endif %} +#endif + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - Colors + +// swiftlint:disable identifier_name line_length type_body_length +{{accessModifier}} extension {{enumName}} { +{% macro h2f hex %}{{hex|hexToInt|int255toFloat}}{% endmacro %} +{% macro enumBlock colors accessPrefix %} + {% for color in colors %} + /// 0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}} (r: {{color.red|hexToInt}}, g: {{color.green|hexToInt}}, b: {{color.blue|hexToInt}}, a: {{color.alpha|hexToInt}}) + {{accessPrefix}}static let {{color.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = #colorLiteral(red: {% call h2f color.red %}, green: {% call h2f color.green %}, blue: {% call h2f color.blue %}, alpha: {% call h2f color.alpha %}) + {% endfor %} +{% endmacro %} + {% if palettes.count > 1 or param.forceFileNameEnum %} + {% set accessPrefix %}{{accessModifier}} {% endset %} + {% for palette in palettes %} + enum {{palette.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call enumBlock palette.colors accessPrefix %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call enumBlock palettes.first.colors "" %} + {% endif %} +} +// swiftlint:enable identifier_name line_length type_body_length +{% else %} +// No color found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/swift4.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/swift4.stencil new file mode 100644 index 0000000..57c2d79 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/swift4.stencil @@ -0,0 +1,84 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if palettes %} +{% set colorAlias %}{{param.colorAliasName|default:"Color"}}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +#if os(macOS) + import AppKit.NSColor + {{accessModifier}} typealias {{colorAlias}} = NSColor +#elseif os(iOS) || os(tvOS) || os(watchOS) + import UIKit.UIColor + {{accessModifier}} typealias {{colorAlias}} = UIColor +#endif + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Colors + +// swiftlint:disable identifier_name line_length type_body_length +{% set enumName %}{{param.enumName|default:"ColorName"}}{% endset %} +{{accessModifier}} struct {{enumName}} { + {{accessModifier}} let rgbaValue: UInt32 + {{accessModifier}} var color: {{colorAlias}} { return {{colorAlias}}(named: self) } + +{% macro rgbaValue color %}0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}}{% endmacro %} +{% macro enumBlock colors %} + {% for color in colors %} + /// + /// Alpha: {{color.alpha|hexToInt|int255toFloat|percent}}
(0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}}) + {{accessModifier}} static let {{color.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}(rgbaValue: {% call rgbaValue color %}) + {% endfor %} +{% endmacro %} + {% if palettes.count > 1 or param.forceFileNameEnum %} + {% for palette in palettes %} + {{accessModifier}} enum {{palette.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call enumBlock palette.colors %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call enumBlock palettes.first.colors %} + {% endif %} +} +// swiftlint:enable identifier_name line_length type_body_length + +// MARK: - Implementation Details + +internal extension {{colorAlias}} { + convenience init(rgbaValue: UInt32) { + let components = RGBAComponents(rgbaValue: rgbaValue).normalized + self.init(red: components[0], green: components[1], blue: components[2], alpha: components[3]) + } +} + +private struct RGBAComponents { + let rgbaValue: UInt32 + + private var shifts: [UInt32] { + [ + rgbaValue >> 24, // red + rgbaValue >> 16, // green + rgbaValue >> 8, // blue + rgbaValue // alpha + ] + } + + private var components: [CGFloat] { + shifts.map { + CGFloat($0 & 0xff) + } + } + + var normalized: [CGFloat] { + components.map { $0 / 255.0 } + } +} + +{{accessModifier}} extension {{colorAlias}} { + convenience init(named color: {{enumName}}) { + self.init(rgbaValue: color.rgbaValue) + } +} +{% else %} +// No color found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/swift5.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/swift5.stencil new file mode 100644 index 0000000..57c2d79 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/swift5.stencil @@ -0,0 +1,84 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if palettes %} +{% set colorAlias %}{{param.colorAliasName|default:"Color"}}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +#if os(macOS) + import AppKit.NSColor + {{accessModifier}} typealias {{colorAlias}} = NSColor +#elseif os(iOS) || os(tvOS) || os(watchOS) + import UIKit.UIColor + {{accessModifier}} typealias {{colorAlias}} = UIColor +#endif + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Colors + +// swiftlint:disable identifier_name line_length type_body_length +{% set enumName %}{{param.enumName|default:"ColorName"}}{% endset %} +{{accessModifier}} struct {{enumName}} { + {{accessModifier}} let rgbaValue: UInt32 + {{accessModifier}} var color: {{colorAlias}} { return {{colorAlias}}(named: self) } + +{% macro rgbaValue color %}0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}}{% endmacro %} +{% macro enumBlock colors %} + {% for color in colors %} + /// + /// Alpha: {{color.alpha|hexToInt|int255toFloat|percent}}
(0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}}) + {{accessModifier}} static let {{color.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}(rgbaValue: {% call rgbaValue color %}) + {% endfor %} +{% endmacro %} + {% if palettes.count > 1 or param.forceFileNameEnum %} + {% for palette in palettes %} + {{accessModifier}} enum {{palette.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call enumBlock palette.colors %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call enumBlock palettes.first.colors %} + {% endif %} +} +// swiftlint:enable identifier_name line_length type_body_length + +// MARK: - Implementation Details + +internal extension {{colorAlias}} { + convenience init(rgbaValue: UInt32) { + let components = RGBAComponents(rgbaValue: rgbaValue).normalized + self.init(red: components[0], green: components[1], blue: components[2], alpha: components[3]) + } +} + +private struct RGBAComponents { + let rgbaValue: UInt32 + + private var shifts: [UInt32] { + [ + rgbaValue >> 24, // red + rgbaValue >> 16, // green + rgbaValue >> 8, // blue + rgbaValue // alpha + ] + } + + private var components: [CGFloat] { + shifts.map { + CGFloat($0 & 0xff) + } + } + + var normalized: [CGFloat] { + components.map { $0 / 255.0 } + } +} + +{{accessModifier}} extension {{colorAlias}} { + convenience init(named color: {{enumName}}) { + self.init(rgbaValue: color.rgbaValue) + } +} +{% else %} +// No color found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/coredata/swift4.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/coredata/swift4.stencil new file mode 100644 index 0000000..9832876 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/coredata/swift4.stencil @@ -0,0 +1,211 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +// swiftlint:disable superfluous_disable_command implicit_return +// swiftlint:disable sorted_imports +import CoreData +import Foundation +{% for import in param.extraImports %} +import {{ import }} +{% empty %} +{# If extraImports is a single String instead of an array, `for` considers it empty but we still have to check if there's a single String value #} +{% if param.extraImports %}import {{ param.extraImports }}{% endif %} +{% endfor %} + +// swiftlint:disable attributes file_length vertical_whitespace_closing_braces +// swiftlint:disable identifier_name line_length type_body_length +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} + +{% for model in models %} +{% for name, entity in model.entities %} +{% set superclass %}{{ model.entities[entity.superEntity].className|default:"NSManagedObject" }}{% endset %} +{% set entityClassName %}{{ entity.className|default:"NSManagedObject" }}{% endset %} +// MARK: - {{ entity.name }} + +{% if not entity.shouldGenerateCode %} +// Note: '{{ entity.name }}' has codegen enabled for Xcode, skipping code generation. + +{% elif entityClassName|contains:"." %} +// Warning: '{{ entityClassName }}' cannot be a valid type name, skipping code generation. + +{% else %} +{% if param.generateObjcName %} +@objc({{ entityClassName }}) +{% endif %} +{{ accessModifier }} class {{ entityClassName }}: {{ superclass }} { + {% set override %}{% if superclass != "NSManagedObject" %}override {% endif %}{% endset %} + {{ override }}{{ accessModifier }} class var entityName: String { + return "{{ entity.name }}" + } + + {{ override }}{{ accessModifier }} class func entity(in managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? { + return NSEntityDescription.entity(forEntityName: entityName, in: managedObjectContext) + } + + @available(*, deprecated, renamed: "makeFetchRequest", message: "To avoid collisions with the less concrete method in `NSManagedObject`, please use `makeFetchRequest()` instead.") + @nonobjc {{ accessModifier }} class func fetchRequest() -> NSFetchRequest<{{ entityClassName }}> { + return NSFetchRequest<{{ entityClassName }}>(entityName: entityName) + } + + @nonobjc {{ accessModifier }} class func makeFetchRequest() -> NSFetchRequest<{{ entityClassName }}> { + return NSFetchRequest<{{ entityClassName }}>(entityName: entityName) + } + + // swiftlint:disable discouraged_optional_boolean discouraged_optional_collection + {% for attribute in entity.attributes %} + {% if attribute.userInfo.RawType %} + {% set rawType attribute.userInfo.RawType %} + {% set unwrapOptional attribute.userInfo.unwrapOptional %} + {{ accessModifier }} var {{ attribute.name }}: {{ rawType }}{% if not unwrapOptional %}?{% endif %} { + get { + let key = "{{ attribute.name }}" + willAccessValue(forKey: key) + defer { didAccessValue(forKey: key) } + + {% if unwrapOptional %} + guard let value = primitiveValue(forKey: key) as? {{ rawType }}.RawValue, + let result = {{ rawType }}(rawValue: value) else { + fatalError("Could not convert value for key '\(key)' to type '{{ rawType }}'") + } + return result + {% else %} + guard let value = primitiveValue(forKey: key) as? {{ rawType }}.RawValue else { + return nil + } + return {{ rawType }}(rawValue: value) + {% endif %} + } + set { + let key = "{{ attribute.name }}" + willChangeValue(forKey: key) + defer { didChangeValue(forKey: key) } + + setPrimitiveValue(newValue{% if not unwrapOptional %}?{% endif %}.rawValue, forKey: key) + } + } + {% elif attribute.usesScalarValueType and attribute.isOptional %} + {{ accessModifier }} var {{ attribute.name }}: {{ attribute.typeName }}? { + get { + let key = "{{ attribute.name }}" + willAccessValue(forKey: key) + defer { didAccessValue(forKey: key) } + + return primitiveValue(forKey: key) as? {{ attribute.typeName }} + } + set { + let key = "{{ attribute.name }}" + willChangeValue(forKey: key) + defer { didChangeValue(forKey: key) } + + setPrimitiveValue(newValue, forKey: key) + } + } + {% else %} + @NSManaged {{ accessModifier }} var {{ attribute.name }}: {{ attribute.typeName }}{% if attribute.isOptional %}?{% endif %} + {% endif %} + {% endfor %} + {% for relationship in entity.relationships %} + {% if relationship.isToMany %} + @NSManaged {{ accessModifier }} var {{ relationship.name }}: {% if relationship.isOrdered %}NSOrderedSet{% else %}Set<{{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}>{% endif %}{% if relationship.isOptional %}?{% endif %} + {% else %} + @NSManaged {{ accessModifier }} var {{ relationship.name }}: {{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}{% if relationship.isOptional %}?{% endif %} + {% endif %} + {% endfor %} + {% for fetchedProperty in entity.fetchedProperties %} + @NSManaged {{ accessModifier }} var {{ fetchedProperty.name }}: [{{ fetchedProperty.fetchRequest.entity }}] + {% endfor %} + // swiftlint:enable discouraged_optional_boolean discouraged_optional_collection +} + +{% for relationship in entity.relationships where relationship.isToMany %} +{% set destinationEntityClassName %}{{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}{% endset %} +{% set collectionClassName %}{% if relationship.isOrdered %}NSOrderedSet{% else %}Set<{{ destinationEntityClassName }}>{% endif %}{% endset %} +{% set relationshipName %}{{ relationship.name | upperFirstLetter }}{% endset %} +// MARK: Relationship {{ relationshipName }} + +extension {{ entityClassName }} { + {% if relationship.isOrdered %} + @objc(insertObject:in{{ relationshipName }}AtIndex:) + @NSManaged public func insertInto{{ relationshipName }}(_ value: {{ destinationEntityClassName }}, at idx: Int) + + @objc(removeObjectFrom{{ relationshipName }}AtIndex:) + @NSManaged public func removeFrom{{ relationshipName }}(at idx: Int) + + @objc(insert{{ relationshipName }}:atIndexes:) + @NSManaged public func insertInto{{ relationshipName }}(_ values: [{{ destinationEntityClassName }}], at indexes: NSIndexSet) + + @objc(remove{{ relationshipName }}AtIndexes:) + @NSManaged public func removeFrom{{ relationshipName }}(at indexes: NSIndexSet) + + @objc(replaceObjectIn{{ relationshipName }}AtIndex:withObject:) + @NSManaged public func replace{{ relationshipName }}(at idx: Int, with value: {{ destinationEntityClassName }}) + + @objc(replace{{ relationshipName }}AtIndexes:with{{ relationshipName }}:) + @NSManaged public func replace{{ relationshipName }}(at indexes: NSIndexSet, with values: [{{ destinationEntityClassName }}]) + + {% endif %} + @objc(add{{ relationshipName }}Object:) + @NSManaged public func addTo{{ relationshipName }}(_ value: {{ destinationEntityClassName }}) + + @objc(remove{{ relationshipName }}Object:) + @NSManaged public func removeFrom{{ relationshipName }}(_ value: {{ destinationEntityClassName }}) + + @objc(add{{ relationshipName }}:) + @NSManaged public func addTo{{ relationshipName }}(_ values: {{ collectionClassName }}) + + @objc(remove{{ relationshipName }}:) + @NSManaged public func removeFrom{{ relationshipName }}(_ values: {{ collectionClassName }}) +} + +{% endfor %} +{% if model.fetchRequests[entity.name].count > 0 %} +// MARK: Fetch Requests + +extension {{ entityClassName }} { + {% for fetchRequest in model.fetchRequests[entity.name] %} + {% set resultTypeName %}{% filter removeNewlines:"leading" %} + {% if fetchRequest.resultType == "Object" %} + {{ entityClassName }} + {% elif fetchRequest.resultType == "Object ID" %} + NSManagedObjectID + {% elif fetchRequest.resultType == "Dictionary" %} + [String: Any] + {% endif %} + {% endfilter %}{% endset %} + class func fetch{{ fetchRequest.name | upperFirstLetter }}({% filter removeNewlines:"leading" %} + managedObjectContext: NSManagedObjectContext + {% for variableName, variableType in fetchRequest.substitutionVariables %} + , {{ variableName | lowerFirstWord }}: {{ variableType }} + {% endfor %} + {% endfilter %}) throws -> [{{ resultTypeName }}] { + guard let persistentStoreCoordinator = managedObjectContext.persistentStoreCoordinator else { + fatalError("Managed object context has no persistent store coordinator for getting fetch request templates") + } + let model = persistentStoreCoordinator.managedObjectModel + let substitutionVariables: [String: Any] = [ + {% for variableName, variableType in fetchRequest.substitutionVariables %} + "{{ variableName }}": {{ variableName | lowerFirstWord }}{{ "," if not forloop.last }} + {% empty %} + : + {% endfor %} + ] + + guard let fetchRequest = model.fetchRequestFromTemplate(withName: "{{ fetchRequest.name }}", substitutionVariables: substitutionVariables) else { + fatalError("No fetch request template named '{{ fetchRequest.name }}' found.") + } + + guard let result = try managedObjectContext.fetch(fetchRequest) as? [{{ resultTypeName }}] else { + fatalError("Unable to cast fetch result to correct result type.") + } + + return result + } + + {% endfor %} +} + +{% endif %} +{% endif %} +{% endfor %} +{% endfor %} +// swiftlint:enable identifier_name line_length type_body_length diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/coredata/swift5.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/coredata/swift5.stencil new file mode 100644 index 0000000..9832876 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/coredata/swift5.stencil @@ -0,0 +1,211 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +// swiftlint:disable superfluous_disable_command implicit_return +// swiftlint:disable sorted_imports +import CoreData +import Foundation +{% for import in param.extraImports %} +import {{ import }} +{% empty %} +{# If extraImports is a single String instead of an array, `for` considers it empty but we still have to check if there's a single String value #} +{% if param.extraImports %}import {{ param.extraImports }}{% endif %} +{% endfor %} + +// swiftlint:disable attributes file_length vertical_whitespace_closing_braces +// swiftlint:disable identifier_name line_length type_body_length +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} + +{% for model in models %} +{% for name, entity in model.entities %} +{% set superclass %}{{ model.entities[entity.superEntity].className|default:"NSManagedObject" }}{% endset %} +{% set entityClassName %}{{ entity.className|default:"NSManagedObject" }}{% endset %} +// MARK: - {{ entity.name }} + +{% if not entity.shouldGenerateCode %} +// Note: '{{ entity.name }}' has codegen enabled for Xcode, skipping code generation. + +{% elif entityClassName|contains:"." %} +// Warning: '{{ entityClassName }}' cannot be a valid type name, skipping code generation. + +{% else %} +{% if param.generateObjcName %} +@objc({{ entityClassName }}) +{% endif %} +{{ accessModifier }} class {{ entityClassName }}: {{ superclass }} { + {% set override %}{% if superclass != "NSManagedObject" %}override {% endif %}{% endset %} + {{ override }}{{ accessModifier }} class var entityName: String { + return "{{ entity.name }}" + } + + {{ override }}{{ accessModifier }} class func entity(in managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? { + return NSEntityDescription.entity(forEntityName: entityName, in: managedObjectContext) + } + + @available(*, deprecated, renamed: "makeFetchRequest", message: "To avoid collisions with the less concrete method in `NSManagedObject`, please use `makeFetchRequest()` instead.") + @nonobjc {{ accessModifier }} class func fetchRequest() -> NSFetchRequest<{{ entityClassName }}> { + return NSFetchRequest<{{ entityClassName }}>(entityName: entityName) + } + + @nonobjc {{ accessModifier }} class func makeFetchRequest() -> NSFetchRequest<{{ entityClassName }}> { + return NSFetchRequest<{{ entityClassName }}>(entityName: entityName) + } + + // swiftlint:disable discouraged_optional_boolean discouraged_optional_collection + {% for attribute in entity.attributes %} + {% if attribute.userInfo.RawType %} + {% set rawType attribute.userInfo.RawType %} + {% set unwrapOptional attribute.userInfo.unwrapOptional %} + {{ accessModifier }} var {{ attribute.name }}: {{ rawType }}{% if not unwrapOptional %}?{% endif %} { + get { + let key = "{{ attribute.name }}" + willAccessValue(forKey: key) + defer { didAccessValue(forKey: key) } + + {% if unwrapOptional %} + guard let value = primitiveValue(forKey: key) as? {{ rawType }}.RawValue, + let result = {{ rawType }}(rawValue: value) else { + fatalError("Could not convert value for key '\(key)' to type '{{ rawType }}'") + } + return result + {% else %} + guard let value = primitiveValue(forKey: key) as? {{ rawType }}.RawValue else { + return nil + } + return {{ rawType }}(rawValue: value) + {% endif %} + } + set { + let key = "{{ attribute.name }}" + willChangeValue(forKey: key) + defer { didChangeValue(forKey: key) } + + setPrimitiveValue(newValue{% if not unwrapOptional %}?{% endif %}.rawValue, forKey: key) + } + } + {% elif attribute.usesScalarValueType and attribute.isOptional %} + {{ accessModifier }} var {{ attribute.name }}: {{ attribute.typeName }}? { + get { + let key = "{{ attribute.name }}" + willAccessValue(forKey: key) + defer { didAccessValue(forKey: key) } + + return primitiveValue(forKey: key) as? {{ attribute.typeName }} + } + set { + let key = "{{ attribute.name }}" + willChangeValue(forKey: key) + defer { didChangeValue(forKey: key) } + + setPrimitiveValue(newValue, forKey: key) + } + } + {% else %} + @NSManaged {{ accessModifier }} var {{ attribute.name }}: {{ attribute.typeName }}{% if attribute.isOptional %}?{% endif %} + {% endif %} + {% endfor %} + {% for relationship in entity.relationships %} + {% if relationship.isToMany %} + @NSManaged {{ accessModifier }} var {{ relationship.name }}: {% if relationship.isOrdered %}NSOrderedSet{% else %}Set<{{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}>{% endif %}{% if relationship.isOptional %}?{% endif %} + {% else %} + @NSManaged {{ accessModifier }} var {{ relationship.name }}: {{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}{% if relationship.isOptional %}?{% endif %} + {% endif %} + {% endfor %} + {% for fetchedProperty in entity.fetchedProperties %} + @NSManaged {{ accessModifier }} var {{ fetchedProperty.name }}: [{{ fetchedProperty.fetchRequest.entity }}] + {% endfor %} + // swiftlint:enable discouraged_optional_boolean discouraged_optional_collection +} + +{% for relationship in entity.relationships where relationship.isToMany %} +{% set destinationEntityClassName %}{{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}{% endset %} +{% set collectionClassName %}{% if relationship.isOrdered %}NSOrderedSet{% else %}Set<{{ destinationEntityClassName }}>{% endif %}{% endset %} +{% set relationshipName %}{{ relationship.name | upperFirstLetter }}{% endset %} +// MARK: Relationship {{ relationshipName }} + +extension {{ entityClassName }} { + {% if relationship.isOrdered %} + @objc(insertObject:in{{ relationshipName }}AtIndex:) + @NSManaged public func insertInto{{ relationshipName }}(_ value: {{ destinationEntityClassName }}, at idx: Int) + + @objc(removeObjectFrom{{ relationshipName }}AtIndex:) + @NSManaged public func removeFrom{{ relationshipName }}(at idx: Int) + + @objc(insert{{ relationshipName }}:atIndexes:) + @NSManaged public func insertInto{{ relationshipName }}(_ values: [{{ destinationEntityClassName }}], at indexes: NSIndexSet) + + @objc(remove{{ relationshipName }}AtIndexes:) + @NSManaged public func removeFrom{{ relationshipName }}(at indexes: NSIndexSet) + + @objc(replaceObjectIn{{ relationshipName }}AtIndex:withObject:) + @NSManaged public func replace{{ relationshipName }}(at idx: Int, with value: {{ destinationEntityClassName }}) + + @objc(replace{{ relationshipName }}AtIndexes:with{{ relationshipName }}:) + @NSManaged public func replace{{ relationshipName }}(at indexes: NSIndexSet, with values: [{{ destinationEntityClassName }}]) + + {% endif %} + @objc(add{{ relationshipName }}Object:) + @NSManaged public func addTo{{ relationshipName }}(_ value: {{ destinationEntityClassName }}) + + @objc(remove{{ relationshipName }}Object:) + @NSManaged public func removeFrom{{ relationshipName }}(_ value: {{ destinationEntityClassName }}) + + @objc(add{{ relationshipName }}:) + @NSManaged public func addTo{{ relationshipName }}(_ values: {{ collectionClassName }}) + + @objc(remove{{ relationshipName }}:) + @NSManaged public func removeFrom{{ relationshipName }}(_ values: {{ collectionClassName }}) +} + +{% endfor %} +{% if model.fetchRequests[entity.name].count > 0 %} +// MARK: Fetch Requests + +extension {{ entityClassName }} { + {% for fetchRequest in model.fetchRequests[entity.name] %} + {% set resultTypeName %}{% filter removeNewlines:"leading" %} + {% if fetchRequest.resultType == "Object" %} + {{ entityClassName }} + {% elif fetchRequest.resultType == "Object ID" %} + NSManagedObjectID + {% elif fetchRequest.resultType == "Dictionary" %} + [String: Any] + {% endif %} + {% endfilter %}{% endset %} + class func fetch{{ fetchRequest.name | upperFirstLetter }}({% filter removeNewlines:"leading" %} + managedObjectContext: NSManagedObjectContext + {% for variableName, variableType in fetchRequest.substitutionVariables %} + , {{ variableName | lowerFirstWord }}: {{ variableType }} + {% endfor %} + {% endfilter %}) throws -> [{{ resultTypeName }}] { + guard let persistentStoreCoordinator = managedObjectContext.persistentStoreCoordinator else { + fatalError("Managed object context has no persistent store coordinator for getting fetch request templates") + } + let model = persistentStoreCoordinator.managedObjectModel + let substitutionVariables: [String: Any] = [ + {% for variableName, variableType in fetchRequest.substitutionVariables %} + "{{ variableName }}": {{ variableName | lowerFirstWord }}{{ "," if not forloop.last }} + {% empty %} + : + {% endfor %} + ] + + guard let fetchRequest = model.fetchRequestFromTemplate(withName: "{{ fetchRequest.name }}", substitutionVariables: substitutionVariables) else { + fatalError("No fetch request template named '{{ fetchRequest.name }}' found.") + } + + guard let result = try managedObjectContext.fetch(fetchRequest) as? [{{ resultTypeName }}] else { + fatalError("Unable to cast fetch result to correct result type.") + } + + return result + } + + {% endfor %} +} + +{% endif %} +{% endif %} +{% endfor %} +{% endfor %} +// swiftlint:enable identifier_name line_length type_body_length diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/flat-swift4.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/flat-swift4.stencil new file mode 100644 index 0000000..09df24d --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/flat-swift4.stencil @@ -0,0 +1,103 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if groups.count > 0 %} +{% set enumName %}{{param.enumName|default:"Files"}}{% endset %} +{% set useExt %}{% if param.useExtension|default:"true" %}true{% endif %}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set resourceType %}{{param.resourceTypeName|default:"File"}}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command file_length line_length implicit_return + +// MARK: - Files + +{% macro groupBlock group %} + {% for file in group.files %} + {% call fileBlock file %} + {% endfor %} + {% for dir in group.directories %} + {% call dirBlock dir %} + {% endfor %} +{% endmacro %} +{% macro fileBlock file %} + /// {% if file.path and param.preservePath %}{{file.path}}/{% endif %}{{file.name}}{% if file.ext %}.{{file.ext}}{% endif %} + {% set identifier %}{{ file.name }}{% if useExt %}.{{ file.ext }}{% endif %}{% endset %} + {{accessModifier}} static let {{identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{resourceType}}(name: "{{file.name}}", ext: {% if file.ext %}"{{file.ext}}"{% else %}nil{% endif %}, relativePath: "{{file.path if param.preservePath}}", mimeType: "{{file.mimeType}}") +{% endmacro %} +{% macro dirBlock directory %} + {% for file in directory.files %} + {% call fileBlock file %} + {% endfor %} + {% for dir in directory.directories %} + {% call dirBlock dir %} + {% endfor %} +{% endmacro %} +// swiftlint:disable explicit_type_interface identifier_name +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +{{accessModifier}} enum {{enumName}} { + {% if groups.count > 1 or param.forceFileNameEnum %} + {% for group in groups %} + {{accessModifier}} enum {{group.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call groupBlock group %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call groupBlock groups.first %} + {% endif %} +} +// swiftlint:enable explicit_type_interface identifier_name +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +{{accessModifier}} struct {{resourceType}} { + {{accessModifier}} let name: String + {{accessModifier}} let ext: String? + {{accessModifier}} let relativePath: String + {{accessModifier}} let mimeType: String + + {{accessModifier}} var url: URL { + return url(locale: nil) + } + + {{accessModifier}} func url(locale: Locale?) -> URL { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + let url = bundle.url( + forResource: name, + withExtension: ext, + subdirectory: relativePath, + localization: locale?.identifier + ) + guard let result = url else { + let file = name + (ext.flatMap { ".\($0)" } ?? "") + fatalError("Could not locate file named \(file)") + } + return result + } + + {{accessModifier}} var path: String { + return path(locale: nil) + } + + {{accessModifier}} func path(locale: Locale?) -> String { + return url(locale: locale).path + } +} +{% if not param.bundle %} + +// swiftlint:disable convenience_type explicit_type_interface +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type explicit_type_interface +{% endif %} +{% else %} +// No files found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/flat-swift5.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/flat-swift5.stencil new file mode 100644 index 0000000..09df24d --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/flat-swift5.stencil @@ -0,0 +1,103 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if groups.count > 0 %} +{% set enumName %}{{param.enumName|default:"Files"}}{% endset %} +{% set useExt %}{% if param.useExtension|default:"true" %}true{% endif %}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set resourceType %}{{param.resourceTypeName|default:"File"}}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command file_length line_length implicit_return + +// MARK: - Files + +{% macro groupBlock group %} + {% for file in group.files %} + {% call fileBlock file %} + {% endfor %} + {% for dir in group.directories %} + {% call dirBlock dir %} + {% endfor %} +{% endmacro %} +{% macro fileBlock file %} + /// {% if file.path and param.preservePath %}{{file.path}}/{% endif %}{{file.name}}{% if file.ext %}.{{file.ext}}{% endif %} + {% set identifier %}{{ file.name }}{% if useExt %}.{{ file.ext }}{% endif %}{% endset %} + {{accessModifier}} static let {{identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{resourceType}}(name: "{{file.name}}", ext: {% if file.ext %}"{{file.ext}}"{% else %}nil{% endif %}, relativePath: "{{file.path if param.preservePath}}", mimeType: "{{file.mimeType}}") +{% endmacro %} +{% macro dirBlock directory %} + {% for file in directory.files %} + {% call fileBlock file %} + {% endfor %} + {% for dir in directory.directories %} + {% call dirBlock dir %} + {% endfor %} +{% endmacro %} +// swiftlint:disable explicit_type_interface identifier_name +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +{{accessModifier}} enum {{enumName}} { + {% if groups.count > 1 or param.forceFileNameEnum %} + {% for group in groups %} + {{accessModifier}} enum {{group.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call groupBlock group %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call groupBlock groups.first %} + {% endif %} +} +// swiftlint:enable explicit_type_interface identifier_name +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +{{accessModifier}} struct {{resourceType}} { + {{accessModifier}} let name: String + {{accessModifier}} let ext: String? + {{accessModifier}} let relativePath: String + {{accessModifier}} let mimeType: String + + {{accessModifier}} var url: URL { + return url(locale: nil) + } + + {{accessModifier}} func url(locale: Locale?) -> URL { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + let url = bundle.url( + forResource: name, + withExtension: ext, + subdirectory: relativePath, + localization: locale?.identifier + ) + guard let result = url else { + let file = name + (ext.flatMap { ".\($0)" } ?? "") + fatalError("Could not locate file named \(file)") + } + return result + } + + {{accessModifier}} var path: String { + return path(locale: nil) + } + + {{accessModifier}} func path(locale: Locale?) -> String { + return url(locale: locale).path + } +} +{% if not param.bundle %} + +// swiftlint:disable convenience_type explicit_type_interface +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type explicit_type_interface +{% endif %} +{% else %} +// No files found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/structured-swift4.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/structured-swift4.stencil new file mode 100644 index 0000000..6d6db96 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/structured-swift4.stencil @@ -0,0 +1,107 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if groups.count > 0 %} +{% set enumName %}{{param.enumName|default:"Files"}}{% endset %} +{% set useExt %}{% if param.useExtension|default:"true" %}true{% endif %}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set resourceType %}{{param.resourceTypeName|default:"File"}}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command file_length line_length implicit_return + +// MARK: - Files + +{% macro groupBlock group %} + {% for file in group.files %} + {% call fileBlock file %} + {% endfor %} + {% for dir in group.directories %} + {% call dirBlock dir "" %} + {% endfor %} +{% endmacro %} +{% macro fileBlock file %} + /// {% if file.path and param.preservePath %}{{file.path}}/{% endif %}{{file.name}}{% if file.ext %}.{{file.ext}}{% endif %} + {% set identifier %}{{ file.name }}{% if useExt %}.{{ file.ext }}{% endif %}{% endset %} + {{accessModifier}} static let {{identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{resourceType}}(name: "{{file.name}}", ext: {% if file.ext %}"{{file.ext}}"{% else %}nil{% endif %}, relativePath: "{{file.path if param.preservePath}}", mimeType: "{{file.mimeType}}") +{% endmacro %} +{% macro dirBlock directory parent %} + {% set fullDir %}{{parent}}{{directory.name}}/{% endset %} + /// {{ fullDir }} + {{accessModifier}} enum {{directory.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% for file in directory.files %} + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + {% endfor %} + {% for dir in directory.directories %} + {% filter indent:2 %}{% call dirBlock dir fullDir %}{% endfilter %} + {% endfor %} + } +{% endmacro %} +// swiftlint:disable explicit_type_interface identifier_name +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +{{accessModifier}} enum {{enumName}} { + {% if groups.count > 1 or param.forceFileNameEnum %} + {% for group in groups %} + {{accessModifier}} enum {{group.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call groupBlock group %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call groupBlock groups.first %} + {% endif %} +} +// swiftlint:enable explicit_type_interface identifier_name +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +{{accessModifier}} struct {{resourceType}} { + {{accessModifier}} let name: String + {{accessModifier}} let ext: String? + {{accessModifier}} let relativePath: String + {{accessModifier}} let mimeType: String + + {{accessModifier}} var url: URL { + return url(locale: nil) + } + + {{accessModifier}} func url(locale: Locale?) -> URL { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + let url = bundle.url( + forResource: name, + withExtension: ext, + subdirectory: relativePath, + localization: locale?.identifier + ) + guard let result = url else { + let file = name + (ext.flatMap { ".\($0)" } ?? "") + fatalError("Could not locate file named \(file)") + } + return result + } + + {{accessModifier}} var path: String { + return path(locale: nil) + } + + {{accessModifier}} func path(locale: Locale?) -> String { + return url(locale: locale).path + } +} +{% if not param.bundle %} + +// swiftlint:disable convenience_type explicit_type_interface +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type explicit_type_interface +{% endif %} +{% else %} +// No files found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/structured-swift5.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/structured-swift5.stencil new file mode 100644 index 0000000..6d6db96 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/structured-swift5.stencil @@ -0,0 +1,107 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if groups.count > 0 %} +{% set enumName %}{{param.enumName|default:"Files"}}{% endset %} +{% set useExt %}{% if param.useExtension|default:"true" %}true{% endif %}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set resourceType %}{{param.resourceTypeName|default:"File"}}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command file_length line_length implicit_return + +// MARK: - Files + +{% macro groupBlock group %} + {% for file in group.files %} + {% call fileBlock file %} + {% endfor %} + {% for dir in group.directories %} + {% call dirBlock dir "" %} + {% endfor %} +{% endmacro %} +{% macro fileBlock file %} + /// {% if file.path and param.preservePath %}{{file.path}}/{% endif %}{{file.name}}{% if file.ext %}.{{file.ext}}{% endif %} + {% set identifier %}{{ file.name }}{% if useExt %}.{{ file.ext }}{% endif %}{% endset %} + {{accessModifier}} static let {{identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{resourceType}}(name: "{{file.name}}", ext: {% if file.ext %}"{{file.ext}}"{% else %}nil{% endif %}, relativePath: "{{file.path if param.preservePath}}", mimeType: "{{file.mimeType}}") +{% endmacro %} +{% macro dirBlock directory parent %} + {% set fullDir %}{{parent}}{{directory.name}}/{% endset %} + /// {{ fullDir }} + {{accessModifier}} enum {{directory.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% for file in directory.files %} + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + {% endfor %} + {% for dir in directory.directories %} + {% filter indent:2 %}{% call dirBlock dir fullDir %}{% endfilter %} + {% endfor %} + } +{% endmacro %} +// swiftlint:disable explicit_type_interface identifier_name +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +{{accessModifier}} enum {{enumName}} { + {% if groups.count > 1 or param.forceFileNameEnum %} + {% for group in groups %} + {{accessModifier}} enum {{group.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call groupBlock group %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call groupBlock groups.first %} + {% endif %} +} +// swiftlint:enable explicit_type_interface identifier_name +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +{{accessModifier}} struct {{resourceType}} { + {{accessModifier}} let name: String + {{accessModifier}} let ext: String? + {{accessModifier}} let relativePath: String + {{accessModifier}} let mimeType: String + + {{accessModifier}} var url: URL { + return url(locale: nil) + } + + {{accessModifier}} func url(locale: Locale?) -> URL { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + let url = bundle.url( + forResource: name, + withExtension: ext, + subdirectory: relativePath, + localization: locale?.identifier + ) + guard let result = url else { + let file = name + (ext.flatMap { ".\($0)" } ?? "") + fatalError("Could not locate file named \(file)") + } + return result + } + + {{accessModifier}} var path: String { + return path(locale: nil) + } + + {{accessModifier}} func path(locale: Locale?) -> String { + return url(locale: locale).path + } +} +{% if not param.bundle %} + +// swiftlint:disable convenience_type explicit_type_interface +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type explicit_type_interface +{% endif %} +{% else %} +// No files found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/fonts/swift4.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/fonts/swift4.stencil new file mode 100644 index 0000000..744d6a4 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/fonts/swift4.stencil @@ -0,0 +1,110 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if families %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set fontType %}{{param.fontTypeName|default:"FontConvertible"}}{% endset %} +#if os(macOS) + import AppKit.NSFont +#elseif os(iOS) || os(tvOS) || os(watchOS) + import UIKit.UIFont +#endif + +// Deprecated typealiases +@available(*, deprecated, renamed: "{{fontType}}.Font", message: "This typealias will be removed in SwiftGen 7.0") +{{accessModifier}} typealias {{param.fontAliasName|default:"Font"}} = {{fontType}}.Font + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length +// swiftlint:disable implicit_return + +// MARK: - Fonts + +// swiftlint:disable identifier_name line_length type_body_length +{% macro transformPath path %}{% filter removeNewlines %} + {% if param.preservePath %} + {{path}} + {% else %} + {{path|basename}} + {% endif %} +{% endfilter %}{% endmacro %} +{{accessModifier}} enum {{param.enumName|default:"FontFamily"}} { + {% for family in families %} + {{accessModifier}} enum {{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% for font in family.fonts %} + {{accessModifier}} static let {{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{fontType}}(name: "{{font.name}}", family: "{{family.name}}", path: "{% call transformPath font.path %}") + {% endfor %} + {{accessModifier}} static let all: [{{fontType}}] = [{% for font in family.fonts %}{{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{{ ", " if not forloop.last }}{% endfor %}] + } + {% endfor %} + {{accessModifier}} static let allCustomFonts: [{{fontType}}] = [{% for family in families %}{{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.all{{ ", " if not forloop.last }}{% endfor %}].flatMap { $0 } + {{accessModifier}} static func registerAllCustomFonts() { + allCustomFonts.forEach { $0.register() } + } +} +// swiftlint:enable identifier_name line_length type_body_length + +// MARK: - Implementation Details + +{{accessModifier}} struct {{fontType}} { + {{accessModifier}} let name: String + {{accessModifier}} let family: String + {{accessModifier}} let path: String + + #if os(macOS) + {{accessModifier}} typealias Font = NSFont + #elseif os(iOS) || os(tvOS) || os(watchOS) + {{accessModifier}} typealias Font = UIFont + #endif + + {{accessModifier}} func font(size: CGFloat) -> Font! { + return Font(font: self, size: size) + } + + {{accessModifier}} func register() { + // swiftlint:disable:next conditional_returns_on_newline + guard let url = url else { return } + CTFontManagerRegisterFontsForURL(url as CFURL, .process, nil) + } + + fileprivate var url: URL? { + {% if param.lookupFunction %} + return {{param.lookupFunction}}(name, family, path) + {% else %} + return {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil) + {% endif %} + } +} + +{{accessModifier}} extension {{fontType}}.Font { + convenience init?(font: {{fontType}}, size: CGFloat) { + #if os(iOS) || os(tvOS) || os(watchOS) + if !UIFont.fontNames(forFamilyName: font.family).contains(font.name) { + font.register() + } + #elseif os(macOS) + if let url = font.url, CTFontManagerGetScopeForURL(url as CFURL) == .none { + font.register() + } + #endif + + self.init(name: font.name, size: size) + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No fonts found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/fonts/swift5.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/fonts/swift5.stencil new file mode 100644 index 0000000..5a268b5 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/fonts/swift5.stencil @@ -0,0 +1,113 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if families %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set fontType %}{{param.fontTypeName|default:"FontConvertible"}}{% endset %} +#if os(macOS) + import AppKit.NSFont +#elseif os(iOS) || os(tvOS) || os(watchOS) + import UIKit.UIFont +#endif + +// Deprecated typealiases +@available(*, deprecated, renamed: "{{fontType}}.Font", message: "This typealias will be removed in SwiftGen 7.0") +{{accessModifier}} typealias {{param.fontAliasName|default:"Font"}} = {{fontType}}.Font + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - Fonts + +// swiftlint:disable identifier_name line_length type_body_length +{% macro transformPath path %}{% filter removeNewlines %} + {% if param.preservePath %} + {{path}} + {% else %} + {{path|basename}} + {% endif %} +{% endfilter %}{% endmacro %} +{{accessModifier}} enum {{param.enumName|default:"FontFamily"}} { + {% for family in families %} + {{accessModifier}} enum {{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% for font in family.fonts %} + {{accessModifier}} static let {{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{fontType}}(name: "{{font.name}}", family: "{{family.name}}", path: "{% call transformPath font.path %}") + {% endfor %} + {{accessModifier}} static let all: [{{fontType}}] = [{% for font in family.fonts %}{{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{{ ", " if not forloop.last }}{% endfor %}] + } + {% endfor %} + {{accessModifier}} static let allCustomFonts: [{{fontType}}] = [{% for family in families %}{{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.all{{ ", " if not forloop.last }}{% endfor %}].flatMap { $0 } + {{accessModifier}} static func registerAllCustomFonts() { + allCustomFonts.forEach { $0.register() } + } +} +// swiftlint:enable identifier_name line_length type_body_length + +// MARK: - Implementation Details + +{{accessModifier}} struct {{fontType}} { + {{accessModifier}} let name: String + {{accessModifier}} let family: String + {{accessModifier}} let path: String + + #if os(macOS) + {{accessModifier}} typealias Font = NSFont + #elseif os(iOS) || os(tvOS) || os(watchOS) + {{accessModifier}} typealias Font = UIFont + #endif + + {{accessModifier}} func font(size: CGFloat) -> Font { + guard let font = Font(font: self, size: size) else { + fatalError("Unable to initialize font '\(name)' (\(family))") + } + return font + } + + {{accessModifier}} func register() { + // swiftlint:disable:next conditional_returns_on_newline + guard let url = url else { return } + CTFontManagerRegisterFontsForURL(url as CFURL, .process, nil) + } + + fileprivate var url: URL? { + // swiftlint:disable:next implicit_return + {% if param.lookupFunction %} + return {{param.lookupFunction}}(name, family, path) + {% else %} + return {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil) + {% endif %} + } +} + +{{accessModifier}} extension {{fontType}}.Font { + convenience init?(font: {{fontType}}, size: CGFloat) { + #if os(iOS) || os(tvOS) || os(watchOS) + if !UIFont.fontNames(forFamilyName: font.family).contains(font.name) { + font.register() + } + #elseif os(macOS) + if let url = font.url, CTFontManagerGetScopeForURL(url as CFURL) == .none { + font.register() + } + #endif + + self.init(name: font.name, size: size) + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No fonts found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/scenes-swift4.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/scenes-swift4.stencil new file mode 100644 index 0000000..9ad52ff --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/scenes-swift4.stencil @@ -0,0 +1,157 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if platform and storyboards %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set isAppKit %}{% if platform == "macOS" %}true{% endif %}{% endset %} +{% set prefix %}{% if isAppKit %}NS{% else %}UI{% endif %}{% endset %} +{% set controller %}{% if isAppKit %}Controller{% else %}ViewController{% endif %}{% endset %} +// swiftlint:disable sorted_imports +import Foundation +{% for module in modules where module != env.PRODUCT_MODULE_NAME and module != param.module %} +import {{module}} +{% endfor %} + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length implicit_return + +// MARK: - Storyboard Scenes + +// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name +{% macro moduleName item %}{% filter removeNewlines %} + {% if item.moduleIsPlaceholder %} + {{ env.PRODUCT_MODULE_NAME|default:param.module }} + {% else %} + {{ item.module }} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro className item %}{% filter removeNewlines %} + {% set module %}{% call moduleName item %}{% endset %} + {% if module and ( not param.ignoreTargetModule or module != env.PRODUCT_MODULE_NAME and module != param.module ) %} + {{module}}. + {% endif %} + {{item.type}} +{% endfilter %}{% endmacro %} +{{accessModifier}} enum {{param.enumName|default:"StoryboardScene"}} { + {% for storyboard in storyboards %} + {% set storyboardName %}{{storyboard.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}{% endset %} + {{accessModifier}} enum {{storyboardName}}: StoryboardType { + {{accessModifier}} static let storyboardName = "{{storyboard.name}}" + {% if storyboard.initialScene %} + + {% set sceneClass %}{% call className storyboard.initialScene %}{% endset %} + {{accessModifier}} static let initialScene = InitialSceneType<{{sceneClass}}>(storyboard: {{storyboardName}}.self) + {% endif %} + {% for scene in storyboard.scenes %} + + {% set sceneID %}{{scene.identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set sceneClass %}{% call className scene %}{% endset %} + {{accessModifier}} static let {{sceneID}} = SceneType<{{sceneClass}}>(storyboard: {{storyboardName}}.self, identifier: "{{scene.identifier}}") + {% endfor %} + } + {% endfor %} +} +// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name + +// MARK: - Implementation Details + +{{accessModifier}} protocol StoryboardType { + static var storyboardName: String { get } +} + +{{accessModifier}} extension StoryboardType { + static var storyboard: {{prefix}}Storyboard { + let name = {% if isAppKit %}NSStoryboard.Name({% endif %}self.storyboardName{% if isAppKit %}){% endif %} + {% if param.lookupFunction %} + return {{param.lookupFunction}}(name) + {% else %} + return {{prefix}}Storyboard(name: name, bundle: {{param.bundle|default:"BundleToken.bundle"}}) + {% endif %} + } +} + +{{accessModifier}} struct SceneType { + {{accessModifier}} let storyboard: StoryboardType.Type + {{accessModifier}} let identifier: String + + {{accessModifier}} func instantiate() -> T { + let identifier = {% if isAppKit %}NSStoryboard.SceneIdentifier({% endif %}self.identifier{% if isAppKit %}){% endif %} + guard let controller = storyboard.storyboard.instantiate{{controller}}(withIdentifier: identifier) as? T else { + fatalError("{{controller}} '\(identifier)' is not of the expected class \(T.self).") + } + return controller + } + + {% if isAppKit %} + @available(macOS 10.15, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSViewController { + return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block) + } + + @available(macOS 10.15, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSWindowController { + return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block) + } + {% else %} + @available(iOS 13.0, tvOS 13.0, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T { + return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block) + } + {% endif %} +} + +{{accessModifier}} struct InitialSceneType { + {{accessModifier}} let storyboard: StoryboardType.Type + + {{accessModifier}} func instantiate() -> T { + guard let controller = storyboard.storyboard.instantiateInitial{{controller}}() as? T else { + fatalError("{{controller}} is not of the expected class \(T.self).") + } + return controller + } + + {% if isAppKit %} + @available(macOS 10.15, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSViewController { + guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else { + fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.") + } + return controller + } + + @available(macOS 10.15, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSWindowController { + guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else { + fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.") + } + return controller + } + {% else %} + @available(iOS 13.0, tvOS 13.0, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T { + guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else { + fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.") + } + return controller + } + {% endif %} +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% elif storyboards %} +// Mixed AppKit and UIKit storyboard files found, please invoke swiftgen with these separately +{% else %} +// No storyboard found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/scenes-swift5.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/scenes-swift5.stencil new file mode 100644 index 0000000..5f29f8b --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/scenes-swift5.stencil @@ -0,0 +1,159 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if platform and storyboards %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set isAppKit %}{% if platform == "macOS" %}true{% endif %}{% endset %} +{% set prefix %}{% if isAppKit %}NS{% else %}UI{% endif %}{% endset %} +{% set controller %}{% if isAppKit %}Controller{% else %}ViewController{% endif %}{% endset %} +// swiftlint:disable sorted_imports +import Foundation +{% for module in modules where module != env.PRODUCT_MODULE_NAME and module != param.module %} +import {{module}} +{% endfor %} + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length implicit_return + +// MARK: - Storyboard Scenes + +// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name +{% macro moduleName item %}{% filter removeNewlines %} + {% if item.moduleIsPlaceholder %} + {{ env.PRODUCT_MODULE_NAME|default:param.module }} + {% else %} + {{ item.module }} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro className item %}{% filter removeNewlines %} + {% set module %}{% call moduleName item %}{% endset %} + {% if module and ( not param.ignoreTargetModule or module != env.PRODUCT_MODULE_NAME and module != param.module ) %} + {{module}}. + {% endif %} + {{item.type}} +{% endfilter %}{% endmacro %} +{{accessModifier}} enum {{param.enumName|default:"StoryboardScene"}} { + {% for storyboard in storyboards %} + {% set storyboardName %}{{storyboard.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}{% endset %} + {{accessModifier}} enum {{storyboardName}}: StoryboardType { + {{accessModifier}} static let storyboardName = "{{storyboard.name}}" + {% if storyboard.initialScene %} + + {% set sceneClass %}{% call className storyboard.initialScene %}{% endset %} + {{accessModifier}} static let initialScene = InitialSceneType<{{sceneClass}}>(storyboard: {{storyboardName}}.self) + {% endif %} + {% for scene in storyboard.scenes %} + + {% set sceneID %}{{scene.identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set sceneClass %}{% call className scene %}{% endset %} + {{accessModifier}} static let {{sceneID}} = SceneType<{{sceneClass}}>(storyboard: {{storyboardName}}.self, identifier: "{{scene.identifier}}") + {% endfor %} + } + {% endfor %} +} +// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name + +// MARK: - Implementation Details + +{{accessModifier}} protocol StoryboardType { + static var storyboardName: String { get } +} + +{{accessModifier}} extension StoryboardType { + static var storyboard: {{prefix}}Storyboard { + let name = {% if isAppKit %}NSStoryboard.Name({% endif %}self.storyboardName{% if isAppKit %}){% endif %} + {% if param.lookupFunction %} + return {{param.lookupFunction}}(name) + {% else %} + return {{prefix}}Storyboard(name: name, bundle: {{param.bundle|default:"BundleToken.bundle"}}) + {% endif %} + } +} + +{{accessModifier}} struct SceneType { + {{accessModifier}} let storyboard: StoryboardType.Type + {{accessModifier}} let identifier: String + + {{accessModifier}} func instantiate() -> T { + let identifier = {% if isAppKit %}NSStoryboard.SceneIdentifier({% endif %}self.identifier{% if isAppKit %}){% endif %} + guard let controller = storyboard.storyboard.instantiate{{controller}}(withIdentifier: identifier) as? T else { + fatalError("{{controller}} '\(identifier)' is not of the expected class \(T.self).") + } + return controller + } + + {% if isAppKit %} + @available(macOS 10.15, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSViewController { + let identifier = NSStoryboard.SceneIdentifier(self.identifier) + return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block) + } + + @available(macOS 10.15, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSWindowController { + let identifier = NSStoryboard.SceneIdentifier(self.identifier) + return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block) + } + {% else %} + @available(iOS 13.0, tvOS 13.0, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T { + return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block) + } + {% endif %} +} + +{{accessModifier}} struct InitialSceneType { + {{accessModifier}} let storyboard: StoryboardType.Type + + {{accessModifier}} func instantiate() -> T { + guard let controller = storyboard.storyboard.instantiateInitial{{controller}}() as? T else { + fatalError("{{controller}} is not of the expected class \(T.self).") + } + return controller + } + + {% if isAppKit %} + @available(macOS 10.15, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSViewController { + guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else { + fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.") + } + return controller + } + + @available(macOS 10.15, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSWindowController { + guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else { + fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.") + } + return controller + } + {% else %} + @available(iOS 13.0, tvOS 13.0, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T { + guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else { + fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.") + } + return controller + } + {% endif %} +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% elif storyboards %} +// Mixed AppKit and UIKit storyboard files found, please invoke swiftgen with these separately +{% else %} +// No storyboard found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/segues-swift4.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/segues-swift4.stencil new file mode 100644 index 0000000..476d546 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/segues-swift4.stencil @@ -0,0 +1,60 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if platform and storyboards %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set isAppKit %}{% if platform == "macOS" %}true{% endif %}{% endset %} +// swiftlint:disable sorted_imports +import Foundation +{% for module in modules where module != env.PRODUCT_MODULE_NAME and module != param.module %} +import {{module}} +{% endfor %} + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - Storyboard Segues + +// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name +{{accessModifier}} enum {{param.enumName|default:"StoryboardSegue"}} { + {% for storyboard in storyboards where storyboard.segues %} + {{accessModifier}} enum {{storyboard.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}: String, SegueType { + {% for segue in storyboard.segues %} + {% set segueID %}{{segue.identifier|swiftIdentifier:"pretty"|lowerFirstWord}}{% endset %} + case {{segueID|escapeReservedKeywords}}{% if segueID != segue.identifier %} = "{{segue.identifier}}"{% endif %} + {% endfor %} + } + {% endfor %} +} +// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name + +// MARK: - Implementation Details + +{{accessModifier}} protocol SegueType: RawRepresentable {} + +{{accessModifier}} extension {% if isAppKit %}NSSeguePerforming{% else %}UIViewController{% endif %} { + func perform(segue: S, sender: Any? = nil) where S.RawValue == String { + let identifier = {% if isAppKit %}NSStoryboardSegue.Identifier({% endif %}segue.rawValue{% if isAppKit %}){% endif %} + performSegue{% if isAppKit %}?{% endif %}(withIdentifier: identifier, sender: sender) + } +} + +{{accessModifier}} extension SegueType where RawValue == String { + init?(_ segue: {% if isAppKit %}NS{% else %}UI{% endif %}StoryboardSegue) { + {% if isAppKit %} + #if swift(>=4.2) + guard let identifier = segue.identifier else { return nil } + #else + guard let identifier = segue.identifier?.rawValue else { return nil } + #endif + {% else %} + guard let identifier = segue.identifier else { return nil } + {% endif %} + self.init(rawValue: identifier) + } +} +{% elif storyboards %} +// Mixed AppKit and UIKit storyboard files found, please invoke swiftgen with these separately +{% else %} +// No storyboard found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/segues-swift5.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/segues-swift5.stencil new file mode 100644 index 0000000..476d546 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/segues-swift5.stencil @@ -0,0 +1,60 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if platform and storyboards %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set isAppKit %}{% if platform == "macOS" %}true{% endif %}{% endset %} +// swiftlint:disable sorted_imports +import Foundation +{% for module in modules where module != env.PRODUCT_MODULE_NAME and module != param.module %} +import {{module}} +{% endfor %} + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - Storyboard Segues + +// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name +{{accessModifier}} enum {{param.enumName|default:"StoryboardSegue"}} { + {% for storyboard in storyboards where storyboard.segues %} + {{accessModifier}} enum {{storyboard.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}: String, SegueType { + {% for segue in storyboard.segues %} + {% set segueID %}{{segue.identifier|swiftIdentifier:"pretty"|lowerFirstWord}}{% endset %} + case {{segueID|escapeReservedKeywords}}{% if segueID != segue.identifier %} = "{{segue.identifier}}"{% endif %} + {% endfor %} + } + {% endfor %} +} +// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name + +// MARK: - Implementation Details + +{{accessModifier}} protocol SegueType: RawRepresentable {} + +{{accessModifier}} extension {% if isAppKit %}NSSeguePerforming{% else %}UIViewController{% endif %} { + func perform(segue: S, sender: Any? = nil) where S.RawValue == String { + let identifier = {% if isAppKit %}NSStoryboardSegue.Identifier({% endif %}segue.rawValue{% if isAppKit %}){% endif %} + performSegue{% if isAppKit %}?{% endif %}(withIdentifier: identifier, sender: sender) + } +} + +{{accessModifier}} extension SegueType where RawValue == String { + init?(_ segue: {% if isAppKit %}NS{% else %}UI{% endif %}StoryboardSegue) { + {% if isAppKit %} + #if swift(>=4.2) + guard let identifier = segue.identifier else { return nil } + #else + guard let identifier = segue.identifier?.rawValue else { return nil } + #endif + {% else %} + guard let identifier = segue.identifier else { return nil } + {% endif %} + self.init(rawValue: identifier) + } +} +{% elif storyboards %} +// Mixed AppKit and UIKit storyboard files found, please invoke swiftgen with these separately +{% else %} +// No storyboard found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/inline-swift4.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/inline-swift4.stencil new file mode 100644 index 0000000..62ca48d --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/inline-swift4.stencil @@ -0,0 +1,82 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - JSON Files +{% macro fileBlock file %} + {% call documentBlock file file.document %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% elif document.metadata.type == "Dictionary" %} + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value document.data %} + {% endfor %} + {% else %} + {{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% elif metadata.type == "Optional" %} + Any? + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %} +{% endfilter %}{% endmacro %} +{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "String" %} + "{{ value }}" + {% elif metadata.type == "Optional" %} + nil + {% elif metadata.type == "Array" and value %} + [{% for value in value %} + {% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %} + {{ ", " if not forloop.last }} + {% endfor %}] + {% elif metadata.type == "Dictionary" %} + [{% for key,value in value %} + "{{key}}": {% call valueBlock value metadata.properties[key] %} + {{ ", " if not forloop.last }} + {% empty %} + : + {% endfor %}] + {% elif metadata.type == "Bool" %} + {% if value %}true{% else %}false{% endif %} + {% else %} + {{ value }} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length number_separator type_body_length +{{accessModifier}} enum {{param.enumName|default:"JSONFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length number_separator type_body_length +{% else %} +// No files found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/inline-swift5.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/inline-swift5.stencil new file mode 100644 index 0000000..62ca48d --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/inline-swift5.stencil @@ -0,0 +1,82 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - JSON Files +{% macro fileBlock file %} + {% call documentBlock file file.document %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% elif document.metadata.type == "Dictionary" %} + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value document.data %} + {% endfor %} + {% else %} + {{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% elif metadata.type == "Optional" %} + Any? + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %} +{% endfilter %}{% endmacro %} +{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "String" %} + "{{ value }}" + {% elif metadata.type == "Optional" %} + nil + {% elif metadata.type == "Array" and value %} + [{% for value in value %} + {% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %} + {{ ", " if not forloop.last }} + {% endfor %}] + {% elif metadata.type == "Dictionary" %} + [{% for key,value in value %} + "{{key}}": {% call valueBlock value metadata.properties[key] %} + {{ ", " if not forloop.last }} + {% empty %} + : + {% endfor %}] + {% elif metadata.type == "Bool" %} + {% if value %}true{% else %}false{% endif %} + {% else %} + {{ value }} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length number_separator type_body_length +{{accessModifier}} enum {{param.enumName|default:"JSONFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length number_separator type_body_length +{% else %} +// No files found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/runtime-swift4.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/runtime-swift4.stencil new file mode 100644 index 0000000..c2466c7 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/runtime-swift4.stencil @@ -0,0 +1,112 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - JSON Files +{% macro fileBlock file %} + {% call documentBlock file file.document %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = objectFromJSON(at: "{% call transformPath file.path %}") + {% elif document.metadata.type == "Dictionary" %} + private static let _document = JSONDocument(path: "{% call transformPath file.path %}") + + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value %} + {% endfor %} + {% else %} + {{accessModifier}} static let value: {{rootType}} = objectFromJSON(at: "{% call transformPath file.path %}") + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% elif metadata.type == "Optional" %} + Any? + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = _document["{{key}}"] +{% endfilter %}{% endmacro %} +{% macro transformPath path %}{% filter removeNewlines %} + {% if param.preservePath %} + {{path}} + {% else %} + {{path|basename}} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length type_body_length +{{accessModifier}} enum {{param.enumName|default:"JSONFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length type_body_length + +// MARK: - Implementation Details + +private func objectFromJSON(at path: String) -> T { + {% if param.lookupFunction %} + guard let url = {{param.lookupFunction}}(path), + {% else %} + guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil), + {% endif %} + let json = try? JSONSerialization.jsonObject(with: Data(contentsOf: url), options: []), + let result = json as? T else { + fatalError("Unable to load JSON at path: \(path)") + } + return result +} + +private struct JSONDocument { + let data: [String: Any] + + init(path: String) { + self.data = objectFromJSON(at: path) + } + + subscript(key: String) -> T { + guard let result = data[key] as? T else { + fatalError("Property '\(key)' is not of type \(T.self)") + } + return result + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No files found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/runtime-swift5.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/runtime-swift5.stencil new file mode 100644 index 0000000..c2466c7 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/runtime-swift5.stencil @@ -0,0 +1,112 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - JSON Files +{% macro fileBlock file %} + {% call documentBlock file file.document %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = objectFromJSON(at: "{% call transformPath file.path %}") + {% elif document.metadata.type == "Dictionary" %} + private static let _document = JSONDocument(path: "{% call transformPath file.path %}") + + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value %} + {% endfor %} + {% else %} + {{accessModifier}} static let value: {{rootType}} = objectFromJSON(at: "{% call transformPath file.path %}") + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% elif metadata.type == "Optional" %} + Any? + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = _document["{{key}}"] +{% endfilter %}{% endmacro %} +{% macro transformPath path %}{% filter removeNewlines %} + {% if param.preservePath %} + {{path}} + {% else %} + {{path|basename}} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length type_body_length +{{accessModifier}} enum {{param.enumName|default:"JSONFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length type_body_length + +// MARK: - Implementation Details + +private func objectFromJSON(at path: String) -> T { + {% if param.lookupFunction %} + guard let url = {{param.lookupFunction}}(path), + {% else %} + guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil), + {% endif %} + let json = try? JSONSerialization.jsonObject(with: Data(contentsOf: url), options: []), + let result = json as? T else { + fatalError("Unable to load JSON at path: \(path)") + } + return result +} + +private struct JSONDocument { + let data: [String: Any] + + init(path: String) { + self.data = objectFromJSON(at: path) + } + + subscript(key: String) -> T { + guard let result = data[key] as? T else { + fatalError("Property '\(key)' is not of type \(T.self)") + } + return result + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No files found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/inline-swift4.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/inline-swift4.stencil new file mode 100644 index 0000000..c8e8831 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/inline-swift4.stencil @@ -0,0 +1,82 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - Plist Files +{% macro fileBlock file %} + {% call documentBlock file file.document %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% elif document.metadata.type == "Dictionary" %} + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value document.data %} + {% endfor %} + {% else %} + {{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %} +{% endfilter %}{% endmacro %} +{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "String" %} + "{{ value }}" + {% elif metadata.type == "Date" %} + Date(timeIntervalSinceReferenceDate: {{ value.timeIntervalSinceReferenceDate }}) + {% elif metadata.type == "Optional" %} + nil + {% elif metadata.type == "Array" and value %} + [{% for value in value %} + {% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %} + {{ ", " if not forloop.last }} + {% endfor %}] + {% elif metadata.type == "Dictionary" %} + [{% for key,value in value %} + "{{key}}": {% call valueBlock value metadata.properties[key] %} + {{ ", " if not forloop.last }} + {% empty %} + : + {% endfor %}] + {% elif metadata.type == "Bool" %} + {% if value %}true{% else %}false{% endif %} + {% else %} + {{ value }} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length number_separator type_body_length +{{accessModifier}} enum {{param.enumName|default:"PlistFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length number_separator type_body_length +{% else %} +// No files found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/inline-swift5.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/inline-swift5.stencil new file mode 100644 index 0000000..c8e8831 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/inline-swift5.stencil @@ -0,0 +1,82 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - Plist Files +{% macro fileBlock file %} + {% call documentBlock file file.document %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% elif document.metadata.type == "Dictionary" %} + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value document.data %} + {% endfor %} + {% else %} + {{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %} +{% endfilter %}{% endmacro %} +{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "String" %} + "{{ value }}" + {% elif metadata.type == "Date" %} + Date(timeIntervalSinceReferenceDate: {{ value.timeIntervalSinceReferenceDate }}) + {% elif metadata.type == "Optional" %} + nil + {% elif metadata.type == "Array" and value %} + [{% for value in value %} + {% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %} + {{ ", " if not forloop.last }} + {% endfor %}] + {% elif metadata.type == "Dictionary" %} + [{% for key,value in value %} + "{{key}}": {% call valueBlock value metadata.properties[key] %} + {{ ", " if not forloop.last }} + {% empty %} + : + {% endfor %}] + {% elif metadata.type == "Bool" %} + {% if value %}true{% else %}false{% endif %} + {% else %} + {{ value }} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length number_separator type_body_length +{{accessModifier}} enum {{param.enumName|default:"PlistFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length number_separator type_body_length +{% else %} +// No files found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/runtime-swift4.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/runtime-swift4.stencil new file mode 100644 index 0000000..a498a8f --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/runtime-swift4.stencil @@ -0,0 +1,117 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - Plist Files +{% macro fileBlock file %} + {% call documentBlock file file.document %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = arrayFromPlist(at: "{% call transformPath file.path %}") + {% elif document.metadata.type == "Dictionary" %} + private static let _document = PlistDocument(path: "{% call transformPath file.path %}") + + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value %} + {% endfor %} + {% else %} + // Unsupported root type `{{rootType}}` + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = _document["{{key}}"] +{% endfilter %}{% endmacro %} +{% macro transformPath path %}{% filter removeNewlines %} + {% if param.preservePath %} + {{path}} + {% else %} + {{path|basename}} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length type_body_length +{{accessModifier}} enum {{param.enumName|default:"PlistFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length type_body_length + +// MARK: - Implementation Details + +private func arrayFromPlist(at path: String) -> [T] { + {% if param.lookupFunction %} + guard let url = {{param.lookupFunction}}(path), + {% else %} + guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil), + {% endif %} + let data = NSArray(contentsOf: url) as? [T] else { + fatalError("Unable to load PLIST at path: \(path)") + } + return data +} + +private struct PlistDocument { + let data: [String: Any] + + init(path: String) { + {% if param.lookupFunction %} + guard let url = {{param.lookupFunction}}(path), + {% else %} + guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil), + {% endif %} + let data = NSDictionary(contentsOf: url) as? [String: Any] else { + fatalError("Unable to load PLIST at path: \(path)") + } + self.data = data + } + + subscript(key: String) -> T { + guard let result = data[key] as? T else { + fatalError("Property '\(key)' is not of type \(T.self)") + } + return result + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No files found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/runtime-swift5.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/runtime-swift5.stencil new file mode 100644 index 0000000..a498a8f --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/runtime-swift5.stencil @@ -0,0 +1,117 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - Plist Files +{% macro fileBlock file %} + {% call documentBlock file file.document %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = arrayFromPlist(at: "{% call transformPath file.path %}") + {% elif document.metadata.type == "Dictionary" %} + private static let _document = PlistDocument(path: "{% call transformPath file.path %}") + + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value %} + {% endfor %} + {% else %} + // Unsupported root type `{{rootType}}` + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = _document["{{key}}"] +{% endfilter %}{% endmacro %} +{% macro transformPath path %}{% filter removeNewlines %} + {% if param.preservePath %} + {{path}} + {% else %} + {{path|basename}} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length type_body_length +{{accessModifier}} enum {{param.enumName|default:"PlistFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length type_body_length + +// MARK: - Implementation Details + +private func arrayFromPlist(at path: String) -> [T] { + {% if param.lookupFunction %} + guard let url = {{param.lookupFunction}}(path), + {% else %} + guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil), + {% endif %} + let data = NSArray(contentsOf: url) as? [T] else { + fatalError("Unable to load PLIST at path: \(path)") + } + return data +} + +private struct PlistDocument { + let data: [String: Any] + + init(path: String) { + {% if param.lookupFunction %} + guard let url = {{param.lookupFunction}}(path), + {% else %} + guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil), + {% endif %} + let data = NSDictionary(contentsOf: url) as? [String: Any] else { + fatalError("Unable to load PLIST at path: \(path)") + } + self.data = data + } + + subscript(key: String) -> T { + guard let result = data[key] as? T else { + fatalError("Property '\(key)' is not of type \(T.self)") + } + return result + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No files found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/flat-swift4.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/flat-swift4.stencil new file mode 100644 index 0000000..5bb4a12 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/flat-swift4.stencil @@ -0,0 +1,99 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if tables.count > 0 %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Strings + +{% macro parametersBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + {% if type == "String" %} + _ p{{forloop.counter}}: Any + {% else %} + _ p{{forloop.counter}}: {{type}} + {% endif %} + {{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + {% if type == "String" %} + String(describing: p{{forloop.counter}}) + {% elif type == "UnsafeRawPointer" %} + Int(bitPattern: p{{forloop.counter}}) + {% else %} + p{{forloop.counter}} + {% endif %} + {{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro recursiveBlock table item %} + {% for string in item.strings %} + {% if not param.noComments %} + {% for line in string.translation|split:"\n" %} + /// {{line}} + {% endfor %} + {% endif %} + {% if string.types %} + {{accessModifier}} static func {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String { + return {{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %}) + } + {% elif param.lookupFunction %} + {# custom localization function is mostly used for in-app lang selection, so we want the loc to be recomputed at each call for those (hence the computed var) #} + {{accessModifier}} static var {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}") } + {% else %} + {{accessModifier}} static let {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}") + {% endif %} + {% endfor %} + {% for child in item.children %} + {% call recursiveBlock table child %} + {% endfor %} +{% endmacro %} +// swiftlint:disable function_parameter_count identifier_name line_length type_body_length +{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %} +{{accessModifier}} enum {{enumName}} { + {% if tables.count > 1 or param.forceFileNameEnum %} + {% for table in tables %} + {{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call recursiveBlock tables.first.name tables.first.levels %} + {% endif %} +} +// swiftlint:enable function_parameter_count identifier_name line_length type_body_length + +// MARK: - Implementation Details + +extension {{enumName}} { + private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { + {% if param.lookupFunction %} + let format = {{ param.lookupFunction }}(key, table) + {% else %} + let format = {{param.bundle|default:"BundleToken.bundle"}}.localizedString(forKey: key, value: nil, table: table) + {% endif %} + return String(format: format, locale: Locale.current, arguments: args) + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No string found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/flat-swift5.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/flat-swift5.stencil new file mode 100644 index 0000000..5bb4a12 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/flat-swift5.stencil @@ -0,0 +1,99 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if tables.count > 0 %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Strings + +{% macro parametersBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + {% if type == "String" %} + _ p{{forloop.counter}}: Any + {% else %} + _ p{{forloop.counter}}: {{type}} + {% endif %} + {{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + {% if type == "String" %} + String(describing: p{{forloop.counter}}) + {% elif type == "UnsafeRawPointer" %} + Int(bitPattern: p{{forloop.counter}}) + {% else %} + p{{forloop.counter}} + {% endif %} + {{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro recursiveBlock table item %} + {% for string in item.strings %} + {% if not param.noComments %} + {% for line in string.translation|split:"\n" %} + /// {{line}} + {% endfor %} + {% endif %} + {% if string.types %} + {{accessModifier}} static func {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String { + return {{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %}) + } + {% elif param.lookupFunction %} + {# custom localization function is mostly used for in-app lang selection, so we want the loc to be recomputed at each call for those (hence the computed var) #} + {{accessModifier}} static var {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}") } + {% else %} + {{accessModifier}} static let {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}") + {% endif %} + {% endfor %} + {% for child in item.children %} + {% call recursiveBlock table child %} + {% endfor %} +{% endmacro %} +// swiftlint:disable function_parameter_count identifier_name line_length type_body_length +{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %} +{{accessModifier}} enum {{enumName}} { + {% if tables.count > 1 or param.forceFileNameEnum %} + {% for table in tables %} + {{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call recursiveBlock tables.first.name tables.first.levels %} + {% endif %} +} +// swiftlint:enable function_parameter_count identifier_name line_length type_body_length + +// MARK: - Implementation Details + +extension {{enumName}} { + private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { + {% if param.lookupFunction %} + let format = {{ param.lookupFunction }}(key, table) + {% else %} + let format = {{param.bundle|default:"BundleToken.bundle"}}.localizedString(forKey: key, value: nil, table: table) + {% endif %} + return String(format: format, locale: Locale.current, arguments: args) + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No string found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/objc-h.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/objc-h.stencil new file mode 100644 index 0000000..7c50291 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/objc-h.stencil @@ -0,0 +1,68 @@ +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if tables.count > 0 %} +#import + +NS_ASSUME_NONNULL_BEGIN + +{% macro parametersBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + ({% call paramTranslate type %})p{{ forloop.counter }}{{ " :" if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + p{{forloop.counter}}{{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro paramTranslate swiftType %} + {% if swiftType == "Any" %} + id + {% elif swiftType == "CChar" %} + char + {% elif swiftType == "Float" %} + float + {% elif swiftType == "Int" %} + NSInteger + {% elif swiftType == "String" %} + id + {% elif swiftType == "UnsafePointer" %} + char* + {% elif swiftType == "UnsafeRawPointer" %} + void* + {% else %} + objc-h.stencil is missing '{{swiftType}}' + {% endif %} +{% endmacro %} +{% macro emitOneMethod table item %} +{% for string in item.strings %} +{% if not param.noComments %} +{% for line in string.translation|split:"\n" %} +/// {{line}} +{% endfor %} +{% endif %} +{% if string.types %} + {% if string.types.count == 1 %} ++ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}}WithValue:{% call parametersBlock string.types %}; + {% else %} ++ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}}WithValues:{% call parametersBlock string.types %}; + {% endif %} +{% else %} ++ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}}; +{% endif %} +{% endfor %} +{% for child in item.children %} +{% call emitOneMethod table child %} +{% endfor %} +{% endmacro %} +{% for table in tables %} +@interface {{ table.name }} : NSObject + {% call emitOneMethod table.name table.levels %} +@end + +{% endfor %} + +NS_ASSUME_NONNULL_END +{% else %} +// No strings found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/objc-m.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/objc-m.stencil new file mode 100644 index 0000000..1f154b5 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/objc-m.stencil @@ -0,0 +1,90 @@ +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if tables.count > 0 %} +#import "{{ param.headerName|default:"Localizable.h" }}" +{% if not param.bundle %} + +@interface BundleToken : NSObject +@end + +@implementation BundleToken +@end +{% endif %} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-security" + +static NSString* tr(NSString *tableName, NSString *key, ...) { + NSBundle *bundle = {{param.bundle|default:"[NSBundle bundleForClass:BundleToken.class]"}}; + NSString *format = [bundle localizedStringForKey:key value:nil table:tableName]; + NSLocale *locale = [NSLocale currentLocale]; + + va_list args; + va_start(args, key); + NSString *result = [[NSString alloc] initWithFormat:format locale:locale arguments:args]; + va_end(args); + + return result; +}; +#pragma clang diagnostic pop + +{% macro parametersBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + ({% call paramTranslate type %})p{{ forloop.counter }}{{ " :" if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + p{{forloop.counter}}{{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro paramTranslate swiftType %} + {% if swiftType == "Any" %} + id + {% elif swiftType == "CChar" %} + char + {% elif swiftType == "Float" %} + float + {% elif swiftType == "Int" %} + NSInteger + {% elif swiftType == "String" %} + id + {% elif swiftType == "UnsafePointer" %} + char* + {% elif swiftType == "UnsafeRawPointer" %} + void* + {% else %} + objc-m.stencil is missing '{{swiftType}}' + {% endif %} +{% endmacro %} +{% macro tableContents table item %} + {% for string in item.strings %} + {% if string.types %} + {% if string.types.count == 1 %} ++ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}}WithValue:{% call parametersBlock string.types %} + {% else %} ++ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}}WithValues:{% call parametersBlock string.types %} + {% endif %} +{ + return tr(@"{{table}}", @"{{string.key}}", {% call argumentsBlock string.types %}); +} +{% else %} ++ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}} { + return tr(@"{{table}}", @"{{string.key}}"); +} + {% endif %} + {% endfor %} + {% for child in item.children %} + {% call tableContents table child %} + {% endfor %} +{% endmacro %} +{% for table in tables %} + {% set tableName %}{{table.name|default:"Localized"}}{% endset %} +@implementation {{ tableName }} : NSObject + {% call tableContents table.name table.levels %} +@end + +{% endfor %} +{% else %} +// No strings found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/structured-swift4.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/structured-swift4.stencil new file mode 100644 index 0000000..f809bc2 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/structured-swift4.stencil @@ -0,0 +1,104 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if tables.count > 0 %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Strings + +{% macro parametersBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + {% if type == "String" %} + _ p{{forloop.counter}}: Any + {% else %} + _ p{{forloop.counter}}: {{type}} + {% endif %} + {{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + {% if type == "String" %} + String(describing: p{{forloop.counter}}) + {% elif type == "UnsafeRawPointer" %} + Int(bitPattern: p{{forloop.counter}}) + {% else %} + p{{forloop.counter}} + {% endif %} + {{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro recursiveBlock table item %} + {% for string in item.strings %} + {% if not param.noComments %} + {% for line in string.translation|split:"\n" %} + /// {{line}} + {% endfor %} + {% endif %} + {% if string.types %} + {{accessModifier}} static func {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String { + return {{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %}) + } + {% elif param.lookupFunction %} + {# custom localization function is mostly used for in-app lang selection, so we want the loc to be recomputed at each call for those (hence the computed var) #} + {{accessModifier}} static var {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}") } + {% else %} + {{accessModifier}} static let {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}") + {% endif %} + {% endfor %} + {% for child in item.children %} + + {{accessModifier}} enum {{child.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call recursiveBlock table child %}{% endfilter %} + } + {% endfor %} +{% endmacro %} +// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %} +{{accessModifier}} enum {{enumName}} { + {% if tables.count > 1 or param.forceFileNameEnum %} + {% for table in tables %} + {{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call recursiveBlock tables.first.name tables.first.levels %} + {% endif %} +} +// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +extension {{enumName}} { + private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { + {% if param.lookupFunction %} + let format = {{ param.lookupFunction }}(key, table) + {% else %} + let format = {{param.bundle|default:"BundleToken.bundle"}}.localizedString(forKey: key, value: nil, table: table) + {% endif %} + return String(format: format, locale: Locale.current, arguments: args) + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No string found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/structured-swift5.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/structured-swift5.stencil new file mode 100644 index 0000000..f809bc2 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/structured-swift5.stencil @@ -0,0 +1,104 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if tables.count > 0 %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Strings + +{% macro parametersBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + {% if type == "String" %} + _ p{{forloop.counter}}: Any + {% else %} + _ p{{forloop.counter}}: {{type}} + {% endif %} + {{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + {% if type == "String" %} + String(describing: p{{forloop.counter}}) + {% elif type == "UnsafeRawPointer" %} + Int(bitPattern: p{{forloop.counter}}) + {% else %} + p{{forloop.counter}} + {% endif %} + {{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro recursiveBlock table item %} + {% for string in item.strings %} + {% if not param.noComments %} + {% for line in string.translation|split:"\n" %} + /// {{line}} + {% endfor %} + {% endif %} + {% if string.types %} + {{accessModifier}} static func {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String { + return {{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %}) + } + {% elif param.lookupFunction %} + {# custom localization function is mostly used for in-app lang selection, so we want the loc to be recomputed at each call for those (hence the computed var) #} + {{accessModifier}} static var {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}") } + {% else %} + {{accessModifier}} static let {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}") + {% endif %} + {% endfor %} + {% for child in item.children %} + + {{accessModifier}} enum {{child.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call recursiveBlock table child %}{% endfilter %} + } + {% endfor %} +{% endmacro %} +// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %} +{{accessModifier}} enum {{enumName}} { + {% if tables.count > 1 or param.forceFileNameEnum %} + {% for table in tables %} + {{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call recursiveBlock tables.first.name tables.first.levels %} + {% endif %} +} +// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +extension {{enumName}} { + private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { + {% if param.lookupFunction %} + let format = {{ param.lookupFunction }}(key, table) + {% else %} + let format = {{param.bundle|default:"BundleToken.bundle"}}.localizedString(forKey: key, value: nil, table: table) + {% endif %} + return String(format: format, locale: Locale.current, arguments: args) + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No string found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/xcassets/swift4.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/xcassets/swift4.stencil new file mode 100644 index 0000000..c856593 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/xcassets/swift4.stencil @@ -0,0 +1,329 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if catalogs %} +{% set enumName %}{{param.enumName|default:"Asset"}}{% endset %} +{% set arResourceGroupType %}{{param.arResourceGroupTypeName|default:"ARResourceGroupAsset"}}{% endset %} +{% set colorType %}{{param.colorTypeName|default:"ColorAsset"}}{% endset %} +{% set dataType %}{{param.dataTypeName|default:"DataAsset"}}{% endset %} +{% set imageType %}{{param.imageTypeName|default:"ImageAsset"}}{% endset %} +{% set symbolType %}{{param.symbolTypeName|default:"SymbolAsset"}}{% endset %} +{% set forceNamespaces %}{{param.forceProvidesNamespaces|default:"false"}}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +#if os(macOS) + import AppKit +#elseif os(iOS) +{% if resourceCount.arresourcegroup > 0 %} + import ARKit +{% endif %} + import UIKit +#elseif os(tvOS) || os(watchOS) + import UIKit +#endif + +// Deprecated typealiases +{% if resourceCount.color > 0 %} +@available(*, deprecated, renamed: "{{colorType}}.Color", message: "This typealias will be removed in SwiftGen 7.0") +{{accessModifier}} typealias {{param.colorAliasName|default:"AssetColorTypeAlias"}} = {{colorType}}.Color +{% endif %} +{% if resourceCount.image > 0 %} +@available(*, deprecated, renamed: "{{imageType}}.Image", message: "This typealias will be removed in SwiftGen 7.0") +{{accessModifier}} typealias {{param.imageAliasName|default:"AssetImageTypeAlias"}} = {{imageType}}.Image +{% endif %} + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Asset Catalogs + +{% macro enumBlock assets %} + {% call casesBlock assets %} + {% if param.allValues %} + + // swiftlint:disable trailing_comma + {% if resourceCount.arresourcegroup > 0 %} + {{accessModifier}} static let allResourceGroups: [{{arResourceGroupType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "arresourcegroup" "" %}{% endfilter %} + ] + {% endif %} + {% if resourceCount.color > 0 %} + {{accessModifier}} static let allColors: [{{colorType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "color" "" %}{% endfilter %} + ] + {% endif %} + {% if resourceCount.data > 0 %} + {{accessModifier}} static let allDataAssets: [{{dataType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "data" "" %}{% endfilter %} + ] + {% endif %} + {% if resourceCount.image > 0 %} + {{accessModifier}} static let allImages: [{{imageType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "image" "" %}{% endfilter %} + ] + {% endif %} + {% if resourceCount.symbol > 0 %} + {{accessModifier}} static let allSymbols: [{{symbolType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "symbol" "" %}{% endfilter %} + ] + {% endif %} + // swiftlint:enable trailing_comma + {% endif %} +{% endmacro %} +{% macro casesBlock assets %} + {% for asset in assets %} + {% if asset.type == "arresourcegroup" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{arResourceGroupType}}(name: "{{asset.value}}") + {% elif asset.type == "color" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{colorType}}(name: "{{asset.value}}") + {% elif asset.type == "data" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{dataType}}(name: "{{asset.value}}") + {% elif asset.type == "image" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{imageType}}(name: "{{asset.value}}") + {% elif asset.type == "symbol" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{symbolType}}(name: "{{asset.value}}") + {% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %} + {{accessModifier}} enum {{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call casesBlock asset.items %}{% endfilter %} + } + {% elif asset.items %} + {% call casesBlock asset.items %} + {% endif %} + {% endfor %} +{% endmacro %} +{% macro allValuesBlock assets filter prefix %} + {% for asset in assets %} + {% if asset.type == filter %} + {{prefix}}{{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}, + {% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %} + {% set prefix2 %}{{prefix}}{{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.{% endset %} + {% call allValuesBlock asset.items filter prefix2 %} + {% elif asset.items %} + {% call allValuesBlock asset.items filter prefix %} + {% endif %} + {% endfor %} +{% endmacro %} +// swiftlint:disable identifier_name line_length nesting type_body_length type_name +{{accessModifier}} enum {{enumName}} { + {% if catalogs.count > 1 or param.forceFileNameEnum %} + {% for catalog in catalogs %} + {{accessModifier}} enum {{catalog.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call enumBlock catalog.assets %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call enumBlock catalogs.first.assets %} + {% endif %} +} +// swiftlint:enable identifier_name line_length nesting type_body_length type_name + +// MARK: - Implementation Details +{% if resourceCount.arresourcegroup > 0 %} + +{{accessModifier}} struct {{arResourceGroupType}} { + {{accessModifier}} fileprivate(set) var name: String + + #if os(iOS) + @available(iOS 11.3, *) + {{accessModifier}} var referenceImages: Set { + return ARReferenceImage.referenceImages(in: self) + } + + @available(iOS 12.0, *) + {{accessModifier}} var referenceObjects: Set { + return ARReferenceObject.referenceObjects(in: self) + } + #endif +} + +#if os(iOS) +@available(iOS 11.3, *) +{{accessModifier}} extension ARReferenceImage { + static func referenceImages(in asset: {{arResourceGroupType}}) -> Set { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + return referenceImages(inGroupNamed: asset.name, bundle: bundle) ?? Set() + } +} + +@available(iOS 12.0, *) +{{accessModifier}} extension ARReferenceObject { + static func referenceObjects(in asset: {{arResourceGroupType}}) -> Set { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + return referenceObjects(inGroupNamed: asset.name, bundle: bundle) ?? Set() + } +} +#endif +{% endif %} +{% if resourceCount.color > 0 %} + +{{accessModifier}} final class {{colorType}} { + {{accessModifier}} fileprivate(set) var name: String + + #if os(macOS) + {{accessModifier}} typealias Color = NSColor + #elseif os(iOS) || os(tvOS) || os(watchOS) + {{accessModifier}} typealias Color = UIColor + #endif + + @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) + {{accessModifier}} private(set) lazy var color: Color = Color(asset: self) + + #if os(iOS) || os(tvOS) + @available(iOS 11.0, tvOS 11.0, *) + {{accessModifier}} func color(compatibleWith traitCollection: UITraitCollection) -> Color { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + guard let color = Color(named: name, in: bundle, compatibleWith: traitCollection) else { + fatalError("Unable to load color asset named \(name).") + } + return color + } + #endif + + fileprivate init(name: String) { + self.name = name + } +} + +{{accessModifier}} extension {{colorType}}.Color { + @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) + convenience init!(asset: {{colorType}}) { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + #if os(iOS) || os(tvOS) + self.init(named: asset.name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + self.init(named: NSColor.Name(asset.name), bundle: bundle) + #elseif os(watchOS) + self.init(named: asset.name) + #endif + } +} +{% endif %} +{% if resourceCount.data > 0 %} + +{{accessModifier}} struct {{dataType}} { + {{accessModifier}} fileprivate(set) var name: String + + @available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *) + {{accessModifier}} var data: NSDataAsset { + return NSDataAsset(asset: self) + } +} + +@available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *) +{{accessModifier}} extension NSDataAsset { + convenience init!(asset: {{dataType}}) { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + #if os(iOS) || os(tvOS) || os(watchOS) + self.init(name: asset.name, bundle: bundle) + #elseif os(macOS) + self.init(name: NSDataAsset.Name(asset.name), bundle: bundle) + #endif + } +} +{% endif %} +{% if resourceCount.image > 0 %} + +{{accessModifier}} struct {{imageType}} { + {{accessModifier}} fileprivate(set) var name: String + + #if os(macOS) + {{accessModifier}} typealias Image = NSImage + #elseif os(iOS) || os(tvOS) || os(watchOS) + {{accessModifier}} typealias Image = UIImage + #endif + + @available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *) + {{accessModifier}} var image: Image { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + #if os(iOS) || os(tvOS) + let image = Image(named: name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + let name = NSImage.Name(self.name) + let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name) + #elseif os(watchOS) + let image = Image(named: name) + #endif + guard let result = image else { + fatalError("Unable to load image asset named \(name).") + } + return result + } + + #if os(iOS) || os(tvOS) + @available(iOS 8.0, tvOS 9.0, *) + {{accessModifier}} func image(compatibleWith traitCollection: UITraitCollection) -> Image { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else { + fatalError("Unable to load image asset named \(name).") + } + return result + } + #endif +} + +{{accessModifier}} extension {{imageType}}.Image { + @available(iOS 8.0, tvOS 9.0, watchOS 2.0, *) + @available(macOS, deprecated, + message: "This initializer is unsafe on macOS, please use the {{imageType}}.image property") + convenience init!(asset: {{imageType}}) { + #if os(iOS) || os(tvOS) + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + self.init(named: asset.name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + self.init(named: NSImage.Name(asset.name)) + #elseif os(watchOS) + self.init(named: asset.name) + #endif + } +} +{% endif %} +{% if resourceCount.symbol > 0 %} + +{{accessModifier}} struct {{symbolType}} { + {{accessModifier}} fileprivate(set) var name: String + + #if os(iOS) || os(tvOS) || os(watchOS) + @available(iOS 13.0, tvOS 13.0, watchOS 6.0, *) + {{accessModifier}} typealias Configuration = UIImage.SymbolConfiguration + {{accessModifier}} typealias Image = UIImage + + @available(iOS 12.0, tvOS 12.0, watchOS 5.0, *) + {{accessModifier}} var image: Image { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + #if os(iOS) || os(tvOS) + let image = Image(named: name, in: bundle, compatibleWith: nil) + #elseif os(watchOS) + let image = Image(named: name) + #endif + guard let result = image else { + fatalError("Unable to load symbol asset named \(name).") + } + return result + } + + @available(iOS 13.0, tvOS 13.0, watchOS 6.0, *) + {{accessModifier}} func image(with configuration: Configuration) -> Image { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + guard let result = Image(named: name, in: bundle, with: configuration) else { + fatalError("Unable to load symbol asset named \(name).") + } + return result + } + #endif +} +{% endif %} +{% if not param.bundle %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No assets found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/xcassets/swift5.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/xcassets/swift5.stencil new file mode 100644 index 0000000..42df7be --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/xcassets/swift5.stencil @@ -0,0 +1,337 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if catalogs %} +{% set enumName %}{{param.enumName|default:"Asset"}}{% endset %} +{% set arResourceGroupType %}{{param.arResourceGroupTypeName|default:"ARResourceGroupAsset"}}{% endset %} +{% set colorType %}{{param.colorTypeName|default:"ColorAsset"}}{% endset %} +{% set dataType %}{{param.dataTypeName|default:"DataAsset"}}{% endset %} +{% set imageType %}{{param.imageTypeName|default:"ImageAsset"}}{% endset %} +{% set symbolType %}{{param.symbolTypeName|default:"SymbolAsset"}}{% endset %} +{% set forceNamespaces %}{{param.forceProvidesNamespaces|default:"false"}}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +#if os(macOS) + import AppKit +#elseif os(iOS) +{% if resourceCount.arresourcegroup > 0 %} + import ARKit +{% endif %} + import UIKit +#elseif os(tvOS) || os(watchOS) + import UIKit +#endif + +// Deprecated typealiases +{% if resourceCount.color > 0 %} +@available(*, deprecated, renamed: "{{colorType}}.Color", message: "This typealias will be removed in SwiftGen 7.0") +{{accessModifier}} typealias {{param.colorAliasName|default:"AssetColorTypeAlias"}} = {{colorType}}.Color +{% endif %} +{% if resourceCount.image > 0 %} +@available(*, deprecated, renamed: "{{imageType}}.Image", message: "This typealias will be removed in SwiftGen 7.0") +{{accessModifier}} typealias {{param.imageAliasName|default:"AssetImageTypeAlias"}} = {{imageType}}.Image +{% endif %} + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Asset Catalogs + +{% macro enumBlock assets %} + {% call casesBlock assets %} + {% if param.allValues %} + + // swiftlint:disable trailing_comma + {% if resourceCount.arresourcegroup > 0 %} + {{accessModifier}} static let allResourceGroups: [{{arResourceGroupType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "arresourcegroup" "" %}{% endfilter %} + ] + {% endif %} + {% if resourceCount.color > 0 %} + {{accessModifier}} static let allColors: [{{colorType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "color" "" %}{% endfilter %} + ] + {% endif %} + {% if resourceCount.data > 0 %} + {{accessModifier}} static let allDataAssets: [{{dataType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "data" "" %}{% endfilter %} + ] + {% endif %} + {% if resourceCount.image > 0 %} + {{accessModifier}} static let allImages: [{{imageType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "image" "" %}{% endfilter %} + ] + {% endif %} + {% if resourceCount.symbol > 0 %} + {{accessModifier}} static let allSymbols: [{{symbolType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "symbol" "" %}{% endfilter %} + ] + {% endif %} + // swiftlint:enable trailing_comma + {% endif %} +{% endmacro %} +{% macro casesBlock assets %} + {% for asset in assets %} + {% if asset.type == "arresourcegroup" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{arResourceGroupType}}(name: "{{asset.value}}") + {% elif asset.type == "color" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{colorType}}(name: "{{asset.value}}") + {% elif asset.type == "data" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{dataType}}(name: "{{asset.value}}") + {% elif asset.type == "image" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{imageType}}(name: "{{asset.value}}") + {% elif asset.type == "symbol" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{symbolType}}(name: "{{asset.value}}") + {% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %} + {{accessModifier}} enum {{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call casesBlock asset.items %}{% endfilter %} + } + {% elif asset.items %} + {% call casesBlock asset.items %} + {% endif %} + {% endfor %} +{% endmacro %} +{% macro allValuesBlock assets filter prefix %} + {% for asset in assets %} + {% if asset.type == filter %} + {{prefix}}{{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}, + {% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %} + {% set prefix2 %}{{prefix}}{{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.{% endset %} + {% call allValuesBlock asset.items filter prefix2 %} + {% elif asset.items %} + {% call allValuesBlock asset.items filter prefix %} + {% endif %} + {% endfor %} +{% endmacro %} +// swiftlint:disable identifier_name line_length nesting type_body_length type_name +{{accessModifier}} enum {{enumName}} { + {% if catalogs.count > 1 or param.forceFileNameEnum %} + {% for catalog in catalogs %} + {{accessModifier}} enum {{catalog.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call enumBlock catalog.assets %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call enumBlock catalogs.first.assets %} + {% endif %} +} +// swiftlint:enable identifier_name line_length nesting type_body_length type_name + +// MARK: - Implementation Details +{% if resourceCount.arresourcegroup > 0 %} + +{{accessModifier}} struct {{arResourceGroupType}} { + {{accessModifier}} fileprivate(set) var name: String + + #if os(iOS) + @available(iOS 11.3, *) + {{accessModifier}} var referenceImages: Set { + return ARReferenceImage.referenceImages(in: self) + } + + @available(iOS 12.0, *) + {{accessModifier}} var referenceObjects: Set { + return ARReferenceObject.referenceObjects(in: self) + } + #endif +} + +#if os(iOS) +@available(iOS 11.3, *) +{{accessModifier}} extension ARReferenceImage { + static func referenceImages(in asset: {{arResourceGroupType}}) -> Set { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + return referenceImages(inGroupNamed: asset.name, bundle: bundle) ?? Set() + } +} + +@available(iOS 12.0, *) +{{accessModifier}} extension ARReferenceObject { + static func referenceObjects(in asset: {{arResourceGroupType}}) -> Set { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + return referenceObjects(inGroupNamed: asset.name, bundle: bundle) ?? Set() + } +} +#endif +{% endif %} +{% if resourceCount.color > 0 %} + +{{accessModifier}} final class {{colorType}} { + {{accessModifier}} fileprivate(set) var name: String + + #if os(macOS) + {{accessModifier}} typealias Color = NSColor + #elseif os(iOS) || os(tvOS) || os(watchOS) + {{accessModifier}} typealias Color = UIColor + #endif + + @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) + {{accessModifier}} private(set) lazy var color: Color = { + guard let color = Color(asset: self) else { + fatalError("Unable to load color asset named \(name).") + } + return color + }() + + #if os(iOS) || os(tvOS) + @available(iOS 11.0, tvOS 11.0, *) + {{accessModifier}} func color(compatibleWith traitCollection: UITraitCollection) -> Color { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + guard let color = Color(named: name, in: bundle, compatibleWith: traitCollection) else { + fatalError("Unable to load color asset named \(name).") + } + return color + } + #endif + + fileprivate init(name: String) { + self.name = name + } +} + +{{accessModifier}} extension {{colorType}}.Color { + @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) + convenience init?(asset: {{colorType}}) { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + #if os(iOS) || os(tvOS) + self.init(named: asset.name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + self.init(named: NSColor.Name(asset.name), bundle: bundle) + #elseif os(watchOS) + self.init(named: asset.name) + #endif + } +} +{% endif %} +{% if resourceCount.data > 0 %} + +{{accessModifier}} struct {{dataType}} { + {{accessModifier}} fileprivate(set) var name: String + + @available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *) + {{accessModifier}} var data: NSDataAsset { + guard let data = NSDataAsset(asset: self) else { + fatalError("Unable to load data asset named \(name).") + } + return data + } +} + +@available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *) +{{accessModifier}} extension NSDataAsset { + convenience init?(asset: {{dataType}}) { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + #if os(iOS) || os(tvOS) || os(watchOS) + self.init(name: asset.name, bundle: bundle) + #elseif os(macOS) + self.init(name: NSDataAsset.Name(asset.name), bundle: bundle) + #endif + } +} +{% endif %} +{% if resourceCount.image > 0 %} + +{{accessModifier}} struct {{imageType}} { + {{accessModifier}} fileprivate(set) var name: String + + #if os(macOS) + {{accessModifier}} typealias Image = NSImage + #elseif os(iOS) || os(tvOS) || os(watchOS) + {{accessModifier}} typealias Image = UIImage + #endif + + @available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *) + {{accessModifier}} var image: Image { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + #if os(iOS) || os(tvOS) + let image = Image(named: name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + let name = NSImage.Name(self.name) + let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name) + #elseif os(watchOS) + let image = Image(named: name) + #endif + guard let result = image else { + fatalError("Unable to load image asset named \(name).") + } + return result + } + + #if os(iOS) || os(tvOS) + @available(iOS 8.0, tvOS 9.0, *) + {{accessModifier}} func image(compatibleWith traitCollection: UITraitCollection) -> Image { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else { + fatalError("Unable to load image asset named \(name).") + } + return result + } + #endif +} + +{{accessModifier}} extension {{imageType}}.Image { + @available(iOS 8.0, tvOS 9.0, watchOS 2.0, *) + @available(macOS, deprecated, + message: "This initializer is unsafe on macOS, please use the {{imageType}}.image property") + convenience init?(asset: {{imageType}}) { + #if os(iOS) || os(tvOS) + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + self.init(named: asset.name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + self.init(named: NSImage.Name(asset.name)) + #elseif os(watchOS) + self.init(named: asset.name) + #endif + } +} +{% endif %} +{% if resourceCount.symbol > 0 %} + +{{accessModifier}} struct {{symbolType}} { + {{accessModifier}} fileprivate(set) var name: String + + #if os(iOS) || os(tvOS) || os(watchOS) + @available(iOS 13.0, tvOS 13.0, watchOS 6.0, *) + {{accessModifier}} typealias Configuration = UIImage.SymbolConfiguration + {{accessModifier}} typealias Image = UIImage + + @available(iOS 12.0, tvOS 12.0, watchOS 5.0, *) + {{accessModifier}} var image: Image { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + #if os(iOS) || os(tvOS) + let image = Image(named: name, in: bundle, compatibleWith: nil) + #elseif os(watchOS) + let image = Image(named: name) + #endif + guard let result = image else { + fatalError("Unable to load symbol asset named \(name).") + } + return result + } + + @available(iOS 13.0, tvOS 13.0, watchOS 6.0, *) + {{accessModifier}} func image(with configuration: Configuration) -> Image { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + guard let result = Image(named: name, in: bundle, with: configuration) else { + fatalError("Unable to load symbol asset named \(name).") + } + return result + } + #endif +} +{% endif %} +{% if not param.bundle %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No assets found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/yaml/inline-swift4.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/yaml/inline-swift4.stencil new file mode 100644 index 0000000..9cc2aa3 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/yaml/inline-swift4.stencil @@ -0,0 +1,92 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set documentPrefix %}{{param.documentName|default:"Document"}}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - YAML Files +{% macro fileBlock file %} + {% if file.documents.count > 1 %} + {% for document in file.documents %} + {% set documentName %}{{documentPrefix}}{{forloop.counter}}{% endset %} + {{accessModifier}} enum {{documentName|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call documentBlock file document %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call documentBlock file file.documents.first %} + {% endif %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% elif document.metadata.type == "Dictionary" %} + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value document.data %} + {% endfor %} + {% else %} + {{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% elif metadata.type == "Optional" %} + Any? + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %} +{% endfilter %}{% endmacro %} +{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "String" %} + "{{ value }}" + {% elif metadata.type == "Optional" %} + nil + {% elif metadata.type == "Array" and value %} + [{% for value in value %} + {% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %} + {{ ", " if not forloop.last }} + {% endfor %}] + {% elif metadata.type == "Dictionary" %} + [{% for key,value in value %} + "{{key}}": {% call valueBlock value metadata.properties[key] %} + {{ ", " if not forloop.last }} + {% empty %} + : + {% endfor %}] + {% elif metadata.type == "Bool" %} + {% if value %}true{% else %}false{% endif %} + {% else %} + {{ value }} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length number_separator type_body_length +{{accessModifier}} enum {{param.enumName|default:"YAMLFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length number_separator type_body_length +{% else %} +// No files found +{% endif %} diff --git a/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/yaml/inline-swift5.stencil b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/yaml/inline-swift5.stencil new file mode 100644 index 0000000..9cc2aa3 --- /dev/null +++ b/.swiftgen/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/yaml/inline-swift5.stencil @@ -0,0 +1,92 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set documentPrefix %}{{param.documentName|default:"Document"}}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - YAML Files +{% macro fileBlock file %} + {% if file.documents.count > 1 %} + {% for document in file.documents %} + {% set documentName %}{{documentPrefix}}{{forloop.counter}}{% endset %} + {{accessModifier}} enum {{documentName|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call documentBlock file document %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call documentBlock file file.documents.first %} + {% endif %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% elif document.metadata.type == "Dictionary" %} + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value document.data %} + {% endfor %} + {% else %} + {{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% elif metadata.type == "Optional" %} + Any? + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %} +{% endfilter %}{% endmacro %} +{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "String" %} + "{{ value }}" + {% elif metadata.type == "Optional" %} + nil + {% elif metadata.type == "Array" and value %} + [{% for value in value %} + {% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %} + {{ ", " if not forloop.last }} + {% endfor %}] + {% elif metadata.type == "Dictionary" %} + [{% for key,value in value %} + "{{key}}": {% call valueBlock value metadata.properties[key] %} + {{ ", " if not forloop.last }} + {% empty %} + : + {% endfor %}] + {% elif metadata.type == "Bool" %} + {% if value %}true{% else %}false{% endif %} + {% else %} + {{ value }} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length number_separator type_body_length +{{accessModifier}} enum {{param.enumName|default:"YAMLFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length number_separator type_body_length +{% else %} +// No files found +{% endif %} diff --git a/.swiftgen/bin/swiftgen b/.swiftgen/bin/swiftgen new file mode 100755 index 0000000..1bc2b87 Binary files /dev/null and b/.swiftgen/bin/swiftgen differ diff --git a/.swiftgen/templates/fonts.stencil b/.swiftgen/templates/fonts.stencil new file mode 100644 index 0000000..c6148bf --- /dev/null +++ b/.swiftgen/templates/fonts.stencil @@ -0,0 +1,29 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if families %} +import SwiftUI +{% for family in families %} +{% set identifierName %}{{family.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} +{% set styleTypeName %}{{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}Style{% endset %} + +extension Font { + public static func {{identifierName}}(_ style: {{styleTypeName}}, fixedSize: CGFloat) -> Font { + return Font.custom(style.rawValue, fixedSize: fixedSize) + } + + public static func {{identifierName}}(_ style: {{styleTypeName}}, size: CGFloat, relativeTo textStyle: TextStyle = .body) -> Font { + return Font.custom(style.rawValue, size: size, relativeTo: textStyle) + } + + public enum {{styleTypeName}}: String { + {% for font in family.fonts %} + case {{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = "{{font.name}}" + {% endfor %} + } +} +{% endfor %} +{% else %} +// No fonts found +{% endif %} +// swiftlint:enable all diff --git a/.swiftgen/templates/strings.stencil b/.swiftgen/templates/strings.stencil new file mode 100644 index 0000000..daeeea4 --- /dev/null +++ b/.swiftgen/templates/strings.stencil @@ -0,0 +1,85 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if tables.count > 0 %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Strings + +{% macro parametersBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + {% if type == "String" %} + _ p{{forloop.counter}}: Any + {% else %} + _ p{{forloop.counter}}: {{type}} + {% endif %} + {{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + {% if type == "String" %} + String(describing: p{{forloop.counter}}) + {% elif type == "UnsafeRawPointer" %} + Int(bitPattern: p{{forloop.counter}}) + {% else %} + p{{forloop.counter}} + {% endif %} + {{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro recursiveBlock table item %} + {% for string in item.strings %} + {% if not param.noComments %} + {% for line in string.translation|split:"\n" %} + /// {{line}} + {% endfor %} + {% endif %} + {% if string.types %} + {{accessModifier}} static func {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String { + {{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %}) + } + {% else %} + {{accessModifier}} static var {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { {{enumName}}.tr("{{table}}", "{{string.key}}") } + {% endif %} + {% endfor %} + {% for child in item.children %} + + {{accessModifier}} enum {{child.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call recursiveBlock table child %}{% endfilter %} + } + {% endfor %} +{% endmacro %} +// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %} +{{accessModifier}} enum {{enumName}} { + {% if tables.count > 1 or param.forceFileNameEnum %} + {% for table in tables %} + {{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call recursiveBlock tables.first.name tables.first.levels %} + {% endif %} +} +// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details +import Localize_Swift +extension {{enumName}} { + static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { + let selectedLanguage = Localize.currentLanguage() + guard let path = Bundle.main.path(forResource: selectedLanguage, ofType: "lproj"), + let bundle = Bundle(path: path) else { return "Setup language error" } + return NSLocalizedString(key, tableName: table, bundle: bundle, comment: "") + } +} + +{% endif %} +// swiftlint: enable all diff --git a/.swiftgen/templates/xcassets.stencil b/.swiftgen/templates/xcassets.stencil new file mode 100644 index 0000000..b6283e7 --- /dev/null +++ b/.swiftgen/templates/xcassets.stencil @@ -0,0 +1,48 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if catalogs %} +import SwiftUI +{% macro casesBlock assets %} + {% for asset in assets %} + {% if asset.items and asset.isNamespaced == "true" %} + public enum {{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call casesBlock asset.items %}{% endfilter %} + } + {% elif asset.items %} + {% call casesBlock asset.items %} + {% elif asset.type == "color" %} + public static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = Color("{{asset.value}}") + {% elif asset.type == "image" %} + public static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = Image("{{asset.value}}") + {% endif %} + {% endfor %} +{% endmacro %} +{% for catalog in catalogs %} +{% if catalog.name == "Colors" %} + +extension Color { + {% for catalog in catalogs %} + {% if catalog.name == "Colors" %} + {% call casesBlock catalog.assets %} + {% endif %} + {% endfor %} +} +{% endif %} +{% endfor %} +{% for catalog in catalogs %} +{% if catalog.name == "Images" %} + +extension Image { + {% for catalog in catalogs %} + {% if catalog.name == "Images" %} + {% call casesBlock catalog.assets %} + {% endif %} + {% endfor %} +} +{% endif %} +{% endfor %} +{% else %} +// No assets found +{% endif %} +// swiftlint: enable all diff --git a/.swiftgen/templates/xcassets_strings.stencil b/.swiftgen/templates/xcassets_strings.stencil new file mode 100644 index 0000000..a5540bc --- /dev/null +++ b/.swiftgen/templates/xcassets_strings.stencil @@ -0,0 +1,36 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if catalogs %} +import Foundation + +typealias AssetStrings = String +{% macro casesBlock assets %} + {% for asset in assets %} + {% if asset.items and asset.isNamespaced == "true" %} + public enum {{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call casesBlock asset.items %}{% endfilter %} + } + {% elif asset.items %} + {% call casesBlock asset.items %} + {% elif asset.type == "image" %} + public static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = String("{{asset.value}}") + {% endif %} + {% endfor %} +{% endmacro %} +{% for catalog in catalogs %} +{% if catalog.name == "Images" %} + +extension String { + {% for catalog in catalogs %} + {% if catalog.name == "Images" %} + {% call casesBlock catalog.assets %} + {% endif %} + {% endfor %} +} +{% endif %} +{% endfor %} +{% else %} +// No assets found +{% endif %} +// swiftlint: enable all diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..e2fe687 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,124 @@ +--- +colon: + severity: error + +line_length: + ignores_comments: true + warning: 260 + error: 300 + +type_body_length: + warning: 300 + error: 500 + +file_length: + warning: 800 + error: 1000 + +function_parameter_count: + warning: 20 + error: 30 + +function_body_length: + warning: 120 + error: 150 + +cyclomatic_complexity: + warning: 40 + error: 50 + +nesting: + type_level: + warning: 3 + error: 6 + function_level: + warning: 500 + error: 10 + +vertical_parameter_alignment: + severity: warning + +implicitly_unwrapped_optional: + severity: warning + +force_unwrapping: + severity: error + +vertical_whitespace: + severity: error + +force_try: + severity: error + +trailing_semicolon: + severity: error + +type_name: + min_length: + warninig: 3 + error: 0 + max_length: + warninig: 40 + error: 80 + excluded: + - Iq + +identifier_name: + min_length: 3 + max_length: 60 + # validates_start_with_lowercase: true + allowed_symbols: "_" + excluded: + - iv + - id + - ip + - on + - ui + - x + - y + - tz + - to + - db + - _db + - iq + +# Disable rules from the default enabled set. +disabled_rules: + - trailing_whitespace + - implicit_getter + - redundant_string_enum_value + - switch_case_alignment + +# Enable rules not from the default set. +opt_in_rules: + # - function_default_parameter_at_end + - empty_count + - indentation_width + # - index_at_zero + - legacy_constant + # - implicitly_unwrapped_optional + - force_unwrapping + # - no header + - file_header + # - for force unwrapping + - implicitly_unwrapped_optional + - vertical_parameter_alignment_on_call + - vertical_whitespace_between_cases + - vertical_whitespace_closing_braces + - vertical_whitespace_opening_braces + +# Acts as a whitelist, only the rules specified in this list will be enabled. Can not be specified alongside disabled_rules or opt_in_rules. +only_rules: + +# This is an entirely separate list of rules that are only run by the analyze command. All analyzer rules are opt-in, so this is the only configurable rule list (there is no disabled/whitelist equivalent). +analyzer_rules: + - unused_import + - unused_declaration + +unused_declaration: + include_public_and_open: true + +# paths to ignore during linting. Takes precedence over `included`. +excluded: + - .swiftgen + - "**/Generated" diff --git a/AnotherIM/AnotherIMApp.swift b/AnotherIM/AnotherIMApp.swift new file mode 100644 index 0000000..412ee7f --- /dev/null +++ b/AnotherIM/AnotherIMApp.swift @@ -0,0 +1,11 @@ +import Combine +import SwiftUI + +@main +struct AnotherIMApp: App { + var body: some Scene { + WindowGroup { + StartScreen() + } + } +} diff --git a/AnotherIM/Generated/.gitignore b/AnotherIM/Generated/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/AnotherIM/Generated/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/material/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/material/Contents.json new file mode 100644 index 0000000..6e96565 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/material/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/material/background/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/material/background/Contents.json new file mode 100644 index 0000000..6e96565 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/material/background/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/material/background/dark.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/material/background/dark.colorset/Contents.json new file mode 100644 index 0000000..bdb682b --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/material/background/dark.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xE4", + "green" : "0xE4", + "red" : "0xE4" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/material/background/light.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/material/background/light.colorset/Contents.json new file mode 100644 index 0000000..b8c6d9e --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/material/background/light.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "235", + "green" : "235", + "red" : "235" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/material/elements/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/material/elements/Contents.json new file mode 100644 index 0000000..6e96565 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/material/elements/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/material/elements/active.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/material/elements/active.colorset/Contents.json new file mode 100644 index 0000000..5a42e0f --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/material/elements/active.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x4D", + "green" : "0x46", + "red" : "0x3C" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/material/elements/inactive.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/material/elements/inactive.colorset/Contents.json new file mode 100644 index 0000000..944aec1 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/material/elements/inactive.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xAC", + "green" : "0xA3", + "red" : "0x95" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/material/shape/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/material/shape/Contents.json new file mode 100644 index 0000000..6e96565 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/material/shape/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/material/shape/alternate.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/material/shape/alternate.colorset/Contents.json new file mode 100644 index 0000000..72469b0 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/material/shape/alternate.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "201", + "green" : "227", + "red" : "199" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/material/shape/black.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/material/shape/black.colorset/Contents.json new file mode 100644 index 0000000..1c18f8d --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/material/shape/black.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x62", + "green" : "0x59", + "red" : "0x4A" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/material/shape/separator.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/material/shape/separator.colorset/Contents.json new file mode 100644 index 0000000..3d66dc2 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/material/shape/separator.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "189", + "green" : "189", + "red" : "189" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/material/shape/white.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/material/shape/white.colorset/Contents.json new file mode 100644 index 0000000..fafa476 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/material/shape/white.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/material/text/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/material/text/Contents.json new file mode 100644 index 0000000..6e96565 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/material/text/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/material/text/main.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/material/text/main.colorset/Contents.json new file mode 100644 index 0000000..dfe1a2d --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/material/text/main.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x36", + "green" : "0x31", + "red" : "0x2A" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/material/text/sub.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/material/text/sub.colorset/Contents.json new file mode 100644 index 0000000..4db6d18 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/material/text/sub.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x78", + "green" : "0x6D", + "red" : "0x5A" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/material/text/white.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/material/text/white.colorset/Contents.json new file mode 100644 index 0000000..2cedebe --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/material/text/white.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xEF", + "green" : "0xEF", + "red" : "0xEF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/Contents.json new file mode 100644 index 0000000..6e96565 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/background/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/background/Contents.json new file mode 100644 index 0000000..6e96565 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/background/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/primary/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/primary/Contents.json new file mode 100644 index 0000000..6e96565 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/primary/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/primary/c100.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/primary/c100.colorset/Contents.json new file mode 100644 index 0000000..2e838f5 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/primary/c100.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xD9", + "green" : "0xD7", + "red" : "0xD3" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/primary/c200.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/primary/c200.colorset/Contents.json new file mode 100644 index 0000000..8bfa21b --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/primary/c200.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xC2", + "green" : "0xBD", + "red" : "0xB5" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/primary/c400.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/primary/c400.colorset/Contents.json new file mode 100644 index 0000000..c61781a --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/primary/c400.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x9A", + "green" : "0x8F", + "red" : "0x7D" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/primary/c50.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/primary/c50.colorset/Contents.json new file mode 100644 index 0000000..a61d481 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/primary/c50.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFE" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/primary/c500main.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/primary/c500main.colorset/Contents.json new file mode 100644 index 0000000..d4b5ad5 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/primary/c500main.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x89", + "green" : "0x7C", + "red" : "0x66" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/Contents.json new file mode 100644 index 0000000..6e96565 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c100.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c100.colorset/Contents.json new file mode 100644 index 0000000..9d735e4 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c100.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xB4", + "green" : "0xEC", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c200.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c200.colorset/Contents.json new file mode 100644 index 0000000..aaeed90 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c200.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x83", + "green" : "0xF0", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c300.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c300.colorset/Contents.json new file mode 100644 index 0000000..a8c8ffc --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c300.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x52", + "green" : "0xD5", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c400.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c400.colorset/Contents.json new file mode 100644 index 0000000..c24864c --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c400.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x2D", + "green" : "0xCA", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c50.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c50.colorset/Contents.json new file mode 100644 index 0000000..c345981 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c50.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xE1", + "green" : "0xF8", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c500main.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c500main.colorset/Contents.json new file mode 100644 index 0000000..11f3048 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c500main.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x14", + "green" : "0xC1", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c600.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c600.colorset/Contents.json new file mode 100644 index 0000000..2589002 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c600.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x0F", + "green" : "0xB3", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c700.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c700.colorset/Contents.json new file mode 100644 index 0000000..b745fb4 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c700.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x0D", + "green" : "0xA0", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c800.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c800.colorset/Contents.json new file mode 100644 index 0000000..9024695 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c800.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x0C", + "green" : "0x8F", + "red" : "0xFE" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c900.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c900.colorset/Contents.json new file mode 100644 index 0000000..8b4228e --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/secondary/c900.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x0B", + "green" : "0x70", + "red" : "0xFE" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/text/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/text/Contents.json new file mode 100644 index 0000000..6e96565 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/text/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/text/main.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/text/main.colorset/Contents.json new file mode 100644 index 0000000..9c8eb66 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/text/main.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x2D", + "green" : "0x2D", + "red" : "0x2D" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/old/text/sub.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/old/text/sub.colorset/Contents.json new file mode 100644 index 0000000..2e42b95 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/old/text/sub.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x78", + "green" : "0x78", + "red" : "0x78" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/Contents.json new file mode 100644 index 0000000..6e96565 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blue200.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blue200.colorset/Contents.json new file mode 100644 index 0000000..12b3b32 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blue200.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.976", + "green" : "0.792", + "red" : "0.565" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blue300.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blue300.colorset/Contents.json new file mode 100644 index 0000000..0b975d5 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blue300.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.965", + "green" : "0.710", + "red" : "0.392" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blue500.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blue500.colorset/Contents.json new file mode 100644 index 0000000..3c05e0a --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blue500.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.953", + "green" : "0.588", + "red" : "0.129" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blue800.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blue800.colorset/Contents.json new file mode 100644 index 0000000..865adb5 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blue800.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.824", + "green" : "0.463", + "red" : "0.098" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueDark200.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueDark200.colorset/Contents.json new file mode 100644 index 0000000..e29b366 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueDark200.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.855", + "green" : "0.659", + "red" : "0.624" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueDark300.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueDark300.colorset/Contents.json new file mode 100644 index 0000000..096e5f1 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueDark300.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.796", + "green" : "0.525", + "red" : "0.475" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueDark500.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueDark500.colorset/Contents.json new file mode 100644 index 0000000..22f8cb4 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueDark500.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.710", + "green" : "0.318", + "red" : "0.247" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueDark800.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueDark800.colorset/Contents.json new file mode 100644 index 0000000..bf615f8 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueDark800.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.624", + "green" : "0.247", + "red" : "0.188" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueLight200.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueLight200.colorset/Contents.json new file mode 100644 index 0000000..1ded918 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueLight200.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.980", + "green" : "0.831", + "red" : "0.506" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueLight300.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueLight300.colorset/Contents.json new file mode 100644 index 0000000..f17585c --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueLight300.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.969", + "green" : "0.765", + "red" : "0.310" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueLight500.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueLight500.colorset/Contents.json new file mode 100644 index 0000000..9453b24 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueLight500.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.957", + "green" : "0.663", + "red" : "0.012" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueLight800.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueLight800.colorset/Contents.json new file mode 100644 index 0000000..2b85944 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/blueLight800.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.820", + "green" : "0.533", + "red" : "0.008" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/brown200.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/brown200.colorset/Contents.json new file mode 100644 index 0000000..ce0b7bb --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/brown200.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.643", + "green" : "0.667", + "red" : "0.737" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/brown300.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/brown300.colorset/Contents.json new file mode 100644 index 0000000..957f0da --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/brown300.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.498", + "green" : "0.533", + "red" : "0.631" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/brown500.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/brown500.colorset/Contents.json new file mode 100644 index 0000000..2926f1b --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/brown500.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.282", + "green" : "0.333", + "red" : "0.475" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/brown800.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/brown800.colorset/Contents.json new file mode 100644 index 0000000..70e2f7c --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/brown800.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.216", + "green" : "0.251", + "red" : "0.365" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenDark100.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenDark100.colorset/Contents.json new file mode 100644 index 0000000..72469b0 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenDark100.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "201", + "green" : "227", + "red" : "199" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenDark200.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenDark200.colorset/Contents.json new file mode 100644 index 0000000..c7b6100 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenDark200.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "167", + "green" : "214", + "red" : "165" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenDark300.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenDark300.colorset/Contents.json new file mode 100644 index 0000000..0f3b846 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenDark300.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "132", + "green" : "199", + "red" : "129" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenDark500.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenDark500.colorset/Contents.json new file mode 100644 index 0000000..ddfea69 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenDark500.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.314", + "green" : "0.686", + "red" : "0.298" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenDark800.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenDark800.colorset/Contents.json new file mode 100644 index 0000000..9dbb7a6 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenDark800.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.235", + "green" : "0.557", + "red" : "0.220" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenLight200.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenLight200.colorset/Contents.json new file mode 100644 index 0000000..54f6a97 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenLight200.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.647", + "green" : "0.882", + "red" : "0.773" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenLight300.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenLight300.colorset/Contents.json new file mode 100644 index 0000000..2c4fe10 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenLight300.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.506", + "green" : "0.835", + "red" : "0.682" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenLight500.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenLight500.colorset/Contents.json new file mode 100644 index 0000000..89db920 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenLight500.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.290", + "green" : "0.765", + "red" : "0.545" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenLight800.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenLight800.colorset/Contents.json new file mode 100644 index 0000000..1acd954 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/greenLight800.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.220", + "green" : "0.624", + "red" : "0.408" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaDark200.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaDark200.colorset/Contents.json new file mode 100644 index 0000000..ef9328c --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaDark200.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.859", + "green" : "0.616", + "red" : "0.702" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaDark300.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaDark300.colorset/Contents.json new file mode 100644 index 0000000..3556135 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaDark300.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.804", + "green" : "0.459", + "red" : "0.584" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaDark500.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaDark500.colorset/Contents.json new file mode 100644 index 0000000..00e5075 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaDark500.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.718", + "green" : "0.227", + "red" : "0.404" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaDark800.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaDark800.colorset/Contents.json new file mode 100644 index 0000000..748a957 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaDark800.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.659", + "green" : "0.176", + "red" : "0.318" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaLight200.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaLight200.colorset/Contents.json new file mode 100644 index 0000000..725c54c --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaLight200.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.847", + "green" : "0.576", + "red" : "0.808" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaLight300.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaLight300.colorset/Contents.json new file mode 100644 index 0000000..d9fbdb8 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaLight300.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.784", + "green" : "0.408", + "red" : "0.729" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaLight500.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaLight500.colorset/Contents.json new file mode 100644 index 0000000..99500b7 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaLight500.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.690", + "green" : "0.153", + "red" : "0.612" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaLight800.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaLight800.colorset/Contents.json new file mode 100644 index 0000000..2921caf --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/magentaLight800.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.635", + "green" : "0.122", + "red" : "0.482" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeDark200.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeDark200.colorset/Contents.json new file mode 100644 index 0000000..fbe5e38 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeDark200.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.569", + "green" : "0.671", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeDark300.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeDark300.colorset/Contents.json new file mode 100644 index 0000000..58e2f9f --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeDark300.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.396", + "green" : "0.541", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeDark500.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeDark500.colorset/Contents.json new file mode 100644 index 0000000..e219ac8 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeDark500.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.133", + "green" : "0.341", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeDark800.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeDark800.colorset/Contents.json new file mode 100644 index 0000000..a63dd3e --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeDark800.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.098", + "green" : "0.290", + "red" : "0.902" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeLight200.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeLight200.colorset/Contents.json new file mode 100644 index 0000000..6e70cd5 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeLight200.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.502", + "green" : "0.800", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeLight300.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeLight300.colorset/Contents.json new file mode 100644 index 0000000..799505c --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeLight300.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.302", + "green" : "0.718", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeLight500.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeLight500.colorset/Contents.json new file mode 100644 index 0000000..4f0878b --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeLight500.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.596", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeLight800.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeLight800.colorset/Contents.json new file mode 100644 index 0000000..dfc0149 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/orangeLight800.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.486", + "red" : "0.961" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/pink200.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/pink200.colorset/Contents.json new file mode 100644 index 0000000..0becef6 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/pink200.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.694", + "green" : "0.561", + "red" : "0.957" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/pink300.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/pink300.colorset/Contents.json new file mode 100644 index 0000000..9e9e4b7 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/pink300.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.573", + "green" : "0.384", + "red" : "0.941" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/pink500.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/pink500.colorset/Contents.json new file mode 100644 index 0000000..ddc3e1d --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/pink500.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.388", + "green" : "0.118", + "red" : "0.914" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/pink800.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/pink800.colorset/Contents.json new file mode 100644 index 0000000..463aa83 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/pink800.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.357", + "green" : "0.094", + "red" : "0.761" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/red200.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/red200.colorset/Contents.json new file mode 100644 index 0000000..518a736 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/red200.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.604", + "green" : "0.604", + "red" : "0.937" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/red300.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/red300.colorset/Contents.json new file mode 100644 index 0000000..94100b6 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/red300.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.451", + "green" : "0.451", + "red" : "0.898" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/red500.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/red500.colorset/Contents.json new file mode 100644 index 0000000..7afafeb --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/red500.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.212", + "green" : "0.263", + "red" : "0.957" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/red800.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/red800.colorset/Contents.json new file mode 100644 index 0000000..8972c91 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/red800.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.184", + "green" : "0.184", + "red" : "0.827" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseDark200.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseDark200.colorset/Contents.json new file mode 100644 index 0000000..89f27af --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseDark200.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.769", + "green" : "0.796", + "red" : "0.502" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseDark300.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseDark300.colorset/Contents.json new file mode 100644 index 0000000..39d4eca --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseDark300.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.675", + "green" : "0.714", + "red" : "0.302" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseDark500.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseDark500.colorset/Contents.json new file mode 100644 index 0000000..13174d8 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseDark500.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.533", + "green" : "0.588", + "red" : "0.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseDark800.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseDark800.colorset/Contents.json new file mode 100644 index 0000000..24f01a5 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseDark800.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.420", + "green" : "0.475", + "red" : "0.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseLight200.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseLight200.colorset/Contents.json new file mode 100644 index 0000000..0f87be6 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseLight200.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.918", + "green" : "0.871", + "red" : "0.502" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseLight300.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseLight300.colorset/Contents.json new file mode 100644 index 0000000..5d7af98 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseLight300.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.882", + "green" : "0.816", + "red" : "0.302" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseLight500.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseLight500.colorset/Contents.json new file mode 100644 index 0000000..20a4a22 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseLight500.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.831", + "green" : "0.737", + "red" : "0.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseLight800.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseLight800.colorset/Contents.json new file mode 100644 index 0000000..68b5f8d --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/tortoiseLight800.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.655", + "green" : "0.592", + "red" : "0.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowDark200.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowDark200.colorset/Contents.json new file mode 100644 index 0000000..98fa97f --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowDark200.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.510", + "green" : "0.878", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowDark300.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowDark300.colorset/Contents.json new file mode 100644 index 0000000..6140117 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowDark300.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.310", + "green" : "0.835", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowDark500.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowDark500.colorset/Contents.json new file mode 100644 index 0000000..6ef924c --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowDark500.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.027", + "green" : "0.757", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowDark800.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowDark800.colorset/Contents.json new file mode 100644 index 0000000..93e32b7 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowDark800.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.627", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowLight200.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowLight200.colorset/Contents.json new file mode 100644 index 0000000..f1b174f --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowLight200.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.612", + "green" : "0.933", + "red" : "0.902" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowLight300.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowLight300.colorset/Contents.json new file mode 100644 index 0000000..74aa8a8 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowLight300.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.459", + "green" : "0.906", + "red" : "0.863" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowLight500.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowLight500.colorset/Contents.json new file mode 100644 index 0000000..ac7f58f --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowLight500.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.224", + "green" : "0.863", + "red" : "0.804" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowLight800.colorset/Contents.json b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowLight800.colorset/Contents.json new file mode 100644 index 0000000..8aa0219 --- /dev/null +++ b/AnotherIM/Resources/Assets/Colors.xcassets/rainbow/yellowLight800.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.169", + "green" : "0.706", + "red" : "0.686" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Images.xcassets/AppIcon.appiconset/Contents.json b/AnotherIM/Resources/Assets/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..e4b468d --- /dev/null +++ b/AnotherIM/Resources/Assets/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "aim_logo_2.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Images.xcassets/AppIcon.appiconset/aim_logo_2.png b/AnotherIM/Resources/Assets/Images.xcassets/AppIcon.appiconset/aim_logo_2.png new file mode 100644 index 0000000..3b06520 Binary files /dev/null and b/AnotherIM/Resources/Assets/Images.xcassets/AppIcon.appiconset/aim_logo_2.png differ diff --git a/AnotherIM/Resources/Assets/Images.xcassets/Contents.json b/AnotherIM/Resources/Assets/Images.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/AnotherIM/Resources/Assets/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Assets/Images.xcassets/logo.imageset/Contents.json b/AnotherIM/Resources/Assets/Images.xcassets/logo.imageset/Contents.json new file mode 100644 index 0000000..f228e4c --- /dev/null +++ b/AnotherIM/Resources/Assets/Images.xcassets/logo.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "aim_logo_2_1.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/AnotherIM/Resources/Assets/Images.xcassets/logo.imageset/aim_logo_2_1.png b/AnotherIM/Resources/Assets/Images.xcassets/logo.imageset/aim_logo_2_1.png new file mode 100644 index 0000000..22ba9ca Binary files /dev/null and b/AnotherIM/Resources/Assets/Images.xcassets/logo.imageset/aim_logo_2_1.png differ diff --git a/AnotherIM/Resources/Preview Content/Preview Assets.xcassets/Contents.json b/AnotherIM/Resources/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/AnotherIM/Resources/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnotherIM/Resources/Strings/Localizable.strings b/AnotherIM/Resources/Strings/Localizable.strings new file mode 100644 index 0000000..6be158f --- /dev/null +++ b/AnotherIM/Resources/Strings/Localizable.strings @@ -0,0 +1,2 @@ +// MARK: General +"Global.ok" = "Ok"; diff --git a/AnotherIM/Resources/launchscreen.storyboard b/AnotherIM/Resources/launchscreen.storyboard new file mode 100644 index 0000000..ed88f70 --- /dev/null +++ b/AnotherIM/Resources/launchscreen.storyboard @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AnotherIM/View/StartScreen.swift b/AnotherIM/View/StartScreen.swift new file mode 100644 index 0000000..abb84b9 --- /dev/null +++ b/AnotherIM/View/StartScreen.swift @@ -0,0 +1,91 @@ +import SwiftUI + +// let login = "kudahtk@conversations.im" +// let pass = "derevo77!" + +// let login = "testmon4@test.anal.company" +// let pass = "12345" + +let login = "testmon3@test.anal.company" +let pass = "12345" + +struct StartScreen: View { + var body: some View { + ZStack { + Color.Material.Background.light + Image.logo + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 200, height: 200) + } + .ignoresSafeArea() + .onAppear { + // doTestA() + doMainTest() + } + } + + func doMainTest() { + let userAgent = UserAgent( + uuid: "aaa65fa7-5555-4749-d1a5-740edbf81764", + software: "another.im", + device: "iOS" + ) + let cls = XMPPClient(storage: TestStorage(), userAgent: userAgent) + + // swiftlint:disable:next force_try + let jid = try! JID(login) + cls.tryLogin(jid: jid, credentialsId: UUID()) + } + + func doTestA() { + let xml = XMLElement( + name: "test-me", + xmlns: "urn:test:me", + attributes: ["some1": "some-val-1", "type": "test-req"], + content: "asdffweqfqefq34234t2tergfsagewr", + nodes: [ + XMLElement( + name: "sub-test-me", + xmlns: "urn:test:me", + attributes: ["some1": "some-val-1"], + content: "asdffweqfqefq34234t2tergfsagewr", + nodes: [ + XMLElement( + name: "sub2-test-me", + xmlns: "urn:test:me", + attributes: [:], + content: nil, + nodes: [] + ) + ] + ) + ] + ) + print("before") + print(xml, "\n") + print("after:") + let encoded = try? JSONEncoder().encode(xml) + if let encoded { + print(String(decoding: encoded, as: UTF8.self)) + let xml2 = try? JSONDecoder().decode(XMLElement.self, from: encoded) + if let xml2 { + print("\n\n\n") + print(xml2) + } + print("after all\n") + print("\(encoded as NSData)") + } + } +} + +final class TestStorage: XMPPClientStorageProtocol { + func updateCredentialsByUUID(_ uuid: UUID, _ credentials: XMPPClientCredentials) async { + print(uuid, credentials) + } + + func getCredentialsByUUID(_ uuid: UUID) async -> XMPPClientCredentials? { + print(uuid) + return ["password": pass] + } +} diff --git a/AnotherIM/xmpp/XMPPClient.swift b/AnotherIM/xmpp/XMPPClient.swift new file mode 100644 index 0000000..f528668 --- /dev/null +++ b/AnotherIM/xmpp/XMPPClient.swift @@ -0,0 +1,136 @@ +import Foundation + +// MARK: Events +enum Event { + case startClientLogin(jid: JID, credsId: UUID) + + case resolveDomain + case domainResolved([SRVRecord]) + case domainResolvingError(SRVResolverError) + + case tryConnect + case socketConnected(SocketType) + case socketDisconnected + case socketError(Error) + case socketReceived(Data) + case allRecordsUnreachable + + case startStream + case streamStarted(args: [String: String]) + case streamEnded + case parserError(Error) + + case xmlInbound(XMLElement) + case xmlOutbound(XMLElement) + + case startTls + case startTlsDone + case startTlsFailed(Error) + + case gotAuthError(AuthorizationError) + case startAuth(XMLElement) + case challengeAuth(XMLElement) + case authDone(sasl: SaslType, args: [String: String]) + + case stanzaInbound(Stanza) + case stanzaOutbound(Stanza) + + case bindStream + case bindStreamDone(String) + case bindStreamError + case streamReady +} + +// MARK: State +struct ClientState: Codable & Equatable { + var jid: JID + var credentialsId: UUID + var userAgent: UserAgent + var sessionState: SessionState + var srvRecords: [SRVRecord] + var srvRecordIndex: Int + var socketType: SocketType + var isSocketSecured: Bool + var streamId: String + + // for allow self-signed or expired certificates + // not secure, but sometimes needed + var allowInsecure: Bool + var allowPlainAuth: Bool + + var authorizationStep: AuthorizationStep + var isStreamBound: Bool + + static var initial: ClientState { + // swiftlint:disable:next force_try + let initJid = try! JID("need@initiali.ze") + + return .init( + jid: initJid, + credentialsId: UUID(), + userAgent: .init(uuid: "", software: "", device: ""), + sessionState: .waitingSRVRecords, + srvRecords: [], + srvRecordIndex: -1, + socketType: .startTls, + isSocketSecured: false, + streamId: "", + allowInsecure: false, + allowPlainAuth: false, + authorizationStep: .notAuthorized, + isStreamBound: false + ) + } +} + +final class XMPPClient { + private var state = ClientState.initial + private let logger = ClientLogger() + private let storage: XMPPClientStorageProtocol + private lazy var modules: [any XmppModule] = [ + SRVResolverModule(), + ConnectionModule(self.fire), + ParserModule(self.fire), + SessionModule(), + AuthorizationModule(self.storage), + StanzaModule(self.storage) + ] + + init(storage: any XMPPClientStorageProtocol, userAgent: UserAgent) { + self.storage = storage + state.userAgent = userAgent + } + + func tryLogin(jid: JID, credentialsId: UUID) { + logger.update(jid.description) + Task { + await fire(.startClientLogin(jid: jid, credsId: credentialsId)) + } + } +} + +// MARK: Private part +private extension XMPPClient { + private func fire(_ event: Event) async { + // log + logger.logEvent(event) + + // apply reducing + let newState = modules.reduce(state) { result, next in + next.reduce(oldState: result, with: event) + } + logger.logState(state, newState) + state = newState + + // apply side effects + await withTaskGroup(of: Event?.self) { [state] group in + for mod in modules { + group.addTask { await mod.process(state: state, with: event) } + } + + for await case let nextEvent? in group { + await fire(nextEvent) + } + } + } +} diff --git a/AnotherIM/xmpp/XMPPClientStorageProtocol.swift b/AnotherIM/xmpp/XMPPClientStorageProtocol.swift new file mode 100644 index 0000000..2630c2e --- /dev/null +++ b/AnotherIM/xmpp/XMPPClientStorageProtocol.swift @@ -0,0 +1,22 @@ +import Foundation + +typealias XMPPClientStorageProtocol = + AnyObject & + XMPPClientStorageCredentials & + XMPPClientStorageHistory + +// For storing credentials +typealias XMPPClientCredentials = [String: String] +protocol XMPPClientStorageCredentials: AnyObject { + func updateCredentialsByUUID(_ uuid: UUID, _ credentials: XMPPClientCredentials) async + func getCredentialsByUUID(_ uuid: UUID) async -> XMPPClientCredentials? +} + +// For storing stanza history +protocol XMPPClientStorageHistory: + XMPPClientStorageHistoryIqs & + XMPPClientStorageHistoryMessages & + XMPPClientStorageHistoryPresences {} +protocol XMPPClientStorageHistoryIqs: AnyObject {} +protocol XMPPClientStorageHistoryMessages: AnyObject {} +protocol XMPPClientStorageHistoryPresences: AnyObject {} diff --git a/AnotherIM/xmpp/models/JID.swift b/AnotherIM/xmpp/models/JID.swift new file mode 100644 index 0000000..058e447 --- /dev/null +++ b/AnotherIM/xmpp/models/JID.swift @@ -0,0 +1,65 @@ +struct JID: Hashable, CustomStringConvertible, Codable, Equatable { + let localPart: String + let domainPart: String + let resourcePart: String? + + init(_ jid: String) throws { + let parts = try JID.parse(jid) + localPart = parts.0 + domainPart = parts.1 + resourcePart = parts.2 + } + + var description: String { + var str = "\(localPart)@\(domainPart)" + if let resource = resourcePart { + str += "/\(resource)" + } + return str + } + + var hash: Int { + if let resourcePart { + return "\(localPart)@\(domainPart)/\(resourcePart)".hashValue + } else { + return "\(localPart)@\(domainPart)".hashValue + } + } + + var bare: String { + "\(localPart)@\(domainPart)" + } + + var bareHash: Int { + "\(localPart)@\(domainPart)".hashValue + } +} + +extension JID { + var uStr: String { + "\(bareHash)".replacingOccurrences(of: "-", with: "_") + } +} + +enum JIDError: String, Error { + case wrongJid = "Can't parse or operate with JID" +} + +private extension JID { + // swiftlint:disable:next large_tuple + static func parse(_ str: String) throws -> (String, String, String?) { + let parts = str.components(separatedBy: "@") + guard parts.count == 2 else { + throw JIDError.wrongJid + } + let secondParts = parts[1].components(separatedBy: "/") + guard !secondParts.isEmpty else { + throw JIDError.wrongJid + } + if secondParts.count > 1 { + return (parts[0], secondParts[0], secondParts[1]) + } else { + return (parts[0], secondParts[0], nil) + } + } +} diff --git a/AnotherIM/xmpp/models/SRVRecord.swift b/AnotherIM/xmpp/models/SRVRecord.swift new file mode 100644 index 0000000..62475e7 --- /dev/null +++ b/AnotherIM/xmpp/models/SRVRecord.swift @@ -0,0 +1,40 @@ +import Foundation + +struct SRVRecord: Codable & Equatable { + let priority: Int + let weight: Int + let port: Int + let target: String + let isSecure: Bool + + init?(data: Data) { + guard data.count > 6 else { + return nil + } + priority = Int(data[0]) * 256 + Int(data[1]) + weight = Int(data[2]) * 256 + Int(data[3]) + port = Int(data[4]) * 256 + Int(data[5]) + + var workingTarget = "" + for byte in data[7 ... (data.count - 1)] { + var symbol = byte + symbol = symbol < 33 ? 46 : symbol + let char = String(decoding: Data([symbol]), as: UTF8.self) + workingTarget += char + } + target = workingTarget + isSecure = target.starts(with: "xmpps") + } + + init(fallbackTarget: String) { + target = fallbackTarget + port = 5222 + priority = 0 + weight = 0 + isSecure = false + } + + var description: String { + "target: \(target) port: \(port) priority: \(priority) weight: \(weight)" + } +} diff --git a/AnotherIM/xmpp/models/Stanza.swift b/AnotherIM/xmpp/models/Stanza.swift new file mode 100644 index 0000000..5ae7200 --- /dev/null +++ b/AnotherIM/xmpp/models/Stanza.swift @@ -0,0 +1,105 @@ +import Foundation + +enum StanzaType { + enum IqType: String { + case get + case set + case result + case error + } + + enum MessageType: String { + case chat + case groupchat + case headline + case normal + case error + case none + } + + enum PresenceType: String { + case subscribe + case unsubscribe + case subscribed + case unsubscribed + } + + case iq(IqType) + case message(MessageType) + case presense(PresenceType) + + // Should never appear + case unknown +} + +struct Stanza { + let wrapped: XMLElement + + init?(wrap: XMLElement) { + guard ["iq", "message", "presence"].contains(wrap.name) else { return nil } + wrapped = wrap + } + + var type: StanzaType { + switch wrapped.name { + case "iq": + if let type = StanzaType.IqType(rawValue: wrapped.attributes["type"] ?? "") { + return .iq(type) + } else { + warn() + return .unknown + } + + case "message": + let type = StanzaType.MessageType(rawValue: wrapped.attributes["type"] ?? "none") ?? .none + return .message(type) + + case "presence": + if let type = StanzaType.PresenceType(rawValue: wrapped.attributes["type"] ?? "") { + return .presense(type) + } else { + warn() + return .unknown + } + + default: + warn() + return .unknown + } + } + + var id: String? { + wrapped.attributes["id"] + } +} + +// MARK: Init +extension Stanza { + static func iqGet(payload: XMLElement) -> Stanza? { + let req = XMLElement( + name: "iq", + xmlns: nil, + attributes: ["type": "get", "id": XMLElement.randomId], + content: nil, + nodes: [payload] + ) + return Stanza(wrap: req) + } + + static func iqSet(payload: XMLElement) -> Stanza? { + let req = XMLElement( + name: "iq", + xmlns: nil, + attributes: ["type": "set", "id": XMLElement.randomId], + content: nil, + nodes: [payload] + ) + return Stanza(wrap: req) + } +} + +private extension Stanza { + func warn() { + print("Something went wrong! with \(wrapped.stringRepresentation.prettyStr)") + } +} diff --git a/AnotherIM/xmpp/models/UserAgent.swift b/AnotherIM/xmpp/models/UserAgent.swift new file mode 100644 index 0000000..d85a66b --- /dev/null +++ b/AnotherIM/xmpp/models/UserAgent.swift @@ -0,0 +1,7 @@ +import Foundation + +struct UserAgent: Codable & Equatable { + let uuid: String + let software: String + let device: String +} diff --git a/AnotherIM/xmpp/models/XMLElement.swift b/AnotherIM/xmpp/models/XMLElement.swift new file mode 100644 index 0000000..f9d9d81 --- /dev/null +++ b/AnotherIM/xmpp/models/XMLElement.swift @@ -0,0 +1,98 @@ +import Foundation + +struct XMLElement: Codable, CustomStringConvertible { + let name: String + let xmlns: String? + let attributes: [String: String] + let content: String? + let nodes: [XMLElement] + var woClose: Bool = false + + var stringRepresentation: String { + var result = "<\(name)" + for (key, value) in attributes { + let val = value.escaped + result += " \(key)='\(val)'" + } + if let xmlns { + result += " xmlns='\(xmlns)'" + } + if !nodes.isEmpty { + result += ">" + for child in nodes { + result += child.stringRepresentation + } + result += "" + } else { + if let content = content { + result += ">" + result += content + result += "" + } else { + result += woClose ? ">" : "/>" + } + } + return result + } + + var description: String { + stringRepresentation.prettyStr + } + + var data: Data { + Data(stringRepresentation.utf8) + } +} + +// I decided to not use mutation functions and just +// make a new element during update +extension XMLElement { + func updateXmlns(_ new: String?) -> XMLElement { + XMLElement( + name: name, + xmlns: new, + attributes: attributes, + content: content, + nodes: nodes, + woClose: woClose + ) + } + + func addNode(_ new: XMLElement) -> XMLElement { + var nodes = nodes + nodes.append(new) + + return XMLElement( + name: name, + xmlns: xmlns, + attributes: attributes, + content: content, + nodes: nodes, + woClose: woClose + ) + } + + func updateContent(_ new: String?) -> XMLElement { + guard let new else { return self } + + var content = self.content ?? "" + content += new + + return XMLElement( + name: name, + xmlns: xmlns, + attributes: attributes, + content: content, + nodes: nodes, + woClose: woClose + ) + } +} + +// +extension XMLElement { + static var randomId: String { + let letters = "abcdefghijklmnopqrstuvwxyz0123456789" + return String((0 ..< 8).map { _ in letters.randomElement() ?? "x" }) + } +} diff --git a/AnotherIM/xmpp/modules/XmppModule.swift b/AnotherIM/xmpp/modules/XmppModule.swift new file mode 100644 index 0000000..5b5b4e6 --- /dev/null +++ b/AnotherIM/xmpp/modules/XmppModule.swift @@ -0,0 +1,8 @@ +import Foundation + +protocol XmppModule: Identifiable { + var id: String { get } + + func reduce(oldState: ClientState, with event: Event) -> ClientState + func process(state: ClientState, with event: Event) async -> Event? +} diff --git a/AnotherIM/xmpp/modules/auth/AuthorizationMechanisms.swift b/AnotherIM/xmpp/modules/auth/AuthorizationMechanisms.swift new file mode 100644 index 0000000..1fbe224 --- /dev/null +++ b/AnotherIM/xmpp/modules/auth/AuthorizationMechanisms.swift @@ -0,0 +1,298 @@ +import Foundation + +enum SaslType { + case sasl1 + case sasl2 + + var xmlns: String { + switch self { + case .sasl1: "urn:ietf:params:xml:ns:xmpp-sasl" + case .sasl2: "urn:xmpp:sasl:2" + } + } +} + +// TODO: Implement SHA-256/SHA-512, the difference only in hmac/pbkdf2 lenght +enum AuthorizationMechanismType: String { + case plain = "PLAIN" + case scramSha1 = "SCRAM-SHA-1" + // case scramSha1Plus = "SCRAM-SHA-1-PLUS" // TODO: check whats wrong with cahnnel binding + + var priority: Int { // less - better + switch self { + case .plain: 1000 + // case .scramSha1Plus: 10 + case .scramSha1: 20 + } + } + + var isPlus: Bool { + switch self { + // case .scramSha1Plus: return true + default: return false + } + } +} + +protocol AuthorizationMechanism { + var initRequest: XMLElement? { get } + func challenge(xml: XMLElement) async -> Event +} + +final class AuthorizationMechanismImpl: AuthorizationMechanism { + private let type: AuthorizationMechanismType + private let jid: JID + private let credentials: XMPPClientCredentials? + private let userAgent: UserAgent + private let saslType: SaslType + private let channelBind: String? + private let inlines: XMLElement? // TODO: Implement inlines + + private let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + private var clientNonce = "" + private var serverSignature = "" + + init(type: AuthorizationMechanismType, jid: JID, credentials: XMPPClientCredentials?, saslType: SaslType, userAgent: UserAgent, channelBind: String?, inlines: XMLElement?) { + self.type = type + self.jid = jid + self.credentials = credentials + self.userAgent = userAgent + self.saslType = saslType + self.channelBind = channelBind + self.inlines = inlines + } + + var initRequest: XMLElement? { + switch type { + case .plain: + return plainRequest + + case .scramSha1: // .scramSha1Plus: + return scramSha1Request + } + } + + func challenge(xml: XMLElement) async -> Event { + switch type { + case .plain: + return await plainChallenge(xml: xml) + + case .scramSha1: // .scramSha1Plus: + return await scramSha1Challenge(xml: xml) + } + } +} + +// MARK: PLAIN +private extension AuthorizationMechanismImpl { + var plainRequest: XMLElement? { + guard let pass = credentials?["password"], !pass.isEmpty else { + print("no credentials...") + return nil + } + let lreq = "\0\(jid.localPart)\0\(pass)" + let utf8str = lreq.data(using: .utf8) + let base64 = utf8str?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0)) + let str = base64 ?? "" + switch saslType { + case .sasl1: + return XMLElement( + name: "auth", + xmlns: saslType.xmlns, + attributes: ["mechanism": "PLAIN"], + content: str, + nodes: [] + ) + + case .sasl2: + return XMLElement( + name: "authenticate", + xmlns: saslType.xmlns, + attributes: ["mechanism": "PLAIN"], + content: nil, + nodes: [ + XMLElement(name: "initial-response", xmlns: nil, attributes: [:], content: str, nodes: []), + userAgentXml + ] + ) + } + } + + func plainChallenge(xml: XMLElement) async -> Event { + if xml.name == "success" { + return succesEvent(xml: xml) + } else { + let error = xml.nodes.first?.name ?? "unknown" + let text = xml.nodes.first(where: { $0.name == "text" })?.content ?? "" + return .gotAuthError(.mechanismError("\(error) \(text)")) + } + } +} + +// MARK: SCRAM-SHA-1 +private extension AuthorizationMechanismImpl { + var scramSha1Request: XMLElement? { + guard let pass = credentials?["password"], !pass.isEmpty else { + print("no credentials...") + return nil + } + + let (requestStr, clientNonce) = scramSha1InitRequestString() + self.clientNonce = clientNonce + switch saslType { + case .sasl1: + return XMLElement( + name: "auth", + xmlns: saslType.xmlns, + attributes: ["mechanism": type.rawValue], + content: requestStr, + nodes: [] + ) + + case .sasl2: + return XMLElement( + name: "authenticate", + xmlns: saslType.xmlns, + attributes: ["mechanism": type.rawValue], + content: nil, + nodes: [ + XMLElement(name: "initial-response", xmlns: nil, attributes: [:], content: requestStr, nodes: []), + userAgentXml + ] + ) + } + } + + func scramSha1Challenge(xml: XMLElement) async -> Event { + switch xml.name { + case "challenge": + guard let pass = credentials?["password"], !pass.isEmpty else { + return .gotAuthError(.mechanismError("No password")) + } + // decode base64 challenge with options + guard let content = xml.content else { return .gotAuthError(.mechanismError("Wrong challenge string")) } + guard let msgBytes = Foundation.Data(base64Encoded: content, options: NSData.Base64DecodingOptions(rawValue: 0)) else { + return .gotAuthError(.mechanismError("Wrong challenge string")) + } + let msg = String(decoding: msgBytes, as: UTF8.self) + + // get payload + let dict = msg.split(separator: ",") + .compactMap { part -> (String, String)? in + guard part.count > 2 else { return nil } + let line1 = part.prefix(1) + let line2 = part.dropFirst(2) + return (String(line1), String(line2)) + } + .reduce(into: [String: String]()) { + $0[$1.0] = $1.1 + } + + guard + let serverNonce = dict["r"], + let salt = dict["s"], + let itrs = dict["i"], + let iterations = Int(itrs) + else { + return .gotAuthError(.mechanismError("Wrong challenge string")) + } + + guard serverNonce.hasPrefix(clientNonce) else { + return .gotAuthError(.mechanismError("Server nonce incorrect")) + } + + guard let saltData = Data(base64Encoded: salt, options: Data.Base64DecodingOptions(rawValue: 0)) else { + return .gotAuthError(.mechanismError("Error forming challenge response")) + } + + let saltedPass = Data(pass.utf8).pbkdf2(salt: saltData, rounds: iterations) + let clientFinalMessageBare = "c=biws,r=\(serverNonce)" + let clientKeyData = Data("Client Key".utf8) + let serverKeyData = Data("Server Key".utf8) + let clientKey = clientKeyData.hmac(key: saltedPass) + let storedKey = clientKey.sha1() + let authMessage = "n=\(jid.localPart),r=\(clientNonce),\(msg),\(clientFinalMessageBare)" + let clientSignature = authMessage.data(using: .utf8)?.hmac(key: storedKey) ?? Data() + let clientProof = clientKey.xor(other: clientSignature) + let serverKey = serverKeyData.hmac(key: saltedPass) + serverSignature = authMessage.data(using: .utf8)?.hmac(key: serverKey).base64EncodedString() ?? "" + let clientFinalMessage = "\(clientFinalMessageBare),p=\(clientProof.base64EncodedString())" + + // make challenge response + let req = XMLElement( + name: "response", + xmlns: saslType.xmlns, + attributes: [:], + content: clientFinalMessage.base64Encoded, + nodes: [] + ) + return .xmlOutbound(req) + + case "success": + // get server signature + let signature: String? + switch saslType { + case .sasl1: + signature = xml.content?.base64Decoded + + case .sasl2: + signature = xml.nodes.first(where: { $0.name == "additional-data" })?.content?.base64Decoded + } + + // check signature + if let signature, signature == "v=\(serverSignature)" { + return succesEvent(xml: xml) + } else { + return .gotAuthError(.mechanismError("Wrong server signature")) + } + + case "failure": + let error = xml.nodes.first?.name ?? "unknown" + let text = xml.nodes.first(where: { $0.name == "text" })?.content ?? "" + return .gotAuthError(.mechanismError("\(error) \(text)")) + + default: + return .gotAuthError(.mechanismError("Unknown server challenge \(xml.name) \(xml.content ?? "")")) + } + } + + func scramSha1InitRequestString() -> (String, String) { + let gssHeader = "n,," + let randString = randomString(length: 20) + let lreq = "\(gssHeader)n=\(jid.localPart),r=\(randString)" + let utf8str = lreq.data(using: .utf8) + let base64 = utf8str?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0)) + return (base64 ?? "", randString) + } + + func randomString(length: Int) -> String { + String((0 ..< length).map { _ in alphabet.randomElement() ?? "A" }) + } + + var userAgentXml: XMLElement { + XMLElement( + name: "user-agent", + xmlns: nil, + attributes: ["id": userAgent.uuid], + content: nil, + nodes: [ + XMLElement(name: "device", xmlns: nil, attributes: [:], content: userAgent.device, nodes: []), + XMLElement(name: "software", xmlns: nil, attributes: [:], content: userAgent.software, nodes: []) + ] + ) + } + + func succesEvent(xml: XMLElement) -> Event { + var args: [String: String] = [:] + switch saslType { + case .sasl1: + break + + case .sasl2: + if let authId = xml.nodes.first(where: { $0.name == "authorization-identifier" })?.content { + args["authorization-identifier"] = authId + } + } + return .authDone(sasl: saslType, args: args) + } +} diff --git a/AnotherIM/xmpp/modules/auth/AuthorizationModule.swift b/AnotherIM/xmpp/modules/auth/AuthorizationModule.swift new file mode 100644 index 0000000..d43c98b --- /dev/null +++ b/AnotherIM/xmpp/modules/auth/AuthorizationModule.swift @@ -0,0 +1,154 @@ +// RFC - 6120: chapter 6 +// XEP-0388: Extensible SASL Profile +import Foundation + +enum AuthorizationStep: Codable { + case notAuthorized + case inProgress + case authorized +} + +enum AuthorizationError: Error { + case noSupportedMechanisms + case channelBindError + case mechanismError(String) +} + +final class AuthorizationModule: XmppModule { + let id = "Authorization module" + + private weak var storage: (any XMPPClientStorageCredentials)? + private var mechanism: AuthorizationMechanism? + + init(_ storage: any XMPPClientStorageCredentials) { + self.storage = storage + } + + func reduce(oldState: ClientState, with event: Event) -> ClientState { + var newState = oldState + switch event { + case .startAuth: + newState.authorizationStep = .inProgress + + case .gotAuthError: + newState.authorizationStep = .notAuthorized + + case .authDone: + newState.authorizationStep = .authorized + + default: + break + } + return newState + } + + func process(state: ClientState, with event: Event) async -> Event? { + switch event { + case .xmlInbound(let xml): + guard state.isSocketSecured else { return nil } + switch (xml.name, state.authorizationStep) { + case ("stream:features", .notAuthorized): + let credentials = await storage?.getCredentialsByUUID(state.credentialsId) + return await selectBestAuthMechanism(xml, state.allowPlainAuth, state, credentials) + + case (_, .inProgress): + return .challengeAuth(xml) + + default: + return nil + } + + case .startAuth(let xml): + return .xmlOutbound(xml) + + case .challengeAuth(let xml): + return await mechanism?.challenge(xml: xml) + + case .authDone: + mechanism = nil + return nil + + default: + return nil + } + } +} + +private extension AuthorizationModule { + var supportedChannelBindings: [String] { + ["tls-exporter", "tls-server-end-point"] + } + + func selectBestAuthMechanism(_ xml: XMLElement, _ isPlainAllowed: Bool, _ state: ClientState, _ creds: XMPPClientCredentials?) async -> Event { + await withCheckedContinuation { continuation in + var sasl1: [AuthorizationMechanismType] = [] + var channelBindings: [String] = [] + var sasl2: [AuthorizationMechanismType] = [] + var inlines: XMLElement? + + // parse features + for element in xml.nodes { + // extract sasl1 mechanisms + if element.name == "mechanisms" && element.xmlns == "urn:ietf:params:xml:ns:xmpp-sasl" { + sasl1 = element.nodes + .compactMap { AuthorizationMechanismType(rawValue: $0.content ?? "") } + .sorted { $0.priority < $1.priority } + } + // extract channel bindings + if element.name == "sasl-channel-binding" && element.xmlns == "urn:xmpp:sasl-cb:0" { + channelBindings = element.nodes + .compactMap { $0.attributes["type"] } + .filter { supportedChannelBindings.contains($0) } + } + // extract sasl2 + if element.name == "authentication" && element.xmlns == "urn:xmpp:sasl:2" { + // sasl2 mechanisms + sasl2 = element.nodes + .filter { $0.name == "mechanism" && $0.xmlns == "urn:xmpp:sasl:2" } + .compactMap { AuthorizationMechanismType(rawValue: $0.content ?? "") } + .sorted { $0.priority < $1.priority } + + // sasl2 inlines + inlines = element.nodes.first(where: { $0.name == "inline" && $0.xmlns == "urn:xmpp:sasl:2" }) + } + } + + // filter out PLAIN if needed + if !isPlainAllowed { + sasl1 = sasl1.filter { $0 != .plain } + sasl2 = sasl2.filter { $0 != .plain } + } + if sasl1.isEmpty && sasl2.isEmpty { + continuation.resume(returning: .gotAuthError(.noSupportedMechanisms)) + } + + // select best authorization way + var best = sasl1.map { ($0, SaslType.sasl1) } + for mechanism in sasl2 { + if best.isEmpty { + best.insert((mechanism, SaslType.sasl2), at: 0) + } else if mechanism.priority <= best[0].0.priority { + best.insert((mechanism, SaslType.sasl2), at: 0) + } + } + let selected = best[0] + + // init mechanism and start auth + mechanism = AuthorizationMechanismImpl( + type: selected.0, + jid: state.jid, + credentials: creds, + saslType: selected.1, + userAgent: state.userAgent, + channelBind: nil, // TODO: check channel binding and implement *-PLUS mechanisms + inlines: nil // TODO: Implement inlines + ) + let request = mechanism?.initRequest + if let request { + continuation.resume(returning: .startAuth(request)) + } else { + continuation.resume(returning: .gotAuthError(.mechanismError("Init request is empty"))) + } + } + } +} diff --git a/AnotherIM/xmpp/modules/connection/ConnectionModule.swift b/AnotherIM/xmpp/modules/connection/ConnectionModule.swift new file mode 100644 index 0000000..cfedb93 --- /dev/null +++ b/AnotherIM/xmpp/modules/connection/ConnectionModule.swift @@ -0,0 +1,133 @@ +// RFC - 6120: chapter 5 +import Foundation +import Network + +enum SocketType: Codable & Equatable { + case startTls + case directTls + // case p2p // in future, maybe..., probably... +} + +enum SocketState { + case connected + case disconnected(Error?) + case startTlsReady +} + +enum SocketEvent { + case state(SocketState) + case dataReceived(Data) +} + +protocol Socket { + var events: AsyncStream { get } + + func connect() async + func send(_ data: Data) async +} + +final class ConnectionModule: XmppModule { + let id = "Connection module" + + private var socket: Socket? + private var callback: ((Event) async -> Void)? + + init(_ fire: @escaping (Event) async -> Void) { + callback = fire + } + + func reduce(oldState: ClientState, with _: Event) -> ClientState { + oldState + } + + func process(state: ClientState, with event: Event) async -> Event? { + switch event { + case .tryConnect: + guard state.srvRecordIndex < state.srvRecords.count else { return .allRecordsUnreachable } + + let record = state.srvRecords[state.srvRecordIndex] + let conId = state.jid.uStr + let type: SocketType + if record.isSecure { + socket = DirectTLSSocket(id: conId, host: record.target, port: record.port, allowInsecure: state.allowInsecure) + type = .directTls + } else { + socket = StartTLSSocket(id: conId, host: record.target, port: record.port, allowInsecure: state.allowInsecure) + type = .startTls + } + + if let socket { + Task { + await socket.connect() + } + Task { + for await msg in socket.events { + switch msg { + case .state(let conn): + switch conn { + case .connected: + await callback?(.socketConnected(type)) + + case .disconnected(let error): + if let error { + await callback?(.socketError(error)) + } else { + await callback?(.socketDisconnected) + } + + case .startTlsReady: + await callback?(.startTlsDone) + } + + case .dataReceived(let data): + await callback?(.socketReceived(data)) + } + } + } + } + return nil + + case .xmlOutbound(let xml): + await socket?.send(xml.data) + return nil + + // For StartTLS process + case .xmlInbound(let element): + // process start tls on first (almost always required) + if element.name == "stream:features", element.nodes.map({ $0.name }).contains("starttls") { + let req = XMLElement( + name: "starttls", + xmlns: "urn:ietf:params:xml:ns:xmpp-tls", + attributes: [:], + content: nil, + nodes: [] + ) + return .xmlOutbound(req) + + // special case for starttls proceed + } else if element.name == "proceed" && element.xmlns == "urn:ietf:params:xml:ns:xmpp-tls" { + return .startTls + } else { + return nil + } + + case .startTls: + guard let socket = socket as? StartTLSSocket else { + fatalError("why its not starttls socket?") + } + do { + try socket.startTls() + return nil + } catch let err { + return .startTlsFailed(err) + } + + case .startTlsFailed: + socket = nil + return nil + + default: + return nil + } + } +} diff --git a/AnotherIM/xmpp/modules/connection/DirectTLSSocket.swift b/AnotherIM/xmpp/modules/connection/DirectTLSSocket.swift new file mode 100644 index 0000000..9cd6561 --- /dev/null +++ b/AnotherIM/xmpp/modules/connection/DirectTLSSocket.swift @@ -0,0 +1,102 @@ +import Foundation +import Network + +private let doPrint = false + +final class DirectTLSSocket: Socket { + let (events, eventsContinuation) = AsyncStream.makeStream(of: SocketEvent.self, bufferingPolicy: .unbounded) + + private let queue: DispatchQueue + private var nwConnection: NWConnection? + + init(id: String, host: String, port: Int, allowInsecure: Bool) { + queue = DispatchQueue(label: "another.xmpp.network.queue_\(id)") + // tcp options + let tcpOptions = NWProtocolTCP.Options() + tcpOptions.noDelay = true + tcpOptions.connectionTimeout = 5 + tcpOptions.enableFastOpen = true + tcpOptions.disableAckStretching = true + let params = NWParameters(tls: nil, tcp: tcpOptions) + params.serviceClass = .responsiveData + + // tls options + let tlsOptions = NWProtocolTLS.Options() + sec_protocol_options_set_min_tls_protocol_version(tlsOptions.securityProtocolOptions, .TLSv12) + sec_protocol_options_set_max_tls_protocol_version(tlsOptions.securityProtocolOptions, .TLSv13) + // sec_protocol_options_set_peer_authentication_required(tlsOptions.securityProtocolOptions, false) + if let domain = host.cString(using: .utf8) { + sec_protocol_options_set_tls_server_name(tlsOptions.securityProtocolOptions, domain) + } + sec_protocol_options_set_verify_block(tlsOptions.securityProtocolOptions, { _, sec_trust, sec_protocol_verify_complete in + if allowInsecure { + sec_protocol_verify_complete(true) + } else { + let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue() + var error: CFError? + if SecTrustEvaluateWithError(trust, &error) { + sec_protocol_verify_complete(true) + } else { + sec_protocol_verify_complete(false) + } + } + }, queue) + + // nw connection + nwConnection = NWConnection(host: .name(host, nil), port: .init(integerLiteral: UInt16(port)), using: .init(tls: tlsOptions, tcp: tcpOptions)) + // swiftlint:disable:next force_unwrapping + nwConnection!.stateUpdateHandler = { [weak self] state in + switch state { + case .ready: + self?.eventsContinuation.yield(.state(.connected)) + self?.read() + + case .waiting(let error), .failed(let error): + print(error.localizedDescription) + self?.eventsContinuation.yield(.state(.disconnected(error))) + + default: + break + } + } + } + + deinit { + eventsContinuation.finish() + nwConnection?.cancel() + } + + func connect() async { + nwConnection?.start(queue: queue) + } + + func send(_ data: Data) async { + log(data: data, read: false) + nwConnection?.send(content: data, completion: .contentProcessed { [weak self] error in + if let err = error { + self?.eventsContinuation.yield(.state(.disconnected(err))) + } + }) + } + + private func read() { + nwConnection?.receive(minimumIncompleteLength: 1, maximumLength: 4096 * 2, completion: { [weak self] data, _, _, error in + if let err = error { + self?.eventsContinuation.yield(.state(.disconnected(err))) + return + } + guard let data else { return } + self?.log(data: data, read: true) + self?.eventsContinuation.yield(.dataReceived(data)) + self?.read() + }) + } + + private func log(data: Data, read: Bool) { + if doPrint { + let direction = read ? "read-in" : "write-out" + let str = String(bytes: data, encoding: .ascii) ?? "?" + print("\nDirectTls \(direction): \(str)\n") + } + } +} diff --git a/AnotherIM/xmpp/modules/connection/StartTLSSocket.swift b/AnotherIM/xmpp/modules/connection/StartTLSSocket.swift new file mode 100644 index 0000000..1187328 --- /dev/null +++ b/AnotherIM/xmpp/modules/connection/StartTLSSocket.swift @@ -0,0 +1,251 @@ +import Foundation +import Network + +private let doPrint = false + +final class StartTLSSocket: Socket { + let (events, eventsContinuation) = AsyncStream.makeStream(of: SocketEvent.self, bufferingPolicy: .unbounded) + + private let queue: DispatchQueue + private let bridgeSocketPath: URL + private let host: String + private let port: Int + private let allowInsecure: Bool + private var rawConnection: NWConnection? + private var secConnection: NWConnection? + private var bridge: NWListener? + private var brgConnection: NWConnection? + + init(id: String, host: String, port: Int, allowInsecure: Bool) { + self.host = host + self.port = port + self.allowInsecure = allowInsecure + queue = DispatchQueue(label: "another.xmpp.network.queue_\(id)") + + bridgeSocketPath = URL(fileURLWithPath: "/tmp/tls_bridge_listener\(id).sock") + try? FileManager.default.removeItem(at: bridgeSocketPath) + + // tcp options + let tcpOptions = NWProtocolTCP.Options() + tcpOptions.noDelay = true + tcpOptions.connectionTimeout = 5 + tcpOptions.enableFastOpen = true + tcpOptions.disableAckStretching = true + let params = NWParameters(tls: nil, tcp: tcpOptions) + params.serviceClass = .responsiveData + + rawConnection = NWConnection(host: .name(host, nil), port: .init(integerLiteral: UInt16(port)), using: .init(tls: nil, tcp: tcpOptions)) + rawConnection?.stateUpdateHandler = { [weak self] state in + self?.logState("Raw connection \(state)") + switch state { + case .ready: + self?.eventsContinuation.yield(.state(.connected)) + self?.rawRead() + + case .waiting(let error), .failed(let error): + print(error.localizedDescription) + self?.eventsContinuation.yield(.state(.disconnected(error))) + + default: + break + } + } + } + + deinit { + eventsContinuation.finish() + rawConnection?.cancel() + brgConnection?.cancel() + secConnection?.cancel() + bridge?.cancel() + } + + func connect() async { + rawConnection?.start(queue: queue) + } + + func send(_ data: Data) async { + if secConnection != nil { + secWrite(data: data) + } else { + rawWrite(data: data) + } + } + + func startTls() throws { + try initBridgeAndSecureConnection() + } +} + +private extension StartTLSSocket { + func rawRead() { + rawConnection?.receive(minimumIncompleteLength: 1, maximumLength: 4096 * 2, completion: { [weak self] data, _, _, error in + if let err = error { + self?.eventsContinuation.yield(.state(.disconnected(err))) + return + } + guard let data else { return } + self?.log("raw", data: data, read: true) + if let brg = self?.brgConnection { + brg.send(content: data, completion: .contentProcessed { [weak self] error in + if let err = error { + self?.eventsContinuation.yield(.state(.disconnected(err))) + } + }) + } else { + self?.eventsContinuation.yield(.dataReceived(data)) + } + self?.rawRead() + }) + } + + func rawWrite(data: Data) { + log("raw", data: data, read: false) + rawConnection?.send(content: data, completion: .contentProcessed { [weak self] error in + if let err = error { + self?.eventsContinuation.yield(.state(.disconnected(err))) + } + }) + } + + func brgRead() { + brgConnection?.receive(minimumIncompleteLength: 1, maximumLength: 4096 * 2, completion: { [weak self] data, _, _, error in + if let err = error { + self?.eventsContinuation.yield(.state(.disconnected(err))) + return + } + guard let data else { return } + self?.rawWrite(data: data) + self?.brgRead() + }) + } + + func secRead() { + secConnection?.receive(minimumIncompleteLength: 1, maximumLength: 4096 * 2, completion: { [weak self] data, _, _, error in + if let err = error { + self?.eventsContinuation.yield(.state(.disconnected(err))) + return + } + guard let data else { return } + self?.log("sec", data: data, read: true) + self?.eventsContinuation.yield(.dataReceived(data)) + self?.secRead() + }) + } + + func secWrite(data: Data) { + log("sec", data: data, read: false) + secConnection?.send(content: data, completion: .contentProcessed { [weak self] error in + if let err = error { + self?.eventsContinuation.yield(.state(.disconnected(err))) + } + }) + } + + func initBridgeAndSecureConnection() throws { + let params = NWParameters() + params.defaultProtocolStack.transportProtocol = NWProtocolTCP.Options() + params.requiredLocalEndpoint = NWEndpoint.unix(path: bridgeSocketPath.path) + params.allowLocalEndpointReuse = false + bridge = try NWListener(using: params) + bridge?.newConnectionLimit = 1 + + // make bridge and connection + bridge?.stateUpdateHandler = { [weak self] state in + guard let self else { return } + self.logState("Bridge \(state)") + switch state { + case .ready: + // tcp options for secure connection + let tcpOptions = NWProtocolTCP.Options() + tcpOptions.noDelay = true + tcpOptions.enableFastOpen = true + tcpOptions.disableAckStretching = true + + // tls options for secure connection + let tlsOptions = NWProtocolTLS.Options() + sec_protocol_options_set_min_tls_protocol_version(tlsOptions.securityProtocolOptions, .TLSv12) + sec_protocol_options_set_max_tls_protocol_version(tlsOptions.securityProtocolOptions, .TLSv13) + // sec_protocol_options_set_peer_authentication_required(tlsOptions.securityProtocolOptions, false) + if let domain = self.host.cString(using: .utf8) { + sec_protocol_options_set_tls_server_name(tlsOptions.securityProtocolOptions, domain) + } + sec_protocol_options_set_verify_block(tlsOptions.securityProtocolOptions, { _, sec_trust, sec_protocol_verify_complete in + if self.allowInsecure { + sec_protocol_verify_complete(true) + } else { + let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue() + var error: CFError? + if SecTrustEvaluateWithError(trust, &error) { + sec_protocol_verify_complete(true) + } else { + sec_protocol_verify_complete(false) + } + } + }, queue) + + // secure connection + let params = NWParameters(tls: tlsOptions, tcp: tcpOptions) + params.serviceClass = .responsiveData + self.secConnection = NWConnection(to: .unix(path: self.bridgeSocketPath.path), using: params) + self.secConnection?.stateUpdateHandler = { [weak self] state in + self?.logState("Secure connection \(state)") + switch state { + case .ready: + self?.eventsContinuation.yield(.state(.startTlsReady)) + self?.secRead() + + case .waiting(let error), .failed(let error): + self?.eventsContinuation.yield(.state(.disconnected(error))) + + default: + break + } + } + self.secConnection?.start(queue: queue) + + case .waiting(let error), .failed(let error): + self.eventsContinuation.yield(.state(.disconnected(error))) + + default: + break + } + } + + // bridge handler + bridge?.newConnectionHandler = { [weak self] connection in + guard let self else { return } + self.brgConnection = connection + self.brgConnection?.stateUpdateHandler = { state in + self.logState("Bridge connection \(state)") + switch state { + case .ready: + self.brgRead() + + case .waiting(let error), .failed(let error): + self.eventsContinuation.yield(.state(.disconnected(error))) + + default: + break + } + } + connection.start(queue: self.queue) + } + bridge?.start(queue: queue) + } + + func log(_ socket: String, data: Data, read: Bool) { + if socket == "raw", secConnection != nil { return } + if doPrint { + let direction = read ? "read-in" : "write-out" + let str = String(bytes: data, encoding: .ascii) ?? "?" + print("\nStartTLSSocket-\(socket) \(direction): \(str)\n") + } + } + + func logState(_ str: String) { + if doPrint { + print("Connection state: \(str)") + } + } +} diff --git a/AnotherIM/xmpp/modules/dns/SRVResolverModule.swift b/AnotherIM/xmpp/modules/dns/SRVResolverModule.swift new file mode 100644 index 0000000..ceedb23 --- /dev/null +++ b/AnotherIM/xmpp/modules/dns/SRVResolverModule.swift @@ -0,0 +1,185 @@ +// RFC - 6120: chapter 3 +import Combine +import dnssd +import Foundation + +// MARK: Public +enum SRVResolverError: Error { + case srvReferenceError + case srvSocketError + case srvTimeout + case srvUnableToComplete + case srvProcessError +} + +final class SRVResolverModule: XmppModule { + let id = "SRV resolver module" + + func reduce(oldState: ClientState, with event: Event) -> ClientState { + var newState = oldState + switch event { + case .domainResolved(let records): + newState.srvRecords = records + newState.srvRecordIndex = -1 // will be increased on each connection attempt + + default: + break + } + return newState + } + + func process(state: ClientState, with event: Event) async -> Event? { + switch event { + case .resolveDomain: + let domain = state.jid.domainPart + do { + let records = try await SRVResolver.resolve(domain: domain) + return .domainResolved(records) + } catch let err { + // swiftlint:disable:next force_cast + return .domainResolvingError(err as! SRVResolverError) + } + + default: + return nil + } + } +} + +// MARK: Private +private enum SRVResolver { + static func resolve(domain: String) async throws -> [SRVRecord] { + // request for non-tls + async let req1 = try withCheckedThrowingContinuation { continuation in + SRVRequest(target: "_xmpp-client._tcp." + domain) { result in + switch result { + case .success(let records): + continuation.resume(returning: records) + + case .failure(let error): + continuation.resume(throwing: error) + } + } + .runQuery() + } + + // request for tls + async let req2 = try withCheckedThrowingContinuation { continuation in + SRVRequest(target: "_xmpps-client._tcp." + domain) { result in + switch result { + case .success(let records): + continuation.resume(returning: records) + + case .failure(let error): + continuation.resume(throwing: error) + } + } + .runQuery() + } + + // sort by priority + let (records1, records2) = try (await req1, await req2) + var result = (records1 + records2).sorted(by: { $0.priority < $1.priority }) + + // for fallback according to RFC 6120 section 3.2.2 + if result.isEmpty { + result.append(.init(fallbackTarget: domain)) + } + + // + return result + } +} + +private typealias SRVRequestCompletion = (Result<[SRVRecord], SRVResolverError>) -> Void + +private class SRVRequest { + private let queue = DispatchQueue(label: "srv.resolving") + private var dispatchSourceRead: DispatchSourceRead? + private var timeoutTimer: DispatchSourceTimer? + private var serviceRef: DNSServiceRef? + private var socket: dnssd_sock_t = -1 + private let timeout = TimeInterval(5) + private let target: String + + var records = [SRVRecord]() + var completion: SRVRequestCompletion + + init(target: String, completion: @escaping SRVRequestCompletion) { + self.target = target + self.completion = completion + } + + func runQuery() { + let result = DNSServiceQueryRecord( + &serviceRef, + kDNSServiceFlagsReturnIntermediates, + UInt32(kDNSServiceInterfaceIndexAny), + target.cString(using: .utf8), + UInt16(kDNSServiceType_SRV), + UInt16(kDNSServiceClass_IN), { _, flags, _, _, _, _, _, rdLen, rdata, _, context in + guard let context = context else { + return + } + let request: SRVRequest = Mem.bridge(context) + + if + let data = rdata?.assumingMemoryBound(to: UInt8.self), + let record = SRVRecord(data: Data(bytes: data, count: Int(rdLen))) + // swiftlint:disable:next opening_brace + { + request.records.append(record) + } + if flags & kDNSServiceFlagsMoreComing == 0 { + request.timeoutTimer?.cancel() + request.dispatchSourceRead?.cancel() + request.completion(.success(request.records)) + } + }, + Mem.bridge(self) + ) + switch result { + case DNSServiceErrorType(kDNSServiceErr_NoError): + guard let sdRef = serviceRef else { + timeoutTimer?.cancel() + dispatchSourceRead?.cancel() + completion(.failure(.srvReferenceError)) + return + } + socket = DNSServiceRefSockFD(serviceRef) + guard socket != -1 else { + timeoutTimer?.cancel() + dispatchSourceRead?.cancel() + completion(.failure(.srvSocketError)) + return + } + dispatchSourceRead = DispatchSource.makeReadSource(fileDescriptor: socket, queue: queue) + dispatchSourceRead?.setEventHandler { [weak self] in + let res = DNSServiceProcessResult(sdRef) + if res != kDNSServiceErr_NoError { + self?.timeoutTimer?.cancel() + self?.dispatchSourceRead?.cancel() + self?.completion(.failure(.srvProcessError)) + } + } + dispatchSourceRead?.setCancelHandler { + DNSServiceRefDeallocate(self.serviceRef) + } + dispatchSourceRead?.resume() + timeoutTimer = DispatchSource.makeTimerSource(flags: [], queue: queue) + timeoutTimer?.setEventHandler { [weak self] in + self?.timeoutTimer?.cancel() + self?.dispatchSourceRead?.cancel() + self?.completion(.failure(.srvTimeout)) + } + let deadline = DispatchTime(uptimeNanoseconds: DispatchTime.now().uptimeNanoseconds + UInt64(timeout * Double(NSEC_PER_SEC))) + timeoutTimer?.schedule(deadline: deadline, repeating: .infinity, leeway: DispatchTimeInterval.never) + timeoutTimer?.resume() + + default: + timeoutTimer?.cancel() + dispatchSourceRead?.cancel() + completion(.failure(.srvUnableToComplete)) + } + } +} diff --git a/AnotherIM/xmpp/modules/parsing/ParserModule.swift b/AnotherIM/xmpp/modules/parsing/ParserModule.swift new file mode 100644 index 0000000..a3669fa --- /dev/null +++ b/AnotherIM/xmpp/modules/parsing/ParserModule.swift @@ -0,0 +1,54 @@ +// RFC - 6120 +import Foundation + +final class ParserModule: XmppModule { + let id = "Parser module" + + private var callback: ((Event) async -> Void)? + private let parser = XMLParser() + private var task: Task? + + init(_ fire: @escaping (Event) async -> Void) { + callback = fire + task = Task { + for await element in parser.element { + switch element { + case .streamStarted(let attributes): + await callback?(.streamStarted(args: attributes)) + + case .streamEnded: + await callback?(.streamEnded) + + case .element(let xml): + await callback?(.xmlInbound(xml)) + + case .parserError(let error): + await callback?(.parserError(error)) + } + } + } + } + + func reduce(oldState: ClientState, with event: Event) -> ClientState { + // small hack here, need be sure that parser resetted before + // start stream xml will send, so using reduce instead process method + // for that (no perfomance issue here) + if case .startStream = event { + parser.restart() + } + return oldState + } + + func process(state _: ClientState, with event: Event) async -> Event? { + switch event { + case .socketReceived(let data): + Task { + parser.parse(data: data) + } + return nil + + default: + return nil + } + } +} diff --git a/AnotherIM/xmpp/modules/parsing/XMLParser.swift b/AnotherIM/xmpp/modules/parsing/XMLParser.swift new file mode 100644 index 0000000..6d7835f --- /dev/null +++ b/AnotherIM/xmpp/modules/parsing/XMLParser.swift @@ -0,0 +1,257 @@ +import Foundation +import libxml2 + +enum XMLParserError: Error { + case xmlDeclarationInside(Int, Int) + case xmlUnknown(Int) +} + +enum XMLParserEvent { + case streamStarted(attributes: [String: String]) + case streamEnded + case element(XMLElement) + case parserError(XMLParserError) +} + +final class XMLParser { + let (element, elementContinuation) = AsyncStream.makeStream(of: XMLParserEvent.self, bufferingPolicy: .unbounded) + + private var ctx: xmlParserCtxtPtr? + private var stack: [XMLElement] = [] + private var xmlnss: [String: String] = [:] + + init() { + ctx = xmlCreatePushParserCtxt(&saxHandler, Mem.bridge(self), nil, 0, nil) + } + + deinit { + elementContinuation.finish() + xmlFreeParserCtxt(ctx) + stack = [] + xmlnss = [:] + ctx = nil + } + + func restart() { + stack = [] + xmlnss = [:] + xmlFreeParserCtxt(ctx) + ctx = xmlCreatePushParserCtxt(&saxHandler, Mem.bridge(self), nil, 0, nil) + } + + func parse(data: Data) { + data.withUnsafeBytes { [weak self] ptr in + if let addr = ptr.baseAddress { + let err = xmlParseChunk(self?.ctx, addr.assumingMemoryBound(to: CChar.self), Int32(data.count), 0) + if err > 0 { + if err == 64 { + let rng = Data("? + var prefix: UnsafePointer? + var attrUri: UnsafePointer? + var valueBegin: UnsafePointer? + var valueEnd: UnsafePointer? +} + +private struct Nss { + var prefix: UnsafePointer? + var uri: UnsafePointer? +} + +private func strFromCUtf8(_ ptr: UnsafePointer?) -> String? { + if let ptr { + return String(cString: ptr) + } + return nil +} + +private var saxHandler = xmlSAXHandler( + internalSubset: nil, + isStandalone: nil, + hasInternalSubset: nil, + hasExternalSubset: nil, + resolveEntity: nil, + getEntity: nil, + entityDecl: nil, + notationDecl: nil, + attributeDecl: nil, + elementDecl: nil, + unparsedEntityDecl: nil, + setDocumentLocator: nil, + startDocument: nil, + endDocument: nil, + startElement: nil, + endElement: nil, + reference: nil, + characters: SAX_charactersFound, + ignorableWhitespace: nil, + processingInstruction: nil, + comment: nil, + warning: nil, + error: nil, // unsafeBitCast(SAX_error, to: errorSAXFunc.self), + fatalError: nil, + getParameterEntity: nil, + cdataBlock: nil, + externalSubset: nil, + initialized: XML_SAX2_MAGIC, + _private: nil, + startElementNs: SAX_startElement, + endElementNs: SAX_endElement, + serror: nil +) + +private let SAX_charactersFound: charactersSAXFunc = { ctx_, chars_, len_ in + guard let ctx_, let chars_ else { + return + } + let data = Data(bytes: UnsafePointer(chars_), count: Int(len_)) + let chars = String(decoding: data, as: UTF8.self) + let parser = unsafeBitCast(ctx_, to: XMLParser.self) + parser.charactersFound(chars) +} + +private let SAX_startElement: startElementNsSAX2Func = { ctx_, localName, prefix_, _, nb_namespaces, namespaces_, nb_attributes, _, attributes_ in + guard let name = strFromCUtf8(localName), let ctx_ else { + return + } + let prefix = strFromCUtf8(prefix_) + var attributes: [String: String] = [:] + var indx = 0 + let parser = unsafeBitCast(ctx_, to: XMLParser.self) + + // attributes + if let attributes_ { + attributes_.withMemoryRebound(to: Attr.self, capacity: Int(nb_attributes)) { + var attrsPtr = $0 + while indx < Int(nb_attributes) { + if let name = strFromCUtf8(attrsPtr.pointee.name), let beginPtr = attrsPtr.pointee.valueBegin, let endPtr = attrsPtr.pointee.valueEnd { + let data = Data(bytes: UnsafePointer(beginPtr), count: endPtr - beginPtr) + var value = String(decoding: data, as: UTF8.self).unescaped + if let prefix = strFromCUtf8(attrsPtr.pointee.prefix) { + attributes[prefix + ":" + name] = value + } else { + attributes[name] = value + } + } + attrsPtr = attrsPtr.successor() + indx += 1 + } + } + } + + // namespaces + if nb_namespaces > 0, let namespaces_ { + var namespaces: [String: String] = [:] + namespaces_.withMemoryRebound(to: Nss.self, capacity: Int(nb_namespaces)) { + var nsPtr = $0 + indx = 0 + while indx < Int(nb_namespaces) { + let prefix = strFromCUtf8(nsPtr.pointee.prefix) ?? "" + if var uri = strFromCUtf8(nsPtr.pointee.uri) { + uri = uri.unescaped + namespaces[prefix] = uri + } + nsPtr = nsPtr.successor() + indx += 1 + } + } + parser.startElement(elementName: name, prefix: prefix, namespaces: namespaces, attributes: attributes) + } else { + parser.startElement(elementName: name, prefix: prefix, namespaces: nil, attributes: attributes) + } +} + +private let SAX_endElement: endElementNsSAX2Func = { ctx_, localName, prefix_, _ in + guard let name = strFromCUtf8(localName), let ctx_ else { + return + } + let prefix = strFromCUtf8(prefix_) + let parser = unsafeBitCast(ctx_, to: XMLParser.self) + parser.endElement(elementName: name, prefix: prefix) +} diff --git a/AnotherIM/xmpp/modules/session/Logger.swift b/AnotherIM/xmpp/modules/session/Logger.swift new file mode 100644 index 0000000..453db24 --- /dev/null +++ b/AnotherIM/xmpp/modules/session/Logger.swift @@ -0,0 +1,25 @@ +import Foundation +import OSLog + +final class ClientLogger { + let oslog = Logger(subsystem: "another.xmpp", category: "xmpp.client.log") + var jid: String = "unknown jid (not set yet)" + + func logState(_ old: ClientState, _ new: ClientState) { + guard old != new else { return } + let oldStr = "\(old)" + let newStr = "\(new)" + let jid = self.jid + oslog.debug("\(jid):\nPREV STATE \(oldStr)\nNEW STATE \(newStr)") + } + + func logEvent(_ event: Event) { + let jid = self.jid + let eventStr = "\(event)" + oslog.debug("\(jid): \(eventStr)") + } + + func update(_ jid: String) { + self.jid = jid + } +} diff --git a/AnotherIM/xmpp/modules/session/SessionModule.swift b/AnotherIM/xmpp/modules/session/SessionModule.swift new file mode 100644 index 0000000..af58dfb --- /dev/null +++ b/AnotherIM/xmpp/modules/session/SessionModule.swift @@ -0,0 +1,166 @@ +// RFC - 6120 +import Foundation + +enum SessionState: Codable & Equatable { + case waitingSRVRecords + case tryingConnect + case readyToStreamInit + case streamActive +} + +// TODO: add stream errors processing +final class SessionModule: XmppModule { + let id = "Session module" + + private var reqId = "" + + func reduce(oldState: ClientState, with event: Event) -> ClientState { + var newState = oldState + switch event { + case .startClientLogin(let jid, let credsId): + newState.jid = jid + newState.credentialsId = credsId + + case .domainResolved: + newState.sessionState = .tryingConnect + + case .tryConnect: + newState.srvRecordIndex += 1 + + case .allRecordsUnreachable: + newState.srvRecords = [] + newState.srvRecordIndex = -1 + newState.sessionState = .waitingSRVRecords + + case .socketConnected(let type): + newState.socketType = type + newState.sessionState = .readyToStreamInit + if type == .directTls { + newState.isSocketSecured = true + } + + case .startTlsFailed: + newState.sessionState = .tryingConnect + newState.isSocketSecured = false + + case .startTlsDone: + newState.sessionState = .readyToStreamInit + newState.isSocketSecured = true + + case .authDone(let saslType, let args): + switch saslType { + case .sasl1: + newState.sessionState = .readyToStreamInit + + case .sasl2: + if let authId = args["authorization-identifier"], let newJid = try? JID(authId) { + newState.jid = newJid + } + } + + case .bindStreamDone(let jidStr): + if let jid = try? JID(jidStr) { + newState.jid = jid + newState.isStreamBound = true + } + + case .bindStreamError: + newState.isStreamBound = false // TODO: implement good error handling + + default: + break + } + return newState + } + + func process(state: ClientState, with event: Event) async -> Event? { + switch (event, state.sessionState) { + case (.startClientLogin, .waitingSRVRecords): + return .resolveDomain + + case (.domainResolved, .tryingConnect): + return .tryConnect + + case (.socketError, .tryingConnect): + return .tryConnect + + case (.socketConnected, .readyToStreamInit): + return .startStream + + case (.startStream, .readyToStreamInit): + let req = XMLElement( + name: "stream:stream", + xmlns: nil, + attributes: [ + "from": state.jid.description, + "to": state.jid.domainPart, + "xml:lang": "en", + "version": "1.0", + "xmlns": "jabber:client", + "xmlns:stream": "http://etherx.jabber.org/streams" + ], + content: nil, + nodes: [], + woClose: true + ) + + return .xmlOutbound(req) + + case (.startTlsFailed, _): + // try reconnect with another srv record if starttls failed + return .tryConnect + + case (.startTlsDone, _): + return .startStream + + case (.authDone, _): + return .startStream + + // Stream binding + case (.xmlInbound(let xml), _): + if !state.isStreamBound, xml.name == "stream:features", xml.nodes.map({ $0.name }).contains("bind") { + let reqXml = XMLElement( + name: "bind", + xmlns: "urn:ietf:params:xml:ns:xmpp-bind", + attributes: [:], + content: nil, + nodes: [] + ) + if let request = Stanza.iqSet(payload: reqXml), let id = request.id { + reqId = id + return .stanzaOutbound(request) + } else { + return nil + } + } else { + return nil + } + + case (.stanzaInbound(let stanza), _): + guard stanza.id == reqId else { return nil } + switch stanza.type { + case .iq(.result): + let jid = stanza.wrapped + .nodes + .first(where: { $0.name == "bind" })? + .nodes + .first(where: { $0.name == "jid" })? + .content + if let jid { + return .bindStreamDone(jid) + } else { + return nil + } + + default: + return .bindStreamError // TODO: implement good error handling + } + + case (.bindStreamDone, _): + return .streamReady + + default: + return nil + } + } +} diff --git a/AnotherIM/xmpp/modules/stanza/StanzaModule.swift b/AnotherIM/xmpp/modules/stanza/StanzaModule.swift new file mode 100644 index 0000000..d5fa365 --- /dev/null +++ b/AnotherIM/xmpp/modules/stanza/StanzaModule.swift @@ -0,0 +1,48 @@ +import Foundation + +final class StanzaModule: XmppModule { + let id = "Stanza module" + + private weak var storage: XMPPClientStorageHistory? + + init(_ storage: any XMPPClientStorageHistory) { + self.storage = storage + } + + func reduce(oldState: ClientState, with _: Event) -> ClientState { + oldState + } + + func process(state: ClientState, with event: Event) async -> Event? { + // try to send/receive stanzas only on ready, secured, and authorized state + guard + state.isSocketSecured && + state.authorizationStep == .authorized + else { return nil } + + // receive/send stanzas from/to xml + switch event { + case .stanzaOutbound(let stanza): + await saveStanza(state, stanza, inbound: false) + return .xmlOutbound(stanza.wrapped) + + case .xmlInbound(let xml): + if let stanza = Stanza(wrap: xml) { + await saveStanza(state, stanza, inbound: true) + return .stanzaInbound(stanza) + } else { + return nil + } + + default: + return nil + } + } +} + +// MARK: Save history +private extension StanzaModule { + func saveStanza(_ state: ClientState, _ stanza: Stanza, inbound: Bool) async { + print(state, stanza, inbound) + } +} diff --git a/AnotherIM/xmpp/utils/Data+Crypto.swift b/AnotherIM/xmpp/utils/Data+Crypto.swift new file mode 100644 index 0000000..7c21888 --- /dev/null +++ b/AnotherIM/xmpp/utils/Data+Crypto.swift @@ -0,0 +1,65 @@ +import CommonCrypto +import Foundation + +extension Data { + var bytes: [UInt8] { + [UInt8](self) + } +} + +extension Array where Element == UInt8 { + var data: Data { + Data(self) + } +} + +extension Data { + func hmac(key: Data) -> Data { + var digest = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH)) + CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA1), key.bytes, key.count, bytes, count, &digest) + return Data(digest) + } + + func sha1() -> Data { + var digest = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH)) + withUnsafeBytes { + _ = CC_SHA1($0.baseAddress, CC_LONG(self.count), &digest) + } + return digest.data + } + + func sha256() -> Data { + var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) + withUnsafeBytes { + _ = CC_SHA256($0.baseAddress, CC_LONG(self.count), &digest) + } + return Data(digest) + } + + func pbkdf2(salt: Data, rounds: Int) -> Data { + var buff = [UInt8](repeating: 0, count: salt.count) + (salt as NSData).getBytes(&buff, length: salt.count) + buff.append(contentsOf: [0, 0, 0, 1]) + + var digest = buff.data.hmac(key: self) + var result = digest + + for _ in 1 ..< rounds { + digest = digest.hmac(key: self) + for indx in 0 ..< digest.count { + result[indx] ^= digest[indx] + } + } + + return result + } + + func xor(other: Data) -> Data { + let otherBytes = other.bytes + var result = bytes + for indx in 0 ..< result.count { + result[indx] ^= otherBytes[indx] + } + return result.data + } +} diff --git a/AnotherIM/xmpp/utils/MemBridge.swift b/AnotherIM/xmpp/utils/MemBridge.swift new file mode 100644 index 0000000..bdcde4d --- /dev/null +++ b/AnotherIM/xmpp/utils/MemBridge.swift @@ -0,0 +1,11 @@ +import Foundation + +enum Mem { + static func bridge(_ obj: T) -> UnsafeMutableRawPointer { + Unmanaged.passUnretained(obj).toOpaque() + } + + static func bridge(_ ptr: UnsafeMutableRawPointer) -> T { + Unmanaged.fromOpaque(ptr).takeUnretainedValue() + } +} diff --git a/AnotherIM/xmpp/utils/String+Base64.swift b/AnotherIM/xmpp/utils/String+Base64.swift new file mode 100644 index 0000000..cb50d91 --- /dev/null +++ b/AnotherIM/xmpp/utils/String+Base64.swift @@ -0,0 +1,16 @@ +import Foundation + +extension String { + var base64Encoded: String { + // swiftlint:disable:next force_unwrapping + let utf8 = data(using: .utf8)! + let base64 = utf8.base64EncodedString() + return base64 + } + + var base64Decoded: String { + // swiftlint:disable:next force_unwrapping + let data = Data(base64Encoded: self)! + return String(decoding: data, as: UTF8.self) + } +} diff --git a/AnotherIM/xmpp/utils/String+XML.swift b/AnotherIM/xmpp/utils/String+XML.swift new file mode 100644 index 0000000..86bce0b --- /dev/null +++ b/AnotherIM/xmpp/utils/String+XML.swift @@ -0,0 +1,68 @@ +import Foundation + +// swiftlint:disable:next large_tuple +private typealias Replace = (orig: String, asName: String, asCode: String) +private let replaces = [ + Replace("&", "&", "&"), + Replace("<", "<", "<"), + Replace(">", ">", ">"), + Replace("\"", """, """), + Replace("'", "'", "'") +] + +extension String { + var unescaped: String { + if !contains("&") { + return self + } + var result = self + var indx = replaces.count - 1 + while indx >= 0 { + // this is for libxml2 as it replaces every & with & which in XML both replaces & + result = result.replacingOccurrences(of: replaces[indx].asCode, with: replaces[indx].orig) + + // this is for normal replacement (may not be needed but let's keep it for compatibility + result = result.replacingOccurrences(of: replaces[indx].asName, with: replaces[indx].orig) + indx -= 1 + } + return result + } + + var escaped: String { + var result = self + var indx = 0 + while indx < replaces.count { + result = result.replacingOccurrences(of: replaces[indx].orig, with: replaces[indx].asName) + indx += 1 + } + return result + } +} + +extension String { + var prettyStr: String { + var line = self + line.replace(">", with: ">\n") + line.replace("") { + indentLevel += 1 + } + } + return formattedString + } +} diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..e72bfdd --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5467577 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Another.im XMPP iOS client + +This is the source code for the another.im XMPP iOS app. + +# License + +Copyright (c) 2024 Narayana OÜ + +Licensed under GPL License Version 3. diff --git a/project.yml b/project.yml new file mode 100644 index 0000000..a2b4e53 --- /dev/null +++ b/project.yml @@ -0,0 +1,80 @@ +--- +name: AnotherIM + +options: + postGenCommand: swiftgen + +packages: + SwiftfulRouting: + url: https://github.com/SwiftfulThinking/SwiftfulRouting + majorVersion: 5.3.5 + KeychainAccess: + url: https://github.com/kishikawakatsumi/KeychainAccess.git + majorVersion: 4.2.2 + GRDB: + url: https://github.com/groue/GRDB.swift.git + majorVersion: 6.29.3 + +settings: + DEVELOPMENT_TEAM: K78H7BT98L + +targets: + # Notification service here... + # + + # Sharing service here... + # + + # iOS App + AnotherIM: + type: application + platform: iOS + deploymentTarget: 16.0 + scheme: {} + info: + path: Info.plist + properties: + UISupportedInterfaceOrientations: [UIInterfaceOrientationPortrait] + UILaunchStoryboardName: launchscreen.storyboard + NSAppTransportSecurity: + NSAllowsArbitraryLoads: true + NSPhotoLibraryUsageDescription: Allow app to send photo from gallery in attachments + NSCameraUsageDescription: Allow app to take picture from camera and send it in atachments + NSMicrophoneUsageDescription: Allow app to take sound from microphone for attachment video + NSLocationWhenInUseUsageDescription: Allow app to take your geo to send it in attachment + NSLocationAlwaysAndWhenInUseUsageDescription: Allow app to take your geo to send it in attachment + UISupportsDocumentBrowser: true + # UIViewControllerBasedStatusBarAppearance: NO + # UIStatusBarStyle: UIStatusBarStyleLightContent + # NSFaceIDUsageDescription: Required for accessing to account info + # UIUserInterfaceStyle: Light + CFBundleDisplayName: another.im + CFBundleShortVersionString: "1.0.0" + CFBundleVersion: "7" + sources: + - path: AnotherIM + excludes: + - .nvim + settings: + TARGETED_DEVICE_FAMILY: 1 + DEBUG_INFORMATION_FORMAT: dwarf-with-dsym + PRODUCT_BUNDLE_IDENTIFIER: im.narayana.anotherim.ios + DEAD_CODE_STRIPPING: true + dependencies: + - sdk: Security.framework + - sdk: CryptoKit.framework + - package: SwiftfulRouting + link: true + - package: KeychainAccess + limk: true + - package: GRDB + link: true + preBuildScripts: + - script: swiftlint + name: Swiftlint + basedOnDependencyAnalysis: false + - script: ./.swiftgen/bin/swiftgen + name: SwiftGen + basedOnDependencyAnalysis: false + +parallelizeBuild: true diff --git a/swiftgen.yml b/swiftgen.yml new file mode 100644 index 0000000..5abf349 --- /dev/null +++ b/swiftgen.yml @@ -0,0 +1,27 @@ +--- +output_dir: AnotherIM/Generated +input_dir: AnotherIM/Resources + +xcassets: + - inputs: Assets/Images.xcassets + outputs: + - templatePath: .swiftgen/templates/xcassets.stencil + params: + forceProvidesNamespaces: true + enumName: Image + output: Images+Generated.swift + - inputs: Assets/Colors.xcassets + outputs: + - templatePath: .swiftgen/templates/xcassets.stencil + params: + forceProvidesNamespaces: true + enumName: Color + output: Colors+Generated.swift + +strings: + inputs: Strings/Localizable.strings + outputs: + # - templatePath: .templates/strings.stencil + - templateName: structured-swift5 + + output: Strings+Generated.swift