From 432c748b97a5bc220f0e71648a6ba5ed67453e3f Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 17 Jun 2022 14:42:32 +0200 Subject: [PATCH 001/161] Rename existing targets to Legacy --- Package.swift | 8 ++++---- .../CallInformation.swift | 0 .../EnvironmentValidation.swift | 0 .../AccessibilityIdentifierStructGenerator.swift | 0 .../Generators/AggregatedStructGenerator.swift | 0 .../Generators/AssetSubfolders.swift | 0 .../Generators/ColorStructGenerator.swift | 0 .../Generators/FontStructGenerator.swift | 0 .../Generators/ImageStructGenerator.swift | 0 .../Generators/NibStructGenerator.swift | 0 .../Generators/PropertyListGenerator.swift | 0 .../Generators/ResourceFileStructGenerator.swift | 0 .../Generators/ReuseIdentifierGenerator.swift | 0 .../Generators/SegueGenerator.swift | 0 .../Generators/StoryboardGenerator.swift | 0 .../Generators/StringsStructGenerator.swift | 0 .../Generators/StructGenerator.swift | 0 .../Generators/ValidatedStructGenerator.swift | 0 .../ResourceTypes/AssetFolder.swift | 0 .../ResourceTypes/Font.swift | 0 .../ResourceTypes/Image.swift | 0 .../ResourceTypes/Locale.swift | 0 .../ResourceTypes/LocalizableStrings.swift | 0 .../ResourceTypes/NameCatalog.swift | 0 .../ResourceTypes/Nib.swift | 0 .../ResourceTypes/PropertyList.swift | 0 .../ResourceTypes/ResourceFile.swift | 0 .../ResourceTypes/Resources.swift | 0 .../ResourceTypes/ReusableContainer.swift | 0 .../ResourceTypes/Storyboard.swift | 0 .../ResourceTypes/StringParam.swift | 0 .../ResourceTypes/Unifiable.swift | 0 .../ResourceTypes/WhiteListedExtensionsResourceType.swift | 0 .../ResourceTypes/Xcodeproj.swift | 0 Sources/{RswiftCore => RswiftCoreLegacy}/RswiftCore.swift | 0 .../SwiftTypes/AccessLevel.swift | 0 .../SwiftTypes/Class.swift | 0 .../SwiftTypes/CodeGenerators/HeaderPrinter.swift | 0 .../SwiftTypes/CodeGenerators/ImportPrinter.swift | 0 .../SwiftTypes/CodeGenerators/OSPrinter.swift | 0 .../SwiftTypes/CodeGenerators/SwiftCodeConverible.swift | 0 .../SwiftTypes/CodeGenerators/TypePrinter.swift | 0 .../SwiftTypes/Function.swift | 0 .../{RswiftCore => RswiftCoreLegacy}/SwiftTypes/Let.swift | 0 .../SwiftTypes/Module.swift | 0 .../SwiftTypes/Struct.swift | 0 .../SwiftTypes/Type.swift | 0 .../SwiftTypes/TypeVar.swift | 0 .../SwiftTypes/Typealias.swift | 0 .../Util/ErrorOutput.swift | 0 Sources/{RswiftCore => RswiftCoreLegacy}/Util/Glob.swift | 0 .../Util/IgnoreFile.swift | 0 .../Util/Struct+InternalProperties.swift | 0 .../Util/SwiftIdentifier.swift | 0 .../Util/TypeSequenceProvider.swift | 0 .../Util/UtilExtensions.swift | 0 Sources/{rswift => rswift-legacy}/Rswift.swift | 0 Sources/{rswift => rswift-legacy}/main.swift | 2 +- .../GlobTests.swift | 2 +- .../MainTests.swift | 2 +- .../NibParserDelegateTests.swift | 2 +- 61 files changed, 8 insertions(+), 8 deletions(-) rename Sources/{RswiftCore => RswiftCoreLegacy}/CallInformation.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/EnvironmentValidation.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Generators/AccessibilityIdentifierStructGenerator.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Generators/AggregatedStructGenerator.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Generators/AssetSubfolders.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Generators/ColorStructGenerator.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Generators/FontStructGenerator.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Generators/ImageStructGenerator.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Generators/NibStructGenerator.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Generators/PropertyListGenerator.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Generators/ResourceFileStructGenerator.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Generators/ReuseIdentifierGenerator.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Generators/SegueGenerator.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Generators/StoryboardGenerator.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Generators/StringsStructGenerator.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Generators/StructGenerator.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Generators/ValidatedStructGenerator.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/ResourceTypes/AssetFolder.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/ResourceTypes/Font.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/ResourceTypes/Image.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/ResourceTypes/Locale.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/ResourceTypes/LocalizableStrings.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/ResourceTypes/NameCatalog.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/ResourceTypes/Nib.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/ResourceTypes/PropertyList.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/ResourceTypes/ResourceFile.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/ResourceTypes/Resources.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/ResourceTypes/ReusableContainer.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/ResourceTypes/Storyboard.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/ResourceTypes/StringParam.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/ResourceTypes/Unifiable.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/ResourceTypes/WhiteListedExtensionsResourceType.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/ResourceTypes/Xcodeproj.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/RswiftCore.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/SwiftTypes/AccessLevel.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/SwiftTypes/Class.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/SwiftTypes/CodeGenerators/HeaderPrinter.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/SwiftTypes/CodeGenerators/ImportPrinter.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/SwiftTypes/CodeGenerators/OSPrinter.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/SwiftTypes/CodeGenerators/SwiftCodeConverible.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/SwiftTypes/CodeGenerators/TypePrinter.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/SwiftTypes/Function.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/SwiftTypes/Let.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/SwiftTypes/Module.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/SwiftTypes/Struct.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/SwiftTypes/Type.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/SwiftTypes/TypeVar.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/SwiftTypes/Typealias.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Util/ErrorOutput.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Util/Glob.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Util/IgnoreFile.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Util/Struct+InternalProperties.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Util/SwiftIdentifier.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Util/TypeSequenceProvider.swift (100%) rename Sources/{RswiftCore => RswiftCoreLegacy}/Util/UtilExtensions.swift (100%) rename Sources/{rswift => rswift-legacy}/Rswift.swift (100%) rename Sources/{rswift => rswift-legacy}/main.swift (99%) rename Tests/{RswiftCoreTests => RswiftCoreLegacyTests}/GlobTests.swift (99%) rename Tests/{RswiftCoreTests => RswiftCoreLegacyTests}/MainTests.swift (97%) rename Tests/{RswiftCoreTests => RswiftCoreLegacyTests}/NibParserDelegateTests.swift (99%) diff --git a/Package.swift b/Package.swift index 4cd7c034..e065009b 100644 --- a/Package.swift +++ b/Package.swift @@ -7,15 +7,15 @@ let package = Package( .macOS(.v10_11) ], products: [ - .executable(name: "rswift", targets: ["rswift"]) + .executable(name: "rswift-legacy", targets: ["rswift-legacy"]) ], dependencies: [ .package(url: "https://github.com/kylef/Commander.git", from: "0.8.0"), .package(url: "https://github.com/tomlokhorst/XcodeEdit", from: "2.8.0") ], targets: [ - .target(name: "rswift", dependencies: ["RswiftCore"]), - .target(name: "RswiftCore", dependencies: ["Commander", "XcodeEdit"]), - .testTarget(name: "RswiftCoreTests", dependencies: ["RswiftCore"]), + .target(name: "rswift-legacy", dependencies: ["RswiftCoreLegacy"]), + .target(name: "RswiftCoreLegacy", dependencies: ["Commander", "XcodeEdit"]), + .testTarget(name: "RswiftCoreLegacyTests", dependencies: ["RswiftCoreLegacy"]), ] ) diff --git a/Sources/RswiftCore/CallInformation.swift b/Sources/RswiftCoreLegacy/CallInformation.swift similarity index 100% rename from Sources/RswiftCore/CallInformation.swift rename to Sources/RswiftCoreLegacy/CallInformation.swift diff --git a/Sources/RswiftCore/EnvironmentValidation.swift b/Sources/RswiftCoreLegacy/EnvironmentValidation.swift similarity index 100% rename from Sources/RswiftCore/EnvironmentValidation.swift rename to Sources/RswiftCoreLegacy/EnvironmentValidation.swift diff --git a/Sources/RswiftCore/Generators/AccessibilityIdentifierStructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/AccessibilityIdentifierStructGenerator.swift similarity index 100% rename from Sources/RswiftCore/Generators/AccessibilityIdentifierStructGenerator.swift rename to Sources/RswiftCoreLegacy/Generators/AccessibilityIdentifierStructGenerator.swift diff --git a/Sources/RswiftCore/Generators/AggregatedStructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/AggregatedStructGenerator.swift similarity index 100% rename from Sources/RswiftCore/Generators/AggregatedStructGenerator.swift rename to Sources/RswiftCoreLegacy/Generators/AggregatedStructGenerator.swift diff --git a/Sources/RswiftCore/Generators/AssetSubfolders.swift b/Sources/RswiftCoreLegacy/Generators/AssetSubfolders.swift similarity index 100% rename from Sources/RswiftCore/Generators/AssetSubfolders.swift rename to Sources/RswiftCoreLegacy/Generators/AssetSubfolders.swift diff --git a/Sources/RswiftCore/Generators/ColorStructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/ColorStructGenerator.swift similarity index 100% rename from Sources/RswiftCore/Generators/ColorStructGenerator.swift rename to Sources/RswiftCoreLegacy/Generators/ColorStructGenerator.swift diff --git a/Sources/RswiftCore/Generators/FontStructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/FontStructGenerator.swift similarity index 100% rename from Sources/RswiftCore/Generators/FontStructGenerator.swift rename to Sources/RswiftCoreLegacy/Generators/FontStructGenerator.swift diff --git a/Sources/RswiftCore/Generators/ImageStructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/ImageStructGenerator.swift similarity index 100% rename from Sources/RswiftCore/Generators/ImageStructGenerator.swift rename to Sources/RswiftCoreLegacy/Generators/ImageStructGenerator.swift diff --git a/Sources/RswiftCore/Generators/NibStructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/NibStructGenerator.swift similarity index 100% rename from Sources/RswiftCore/Generators/NibStructGenerator.swift rename to Sources/RswiftCoreLegacy/Generators/NibStructGenerator.swift diff --git a/Sources/RswiftCore/Generators/PropertyListGenerator.swift b/Sources/RswiftCoreLegacy/Generators/PropertyListGenerator.swift similarity index 100% rename from Sources/RswiftCore/Generators/PropertyListGenerator.swift rename to Sources/RswiftCoreLegacy/Generators/PropertyListGenerator.swift diff --git a/Sources/RswiftCore/Generators/ResourceFileStructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/ResourceFileStructGenerator.swift similarity index 100% rename from Sources/RswiftCore/Generators/ResourceFileStructGenerator.swift rename to Sources/RswiftCoreLegacy/Generators/ResourceFileStructGenerator.swift diff --git a/Sources/RswiftCore/Generators/ReuseIdentifierGenerator.swift b/Sources/RswiftCoreLegacy/Generators/ReuseIdentifierGenerator.swift similarity index 100% rename from Sources/RswiftCore/Generators/ReuseIdentifierGenerator.swift rename to Sources/RswiftCoreLegacy/Generators/ReuseIdentifierGenerator.swift diff --git a/Sources/RswiftCore/Generators/SegueGenerator.swift b/Sources/RswiftCoreLegacy/Generators/SegueGenerator.swift similarity index 100% rename from Sources/RswiftCore/Generators/SegueGenerator.swift rename to Sources/RswiftCoreLegacy/Generators/SegueGenerator.swift diff --git a/Sources/RswiftCore/Generators/StoryboardGenerator.swift b/Sources/RswiftCoreLegacy/Generators/StoryboardGenerator.swift similarity index 100% rename from Sources/RswiftCore/Generators/StoryboardGenerator.swift rename to Sources/RswiftCoreLegacy/Generators/StoryboardGenerator.swift diff --git a/Sources/RswiftCore/Generators/StringsStructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/StringsStructGenerator.swift similarity index 100% rename from Sources/RswiftCore/Generators/StringsStructGenerator.swift rename to Sources/RswiftCoreLegacy/Generators/StringsStructGenerator.swift diff --git a/Sources/RswiftCore/Generators/StructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/StructGenerator.swift similarity index 100% rename from Sources/RswiftCore/Generators/StructGenerator.swift rename to Sources/RswiftCoreLegacy/Generators/StructGenerator.swift diff --git a/Sources/RswiftCore/Generators/ValidatedStructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/ValidatedStructGenerator.swift similarity index 100% rename from Sources/RswiftCore/Generators/ValidatedStructGenerator.swift rename to Sources/RswiftCoreLegacy/Generators/ValidatedStructGenerator.swift diff --git a/Sources/RswiftCore/ResourceTypes/AssetFolder.swift b/Sources/RswiftCoreLegacy/ResourceTypes/AssetFolder.swift similarity index 100% rename from Sources/RswiftCore/ResourceTypes/AssetFolder.swift rename to Sources/RswiftCoreLegacy/ResourceTypes/AssetFolder.swift diff --git a/Sources/RswiftCore/ResourceTypes/Font.swift b/Sources/RswiftCoreLegacy/ResourceTypes/Font.swift similarity index 100% rename from Sources/RswiftCore/ResourceTypes/Font.swift rename to Sources/RswiftCoreLegacy/ResourceTypes/Font.swift diff --git a/Sources/RswiftCore/ResourceTypes/Image.swift b/Sources/RswiftCoreLegacy/ResourceTypes/Image.swift similarity index 100% rename from Sources/RswiftCore/ResourceTypes/Image.swift rename to Sources/RswiftCoreLegacy/ResourceTypes/Image.swift diff --git a/Sources/RswiftCore/ResourceTypes/Locale.swift b/Sources/RswiftCoreLegacy/ResourceTypes/Locale.swift similarity index 100% rename from Sources/RswiftCore/ResourceTypes/Locale.swift rename to Sources/RswiftCoreLegacy/ResourceTypes/Locale.swift diff --git a/Sources/RswiftCore/ResourceTypes/LocalizableStrings.swift b/Sources/RswiftCoreLegacy/ResourceTypes/LocalizableStrings.swift similarity index 100% rename from Sources/RswiftCore/ResourceTypes/LocalizableStrings.swift rename to Sources/RswiftCoreLegacy/ResourceTypes/LocalizableStrings.swift diff --git a/Sources/RswiftCore/ResourceTypes/NameCatalog.swift b/Sources/RswiftCoreLegacy/ResourceTypes/NameCatalog.swift similarity index 100% rename from Sources/RswiftCore/ResourceTypes/NameCatalog.swift rename to Sources/RswiftCoreLegacy/ResourceTypes/NameCatalog.swift diff --git a/Sources/RswiftCore/ResourceTypes/Nib.swift b/Sources/RswiftCoreLegacy/ResourceTypes/Nib.swift similarity index 100% rename from Sources/RswiftCore/ResourceTypes/Nib.swift rename to Sources/RswiftCoreLegacy/ResourceTypes/Nib.swift diff --git a/Sources/RswiftCore/ResourceTypes/PropertyList.swift b/Sources/RswiftCoreLegacy/ResourceTypes/PropertyList.swift similarity index 100% rename from Sources/RswiftCore/ResourceTypes/PropertyList.swift rename to Sources/RswiftCoreLegacy/ResourceTypes/PropertyList.swift diff --git a/Sources/RswiftCore/ResourceTypes/ResourceFile.swift b/Sources/RswiftCoreLegacy/ResourceTypes/ResourceFile.swift similarity index 100% rename from Sources/RswiftCore/ResourceTypes/ResourceFile.swift rename to Sources/RswiftCoreLegacy/ResourceTypes/ResourceFile.swift diff --git a/Sources/RswiftCore/ResourceTypes/Resources.swift b/Sources/RswiftCoreLegacy/ResourceTypes/Resources.swift similarity index 100% rename from Sources/RswiftCore/ResourceTypes/Resources.swift rename to Sources/RswiftCoreLegacy/ResourceTypes/Resources.swift diff --git a/Sources/RswiftCore/ResourceTypes/ReusableContainer.swift b/Sources/RswiftCoreLegacy/ResourceTypes/ReusableContainer.swift similarity index 100% rename from Sources/RswiftCore/ResourceTypes/ReusableContainer.swift rename to Sources/RswiftCoreLegacy/ResourceTypes/ReusableContainer.swift diff --git a/Sources/RswiftCore/ResourceTypes/Storyboard.swift b/Sources/RswiftCoreLegacy/ResourceTypes/Storyboard.swift similarity index 100% rename from Sources/RswiftCore/ResourceTypes/Storyboard.swift rename to Sources/RswiftCoreLegacy/ResourceTypes/Storyboard.swift diff --git a/Sources/RswiftCore/ResourceTypes/StringParam.swift b/Sources/RswiftCoreLegacy/ResourceTypes/StringParam.swift similarity index 100% rename from Sources/RswiftCore/ResourceTypes/StringParam.swift rename to Sources/RswiftCoreLegacy/ResourceTypes/StringParam.swift diff --git a/Sources/RswiftCore/ResourceTypes/Unifiable.swift b/Sources/RswiftCoreLegacy/ResourceTypes/Unifiable.swift similarity index 100% rename from Sources/RswiftCore/ResourceTypes/Unifiable.swift rename to Sources/RswiftCoreLegacy/ResourceTypes/Unifiable.swift diff --git a/Sources/RswiftCore/ResourceTypes/WhiteListedExtensionsResourceType.swift b/Sources/RswiftCoreLegacy/ResourceTypes/WhiteListedExtensionsResourceType.swift similarity index 100% rename from Sources/RswiftCore/ResourceTypes/WhiteListedExtensionsResourceType.swift rename to Sources/RswiftCoreLegacy/ResourceTypes/WhiteListedExtensionsResourceType.swift diff --git a/Sources/RswiftCore/ResourceTypes/Xcodeproj.swift b/Sources/RswiftCoreLegacy/ResourceTypes/Xcodeproj.swift similarity index 100% rename from Sources/RswiftCore/ResourceTypes/Xcodeproj.swift rename to Sources/RswiftCoreLegacy/ResourceTypes/Xcodeproj.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCoreLegacy/RswiftCore.swift similarity index 100% rename from Sources/RswiftCore/RswiftCore.swift rename to Sources/RswiftCoreLegacy/RswiftCore.swift diff --git a/Sources/RswiftCore/SwiftTypes/AccessLevel.swift b/Sources/RswiftCoreLegacy/SwiftTypes/AccessLevel.swift similarity index 100% rename from Sources/RswiftCore/SwiftTypes/AccessLevel.swift rename to Sources/RswiftCoreLegacy/SwiftTypes/AccessLevel.swift diff --git a/Sources/RswiftCore/SwiftTypes/Class.swift b/Sources/RswiftCoreLegacy/SwiftTypes/Class.swift similarity index 100% rename from Sources/RswiftCore/SwiftTypes/Class.swift rename to Sources/RswiftCoreLegacy/SwiftTypes/Class.swift diff --git a/Sources/RswiftCore/SwiftTypes/CodeGenerators/HeaderPrinter.swift b/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/HeaderPrinter.swift similarity index 100% rename from Sources/RswiftCore/SwiftTypes/CodeGenerators/HeaderPrinter.swift rename to Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/HeaderPrinter.swift diff --git a/Sources/RswiftCore/SwiftTypes/CodeGenerators/ImportPrinter.swift b/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/ImportPrinter.swift similarity index 100% rename from Sources/RswiftCore/SwiftTypes/CodeGenerators/ImportPrinter.swift rename to Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/ImportPrinter.swift diff --git a/Sources/RswiftCore/SwiftTypes/CodeGenerators/OSPrinter.swift b/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/OSPrinter.swift similarity index 100% rename from Sources/RswiftCore/SwiftTypes/CodeGenerators/OSPrinter.swift rename to Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/OSPrinter.swift diff --git a/Sources/RswiftCore/SwiftTypes/CodeGenerators/SwiftCodeConverible.swift b/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/SwiftCodeConverible.swift similarity index 100% rename from Sources/RswiftCore/SwiftTypes/CodeGenerators/SwiftCodeConverible.swift rename to Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/SwiftCodeConverible.swift diff --git a/Sources/RswiftCore/SwiftTypes/CodeGenerators/TypePrinter.swift b/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/TypePrinter.swift similarity index 100% rename from Sources/RswiftCore/SwiftTypes/CodeGenerators/TypePrinter.swift rename to Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/TypePrinter.swift diff --git a/Sources/RswiftCore/SwiftTypes/Function.swift b/Sources/RswiftCoreLegacy/SwiftTypes/Function.swift similarity index 100% rename from Sources/RswiftCore/SwiftTypes/Function.swift rename to Sources/RswiftCoreLegacy/SwiftTypes/Function.swift diff --git a/Sources/RswiftCore/SwiftTypes/Let.swift b/Sources/RswiftCoreLegacy/SwiftTypes/Let.swift similarity index 100% rename from Sources/RswiftCore/SwiftTypes/Let.swift rename to Sources/RswiftCoreLegacy/SwiftTypes/Let.swift diff --git a/Sources/RswiftCore/SwiftTypes/Module.swift b/Sources/RswiftCoreLegacy/SwiftTypes/Module.swift similarity index 100% rename from Sources/RswiftCore/SwiftTypes/Module.swift rename to Sources/RswiftCoreLegacy/SwiftTypes/Module.swift diff --git a/Sources/RswiftCore/SwiftTypes/Struct.swift b/Sources/RswiftCoreLegacy/SwiftTypes/Struct.swift similarity index 100% rename from Sources/RswiftCore/SwiftTypes/Struct.swift rename to Sources/RswiftCoreLegacy/SwiftTypes/Struct.swift diff --git a/Sources/RswiftCore/SwiftTypes/Type.swift b/Sources/RswiftCoreLegacy/SwiftTypes/Type.swift similarity index 100% rename from Sources/RswiftCore/SwiftTypes/Type.swift rename to Sources/RswiftCoreLegacy/SwiftTypes/Type.swift diff --git a/Sources/RswiftCore/SwiftTypes/TypeVar.swift b/Sources/RswiftCoreLegacy/SwiftTypes/TypeVar.swift similarity index 100% rename from Sources/RswiftCore/SwiftTypes/TypeVar.swift rename to Sources/RswiftCoreLegacy/SwiftTypes/TypeVar.swift diff --git a/Sources/RswiftCore/SwiftTypes/Typealias.swift b/Sources/RswiftCoreLegacy/SwiftTypes/Typealias.swift similarity index 100% rename from Sources/RswiftCore/SwiftTypes/Typealias.swift rename to Sources/RswiftCoreLegacy/SwiftTypes/Typealias.swift diff --git a/Sources/RswiftCore/Util/ErrorOutput.swift b/Sources/RswiftCoreLegacy/Util/ErrorOutput.swift similarity index 100% rename from Sources/RswiftCore/Util/ErrorOutput.swift rename to Sources/RswiftCoreLegacy/Util/ErrorOutput.swift diff --git a/Sources/RswiftCore/Util/Glob.swift b/Sources/RswiftCoreLegacy/Util/Glob.swift similarity index 100% rename from Sources/RswiftCore/Util/Glob.swift rename to Sources/RswiftCoreLegacy/Util/Glob.swift diff --git a/Sources/RswiftCore/Util/IgnoreFile.swift b/Sources/RswiftCoreLegacy/Util/IgnoreFile.swift similarity index 100% rename from Sources/RswiftCore/Util/IgnoreFile.swift rename to Sources/RswiftCoreLegacy/Util/IgnoreFile.swift diff --git a/Sources/RswiftCore/Util/Struct+InternalProperties.swift b/Sources/RswiftCoreLegacy/Util/Struct+InternalProperties.swift similarity index 100% rename from Sources/RswiftCore/Util/Struct+InternalProperties.swift rename to Sources/RswiftCoreLegacy/Util/Struct+InternalProperties.swift diff --git a/Sources/RswiftCore/Util/SwiftIdentifier.swift b/Sources/RswiftCoreLegacy/Util/SwiftIdentifier.swift similarity index 100% rename from Sources/RswiftCore/Util/SwiftIdentifier.swift rename to Sources/RswiftCoreLegacy/Util/SwiftIdentifier.swift diff --git a/Sources/RswiftCore/Util/TypeSequenceProvider.swift b/Sources/RswiftCoreLegacy/Util/TypeSequenceProvider.swift similarity index 100% rename from Sources/RswiftCore/Util/TypeSequenceProvider.swift rename to Sources/RswiftCoreLegacy/Util/TypeSequenceProvider.swift diff --git a/Sources/RswiftCore/Util/UtilExtensions.swift b/Sources/RswiftCoreLegacy/Util/UtilExtensions.swift similarity index 100% rename from Sources/RswiftCore/Util/UtilExtensions.swift rename to Sources/RswiftCoreLegacy/Util/UtilExtensions.swift diff --git a/Sources/rswift/Rswift.swift b/Sources/rswift-legacy/Rswift.swift similarity index 100% rename from Sources/rswift/Rswift.swift rename to Sources/rswift-legacy/Rswift.swift diff --git a/Sources/rswift/main.swift b/Sources/rswift-legacy/main.swift similarity index 99% rename from Sources/rswift/main.swift rename to Sources/rswift-legacy/main.swift index 38f2077c..39d40ef6 100644 --- a/Sources/rswift/main.swift +++ b/Sources/rswift-legacy/main.swift @@ -9,7 +9,7 @@ import Foundation import Commander -import RswiftCore +import RswiftCoreLegacy import XcodeEdit // Argument convertibles diff --git a/Tests/RswiftCoreTests/GlobTests.swift b/Tests/RswiftCoreLegacyTests/GlobTests.swift similarity index 99% rename from Tests/RswiftCoreTests/GlobTests.swift rename to Tests/RswiftCoreLegacyTests/GlobTests.swift index b56d490e..ea7eeff0 100644 --- a/Tests/RswiftCoreTests/GlobTests.swift +++ b/Tests/RswiftCoreLegacyTests/GlobTests.swift @@ -7,7 +7,7 @@ // Adapted from https://gist.github.com/efirestone/ce01ae109e08772647eb061b3bb387c3 import XCTest -@testable import RswiftCore +@testable import RswiftCoreLegacy class GlobTests : XCTestCase { diff --git a/Tests/RswiftCoreTests/MainTests.swift b/Tests/RswiftCoreLegacyTests/MainTests.swift similarity index 97% rename from Tests/RswiftCoreTests/MainTests.swift rename to Tests/RswiftCoreLegacyTests/MainTests.swift index 24fa34ca..6afc07e7 100644 --- a/Tests/RswiftCoreTests/MainTests.swift +++ b/Tests/RswiftCoreLegacyTests/MainTests.swift @@ -8,7 +8,7 @@ // import XCTest -@testable import RswiftCore +@testable import RswiftCoreLegacy class MainTests: XCTestCase { override func setUp() { diff --git a/Tests/RswiftCoreTests/NibParserDelegateTests.swift b/Tests/RswiftCoreLegacyTests/NibParserDelegateTests.swift similarity index 99% rename from Tests/RswiftCoreTests/NibParserDelegateTests.swift rename to Tests/RswiftCoreLegacyTests/NibParserDelegateTests.swift index b17a2e3d..eeb7ba44 100644 --- a/Tests/RswiftCoreTests/NibParserDelegateTests.swift +++ b/Tests/RswiftCoreLegacyTests/NibParserDelegateTests.swift @@ -6,7 +6,7 @@ // import XCTest -@testable import RswiftCore +@testable import RswiftCoreLegacy class NibParserTests: XCTestCase { From 292c7b128d0d8f61975b7f10118cf3d7b34cfe1d Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 17 Jun 2022 14:48:02 +0200 Subject: [PATCH 002/161] Add RswiftResources target --- Package.swift | 3 +++ Sources/RswiftResources/Font.swift | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 Sources/RswiftResources/Font.swift diff --git a/Package.swift b/Package.swift index e065009b..7b2b532a 100644 --- a/Package.swift +++ b/Package.swift @@ -14,6 +14,9 @@ let package = Package( .package(url: "https://github.com/tomlokhorst/XcodeEdit", from: "2.8.0") ], targets: [ + .target(name: "RswiftResources"), + + // Legacy code .target(name: "rswift-legacy", dependencies: ["RswiftCoreLegacy"]), .target(name: "RswiftCoreLegacy", dependencies: ["Commander", "XcodeEdit"]), .testTarget(name: "RswiftCoreLegacyTests", dependencies: ["RswiftCoreLegacy"]), diff --git a/Sources/RswiftResources/Font.swift b/Sources/RswiftResources/Font.swift new file mode 100644 index 00000000..95a886b9 --- /dev/null +++ b/Sources/RswiftResources/Font.swift @@ -0,0 +1,20 @@ +// +// Font.swift +// R.swift +// +// Created by Mathijs Kadijk on 09-12-15. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation + +public struct Font { + public let filename: String + public let name: String + + public init(filename: String, name: String) { + self.filename = filename + self.name = name + } +} From 7bfc2a1da0f8a28c2e9241ed9feddb992386488f Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 17 Jun 2022 14:56:25 +0200 Subject: [PATCH 003/161] Add RswiftParsers target --- Package.swift | 1 + Sources/RswiftParsers/Font+Parser.swift | 30 +++++++ .../Shared/ResourceParsingError.swift | 16 ++++ .../Shared/SupportedExtensions.swift | 40 +++++++++ .../RswiftParsers/Shared/URL+Extensions.swift | 15 ++++ Sources/RswiftParsers/Shared/Xcodeproj.swift | 84 +++++++++++++++++++ 6 files changed, 186 insertions(+) create mode 100644 Sources/RswiftParsers/Font+Parser.swift create mode 100644 Sources/RswiftParsers/Shared/ResourceParsingError.swift create mode 100644 Sources/RswiftParsers/Shared/SupportedExtensions.swift create mode 100644 Sources/RswiftParsers/Shared/URL+Extensions.swift create mode 100644 Sources/RswiftParsers/Shared/Xcodeproj.swift diff --git a/Package.swift b/Package.swift index 7b2b532a..99f5bce7 100644 --- a/Package.swift +++ b/Package.swift @@ -15,6 +15,7 @@ let package = Package( ], targets: [ .target(name: "RswiftResources"), + .target(name: "RswiftParsers", dependencies: ["RswiftResources", "XcodeEdit"]), // Legacy code .target(name: "rswift-legacy", dependencies: ["RswiftCoreLegacy"]), diff --git a/Sources/RswiftParsers/Font+Parser.swift b/Sources/RswiftParsers/Font+Parser.swift new file mode 100644 index 00000000..6606ca62 --- /dev/null +++ b/Sources/RswiftParsers/Font+Parser.swift @@ -0,0 +1,30 @@ +// +// Font.swift +// R.swift +// +// Created by Mathijs Kadijk on 09-12-15. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation +import RswiftResources +import CoreGraphics + + +extension Font: SupportedExtensions { + static public let supportedExtensions: Set = ["otf", "ttf"] + + static public func parse(url: URL) throws -> Font { + guard let dataProvider = CGDataProvider(url: url as CFURL) else { + throw ResourceParsingError("Unable to create data provider for font at \(url)") + } + + let font = CGFont(dataProvider) + guard let postScriptName = font?.postScriptName else { + throw ResourceParsingError("No postscriptName associated to font at \(url)") + } + + return Font(filename: url.lastPathComponent, name: postScriptName as String) + } +} diff --git a/Sources/RswiftParsers/Shared/ResourceParsingError.swift b/Sources/RswiftParsers/Shared/ResourceParsingError.swift new file mode 100644 index 00000000..ceed09c3 --- /dev/null +++ b/Sources/RswiftParsers/Shared/ResourceParsingError.swift @@ -0,0 +1,16 @@ +// +// ResourceParsingError.swift +// RswiftCore +// +// Created by Tom Lokhorst on 2021-04-16. +// + +import Foundation + +public struct ResourceParsingError: Error { + public var description: String + + public init(_ description: String) { + self.description = description + } +} diff --git a/Sources/RswiftParsers/Shared/SupportedExtensions.swift b/Sources/RswiftParsers/Shared/SupportedExtensions.swift new file mode 100644 index 00000000..e66f60ff --- /dev/null +++ b/Sources/RswiftParsers/Shared/SupportedExtensions.swift @@ -0,0 +1,40 @@ +// +// SupportedExtensions.swift +// R.swift +// +// Created by Mathijs Kadijk on 10-12-15. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation + +public protocol SupportedExtensions { + static var supportedExtensions: Set { get } +} + +extension SupportedExtensions { + static func throwIfUnsupportedExtension(_ url: URL) throws { + let pathExtension = url.pathExtension + + if !supportedExtensions.contains(pathExtension.lowercased()) { + throw ResourceUnsupportedExtensionError(url: url, typeName: "\(Self.self)", supportedExtensions: supportedExtensions) + } + } +} + +public struct ResourceUnsupportedExtensionError: LocalizedError { + public let url: URL + public let typeName: String + public let supportedExtensions: Set + + public init(url: URL, typeName: String, supportedExtensions: Set) { + self.url = url + self.typeName = typeName + self.supportedExtensions = supportedExtensions + } + + public var errorDescription: String { + "URL '\(url)' has not supported extension, for type '\(typeName)', supported extensions \(supportedExtensions.joined(separator: ", "))" + } +} diff --git a/Sources/RswiftParsers/Shared/URL+Extensions.swift b/Sources/RswiftParsers/Shared/URL+Extensions.swift new file mode 100644 index 00000000..00d3622a --- /dev/null +++ b/Sources/RswiftParsers/Shared/URL+Extensions.swift @@ -0,0 +1,15 @@ +// +// URL+Extensions.swift +// RswiftResources +// +// Created by Tom Lokhorst on 2021-04-25. +// + +import Foundation + +internal extension URL { + var filenameWithoutExtension: String? { + let name = self.deletingPathExtension().lastPathComponent + return name == "" ? nil : name + } +} diff --git a/Sources/RswiftParsers/Shared/Xcodeproj.swift b/Sources/RswiftParsers/Shared/Xcodeproj.swift new file mode 100644 index 00000000..156731e1 --- /dev/null +++ b/Sources/RswiftParsers/Shared/Xcodeproj.swift @@ -0,0 +1,84 @@ +// +// Xcodeproj.swift +// RswiftCore +// +// Created by Mathijs Kadijk on 09-12-15. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation +import XcodeEdit + +public struct Xcodeproj: SupportedExtensions { + static public let supportedExtensions: Set = ["xcodeproj"] + + private let projectFile: XCProjectFile + + let developmentLanguage: String + + public init(url: URL, warning: (String) -> Void) throws { + try Xcodeproj.throwIfUnsupportedExtension(url) + let projectFile: XCProjectFile + + // Parse project file + do { + do { + projectFile = try XCProjectFile(xcodeprojURL: url, ignoreReferenceErrors: false) + } + catch let error as ProjectFileError { + warning(error.localizedDescription) + + projectFile = try XCProjectFile(xcodeprojURL: url, ignoreReferenceErrors: true) + } + } + catch { + throw ResourceParsingError("Project file at '\(url)' could not be parsed, is this a valid Xcode project file ending in *.xcodeproj?\n\(error.localizedDescription)") + } + + self.projectFile = projectFile + self.developmentLanguage = projectFile.project.developmentRegion + } + + private func findTarget(name: String) throws -> PBXTarget { + // Look for target in project file + let allTargets = projectFile.project.targets.compactMap { $0.value } + guard let target = allTargets.filter({ $0.name == name }).first else { + let availableTargets = allTargets.compactMap { $0.name }.joined(separator: ", ") + throw ResourceParsingError("Target '\(name)' not found in project file, available targets are: \(availableTargets)") + } + + return target + } + + public func resourcePaths(forTarget targetName: String) throws -> [Path] { + let target = try findTarget(name: targetName) + + let resourcesFileRefs = target.buildPhases + .compactMap { $0.value as? PBXResourcesBuildPhase } + .flatMap { $0.files } + .compactMap { $0.value?.fileRef } + + let fileRefPaths = resourcesFileRefs + .compactMap { $0.value as? PBXFileReference } + .compactMap { $0.fullPath } + + let variantGroupPaths = resourcesFileRefs + .compactMap { $0.value as? PBXVariantGroup } + .flatMap { $0.fileRefs } + .compactMap { $0.value?.fullPath } + + return fileRefPaths + variantGroupPaths + } + + public func buildConfigurations(forTarget targetName: String) throws -> [XCBuildConfiguration] { + let target = try findTarget(name: targetName) + + guard let buildConfigurationList = target.buildConfigurationList.value else { return [] } + + let buildConfigurations = buildConfigurationList.buildConfigurations + .compactMap { $0.value } + + return buildConfigurations + } +} From a2e62b85fb443af7bf2c433c18ee6465d556cb22 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 17 Jun 2022 18:09:42 +0200 Subject: [PATCH 004/161] Add RswiftGenerators + RswiftCore --- Package.swift | 8 + Sources/RswiftCore/RswiftCore.swift | 85 ++++++++++ .../Extensions/String+Extensions.swift | 49 ++++++ Sources/RswiftGenerators/Font+Generator.swift | 16 ++ .../RswiftGenerators/SwiftIdentifier.swift | 147 ++++++++++++++++++ Sources/rswift/main.swift | 31 ++++ 6 files changed, 336 insertions(+) create mode 100644 Sources/RswiftCore/RswiftCore.swift create mode 100644 Sources/RswiftGenerators/Extensions/String+Extensions.swift create mode 100644 Sources/RswiftGenerators/Font+Generator.swift create mode 100644 Sources/RswiftGenerators/SwiftIdentifier.swift create mode 100644 Sources/rswift/main.swift diff --git a/Package.swift b/Package.swift index 99f5bce7..82700b42 100644 --- a/Package.swift +++ b/Package.swift @@ -7,6 +7,7 @@ let package = Package( .macOS(.v10_11) ], products: [ + .executable(name: "rswift", targets: ["rswift"]), .executable(name: "rswift-legacy", targets: ["rswift-legacy"]) ], dependencies: [ @@ -16,6 +17,13 @@ let package = Package( targets: [ .target(name: "RswiftResources"), .target(name: "RswiftParsers", dependencies: ["RswiftResources", "XcodeEdit"]), + .target(name: "RswiftGenerators", dependencies: ["RswiftResources"]), + + // Core of R.swift, brings all previous parts together + .target(name: "RswiftCore", dependencies: ["RswiftParsers", "RswiftGenerators"]), + + // Executable that calls Core + .target(name: "rswift", dependencies: ["RswiftCore"]), // Legacy code .target(name: "rswift-legacy", dependencies: ["RswiftCoreLegacy"]), diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift new file mode 100644 index 00000000..ba4d6d6c --- /dev/null +++ b/Sources/RswiftCore/RswiftCore.swift @@ -0,0 +1,85 @@ +// +// RswiftCore.swift +// rswift +// +// Created by Tom Lokhorst on 2021-04-16. +// + +import Foundation +import XcodeEdit +import RswiftParsers +import RswiftResources +import RswiftGenerators + +public struct RswiftCore { + let xcodeprojURL: URL + let targetName: String + let bundleIdentifier: String + let productModuleName: String + let infoPlistFile: URL? + let codeSignEntitlements: URL? + + let builtProductsDirURL: URL + let developerDirURL: URL + let sourceRootURL: URL + let sdkRootURL: URL + let platformURL: URL + + public init( + xcodeprojURL: URL, + targetName: String, + bundleIdentifier: String, + productModuleName: String, + infoPlistFile: URL?, + codeSignEntitlements: URL?, + builtProductsDirURL: URL, + developerDirURL: URL, + sourceRootURL: URL, + sdkRootURL: URL, + platformURL: URL + ) { + self.xcodeprojURL = xcodeprojURL + self.targetName = targetName + self.bundleIdentifier = bundleIdentifier + self.productModuleName = productModuleName + self.infoPlistFile = infoPlistFile + self.codeSignEntitlements = codeSignEntitlements + self.builtProductsDirURL = builtProductsDirURL + self.developerDirURL = developerDirURL + self.sourceRootURL = sourceRootURL + self.sdkRootURL = sdkRootURL + self.platformURL = platformURL + } + + // Temporary function for use during development + public func developRun() throws { + let xcodeproj = try! Xcodeproj(url: xcodeprojURL, warning: { print($0) }) + let paths = try xcodeproj.resourcePaths(forTarget: targetName) + let urls = paths + .map { $0.url(with: urlForSourceTreeFolder) } + + let fonts = try urls + .filter { Font.supportedExtensions.contains($0.pathExtension) } + .map { try Font.parse(url: $0) } + + for font in fonts { + print(try font.generateResourceLetString()) + } + print() + } + + private func urlForSourceTreeFolder(_ sourceTreeFolder: SourceTreeFolder) -> URL { + switch sourceTreeFolder { + case .buildProductsDir: + return builtProductsDirURL + case .developerDir: + return developerDirURL + case .sdkRoot: + return sdkRootURL + case .sourceRoot: + return sourceRootURL + case .platformDir: + return platformURL + } + } +} diff --git a/Sources/RswiftGenerators/Extensions/String+Extensions.swift b/Sources/RswiftGenerators/Extensions/String+Extensions.swift new file mode 100644 index 00000000..a4cb1f90 --- /dev/null +++ b/Sources/RswiftGenerators/Extensions/String+Extensions.swift @@ -0,0 +1,49 @@ +// +// String+Extensions.swift +// RswiftGenerators +// +// Created by Tom Lokhorst on 2021-04-18. +// + +import Foundation + +extension String { + var lowercaseFirstCharacter: String { + if self.count <= 1 { return self.lowercased() } + let index = self.index(startIndex, offsetBy: 1) + return self[.. String { + return self + .components(separatedBy: "\n") + .map { line in line .isEmpty ? "" : "\(indentation)\(line)" } + .joined(separator: "\n") + } + + var fullRange: NSRange { + return NSRange(location: 0, length: self.count) + } + + var escapedStringLiteral: String { + return self + .replacingOccurrences(of: "\\", with: "\\\\") + .replacingOccurrences(of: "\"", with: "\\\"") + .replacingOccurrences(of: "\t", with: "\\t") + .replacingOccurrences(of: "\r", with: "\\r") + .replacingOccurrences(of: "\n", with: "\\n") + } + + var commentString: String { + return self + .replacingOccurrences(of: "\r\n", with: " ") + .replacingOccurrences(of: "\r", with: " ") + .replacingOccurrences(of: "\n", with: " ") + } +} diff --git a/Sources/RswiftGenerators/Font+Generator.swift b/Sources/RswiftGenerators/Font+Generator.swift new file mode 100644 index 00000000..842bfbe4 --- /dev/null +++ b/Sources/RswiftGenerators/Font+Generator.swift @@ -0,0 +1,16 @@ +// +// Font+Generator.swift +// rswift +// +// Created by Tom Lokhorst on 2021-04-18. +// + +import Foundation +import RswiftResources + +extension Font { + public func generateResourceLetString() throws -> String { + "static let \(SwiftIdentifier(name: self.name).value) = \(self)" + } +} + diff --git a/Sources/RswiftGenerators/SwiftIdentifier.swift b/Sources/RswiftGenerators/SwiftIdentifier.swift new file mode 100644 index 00000000..a91582d7 --- /dev/null +++ b/Sources/RswiftGenerators/SwiftIdentifier.swift @@ -0,0 +1,147 @@ +// +// SwiftIdentifier.swift +// R.swift +// +// Created by Mathijs Kadijk on 11-12-15. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation + +private let numberPrefixRegex = try! NSRegularExpression(pattern: "^[0-9]+") +private let upperCasedPrefixRegex = try! NSRegularExpression(pattern: "^([A-Z]+)(?=[^a-z]{1})") + +/* + Disallowed characters: whitespace, mathematical symbols, arrows, private-use and invalid Unicode points, line- and boxdrawing characters + Special rules: Can't begin with a number + */ +public struct SwiftIdentifier : Hashable { + public let value: String + + public init(name: String, lowercaseStartingCharacters: Bool = true) { + // Remove all disallowed characters from the name and uppercase the character after a disallowed character + var nameComponents = name.components(separatedBy: disallowedCharacters) + let firstComponent = nameComponents.remove(at: 0) + let cleanedSwiftName = nameComponents.reduce(firstComponent) { $0 + $1.uppercaseFirstCharacter } + + // Remove numbers at the start of the name + let sanitizedSwiftName = numberPrefixRegex.stringByReplacingMatches(in: cleanedSwiftName, options: [], range: cleanedSwiftName.fullRange, withTemplate: "") + + // Lowercase the start of the name + let capitalizedSwiftName = lowercaseStartingCharacters ? SwiftIdentifier.lowercasePrefix(sanitizedSwiftName) : sanitizedSwiftName + + // Escape the name if it is a keyword + if SwiftKeywords.contains(capitalizedSwiftName) { + value = "`\(capitalizedSwiftName)`" + } else { + value = capitalizedSwiftName + } + } + + public init(rawValue: String) { + value = rawValue + } + + private static func lowercasePrefix(_ name: String) -> String { + let prefixRange = upperCasedPrefixRegex.rangeOfFirstMatch(in: name, options: [], range: name.fullRange) + + if prefixRange.location == NSNotFound { + return name.lowercaseFirstCharacter + } else { + let lowercasedPrefix = (name as NSString).substring(with: prefixRange).lowercased() + return (name as NSString).replacingCharacters(in: prefixRange, with: lowercasedPrefix) + } + } + + static func +(lhs: SwiftIdentifier, rhs: SwiftIdentifier) -> SwiftIdentifier { + return SwiftIdentifier(rawValue: "\(lhs.value).\(rhs.value)") + } +} + + +struct SwiftNameGroups { + let uniques: [T] + let duplicates: [(SwiftIdentifier, [String])] // Identifiers that result in duplicate Swift names + let empties: [String] // Identifiers (wrapped in quotes) that result in empty swift names + + func reportWarningsForDuplicatesAndEmpties(source: String, container: String? = nil, result: String, warning: (String) -> Void) { + + let sourceSingular = [source, container].compactMap { $0 }.joined(separator: " ") + let sourcePlural = ["\(source)s", container].compactMap { $0 }.joined(separator: " ") + + let resultSingular = result + let resultPlural = "\(result)s" + + for (sanitizedName, dups) in duplicates { + warning("Skipping \(dups.count) \(sourcePlural) because symbol '\(sanitizedName)' would be generated for all of these \(resultPlural): \(dups.joined(separator: ", "))") + } + + if let empty = empties.first , empties.count == 1 { + warning("Skipping 1 \(sourceSingular) because no swift identifier can be generated for \(resultSingular): \(empty)") + } + else if empties.count > 1 { + warning("Skipping \(empties.count) \(sourcePlural) because no swift identifier can be generated for all of these \(resultPlural): \(empties.joined(separator: ", "))") + } + } +} + +extension Sequence { + func grouped(bySwiftIdentifier identifierSelector: @escaping (Iterator.Element) -> String) -> SwiftNameGroups { + var groupedBy = Dictionary(grouping: self, by: { SwiftIdentifier(name: identifierSelector($0)) }) + let empty = SwiftIdentifier(name: "") + let empties = groupedBy[empty]?.map { "'\(identifierSelector($0))'" }.sorted() + groupedBy[empty] = nil + + let uniques = Array(groupedBy.values.filter { $0.count == 1 }.joined()) + .sorted { identifierSelector($0) < identifierSelector($1) } + let duplicates = groupedBy + .filter { $0.1.count > 1 } + .map { ($0.0, $0.1.map(identifierSelector).sorted()) } + .sorted { $0.0.value < $1.0.value } + + return SwiftNameGroups(uniques: uniques, duplicates: duplicates, empties: empties ?? []) + } +} + +private let disallowedCharacters: CharacterSet = { + let disallowed = NSMutableCharacterSet(charactersIn: "") + disallowed.formUnion(with: CharacterSet.whitespacesAndNewlines) + disallowed.formUnion(with: CharacterSet.punctuationCharacters) + disallowed.formUnion(with: CharacterSet.symbols) + disallowed.formUnion(with: CharacterSet.illegalCharacters) + disallowed.formUnion(with: CharacterSet.controlCharacters) + disallowed.removeCharacters(in: "_") + + // Emoji ranges, roughly based on http://www.unicode.org/Public/emoji/1.0//emoji-data.txt + [ + 0x2600...0x27BF, + 0x1F300...0x1F6FF, + 0x1F900...0x1F9FF, + 0x1F1E6...0x1F1FF, + ].forEach { + let range = NSRange(location: $0.lowerBound, length: $0.upperBound - $0.lowerBound) + disallowed.removeCharacters(in: range) + } + + return disallowed as CharacterSet +}() + +// Based on https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID413 +private let SwiftKeywords = [ + // Keywords used in declarations + "associatedtype", "class", "deinit", "enum", "extension", "fileprivate", "func", "import", "init", "inout", "internal", "let", "open", "operator", "private", "protocol", "public", "static", "struct", "subscript", "typealias", "var", + + // Keywords used in statements + "break", "case", "continue", "default", "defer", "do", "else", "fallthrough", "for", "guard", "if", "in", "repeat", "return", "switch", "where", "while", + + // Keywords used in expressions and types + "as", "Any", "catch", "false", "is", "nil", "rethrows", "super", "self", "Self", "throw", "throws", "true", "try", + + // Keywords that begin with a number sign (#) + "#available", "#colorLiteral", "#column", "#else", "#elseif", "#endif", "#error", "#file", "#fileLiteral", "#function", "#if", "#imageLiteral", "#line", "#selector", "#sourceLocation", "#warning", + + // Keywords from Swift 2 that are still reserved + "__COLUMN__", "__FILE__", "__FUNCTION__", "__LINE__", +] + diff --git a/Sources/rswift/main.swift b/Sources/rswift/main.swift new file mode 100644 index 00000000..794974b6 --- /dev/null +++ b/Sources/rswift/main.swift @@ -0,0 +1,31 @@ +// +// main.swift +// rswift +// +// Created by Tom Lokhorst on 2021-04-18. +// + +import Foundation +import RswiftCore + +// Temporary development code +let xcodeprojURL = URL(fileURLWithPath: "/Users/tom/Projects/R.swift/Examples/ResourceApp/ResourceApp.xcodeproj") +let targetName = "ResourceApp" +let sourceRootURL = xcodeprojURL.deletingLastPathComponent() +let fakeURL = URL(fileURLWithPath: "/FAKE") + +let core = RswiftCore( + xcodeprojURL: xcodeprojURL, + targetName: targetName, + bundleIdentifier: "FAKE", + productModuleName: "FAKE", + infoPlistFile: nil, + codeSignEntitlements: nil, + builtProductsDirURL: fakeURL, + developerDirURL: fakeURL, + sourceRootURL: sourceRootURL, + sdkRootURL: fakeURL, + platformURL: fakeURL +) + +try core.developRun() From c9b8527c7ac8479eaa5e673cce9fac4618fd6f3c Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 17 Jun 2022 22:17:57 +0200 Subject: [PATCH 005/161] Add rswift development executable --- .../ResourceApp.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 14 ++++++++ Package.swift | 6 ++-- Sources/RswiftCore/RswiftCore.swift | 9 ++--- Sources/RswiftGenerators/Font+Generator.swift | 4 +-- Sources/rswift/main.swift | 34 +++++++++++++++---- 6 files changed, 51 insertions(+), 18 deletions(-) create mode 100644 Examples/ResourceApp/ResourceApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj b/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj index f1b4202f..9b023507 100644 --- a/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj +++ b/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj @@ -728,7 +728,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"$SRCROOT/../../build/Debug/rswift\" generate --generateUITestFile \"$SRCROOT/R.UITest.generated.swift\" --import SWRevealViewController \"$SRCROOT/R.generated.swift\" > \"$SRCROOT/rswift.log\"\n"; + shellScript = "#\"$SRCROOT/../../build/Debug/rswift\" generate --generateUITestFile \"$SRCROOT/R.UITest.generated.swift\" --import SWRevealViewController \"$SRCROOT/R.generated.swift\" > \"$SRCROOT/rswift.log\"\n/Users/tom/Library/Developer/Xcode/DerivedData/R-gzdnepbnjmlzctbxokifiegoyhmk/Build/Products/Debug/rswift generate --generateUITestFile \"$SRCROOT/R.UITest.generated.swift\" --import SWRevealViewController \"$SRCROOT/R.generated.swift\" > \"$SRCROOT/rswift.log\"\n"; }; ED8FCF67313DC003391627B7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; diff --git a/Examples/ResourceApp/ResourceApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/ResourceApp/ResourceApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..c02a46a4 --- /dev/null +++ b/Examples/ResourceApp/ResourceApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "r.swift.library", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mac-cain13/R.swift.Library.git", + "state" : { + "revision" : "8998cfe77f4fce79ee6dfab0c88a7d551659d8fb", + "version" : "5.4.0" + } + } + ], + "version" : 2 +} diff --git a/Package.swift b/Package.swift index 82700b42..f6ae3545 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.0 +// swift-tools-version:5.3 import PackageDescription let package = Package( @@ -8,11 +8,11 @@ let package = Package( ], products: [ .executable(name: "rswift", targets: ["rswift"]), - .executable(name: "rswift-legacy", targets: ["rswift-legacy"]) + .executable(name: "rswift-legacy", targets: ["rswift-legacy"]), ], dependencies: [ .package(url: "https://github.com/kylef/Commander.git", from: "0.8.0"), - .package(url: "https://github.com/tomlokhorst/XcodeEdit", from: "2.8.0") + .package(url: "https://github.com/tomlokhorst/XcodeEdit", from: "2.8.0"), ], targets: [ .target(name: "RswiftResources"), diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index ba4d6d6c..0b38b299 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -14,8 +14,7 @@ import RswiftGenerators public struct RswiftCore { let xcodeprojURL: URL let targetName: String - let bundleIdentifier: String - let productModuleName: String + let productModuleName: String? let infoPlistFile: URL? let codeSignEntitlements: URL? @@ -28,8 +27,7 @@ public struct RswiftCore { public init( xcodeprojURL: URL, targetName: String, - bundleIdentifier: String, - productModuleName: String, + productModuleName: String?, infoPlistFile: URL?, codeSignEntitlements: URL?, builtProductsDirURL: URL, @@ -40,7 +38,6 @@ public struct RswiftCore { ) { self.xcodeprojURL = xcodeprojURL self.targetName = targetName - self.bundleIdentifier = bundleIdentifier self.productModuleName = productModuleName self.infoPlistFile = infoPlistFile self.codeSignEntitlements = codeSignEntitlements @@ -63,7 +60,7 @@ public struct RswiftCore { .map { try Font.parse(url: $0) } for font in fonts { - print(try font.generateResourceLetString()) + print(font.generateResourceLetCodeString()) } print() } diff --git a/Sources/RswiftGenerators/Font+Generator.swift b/Sources/RswiftGenerators/Font+Generator.swift index 842bfbe4..e60c392e 100644 --- a/Sources/RswiftGenerators/Font+Generator.swift +++ b/Sources/RswiftGenerators/Font+Generator.swift @@ -9,8 +9,8 @@ import Foundation import RswiftResources extension Font { - public func generateResourceLetString() throws -> String { - "static let \(SwiftIdentifier(name: self.name).value) = \(self)" + public func generateResourceLetCodeString() -> String { + "let \(SwiftIdentifier(name: self.name).value) = \(self)" } } diff --git a/Sources/rswift/main.swift b/Sources/rswift/main.swift index 794974b6..0bc8c293 100644 --- a/Sources/rswift/main.swift +++ b/Sources/rswift/main.swift @@ -7,20 +7,41 @@ import Foundation import RswiftCore +import XcodeEdit + // Temporary development code -let xcodeprojURL = URL(fileURLWithPath: "/Users/tom/Projects/R.swift/Examples/ResourceApp/ResourceApp.xcodeproj") -let targetName = "ResourceApp" + +struct EnvironmentKeys { + static let action = "ACTION" + + static let bundleIdentifier = "PRODUCT_BUNDLE_IDENTIFIER" + static let productModuleName = "PRODUCT_MODULE_NAME" + static let target = "TARGET_NAME" + static let xcodeproj = "PROJECT_FILE_PATH" + static let infoPlistFile = "INFOPLIST_FILE" + static let codeSignEntitlements = "CODE_SIGN_ENTITLEMENTS" + + static let builtProductsDir = SourceTreeFolder.buildProductsDir.rawValue + static let developerDir = SourceTreeFolder.developerDir.rawValue + static let platformDir = SourceTreeFolder.platformDir.rawValue + static let sdkRoot = SourceTreeFolder.sdkRoot.rawValue + static let sourceRoot = SourceTreeFolder.sourceRoot.rawValue +} + +let processInfo = ProcessInfo() +let targetName = processInfo.environment[EnvironmentKeys.target] ?? "ResourceApp" + +let xcodeprojURL = URL(fileURLWithPath: processInfo.environment[EnvironmentKeys.xcodeproj] ?? "/Users/tom/Projects/R.swift/Examples/ResourceApp/ResourceApp.xcodeproj") let sourceRootURL = xcodeprojURL.deletingLastPathComponent() let fakeURL = URL(fileURLWithPath: "/FAKE") let core = RswiftCore( xcodeprojURL: xcodeprojURL, targetName: targetName, - bundleIdentifier: "FAKE", - productModuleName: "FAKE", - infoPlistFile: nil, - codeSignEntitlements: nil, + productModuleName: processInfo.environment[EnvironmentKeys.productModuleName], + infoPlistFile: processInfo.environment[EnvironmentKeys.infoPlistFile].map { URL(fileURLWithPath: $0) }, + codeSignEntitlements: processInfo.environment[EnvironmentKeys.codeSignEntitlements].map { URL(fileURLWithPath: $0) }, builtProductsDirURL: fakeURL, developerDirURL: fakeURL, sourceRootURL: sourceRootURL, @@ -28,4 +49,5 @@ let core = RswiftCore( platformURL: fakeURL ) +print("Start") try core.developRun() From b67c7cba40ad2bd78ad5db0064b2d1615dd58991 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 24 Jun 2022 11:26:07 +0200 Subject: [PATCH 006/161] Add Image parser --- Sources/RswiftCore/RswiftCore.swift | 11 ++++-- .../RswiftGenerators/Image+Generator.swift | 16 +++++++++ Sources/RswiftParsers/Image+Parser.swift | 34 +++++++++++++++++++ Sources/RswiftResources/Image.swift | 20 +++++++++++ 4 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 Sources/RswiftGenerators/Image+Generator.swift create mode 100644 Sources/RswiftParsers/Image+Parser.swift create mode 100644 Sources/RswiftResources/Image.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 0b38b299..9f9fe6f1 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -59,8 +59,15 @@ public struct RswiftCore { .filter { Font.supportedExtensions.contains($0.pathExtension) } .map { try Font.parse(url: $0) } - for font in fonts { - print(font.generateResourceLetCodeString()) + let images = try urls + .filter { Image.supportedExtensions.contains($0.pathExtension) } + .map { try Image.parse(url: $0) } + +// for font in fonts { +// print(font.generateResourceLetCodeString()) +// } + for image in images { + print(image.generateResourceLetCodeString()) } print() } diff --git a/Sources/RswiftGenerators/Image+Generator.swift b/Sources/RswiftGenerators/Image+Generator.swift new file mode 100644 index 00000000..2d610565 --- /dev/null +++ b/Sources/RswiftGenerators/Image+Generator.swift @@ -0,0 +1,16 @@ +// +// Image+Generator.swift +// +// +// Created by Tom Lokhorst on 2022-06-24. +// + +import Foundation +import RswiftResources + +extension Image { + public func generateResourceLetCodeString() -> String { + "let \(SwiftIdentifier(name: self.name).value) = \(self)" + } +} + diff --git a/Sources/RswiftParsers/Image+Parser.swift b/Sources/RswiftParsers/Image+Parser.swift new file mode 100644 index 00000000..00a3fdef --- /dev/null +++ b/Sources/RswiftParsers/Image+Parser.swift @@ -0,0 +1,34 @@ +// +// Image.swift +// R.swift +// +// Created by Mathijs Kadijk on 09-12-15. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation +import RswiftResources +import CoreGraphics + + +extension Image: SupportedExtensions { + // See "Supported Image Formats" on https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/ + static public let supportedExtensions: Set = ["tiff", "tif", "jpg", "jpeg", "gif", "png", "bmp", "bmpf", "ico", "cur", "xbm"] + + static public func parse(url: URL) throws -> Image { + let filename = url.lastPathComponent + let pathExtension = url.pathExtension + guard filename.count > 0 && pathExtension.count > 0 else { + throw ResourceParsingError("Filename and/or extension could not be parsed from URL: \(url.absoluteString)") + } + + let extensions = Image.supportedExtensions.joined(separator: "|") + let regex = try! NSRegularExpression(pattern: "(~(ipad|iphone))?(@[2,3]x)?\\.(\(extensions))$", options: .caseInsensitive) + let fullFileNameRange = NSRange(location: 0, length: filename.count) + let pathExtensionToUse = (pathExtension == "png") ? "" : ".\(pathExtension)" + let name = regex.stringByReplacingMatches(in: filename, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: fullFileNameRange, withTemplate: pathExtensionToUse) + + return Image(filename: url.lastPathComponent, name: name) + } +} diff --git a/Sources/RswiftResources/Image.swift b/Sources/RswiftResources/Image.swift new file mode 100644 index 00000000..1f03e0a4 --- /dev/null +++ b/Sources/RswiftResources/Image.swift @@ -0,0 +1,20 @@ +// +// Image.swift +// R.swift +// +// Created by Mathijs Kadijk on 09-12-15. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation + +public struct Image { + public let filename: String + public let name: String + + public init(filename: String, name: String) { + self.filename = filename + self.name = name + } +} From f47836f90596a487e9ef2be4afc7b324535d2c5e Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 24 Jun 2022 13:57:15 +0200 Subject: [PATCH 007/161] Work on PropertyList --- Sources/RswiftCore/RswiftCore.swift | 25 +++++++++++-------- .../RswiftParsers/PropertyList+Parser.swift | 24 ++++++++++++++++++ Sources/RswiftResources/PropertyList.swift | 24 ++++++++++++++++++ 3 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 Sources/RswiftParsers/PropertyList+Parser.swift create mode 100644 Sources/RswiftResources/PropertyList.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 9f9fe6f1..0642d58c 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -51,24 +51,27 @@ public struct RswiftCore { // Temporary function for use during development public func developRun() throws { let xcodeproj = try! Xcodeproj(url: xcodeprojURL, warning: { print($0) }) + + let buildConfigurations = try xcodeproj.buildConfigurations(forTarget: targetName) + let paths = try xcodeproj.resourcePaths(forTarget: targetName) let urls = paths .map { $0.url(with: urlForSourceTreeFolder) } - let fonts = try urls - .filter { Font.supportedExtensions.contains($0.pathExtension) } - .map { try Font.parse(url: $0) } - - let images = try urls - .filter { Image.supportedExtensions.contains($0.pathExtension) } - .map { try Image.parse(url: $0) } - +// let fonts = try urls +// .filter { Font.supportedExtensions.contains($0.pathExtension) } +// .map { try Font.parse(url: $0) } // for font in fonts { // print(font.generateResourceLetCodeString()) // } - for image in images { - print(image.generateResourceLetCodeString()) - } + +// let images = try urls +// .filter { Image.supportedExtensions.contains($0.pathExtension) } +// .map { try Image.parse(url: $0) } +// for image in images { +// print(image.generateResourceLetCodeString()) +// } + print() } diff --git a/Sources/RswiftParsers/PropertyList+Parser.swift b/Sources/RswiftParsers/PropertyList+Parser.swift new file mode 100644 index 00000000..9e356c32 --- /dev/null +++ b/Sources/RswiftParsers/PropertyList+Parser.swift @@ -0,0 +1,24 @@ +// +// PropertyList.swift +// R.swift +// +// Created by Tom Lokhorst on 2018-07-08. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation +import RswiftResources + +extension PropertyList { + static public func parse(url: URL, buildConfigurationName: String) throws -> PropertyList { + guard + let nsDictionary = NSDictionary(contentsOf: url), + let dictionary = nsDictionary as? [String: Any] + else { + throw ResourceParsingError("File could not be parsed as InfoPlist from URL: \(url.absoluteString)") + } + + return PropertyList(buildConfigurationName: buildConfigurationName, contents: dictionary, url: url) + } +} diff --git a/Sources/RswiftResources/PropertyList.swift b/Sources/RswiftResources/PropertyList.swift new file mode 100644 index 00000000..2df6cc70 --- /dev/null +++ b/Sources/RswiftResources/PropertyList.swift @@ -0,0 +1,24 @@ +// +// PropertyList.swift +// R.swift +// +// Created by Tom Lokhorst on 2018-07-08. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation + +public struct PropertyList { + public typealias Contents = [String: Any] + + public let buildConfigurationName: String + public let contents: Contents + public let url: URL + + public init(buildConfigurationName: String, contents: Contents, url: URL) { + self.buildConfigurationName = buildConfigurationName + self.contents = contents + self.url = url + } +} From 92d0d08255bb09a2c398549e03f870b251f5fb06 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 24 Jun 2022 14:43:01 +0200 Subject: [PATCH 008/161] Work on ResourceFile --- Sources/RswiftCore/RswiftCore.swift | 7 +++++ .../ResourceFile+Generator.swift | 15 +++++++++ Sources/RswiftParsers/Font+Parser.swift | 1 - .../RswiftParsers/ResourceFile+Parser.swift | 31 +++++++++++++++++++ Sources/RswiftResources/ResourceFile.swift | 22 +++++++++++++ 5 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 Sources/RswiftGenerators/ResourceFile+Generator.swift create mode 100644 Sources/RswiftParsers/ResourceFile+Parser.swift create mode 100644 Sources/RswiftResources/ResourceFile.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 0642d58c..d7764001 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -72,6 +72,13 @@ public struct RswiftCore { // print(image.generateResourceLetCodeString()) // } + let resources = try urls +// .filter { Image.supportedExtensions.contains($0.pathExtension) } + .map { try ResourceFile.parse(url: $0) } + for resource in resources { + print(resource.generateResourceLetCodeString()) + } + print() } diff --git a/Sources/RswiftGenerators/ResourceFile+Generator.swift b/Sources/RswiftGenerators/ResourceFile+Generator.swift new file mode 100644 index 00000000..e6e9ff93 --- /dev/null +++ b/Sources/RswiftGenerators/ResourceFile+Generator.swift @@ -0,0 +1,15 @@ +// +// ResourceFile+Generator.swift +// +// +// Created by Tom Lokhorst on 2022-06-24. +// + +import Foundation +import RswiftResources + +extension ResourceFile { + public func generateResourceLetCodeString() -> String { + "let \(SwiftIdentifier(name: self.fullname).value) = \(self)" + } +} diff --git a/Sources/RswiftParsers/Font+Parser.swift b/Sources/RswiftParsers/Font+Parser.swift index 6606ca62..7528b50a 100644 --- a/Sources/RswiftParsers/Font+Parser.swift +++ b/Sources/RswiftParsers/Font+Parser.swift @@ -11,7 +11,6 @@ import Foundation import RswiftResources import CoreGraphics - extension Font: SupportedExtensions { static public let supportedExtensions: Set = ["otf", "ttf"] diff --git a/Sources/RswiftParsers/ResourceFile+Parser.swift b/Sources/RswiftParsers/ResourceFile+Parser.swift new file mode 100644 index 00000000..50a6c27c --- /dev/null +++ b/Sources/RswiftParsers/ResourceFile+Parser.swift @@ -0,0 +1,31 @@ +// +// ResourceFile.swift +// R.swift +// +// Created by Mathijs Kadijk on 09-12-15. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation +import RswiftResources + +extension ResourceFile { + // These are all extensions of resources that are passed to some special compiler step and not directly available at runtime + static let unsupportedExtensions: Set = [ +// AssetFolder.supportedExtensions, +// Storyboard.supportedExtensions, +// Nib.supportedExtensions, +// LocalizableStrings.supportedExtensions, + ] +// .reduce([]) { $0.union($1) } + + static public func parse(url: URL) throws -> ResourceFile { + let basename = url.deletingPathExtension().lastPathComponent + if basename.isEmpty { + throw ResourceParsingError("Couldn't extract filename from URL: \(url)") + } + + return ResourceFile(fullname: url.lastPathComponent, name: basename, pathExtension: url.pathExtension) + } +} diff --git a/Sources/RswiftResources/ResourceFile.swift b/Sources/RswiftResources/ResourceFile.swift new file mode 100644 index 00000000..afa2e9e7 --- /dev/null +++ b/Sources/RswiftResources/ResourceFile.swift @@ -0,0 +1,22 @@ +// +// ResourceFile.swift +// R.swift +// +// Created by Mathijs Kadijk on 09-12-15. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation + +public struct ResourceFile { + public let fullname: String + public let name: String + public let pathExtension: String + + public init(fullname: String, name: String, pathExtension: String) { + self.fullname = fullname + self.name = name + self.pathExtension = pathExtension + } +} From 071843576633f5b908bf315e63c83b73b5a65250 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 24 Jun 2022 19:38:30 +0200 Subject: [PATCH 009/161] Add Nib parser --- Sources/RswiftCore/RswiftCore.swift | 16 +- Sources/RswiftGenerators/Nib+Generator.swift | 16 ++ Sources/RswiftParsers/Nib+Parser.swift | 142 ++++++++++++++++++ .../Shared/TypeReference+Extensions.swift | 42 ++++++ Sources/RswiftResources/Nib.swift | 28 ++++ .../Shared/ModuleReference.swift | 36 +++++ .../RswiftResources/Shared/NameCatalog.swift | 27 ++++ Sources/RswiftResources/Shared/Reusable.swift | 20 +++ .../Shared/TypeReference.swift | 20 +++ 9 files changed, 342 insertions(+), 5 deletions(-) create mode 100644 Sources/RswiftGenerators/Nib+Generator.swift create mode 100644 Sources/RswiftParsers/Nib+Parser.swift create mode 100644 Sources/RswiftParsers/Shared/TypeReference+Extensions.swift create mode 100644 Sources/RswiftResources/Nib.swift create mode 100644 Sources/RswiftResources/Shared/ModuleReference.swift create mode 100644 Sources/RswiftResources/Shared/NameCatalog.swift create mode 100644 Sources/RswiftResources/Shared/Reusable.swift create mode 100644 Sources/RswiftResources/Shared/TypeReference.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index d7764001..c05a12ed 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -72,13 +72,19 @@ public struct RswiftCore { // print(image.generateResourceLetCodeString()) // } - let resources = try urls -// .filter { Image.supportedExtensions.contains($0.pathExtension) } - .map { try ResourceFile.parse(url: $0) } - for resource in resources { - print(resource.generateResourceLetCodeString()) + let nibs = try urls + .filter { Nib.supportedExtensions.contains($0.pathExtension) } + .map { try Nib.parse(url: $0) } + for nib in nibs { + print(nib.generateResourceLetCodeString()) } +// let resources = try urls +// .map { try ResourceFile.parse(url: $0) } +// for resource in resources { +// print(resource.generateResourceLetCodeString()) +// } + print() } diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift new file mode 100644 index 00000000..5446dc9f --- /dev/null +++ b/Sources/RswiftGenerators/Nib+Generator.swift @@ -0,0 +1,16 @@ +// +// Nib+Generator.swift +// +// +// Created by Tom Lokhorst on 2022-06-24. +// + +import Foundation +import RswiftResources + +extension Nib { + public func generateResourceLetCodeString() -> String { + "let \(SwiftIdentifier(name: self.name).value) = \(self)" + } +} + diff --git a/Sources/RswiftParsers/Nib+Parser.swift b/Sources/RswiftParsers/Nib+Parser.swift new file mode 100644 index 00000000..a0c633ab --- /dev/null +++ b/Sources/RswiftParsers/Nib+Parser.swift @@ -0,0 +1,142 @@ +// +// Nib.swift +// R.swift +// +// Created by Mathijs Kadijk on 09-12-15. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation +import RswiftResources + + +extension Nib: SupportedExtensions { + static public let supportedExtensions: Set = ["xib"] + + static public func parse(url: URL) throws -> Nib { + let basename = url.deletingPathExtension().lastPathComponent + if basename.isEmpty { + throw ResourceParsingError("Couldn't extract filename from URL: \(url)") + } + + guard let parser = XMLParser(contentsOf: url) else { + throw ResourceParsingError("Couldn't load file at: '\(url)'") + } + + let parserDelegate = NibParserDelegate() + parser.delegate = parserDelegate + + guard parser.parse() else { + throw ResourceParsingError("Invalid XML in file at: '\(url)'") + } + + return Nib( + name: basename, + rootViews: parserDelegate.rootViews, + reusables: parserDelegate.reusables, + usedImageIdentifiers: parserDelegate.usedImageIdentifiers, + usedColorResources: parserDelegate.usedColorReferences, + usedAccessibilityIdentifiers: parserDelegate.usedAccessibilityIdentifiers + ) + } +} + +private let ElementNameToTypeMapping = [ + // TODO: Should contain all standard view elements, like button -> UIButton, view -> UIView etc + "view": TypeReference._UIView, + "tableViewCell": TypeReference._UITableViewCell, + "collectionViewCell": TypeReference._UICollectionViewCell, + "collectionReusableView": TypeReference._UICollectionReusableView +] + +private class NibParserDelegate: NSObject, XMLParserDelegate { + let ignoredRootViewElements = ["placeholder"] + var rootViews: [TypeReference] = [] + var reusables: [Reusable] = [] + var usedImageIdentifiers: [NameCatalog] = [] + var usedColorReferences: [NameCatalog] = [] + var usedAccessibilityIdentifiers: [String] = [] + + // State + var isObjectsTagOpened = false; + var levelSinceObjectsTagOpened = 0; + + @objc func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) { + if isObjectsTagOpened { + levelSinceObjectsTagOpened += 1 + } + if elementName == "objects" { + isObjectsTagOpened = true + } + + switch elementName { + case "image": + if let imageIdentifier = attributeDict["name"] { + usedImageIdentifiers.append(NameCatalog(name: imageIdentifier, catalog: attributeDict["catalog"])) + } + + case "color": + if let colorName = attributeDict["name"] { + usedColorReferences.append(NameCatalog(name: colorName, catalog: attributeDict["catalog"])) + } + + case "accessibility": + if let accessibilityIdentifier = attributeDict["identifier"] { + usedAccessibilityIdentifiers.append(accessibilityIdentifier) + } + + case "userDefinedRuntimeAttribute": + if let accessibilityIdentifier = attributeDict["value"], "accessibilityIdentifier" == attributeDict["keyPath"] && "string" == attributeDict["type"] { + usedAccessibilityIdentifiers.append(accessibilityIdentifier) + } + + default: + if let rootView = viewWithAttributes(attributeDict, elementName: elementName), + levelSinceObjectsTagOpened == 1 && ignoredRootViewElements.allSatisfy({ $0 != elementName }) { + rootViews.append(rootView) + } + if let reusable = reusableFromAttributes(attributeDict, elementName: elementName) { + reusables.append(reusable) + } + } + } + + @objc func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { + switch elementName { + case "objects": + isObjectsTagOpened = false; + + default: + if isObjectsTagOpened { + levelSinceObjectsTagOpened -= 1 + } + } + } + + func viewWithAttributes(_ attributeDict: [String : String], elementName: String) -> TypeReference? { + let customModuleProvider = attributeDict["customModuleProvider"] + let customModule = (customModuleProvider == "target") ? nil : attributeDict["customModule"] + let customClass = attributeDict["customClass"] + let customType = customClass + .map { TypeReference(module: ModuleReference(name: customModule), rawName: $0) } + + return customType ?? ElementNameToTypeMapping[elementName] ?? TypeReference._UIView + } + + func reusableFromAttributes(_ attributeDict: [String : String], elementName: String) -> Reusable? { + guard let reuseIdentifier = attributeDict["reuseIdentifier"] , reuseIdentifier != "" else { + return nil + } + + let customModuleProvider = attributeDict["customModuleProvider"] + let customModule = (customModuleProvider == "target") ? nil : attributeDict["customModule"] + let customClass = attributeDict["customClass"] + let customType = customClass + .map { TypeReference(module: ModuleReference(name: customModule), rawName: $0) } + + let type = customType ?? ElementNameToTypeMapping[elementName] ?? TypeReference._UIView + + return Reusable(identifier: reuseIdentifier, type: type) + } +} diff --git a/Sources/RswiftParsers/Shared/TypeReference+Extensions.swift b/Sources/RswiftParsers/Shared/TypeReference+Extensions.swift new file mode 100644 index 00000000..30c5f4d0 --- /dev/null +++ b/Sources/RswiftParsers/Shared/TypeReference+Extensions.swift @@ -0,0 +1,42 @@ +// +// TypeReference+Extensions.swift +// +// +// Created by Tom Lokhorst on 2022-06-24. +// + +import Foundation +import RswiftResources + +extension TypeReference { + static let _Void = TypeReference(module: .stdLib, rawName: "Void") + static let _Any = TypeReference(module: .stdLib, rawName: "Any") + static let _AnyObject = TypeReference(module: .stdLib, rawName: "AnyObject") + static let _String = TypeReference(module: .stdLib, rawName: "String") + static let _Bool = TypeReference(module: .stdLib, rawName: "Bool") + static let _Array = TypeReference(module: .stdLib, rawName: "Array") + static let _Tuple = TypeReference(module: .stdLib, rawName: "_TUPLE_") + static let _Int = TypeReference(module: .stdLib, rawName: "Int") + static let _UInt = TypeReference(module: .stdLib, rawName: "UInt") + static let _Double = TypeReference(module: .stdLib, rawName: "Double") + static let _Character = TypeReference(module: .stdLib, rawName: "Character") + static let _CStringPointer = TypeReference(module: .stdLib, rawName: "UnsafePointer") + static let _VoidPointer = TypeReference(module: .stdLib, rawName: "UnsafePointer") + static let _URL = TypeReference(module: .foundation, rawName: "URL") + static let _Bundle = TypeReference(module: .foundation, rawName: "Bundle") + static let _Locale = TypeReference(module: .foundation, rawName: "Locale") + static let _UINib = TypeReference(module: .uiKit, rawName: "UINib") + static let _UIView = TypeReference(module: .uiKit, rawName: "UIView") + static let _UIImage = TypeReference(module: .uiKit, rawName: "UIImage") + static let _UIStoryboard = TypeReference(module: .uiKit, rawName: "UIStoryboard") + static let _UITableViewCell = TypeReference(module: .uiKit, rawName: "UITableViewCell") + static let _UICollectionViewCell = TypeReference(module: .uiKit, rawName: "UICollectionViewCell") + static let _UICollectionReusableView = TypeReference(module: .uiKit, rawName: "UICollectionReusableView") + static let _UIStoryboardSegue = TypeReference(module: .uiKit, rawName: "UIStoryboardSegue") + static let _UITraitCollection = TypeReference(module: .uiKit, rawName: "UITraitCollection") + static let _UIViewController = TypeReference(module: .uiKit, rawName: "UIViewController") + static let _UIFont = TypeReference(module: .uiKit, rawName: "UIFont") + static let _UIColor = TypeReference(module: .uiKit, rawName: "UIColor") + static let _CGFloat = TypeReference(module: .stdLib, rawName: "CGFloat") + static let _CVarArgType = TypeReference(module: .stdLib, rawName: "CVarArgType...") +} diff --git a/Sources/RswiftResources/Nib.swift b/Sources/RswiftResources/Nib.swift new file mode 100644 index 00000000..c76cfd99 --- /dev/null +++ b/Sources/RswiftResources/Nib.swift @@ -0,0 +1,28 @@ +// +// Nib.swift +// R.swift +// +// Created by Mathijs Kadijk on 09-12-15. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation + +public struct Nib { + public let name: String + public let rootViews: [TypeReference] + public let reusables: [Reusable] + public let usedImageIdentifiers: [NameCatalog] + public let usedColorResources: [NameCatalog] + public let usedAccessibilityIdentifiers: [String] + + public init(name: String, rootViews: [TypeReference], reusables: [Reusable], usedImageIdentifiers: [NameCatalog], usedColorResources: [NameCatalog], usedAccessibilityIdentifiers: [String]) { + self.name = name + self.rootViews = rootViews + self.reusables = reusables + self.usedImageIdentifiers = usedImageIdentifiers + self.usedColorResources = usedColorResources + self.usedAccessibilityIdentifiers = usedAccessibilityIdentifiers + } +} diff --git a/Sources/RswiftResources/Shared/ModuleReference.swift b/Sources/RswiftResources/Shared/ModuleReference.swift new file mode 100644 index 00000000..f12dfa0c --- /dev/null +++ b/Sources/RswiftResources/Shared/ModuleReference.swift @@ -0,0 +1,36 @@ +// +// ModuleReference.swift +// R.swift +// +// Created by Mathijs Kadijk on 11-12-15. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation + +public enum ModuleReference: Hashable { + case host + case stdLib + case custom(name: String) + + var isCustom: Bool { + switch self { + case .custom: + return true + default: + return false + } + } + + public init(name: String?, fallback: ModuleReference = .host) { + let cleaned = name?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + self = cleaned.isEmpty ? fallback : .custom(name: cleaned) + } +} + +extension ModuleReference { + public static var foundation: ModuleReference { .custom(name: "Foundation") } + public static var uiKit: ModuleReference { .custom(name: "UIKit") } + public static var rswift: ModuleReference { .custom(name: "Rswift") } +} diff --git a/Sources/RswiftResources/Shared/NameCatalog.swift b/Sources/RswiftResources/Shared/NameCatalog.swift new file mode 100644 index 00000000..ddffe567 --- /dev/null +++ b/Sources/RswiftResources/Shared/NameCatalog.swift @@ -0,0 +1,27 @@ +// +// NameCatalog.swift +// RswiftCore +// +// Created by Tom Lokhorst on 2020-05-08. +// + +import Foundation + +public struct NameCatalog: Hashable, Comparable { + public let name: String + public let catalog: String? + + public var isSystemCatalog: Bool { + catalog == "System" // for colors + || catalog == "system" // for images + } + + public init(name: String, catalog: String?) { + self.name = name + self.catalog = catalog + } + + static public func < (lhs: NameCatalog, rhs: NameCatalog) -> Bool { + lhs.name < rhs.name + } +} diff --git a/Sources/RswiftResources/Shared/Reusable.swift b/Sources/RswiftResources/Shared/Reusable.swift new file mode 100644 index 00000000..3729fe89 --- /dev/null +++ b/Sources/RswiftResources/Shared/Reusable.swift @@ -0,0 +1,20 @@ +// +// ReusableContainer.swift +// R.swift +// +// Created by Mathijs Kadijk on 10-12-15. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation + +public struct Reusable: Hashable { + public let identifier: String + public let type: TypeReference + + public init(identifier: String, type: TypeReference) { + self.identifier = identifier + self.type = type + } +} diff --git a/Sources/RswiftResources/Shared/TypeReference.swift b/Sources/RswiftResources/Shared/TypeReference.swift new file mode 100644 index 00000000..040f12b4 --- /dev/null +++ b/Sources/RswiftResources/Shared/TypeReference.swift @@ -0,0 +1,20 @@ +// +// TypeReference.swift +// R.swift +// +// Created by Mathijs Kadijk on 10-12-15. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation + +public struct TypeReference: Hashable { + public let module: ModuleReference + public let rawName: String + + public init(module: ModuleReference, rawName: String) { + self.module = module + self.rawName = rawName + } +} From 75920df89b0dab8e62f92b78bb6b565af64fd884 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 24 Jun 2022 20:12:35 +0200 Subject: [PATCH 010/161] Add Storyboard parser --- Sources/RswiftCore/RswiftCore.swift | 17 +- .../Storyboard+Generator.swift | 15 ++ Sources/RswiftParsers/Storyboard+Parser.swift | 191 ++++++++++++++++++ Sources/RswiftResources/Storyboard.swift | 80 ++++++++ 4 files changed, 298 insertions(+), 5 deletions(-) create mode 100644 Sources/RswiftGenerators/Storyboard+Generator.swift create mode 100644 Sources/RswiftParsers/Storyboard+Parser.swift create mode 100644 Sources/RswiftResources/Storyboard.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index c05a12ed..95c73962 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -72,11 +72,18 @@ public struct RswiftCore { // print(image.generateResourceLetCodeString()) // } - let nibs = try urls - .filter { Nib.supportedExtensions.contains($0.pathExtension) } - .map { try Nib.parse(url: $0) } - for nib in nibs { - print(nib.generateResourceLetCodeString()) +// let nibs = try urls +// .filter { Nib.supportedExtensions.contains($0.pathExtension) } +// .map { try Nib.parse(url: $0) } +// for nib in nibs { +// print(nib.generateResourceLetCodeString()) +// } + + let storyboards = try urls + .filter { Storyboard.supportedExtensions.contains($0.pathExtension) } + .map { try Storyboard.parse(url: $0) } + for storyboard in storyboards { + print(storyboard.generateResourceLetCodeString()) } // let resources = try urls diff --git a/Sources/RswiftGenerators/Storyboard+Generator.swift b/Sources/RswiftGenerators/Storyboard+Generator.swift new file mode 100644 index 00000000..7b0ef2de --- /dev/null +++ b/Sources/RswiftGenerators/Storyboard+Generator.swift @@ -0,0 +1,15 @@ +// +// Storyboard+Generator.swift +// +// +// Created by Tom Lokhorst on 2022-06-24. +// + +import Foundation +import RswiftResources + +extension Storyboard { + public func generateResourceLetCodeString() -> String { + "let \(SwiftIdentifier(name: self.name).value) = \(self)" + } +} diff --git a/Sources/RswiftParsers/Storyboard+Parser.swift b/Sources/RswiftParsers/Storyboard+Parser.swift new file mode 100644 index 00000000..811cafbe --- /dev/null +++ b/Sources/RswiftParsers/Storyboard+Parser.swift @@ -0,0 +1,191 @@ +// +// Storyboard.swift +// R.swift +// +// Created by Mathijs Kadijk on 09-12-15. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation +import RswiftResources + + +extension Storyboard: SupportedExtensions { + static public let supportedExtensions: Set = ["storyboard"] + + static public func parse(url: URL) throws -> Storyboard { + let basename = url.deletingPathExtension().lastPathComponent + if basename.isEmpty { + throw ResourceParsingError("Couldn't extract filename from URL: \(url)") + } + + guard let parser = XMLParser(contentsOf: url) else { + throw ResourceParsingError("Couldn't load file at: '\(url)'") + } + + let parserDelegate = StoryboardParserDelegate() + parser.delegate = parserDelegate + + guard parser.parse() else { + throw ResourceParsingError("Invalid XML in file at: '\(url)'") + } + + return Storyboard( + name: basename, + initialViewControllerIdentifier: parserDelegate.initialViewControllerIdentifier, + viewControllers: parserDelegate.viewControllers, + viewControllerPlaceholders: parserDelegate.viewControllerPlaceholders, + usedAccessibilityIdentifiers: parserDelegate.usedAccessibilityIdentifiers, + usedImageIdentifiers: parserDelegate.usedImageIdentifiers, + usedColorResources: parserDelegate.usedColorReferences, + reusables: parserDelegate.reusables + ) + } +} + +private let ElementNameToTypeMapping: [String: TypeReference] = [ + "viewController": TypeReference._UIViewController, + "tableViewCell": TypeReference(module: .uiKit, rawName: "UITableViewCell"), + "tabBarController": TypeReference(module: .uiKit, rawName: "UITabBarController"), + "glkViewController": TypeReference(module: .custom(name: "GLKit"), rawName: "GLKViewController"), + "hostingController": TypeReference(module: .custom(name: "SwiftUI"), rawName: "UIHostingController"), + "pageViewController": TypeReference(module: .uiKit, rawName: "UIPageViewController"), + "tableViewController": TypeReference(module: .uiKit, rawName: "UITableViewController"), + "splitViewController": TypeReference(module: .uiKit, rawName: "UISplitViewController"), + "navigationController": TypeReference(module: .uiKit, rawName: "UINavigationController"), + "avPlayerViewController": TypeReference(module: .custom(name: "AVKit"), rawName: "AVPlayerViewController"), + "collectionViewController": TypeReference(module: .uiKit, rawName: "UICollectionViewController"), + "lookAroundViewController": TypeReference(module: .custom(name: "MapKit"), rawName: "MKLookAroundViewController"), +] + +private class StoryboardParserDelegate: NSObject, XMLParserDelegate { + var initialViewControllerIdentifier: String? + var viewControllers: [Storyboard.ViewController] = [] + var viewControllerPlaceholders: [Storyboard.ViewControllerPlaceholder] = [] + var usedImageIdentifiers: [NameCatalog] = [] + var usedColorReferences: [NameCatalog] = [] + var usedAccessibilityIdentifiers: [String] = [] + var reusables: [Reusable] = [] + + // State + var currentViewController: Storyboard.ViewController? + + @objc func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) { + switch elementName { + case "document": + if let initialViewController = attributeDict["initialViewController"] { + initialViewControllerIdentifier = initialViewController + } + + case "segue": + let customModuleProvider = attributeDict["customModuleProvider"] + let customModule = (customModuleProvider == "target") ? nil : attributeDict["customModule"] + let customClass = attributeDict["customClass"] + let customType = customClass + .map { TypeReference(module: ModuleReference(name: customModule), rawName: $0) } + + if let segueIdentifier = attributeDict["identifier"], + let destination = attributeDict["destination"], + let kind = attributeDict["kind"] + { + let type = customType ?? TypeReference._UIStoryboardSegue + + let segue = Storyboard.Segue(identifier: segueIdentifier, type: type, destination: destination, kind: kind) + currentViewController?.segues.append(segue) + } + + case "image": + if let imageIdentifier = attributeDict["name"] { + usedImageIdentifiers.append(NameCatalog(name: imageIdentifier, catalog: attributeDict["catalog"])) + } + + case "color": + if let colorName = attributeDict["name"] { + usedColorReferences.append(NameCatalog(name: colorName, catalog: attributeDict["catalog"])) + } + + case "accessibility": + if let accessibilityIdentifier = attributeDict["identifier"] { + usedAccessibilityIdentifiers.append(accessibilityIdentifier) + } + + case "userDefinedRuntimeAttribute": + if let accessibilityIdentifier = attributeDict["value"], "accessibilityIdentifier" == attributeDict["keyPath"] && "string" == attributeDict["type"] { + usedAccessibilityIdentifiers.append(accessibilityIdentifier) + } + + case "viewControllerPlaceholder": + if let id = attributeDict["id"] , attributeDict["sceneMemberID"] == "viewController" { + let placeholder = Storyboard.ViewControllerPlaceholder( + id: id, + storyboardName: attributeDict["storyboardName"], + referencedIdentifier: attributeDict["referencedIdentifier"], + bundleIdentifier: attributeDict["bundleIdentifier"] + ) + viewControllerPlaceholders.append(placeholder) + } + + default: + if let viewController = viewControllerFromAttributes(attributeDict, elementName: elementName) { + currentViewController = viewController + } + + if let reusable = reusableFromAttributes(attributeDict, elementName: elementName) { + reusables.append(reusable) + } + } + } + + @objc func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { + // We keep the current view controller open to collect segues until the closing scene: + // + // + // ... + // + // + // + // + if elementName == "scene" { + if let currentViewController = currentViewController { + viewControllers.append(currentViewController) + self.currentViewController = nil + } + } + } + + func viewControllerFromAttributes(_ attributeDict: [String : String], elementName: String) -> Storyboard.ViewController? { + guard let id = attributeDict["id"] , attributeDict["sceneMemberID"] == "viewController" else { + return nil + } + + let storyboardIdentifier = attributeDict["storyboardIdentifier"] + + let customModuleProvider = attributeDict["customModuleProvider"] + let customModule = (customModuleProvider == "target") ? nil : attributeDict["customModule"] + let customClass = attributeDict["customClass"] + let customType = customClass + .map { TypeReference(module: ModuleReference(name: customModule), rawName: $0) } + + let type = customType ?? ElementNameToTypeMapping[elementName] ?? TypeReference._UIViewController + + return Storyboard.ViewController(id: id, storyboardIdentifier: storyboardIdentifier, type: type, segues: []) + } + + func reusableFromAttributes(_ attributeDict: [String : String], elementName: String) -> Reusable? { + guard let reuseIdentifier = attributeDict["reuseIdentifier"] , reuseIdentifier != "" else { + return nil + } + + let customModuleProvider = attributeDict["customModuleProvider"] + let customModule = (customModuleProvider == "target") ? nil : attributeDict["customModule"] + let customClass = attributeDict["customClass"] + let customType = customClass + .map { TypeReference(module: ModuleReference(name: customModule), rawName: $0) } + + let type = customType ?? ElementNameToTypeMapping[elementName] ?? TypeReference._UIView + + return Reusable(identifier: reuseIdentifier, type: type) + } +} + diff --git a/Sources/RswiftResources/Storyboard.swift b/Sources/RswiftResources/Storyboard.swift new file mode 100644 index 00000000..9c3afb28 --- /dev/null +++ b/Sources/RswiftResources/Storyboard.swift @@ -0,0 +1,80 @@ +// +// Storyboard.swift +// R.swift +// +// Created by Mathijs Kadijk on 09-12-15. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation + +public struct Storyboard { + public let name: String + private let initialViewControllerIdentifier: String? + public let viewControllers: [ViewController] + public let viewControllerPlaceholders: [ViewControllerPlaceholder] + public let usedAccessibilityIdentifiers: [String] + public let usedImageIdentifiers: [NameCatalog] + public let usedColorResources: [NameCatalog] + public let reusables: [Reusable] + + public var initialViewController: ViewController? { + viewControllers + .filter { $0.id == self.initialViewControllerIdentifier } + .first + } + + public init(name: String, initialViewControllerIdentifier: String?, viewControllers: [ViewController], viewControllerPlaceholders: [ViewControllerPlaceholder], usedAccessibilityIdentifiers: [String], usedImageIdentifiers: [NameCatalog], usedColorResources: [NameCatalog], reusables: [Reusable]) { + self.name = name + self.initialViewControllerIdentifier = initialViewControllerIdentifier + self.viewControllers = viewControllers + self.viewControllerPlaceholders = viewControllerPlaceholders + self.usedAccessibilityIdentifiers = usedAccessibilityIdentifiers + self.usedImageIdentifiers = usedImageIdentifiers + self.usedColorResources = usedColorResources + self.reusables = reusables + } + + public struct ViewController { + public let id: String + public let storyboardIdentifier: String? + public let type: TypeReference + public var segues: [Segue] + + public init(id: String, storyboardIdentifier: String?, type: TypeReference, segues: [Segue]) { + self.id = id + self.storyboardIdentifier = storyboardIdentifier + self.type = type + self.segues = segues + } + } + + public struct ViewControllerPlaceholder { + public let id: String + public let storyboardName: String? + public let referencedIdentifier: String? + public let bundleIdentifier: String? + + public init(id: String, storyboardName: String?, referencedIdentifier: String?, bundleIdentifier: String?) { + self.id = id + self.storyboardName = storyboardName + self.referencedIdentifier = referencedIdentifier + self.bundleIdentifier = bundleIdentifier + } + } + + public struct Segue { + public let identifier: String + public let type: TypeReference + public let destination: String + public let kind: String + + public init(identifier: String, type: TypeReference, destination: String, kind: String) { + self.identifier = identifier + self.type = type + self.destination = destination + self.kind = kind + } + } +} From 698e6848dcce0abd2e19ab8a9c9c367b897afcb7 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 24 Jun 2022 22:12:17 +0200 Subject: [PATCH 011/161] Add StringParam --- .../Shared/StringParam+Extensions.swift | 238 ++++++++++++++++++ Sources/RswiftParsers/Shared/Unifiable.swift | 42 ++++ .../RswiftResources/Shared/StringParam.swift | 54 ++++ 3 files changed, 334 insertions(+) create mode 100644 Sources/RswiftParsers/Shared/StringParam+Extensions.swift create mode 100644 Sources/RswiftParsers/Shared/Unifiable.swift create mode 100644 Sources/RswiftResources/Shared/StringParam.swift diff --git a/Sources/RswiftParsers/Shared/StringParam+Extensions.swift b/Sources/RswiftParsers/Shared/StringParam+Extensions.swift new file mode 100644 index 00000000..b316de5c --- /dev/null +++ b/Sources/RswiftParsers/Shared/StringParam+Extensions.swift @@ -0,0 +1,238 @@ +// +// StringParam.swift +// R.swift +// +// Created by Tom Lokhorst on 2016-04-18. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// +// Parts of the content of this file are loosly based on StringsFileParser.swift from SwiftGen/GenumKit. +// We don't feel this is a "substantial portion of the Software" so are not including their MIT license, +// eventhough we would like to give credit where credit is due by referring to SwiftGen thanking Olivier +// Halligon for creating SwiftGen and GenumKit. +// +// See: https://github.com/AliSoftware/SwiftGen/blob/master/GenumKit/Parsers/StringsFileParser.swift +// + +import Foundation +import RswiftResources + +extension StringParam: Unifiable { + public func unify(_ other: StringParam) -> StringParam? { + if let name = name, let otherName = other.name , name != otherName { + return nil + } + + if let spec = spec.unify(other.spec) { + return StringParam(name: name ?? other.name, spec: spec) + } + + return nil + } +} + +extension FormatPart: Unifiable { + static public func formatParts(formatString: String) -> [FormatPart] { + createFormatParts(formatString) + } + + public func unify(_ other: FormatPart) -> FormatPart? { + switch (self, other) { + case let (.spec(l), .spec(r)): + if let spec = l.unify(r) { + return .spec(spec) + } + else { + return nil + } + + case let (.reference(l), .reference(r)) where l == r: + return .reference(l) + + default: + return nil + } + } +} + +extension FormatSpecifier { + var type: TypeReference { + switch self { + case .object: + return ._String + case .double: + return ._Double + case .int: + return ._Int + case .uInt: + return ._UInt + case .character: + return ._Character + case .cStringPointer: + return ._CStringPointer + case .voidPointer: + return ._VoidPointer + case .topType: + return ._Any + } + } +} + +extension FormatSpecifier : Unifiable { + + // Convenience initializer, uses last character of string, + // ignoring lengt modifiers, e.g. "lld" + public init?(formatString string: String) { + guard let last = string.last else { + return nil + } + + self.init(formatChar: last) + } + + public init?(formatChar char: Swift.Character) { + let lcChar = Swift.String(char).lowercased().first! + switch lcChar { + case "@": + self = .object + case "a", "e", "f", "g": + self = .double + case "d", "i": + self = .int + case "o", "u", "x": + self = .uInt + case "c": + self = .character + case "s": + self = .cStringPointer + case "p": + self = .voidPointer + default: + return nil + } + } + + public func unify(_ other: FormatSpecifier) -> FormatSpecifier? { + if self == .topType { + return other + } + + if other == .topType { + return self + } + + if self == other { + return self + } + + return nil + } +} + +private let referenceRegEx: NSRegularExpression = { + do { + return try NSRegularExpression(pattern: "#@([^@]+)@", options: [.caseInsensitive]) + } catch { + fatalError("Error building the regular expression used to match reference") + } +}() + +private let formatTypesRegEx: NSRegularExpression = { + let pattern_int = "(?:h|hh|l|ll|q|z|t|j)?([dioux])" // %d/%i/%o/%u/%x with their optional length modifiers like in "%lld" + let pattern_float = "[aefg]" + let position = "([1-9]\\d*\\$)?" // like in "%3$" to make positional specifiers + let precision = "[-+]?\\d*(?:\\.\\d*)?" // precision like in "%1.2f" or "%012.10" + let reference = "#@([^@]+)@" // reference to NSStringFormatSpecType in .stringsdict + do { + return try NSRegularExpression(pattern: "(? [.Spec(.Int), .Spec(.String), .Reference("named")] +private func createFormatParts(_ formatString: String) -> [FormatPart] { + let nsString = formatString as NSString + let range = NSRange(location: 0, length: nsString.length) + + // Extract the list of chars (conversion specifiers) and their optional positional specifier + let chars = formatTypesRegEx.matches(in: formatString, options: [], range: range).map { match -> (String, Int?) in + let range: NSRange + if match.range(at: 3).location != NSNotFound { + // [dioux] are in range #3 because in #2 there may be length modifiers (like in "lld") + range = match.range(at: 3) + } else { + // otherwise, no length modifier, the conversion specifier is in #2 + range = match.range(at: 2) + } + let char = nsString.substring(with: range) + + let posRange = match.range(at: 1) + if posRange.location == NSNotFound { + // No positional specifier + return (char, nil) + } else { + // Remove the "$" at the end of the positional specifier, and convert to Int + let posRange1 = NSRange(location: posRange.location, length: posRange.length-1) + let pos = nsString.substring(with: posRange1) + return (char, Int(pos)) + } + } + + // Build up params array + var params = [FormatPart]() + var nextNonPositional = 1 + for (str, pos) in chars { + let insertionPos: Int + if let pos = pos { + insertionPos = pos + } + else { + insertionPos = nextNonPositional + nextNonPositional += 1 + } + + let param: FormatPart? + + if let reference = referenceRegEx.firstSubstring(input: str) { + param = FormatPart.reference(reference) + } + else if let char = str.first, let fs = FormatSpecifier(formatChar: char) + { + param = FormatPart.spec(fs) + } + else { + param = nil + } + + if let param = param { + if insertionPos > 0 { + while params.count <= insertionPos - 1 { + params.append(FormatPart.spec(FormatSpecifier.topType)) + } + + params[insertionPos - 1] = param + } + } + } + + return params +} + +extension NSRegularExpression { + fileprivate func firstSubstring(input: String) -> String? { + let nsInput = input as NSString + let inputRange = NSMakeRange(0, nsInput.length) + + guard let match = self.firstMatch(in: input, options: [], range: inputRange) else { + return nil + } + + guard match.numberOfRanges > 0 else { + return nil + } + + let range = match.range(at: 1) + return nsInput.substring(with: range) + } +} diff --git a/Sources/RswiftParsers/Shared/Unifiable.swift b/Sources/RswiftParsers/Shared/Unifiable.swift new file mode 100644 index 00000000..f0e690ff --- /dev/null +++ b/Sources/RswiftParsers/Shared/Unifiable.swift @@ -0,0 +1,42 @@ +// +// Unifiable.swift +// R.swift +// +// Created by Tom Lokhorst on 2016-04-30. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation + +public protocol Unifiable { + func unify(_ other: Self) -> Self? +} + +extension Array where Element : Unifiable { + public func unify(_ other: [Element]) -> [Element]? { + var result = self + + for (ix, right) in other.enumerated() { + if let left = result[safe: ix] { + if let unified = left.unify(right) { + result[ix] = unified + } + else { + return nil + } + } + else { + result.append(right) + } + } + + return result + } +} + +private extension Array { + subscript (safe index: Int) -> Element? { + indices ~= index ? self[index] : nil + } +} diff --git a/Sources/RswiftResources/Shared/StringParam.swift b/Sources/RswiftResources/Shared/StringParam.swift new file mode 100644 index 00000000..c372088b --- /dev/null +++ b/Sources/RswiftResources/Shared/StringParam.swift @@ -0,0 +1,54 @@ +// +// StringParam.swift +// R.swift +// +// Created by Tom Lokhorst on 2016-04-18. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// +// Parts of the content of this file are loosly based on StringsFileParser.swift from SwiftGen/GenumKit. +// We don't feel this is a "substantial portion of the Software" so are not including their MIT license, +// eventhough we would like to give credit where credit is due by referring to SwiftGen thanking Olivier +// Halligon for creating SwiftGen and GenumKit. +// +// See: https://github.com/AliSoftware/SwiftGen/blob/master/GenumKit/Parsers/StringsFileParser.swift +// + +import Foundation + +public struct StringParam: Equatable { + public let name: String? + public let spec: FormatSpecifier + + public init(name: String?, spec: FormatSpecifier) { + self.name = name + self.spec = spec + } +} + +public enum FormatPart { + case spec(FormatSpecifier) + case reference(String) + + public var formatSpecifier: FormatSpecifier? { + switch self { + case .spec(let formatSpecifier): + return formatSpecifier + + case .reference: + return nil + } + } +} + +// https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html#//apple_ref/doc/uid/TP40004265-SW1 +public enum FormatSpecifier { + case object + case double + case int + case uInt + case character + case cStringPointer + case voidPointer + case topType +} From 6d73d40ae5021646ab68181083f23436f8548eba Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 24 Jun 2022 22:47:19 +0200 Subject: [PATCH 012/161] Add LocalizableStrings --- Sources/RswiftCore/RswiftCore.swift | 18 +- .../LocalizableStrings+Generator.swift | 15 ++ .../LocalizableStrings+Parser.swift | 183 ++++++++++++++++++ .../RswiftResources/LocalizableStrings.swift | 33 ++++ .../Shared/LocaleReference.swift | 70 +++++++ 5 files changed, 314 insertions(+), 5 deletions(-) create mode 100644 Sources/RswiftGenerators/LocalizableStrings+Generator.swift create mode 100644 Sources/RswiftParsers/LocalizableStrings+Parser.swift create mode 100644 Sources/RswiftResources/LocalizableStrings.swift create mode 100644 Sources/RswiftResources/Shared/LocaleReference.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 95c73962..2276487d 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -79,11 +79,19 @@ public struct RswiftCore { // print(nib.generateResourceLetCodeString()) // } - let storyboards = try urls - .filter { Storyboard.supportedExtensions.contains($0.pathExtension) } - .map { try Storyboard.parse(url: $0) } - for storyboard in storyboards { - print(storyboard.generateResourceLetCodeString()) +// let storyboards = try urls +// .filter { Storyboard.supportedExtensions.contains($0.pathExtension) } +// .map { try Storyboard.parse(url: $0) } +// for storyboard in storyboards { +// print(storyboard.generateResourceLetCodeString()) +// } + + + let localizableStringses = try urls + .filter { LocalizableStrings.supportedExtensions.contains($0.pathExtension) } + .map { try LocalizableStrings.parse(url: $0) } + for localizableStrings in localizableStringses { + print(localizableStrings.generateResourceLetCodeString()) } // let resources = try urls diff --git a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift new file mode 100644 index 00000000..ab8f9d85 --- /dev/null +++ b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift @@ -0,0 +1,15 @@ +// +// LocalizableStrings+Generator.swift +// +// +// Created by Tom Lokhorst on 2022-06-24. +// + +import Foundation +import RswiftResources + +extension LocalizableStrings { + public func generateResourceLetCodeString() -> String { + "let \(SwiftIdentifier(name: self.filename).value) = \(self)" + } +} diff --git a/Sources/RswiftParsers/LocalizableStrings+Parser.swift b/Sources/RswiftParsers/LocalizableStrings+Parser.swift new file mode 100644 index 00000000..1c8fa7e1 --- /dev/null +++ b/Sources/RswiftParsers/LocalizableStrings+Parser.swift @@ -0,0 +1,183 @@ +// +// LocalizableStrings.swift +// R.swift +// +// Created by Tom Lokhorst on 2016-04-24. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation +import RswiftResources + +extension LocalizableStrings: SupportedExtensions { + static public let supportedExtensions: Set = ["strings", "stringsdict"] + + static public func parse(url: URL) throws -> LocalizableStrings { + let basename = url.deletingPathExtension().lastPathComponent + if basename.isEmpty { + throw ResourceParsingError("Couldn't extract filename from URL: \(url)") + } + + // Get locale from url (second to last component) + let locale = LocaleReference(url: url) + + // Check to make sure url can be parsed as a dictionary + guard let nsDictionary = NSDictionary(contentsOf: url) else { + throw ResourceParsingError("File could not be parsed as a strings file: \(url.absoluteString)") + } + + // Parse dicts from NSDictionary + let dictionary: [LocalizableStrings.Key: LocalizableStrings.Value] + switch url.pathExtension { + case "strings": + dictionary = try parseStrings(nsDictionary, source: locale.withFilename("\(basename).strings")) + case "stringsdict": + dictionary = try parseStringsdict(nsDictionary, source: locale.withFilename("\(basename).stringsdict")) + default: + throw ResourceParsingError("File could not be parsed as a strings file: \(url.absoluteString)") + } + + return LocalizableStrings(filename: basename, locale: locale, dictionary: dictionary) + } +} + +private extension LocaleReference { + func withFilename(_ filename: String) -> String { + switch self { + case .none: + return "'\(filename)'" + case .base: + return "'\(filename)' (Base)" + case .language(let language): + return "'\(filename)' (\(language))" + } + } +} + +private func parseStrings(_ nsDictionary: NSDictionary, source: String) throws -> [LocalizableStrings.Key: LocalizableStrings.Value] { + var dictionary: [LocalizableStrings.Key: LocalizableStrings.Value] = [:] + + for (key, obj) in nsDictionary { + if let + key = key as? String, + let val = obj as? String + { + var params: [StringParam] = [] + + for part in FormatPart.formatParts(formatString: val) { + switch part { + case .reference: + throw ResourceParsingError("Non-specifier reference in \(source): \(key) = \(val)") + + case .spec(let formatSpecifier): + params.append(StringParam(name: nil, spec: formatSpecifier)) + } + } + + + dictionary[key] = .init(params: params, commentValue: val) + } + else { + throw ResourceParsingError("Non-string value in \(source): \(key) = \(obj)") + } + } + + return dictionary +} + +private func parseStringsdict(_ nsDictionary: NSDictionary, source: String) throws -> [LocalizableStrings.Key: LocalizableStrings.Value] { + var dictionary: [LocalizableStrings.Key: LocalizableStrings.Value] = [:] + + for (key, obj) in nsDictionary { + if let + key = key as? String, + let dict = obj as? [String: AnyObject] + { + guard let localizedFormat = dict["NSStringLocalizedFormatKey"] as? String else { + continue + } + + do { + let params = try parseStringsdictParams(localizedFormat, dict: dict) + dictionary[key] = .init(params: params, commentValue: localizedFormat) + } catch let error as ResourceParsingError { +// warn("\(error) in '\(key)' \(source)") + } + } + else { + throw ResourceParsingError("Non-dict value in \(source): \(key) = \(obj)") + } + } + + return dictionary +} + +private func parseStringsdictParams(_ format: String, dict: [String: AnyObject]) throws -> [StringParam] { + var params: [StringParam] = [] + + let parts = FormatPart.formatParts(formatString: format) + for part in parts { + switch part { + case .reference(let reference): + params += try lookup(key: reference, in: dict) + + case .spec(let formatSpecifier): + params.append(StringParam(name: nil, spec: formatSpecifier)) + } + } + + return params +} + +private func lookup(key: String, in dict: [String: AnyObject], processedReferences: [String] = []) throws -> [StringParam] { + var processedReferences = processedReferences + + if processedReferences.contains(key) { + throw ResourceParsingError("Cyclic reference '\(key)'") + } + + processedReferences.append(key) + + guard let obj = dict[key], let nested = obj as? [String: AnyObject] else { + throw ResourceParsingError("Missing reference '\(key)'") + } + + guard let formatSpecType = nested["NSStringFormatSpecTypeKey"] as? String, + let formatValueType = nested["NSStringFormatValueTypeKey"] as? String + , formatSpecType == "NSStringPluralRuleType" + else { + throw ResourceParsingError("Incorrect reference '\(key)'") + } + guard let formatSpecifier = FormatSpecifier(formatString: formatValueType) + else { + throw ResourceParsingError("Incorrect reference format specifier \"\(formatValueType)\" for '\(key)'") + } + + var results = [StringParam(name: nil, spec: formatSpecifier)] + + let stringValues = nested.values.compactMap { $0 as? String }.sorted() + + for stringValue in stringValues { + var alternative: [StringParam] = [] + let parts = FormatPart.formatParts(formatString: stringValue) + for part in parts { + switch part { + case .reference(let reference): + alternative += try lookup(key: reference, in: dict, processedReferences: processedReferences) + + case .spec(let formatSpecifier): + alternative.append(StringParam(name: key, spec: formatSpecifier)) + } + } + + if let unified = results.unify(alternative) { + results = unified + } + else { + throw ResourceParsingError("Can't unify '\(key)'") + } + } + + return results +} diff --git a/Sources/RswiftResources/LocalizableStrings.swift b/Sources/RswiftResources/LocalizableStrings.swift new file mode 100644 index 00000000..1e79cf1a --- /dev/null +++ b/Sources/RswiftResources/LocalizableStrings.swift @@ -0,0 +1,33 @@ +// +// LocalizableStrings.swift +// R.swift +// +// Created by Tom Lokhorst on 2016-04-24. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation + +public struct LocalizableStrings { + public typealias Key = String + public struct Value { + public let params: [StringParam] + public let commentValue: String + + public init(params: [StringParam], commentValue: String) { + self.params = params + self.commentValue = commentValue + } + } + + public let filename: String + public let locale: LocaleReference + public let dictionary: [Key: Value] + + public init(filename: String, locale: LocaleReference, dictionary: [Key: Value]) { + self.filename = filename + self.locale = locale + self.dictionary = dictionary + } +} diff --git a/Sources/RswiftResources/Shared/LocaleReference.swift b/Sources/RswiftResources/Shared/LocaleReference.swift new file mode 100644 index 00000000..e1022cb9 --- /dev/null +++ b/Sources/RswiftResources/Shared/LocaleReference.swift @@ -0,0 +1,70 @@ +// +// Locale.swift +// R.swift +// +// Created by Tom Lokhorst on 2016-04-24. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation + +public enum LocaleReference: Hashable { + case none + case base // Older projects use a "Base" locale + case language(String) + + public var isNone: Bool { + if case .none = self { + return true + } + + return false + } + + public var isBase: Bool { + if case .base = self { + return true + } + + return false + } + + public var language: String? { + if case .language(let language) = self { + return language + } + + return nil + } +} + +extension LocaleReference { + public init(url: URL) { + if let localeComponent = url.pathComponents.dropLast().last , localeComponent.hasSuffix(".lproj") { + let lang = localeComponent.replacingOccurrences(of: ".lproj", with: "") + + if lang == "Base" { + self = .base + } else { + self = .language(lang) + } + } + else { + self = .none + } + } + + public var localeDescription: String? { + switch self { + case .none: + return nil + + case .base: + return "Base" + + case .language(let language): + return language + } + } +} From 75f471e9a45dfd9197743db5017b43dd90e92b8f Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sat, 9 Jul 2022 22:46:28 +0200 Subject: [PATCH 013/161] Add AssetCatalog parser --- Package.swift | 2 +- Sources/RswiftCore/RswiftCore.swift | 46 +----- .../RswiftParsers/AssetCatalog+Parser.swift | 135 ++++++++++++++++++ .../LocalizableStrings+Parser.swift | 4 +- Sources/RswiftParsers/Nib+Parser.swift | 3 +- .../RswiftParsers/ResourceFile+Parser.swift | 3 +- Sources/RswiftParsers/Storyboard+Parser.swift | 3 +- Sources/RswiftResources/AssetCatalog.swift | 39 +++++ 8 files changed, 187 insertions(+), 48 deletions(-) create mode 100644 Sources/RswiftParsers/AssetCatalog+Parser.swift create mode 100644 Sources/RswiftResources/AssetCatalog.swift diff --git a/Package.swift b/Package.swift index f6ae3545..16d2f0df 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,7 @@ import PackageDescription let package = Package( name: "rswift", platforms: [ - .macOS(.v10_11) + .macOS(.v10_15) ], products: [ .executable(name: "rswift", targets: ["rswift"]), diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 2276487d..4d915241 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -58,48 +58,16 @@ public struct RswiftCore { let urls = paths .map { $0.url(with: urlForSourceTreeFolder) } -// let fonts = try urls -// .filter { Font.supportedExtensions.contains($0.pathExtension) } -// .map { try Font.parse(url: $0) } -// for font in fonts { -// print(font.generateResourceLetCodeString()) -// } -// let images = try urls -// .filter { Image.supportedExtensions.contains($0.pathExtension) } -// .map { try Image.parse(url: $0) } -// for image in images { -// print(image.generateResourceLetCodeString()) -// } - -// let nibs = try urls -// .filter { Nib.supportedExtensions.contains($0.pathExtension) } -// .map { try Nib.parse(url: $0) } -// for nib in nibs { -// print(nib.generateResourceLetCodeString()) -// } - -// let storyboards = try urls -// .filter { Storyboard.supportedExtensions.contains($0.pathExtension) } -// .map { try Storyboard.parse(url: $0) } -// for storyboard in storyboards { -// print(storyboard.generateResourceLetCodeString()) -// } - - - let localizableStringses = try urls - .filter { LocalizableStrings.supportedExtensions.contains($0.pathExtension) } - .map { try LocalizableStrings.parse(url: $0) } - for localizableStrings in localizableStringses { - print(localizableStrings.generateResourceLetCodeString()) + let catalogs = try urls + .filter { AssetCatalog.supportedExtensions.contains($0.pathExtension) } +// .filter { $0.lastPathComponent == "Images2.xcassets" } +// .reversed().prefix(1) // DEVELOP + .map { try AssetCatalog.parse(url: $0) } + for catalog in catalogs { + print("RSWIFTCORE", catalog) } -// let resources = try urls -// .map { try ResourceFile.parse(url: $0) } -// for resource in resources { -// print(resource.generateResourceLetCodeString()) -// } - print() } diff --git a/Sources/RswiftParsers/AssetCatalog+Parser.swift b/Sources/RswiftParsers/AssetCatalog+Parser.swift new file mode 100644 index 00000000..5f55e5cd --- /dev/null +++ b/Sources/RswiftParsers/AssetCatalog+Parser.swift @@ -0,0 +1,135 @@ +// +// AssetFolder.swift +// R.swift +// +// Created by Mathijs Kadijk on 09-12-15. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation +import RswiftResources +import CoreGraphics + + +extension AssetCatalog: SupportedExtensions { + static public let supportedExtensions: Set = ["xcassets"] + + static public func parse(url: URL) throws -> AssetCatalog { + + guard let basename = url.filenameWithoutExtension else { + throw ResourceParsingError("Couldn't extract filename from URL: \(url)") + } + + return AssetCatalog(filename: basename, root: try parseNamespace(url: url).namespace) + } + + static private func parseNamespace(url: URL) throws -> NamespaceDirectory { + let fileManager = FileManager.default + func errorHandler(_ url: URL, _ error: Error) -> Bool { + assertionFailure((error as NSError).debugDescription) + return true + } + guard let directoryEnumerator = fileManager.enumerator(at: url, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles, .producesRelativePathURLs], errorHandler: errorHandler) else { + throw ResourceParsingError("Supposed AssetCatalog \(url) can't be enumerated") + } + + let root = NamespaceDirectory() + var namespaces: [URL: NamespaceDirectory] = [URL(fileURLWithPath: ".", relativeTo: url): root] + + for case let fileURL as URL in directoryEnumerator { + guard fileURL.baseURL == url else { + throw ResourceParsingError("File \(fileURL) is not in AssetCatalog \(url)") + } + + let resourceValues = try fileURL.resourceValues(forKeys: [.isDirectoryKey]) + let isDirectory = resourceValues.isDirectory! + + let name = fileURL.lastPathComponent + let pathExtension = fileURL.pathExtension + + var parentURL = URL(fileURLWithPath: fileURL.relativePath, relativeTo: url).deletingLastPathComponent() + var parent: NamespaceDirectory? = namespaces[parentURL] + for _ in 0.. Bool { + let decoder = JSONDecoder() + guard + let contentsFile = URL(string: "Contents.json", relativeTo: directory), + let contentsData = try? Data(contentsOf: contentsFile), + let contents = try? decoder.decode(ContentsJson.self, from: contentsData) + else { return false } + + return contents.properties.providesNamespace +} + + +// Note: "appiconset" is not loadable by default, so it's not included here +private let imageExtensions: Set = ["launchimage", "imageset", "imagestack", "symbolset"] + +private let colorExtensions: Set = ["colorset"] + +// Ignore everything in folders with these extensions +private let ignoredExtensions: Set = ["brandassets", "imagestacklayer", "appiconset"] + +private class NamespaceDirectory: CustomDebugStringConvertible { + var subnamespaces: [String: NamespaceDirectory] = [:] + var colors: [String] = [] + var images: [String] = [] + var files: [String] = [] + + var namespace: AssetCatalog.Namespace { + AssetCatalog.Namespace( + subnamespaces: subnamespaces.mapValues(\.namespace), + colors: colors, + images: images, + files: files + ) + } + + var debugDescription: String { + "Directory(subnamespaces: \(subnamespaces), files: \(files))" + } +} + +private struct ContentsJson: Decodable { + let properties: Properties + + struct Properties: Decodable { + let providesNamespace: Bool + + enum CodingKeys: String, CodingKey { + case providesNamespace = "provides-namespace" + } + } +} diff --git a/Sources/RswiftParsers/LocalizableStrings+Parser.swift b/Sources/RswiftParsers/LocalizableStrings+Parser.swift index 1c8fa7e1..42bad2ef 100644 --- a/Sources/RswiftParsers/LocalizableStrings+Parser.swift +++ b/Sources/RswiftParsers/LocalizableStrings+Parser.swift @@ -14,8 +14,7 @@ extension LocalizableStrings: SupportedExtensions { static public let supportedExtensions: Set = ["strings", "stringsdict"] static public func parse(url: URL) throws -> LocalizableStrings { - let basename = url.deletingPathExtension().lastPathComponent - if basename.isEmpty { + guard let basename = url.filenameWithoutExtension else { throw ResourceParsingError("Couldn't extract filename from URL: \(url)") } @@ -102,6 +101,7 @@ private func parseStringsdict(_ nsDictionary: NSDictionary, source: String) thro let params = try parseStringsdictParams(localizedFormat, dict: dict) dictionary[key] = .init(params: params, commentValue: localizedFormat) } catch let error as ResourceParsingError { + // TODO: Log warning // warn("\(error) in '\(key)' \(source)") } } diff --git a/Sources/RswiftParsers/Nib+Parser.swift b/Sources/RswiftParsers/Nib+Parser.swift index a0c633ab..e8b139cd 100644 --- a/Sources/RswiftParsers/Nib+Parser.swift +++ b/Sources/RswiftParsers/Nib+Parser.swift @@ -15,8 +15,7 @@ extension Nib: SupportedExtensions { static public let supportedExtensions: Set = ["xib"] static public func parse(url: URL) throws -> Nib { - let basename = url.deletingPathExtension().lastPathComponent - if basename.isEmpty { + guard let basename = url.filenameWithoutExtension else { throw ResourceParsingError("Couldn't extract filename from URL: \(url)") } diff --git a/Sources/RswiftParsers/ResourceFile+Parser.swift b/Sources/RswiftParsers/ResourceFile+Parser.swift index 50a6c27c..f5094345 100644 --- a/Sources/RswiftParsers/ResourceFile+Parser.swift +++ b/Sources/RswiftParsers/ResourceFile+Parser.swift @@ -21,8 +21,7 @@ extension ResourceFile { // .reduce([]) { $0.union($1) } static public func parse(url: URL) throws -> ResourceFile { - let basename = url.deletingPathExtension().lastPathComponent - if basename.isEmpty { + guard let basename = url.filenameWithoutExtension else { throw ResourceParsingError("Couldn't extract filename from URL: \(url)") } diff --git a/Sources/RswiftParsers/Storyboard+Parser.swift b/Sources/RswiftParsers/Storyboard+Parser.swift index 811cafbe..e6d68db3 100644 --- a/Sources/RswiftParsers/Storyboard+Parser.swift +++ b/Sources/RswiftParsers/Storyboard+Parser.swift @@ -15,8 +15,7 @@ extension Storyboard: SupportedExtensions { static public let supportedExtensions: Set = ["storyboard"] static public func parse(url: URL) throws -> Storyboard { - let basename = url.deletingPathExtension().lastPathComponent - if basename.isEmpty { + guard let basename = url.filenameWithoutExtension else { throw ResourceParsingError("Couldn't extract filename from URL: \(url)") } diff --git a/Sources/RswiftResources/AssetCatalog.swift b/Sources/RswiftResources/AssetCatalog.swift new file mode 100644 index 00000000..89d936c7 --- /dev/null +++ b/Sources/RswiftResources/AssetCatalog.swift @@ -0,0 +1,39 @@ +// +// AssetCatalog.swift +// RswiftResources +// +// Created by Tom Lokhorst on 2021-06-13. +// + +import Foundation + +public struct AssetCatalog { + public let filename: String + public let root: Namespace + + public init(filename: String, root: Namespace) { + self.filename = filename + self.root = root + } +} + +extension AssetCatalog { + public struct Namespace { + public var subnamespaces: [String: Namespace] = [:] + public var colors: [String] = [] + public var images: [String] = [] + public var files: [String] = [] + + public init( + subnamespaces: [String: Namespace], + colors: [String], + images: [String], + files: [String] + ) { + self.subnamespaces = subnamespaces + self.colors = colors + self.images = images + self.files = files + } + } +} From 09f701fab7f8ad3a54f657548cd6ff17cccc5b79 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 10 Jul 2022 14:05:12 +0200 Subject: [PATCH 014/161] Parse on demand resource tags --- Sources/RswiftCore/RswiftCore.swift | 2 + .../RswiftParsers/AssetCatalog+Parser.swift | 136 ++++++++++++------ Sources/RswiftParsers/Image+Parser.swift | 4 +- Sources/RswiftResources/AssetCatalog.swift | 32 ++++- Sources/RswiftResources/Image.swift | 6 +- 5 files changed, 123 insertions(+), 57 deletions(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 4d915241..85701bec 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -59,6 +59,7 @@ public struct RswiftCore { .map { $0.url(with: urlForSourceTreeFolder) } + let start = Date() let catalogs = try urls .filter { AssetCatalog.supportedExtensions.contains($0.pathExtension) } // .filter { $0.lastPathComponent == "Images2.xcassets" } @@ -68,6 +69,7 @@ public struct RswiftCore { print("RSWIFTCORE", catalog) } + print("TOTAL", Date().timeIntervalSince(start)) print() } diff --git a/Sources/RswiftParsers/AssetCatalog+Parser.swift b/Sources/RswiftParsers/AssetCatalog+Parser.swift index 5f55e5cd..40812010 100644 --- a/Sources/RswiftParsers/AssetCatalog+Parser.swift +++ b/Sources/RswiftParsers/AssetCatalog+Parser.swift @@ -11,6 +11,14 @@ import Foundation import RswiftResources import CoreGraphics +// Note: "appiconset" is not loadable by default, so it's not included here +private let imageExtensions: Set = ["launchimage", "imageset", "imagestack", "symbolset"] + +private let colorExtensions: Set = ["colorset"] +private let datasetExtensions: Set = ["dataset"] + +// Ignore everything in folders with these extensions +private let ignoredExtensions: Set = ["brandassets", "imagestacklayer", "appiconset"] extension AssetCatalog: SupportedExtensions { static public let supportedExtensions: Set = ["xcassets"] @@ -21,55 +29,63 @@ extension AssetCatalog: SupportedExtensions { throw ResourceParsingError("Couldn't extract filename from URL: \(url)") } - return AssetCatalog(filename: basename, root: try parseNamespace(url: url).namespace) + let directory = try parseDirectory(catalogURL: url) + let namespace = try createNamespace(directory: directory, path: []) + + return AssetCatalog(filename: basename, root: namespace) } - static private func parseNamespace(url: URL) throws -> NamespaceDirectory { + static private func parseDirectory(catalogURL: URL) throws -> NamespaceDirectory { let fileManager = FileManager.default func errorHandler(_ url: URL, _ error: Error) -> Bool { assertionFailure((error as NSError).debugDescription) return true } - guard let directoryEnumerator = fileManager.enumerator(at: url, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles, .producesRelativePathURLs], errorHandler: errorHandler) else { - throw ResourceParsingError("Supposed AssetCatalog \(url) can't be enumerated") + guard let directoryEnumerator = fileManager.enumerator(at: catalogURL, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles, .producesRelativePathURLs], errorHandler: errorHandler) else { + throw ResourceParsingError("Supposed AssetCatalog \(catalogURL) can't be enumerated") } let root = NamespaceDirectory() - var namespaces: [URL: NamespaceDirectory] = [URL(fileURLWithPath: ".", relativeTo: url): root] + var namespaces: [URL: NamespaceDirectory] = [URL(fileURLWithPath: ".", relativeTo: catalogURL): root] for case let fileURL as URL in directoryEnumerator { - guard fileURL.baseURL == url else { - throw ResourceParsingError("File \(fileURL) is not in AssetCatalog \(url)") + guard fileURL.baseURL == catalogURL else { + throw ResourceParsingError("File \(fileURL) is not in AssetCatalog \(catalogURL)") } let resourceValues = try fileURL.resourceValues(forKeys: [.isDirectoryKey]) let isDirectory = resourceValues.isDirectory! - let name = fileURL.lastPathComponent + guard let filename = fileURL.filenameWithoutExtension else { + throw ResourceParsingError("Missing filename in \(fileURL)") + } let pathExtension = fileURL.pathExtension - var parentURL = URL(fileURLWithPath: fileURL.relativePath, relativeTo: url).deletingLastPathComponent() - var parent: NamespaceDirectory? = namespaces[parentURL] + let relativeURL = URL(fileURLWithPath: fileURL.relativePath, relativeTo: catalogURL) + var parentURL = relativeURL + var parent: NamespaceDirectory? for _ in 0.. Bool { - let decoder = JSONDecoder() - guard - let contentsFile = URL(string: "Contents.json", relativeTo: directory), - let contentsData = try? Data(contentsOf: contentsFile), - let contents = try? decoder.decode(ContentsJson.self, from: contentsData) - else { return false } - - return contents.properties.providesNamespace -} - + static private func createNamespace(directory: NamespaceDirectory, path: [String]) throws -> Namespace { -// Note: "appiconset" is not loadable by default, so it's not included here -private let imageExtensions: Set = ["launchimage", "imageset", "imagestack", "symbolset"] + var subnamespaces: [String: AssetCatalog.Namespace] = [:] + for (name, directory) in directory.subnamespaces { + subnamespaces[name] = try createNamespace(directory: directory, path: path + [name]) + } -private let colorExtensions: Set = ["colorset"] + var colors: [AssetCatalog.Color] = [] + for fileURL in directory.colors { + colors.append(.init(name: fileURL.filenameWithoutExtension!)) + } -// Ignore everything in folders with these extensions -private let ignoredExtensions: Set = ["brandassets", "imagestacklayer", "appiconset"] + var images: [Image] = [] + for fileURL in directory.images { + let tags = onDemandResourceTags(directory: fileURL) + images.append(.init(name: fileURL.filenameWithoutExtension!, onDemandResourceTags: tags)) + } -private class NamespaceDirectory: CustomDebugStringConvertible { - var subnamespaces: [String: NamespaceDirectory] = [:] - var colors: [String] = [] - var images: [String] = [] - var files: [String] = [] + var dataAssets: [AssetCatalog.DataAsset] = [] + for fileURL in directory.dataAssets { + let tags = onDemandResourceTags(directory: fileURL) + dataAssets.append(.init(name: fileURL.filenameWithoutExtension!, onDemandResourceTags: tags)) + } - var namespace: AssetCatalog.Namespace { - AssetCatalog.Namespace( - subnamespaces: subnamespaces.mapValues(\.namespace), + return AssetCatalog.Namespace( + subnamespaces: subnamespaces, colors: colors, images: images, - files: files + dataAssets: dataAssets ) } +} + +private class NamespaceDirectory: CustomDebugStringConvertible { + var subnamespaces: [String: NamespaceDirectory] = [:] + var colors: [URL] = [] + var images: [URL] = [] + var dataAssets: [URL] = [] var debugDescription: String { - "Directory(subnamespaces: \(subnamespaces), files: \(files))" + "Directory(subnamespaces: \(subnamespaces), images: \(images)" } } +private func providesNamespace(directory: URL) -> Bool { + guard + let contents = try? ContentsJson.parse(directory: directory), + let providesNamespace = contents.properties.providesNamespace + else { return false } + + return providesNamespace +} + +private func onDemandResourceTags(directory: URL) -> [String]? { + guard + let contents = try? ContentsJson.parse(directory: directory) + else { return nil } + + return contents.properties.onDemandResourceTags +} + private struct ContentsJson: Decodable { let properties: Properties struct Properties: Decodable { - let providesNamespace: Bool + let providesNamespace: Bool? + let onDemandResourceTags: [String]? enum CodingKeys: String, CodingKey { case providesNamespace = "provides-namespace" + case onDemandResourceTags = "on-demand-resource-tags" } } + + static func parse(directory: URL) throws -> ContentsJson { + let decoder = JSONDecoder() + let contentsFile = URL(string: "Contents.json", relativeTo: directory)! + let contentsData = try Data(contentsOf: contentsFile) + let contents = try decoder.decode(ContentsJson.self, from: contentsData) + + return contents + } } diff --git a/Sources/RswiftParsers/Image+Parser.swift b/Sources/RswiftParsers/Image+Parser.swift index 00a3fdef..4f49736d 100644 --- a/Sources/RswiftParsers/Image+Parser.swift +++ b/Sources/RswiftParsers/Image+Parser.swift @@ -16,7 +16,7 @@ extension Image: SupportedExtensions { // See "Supported Image Formats" on https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/ static public let supportedExtensions: Set = ["tiff", "tif", "jpg", "jpeg", "gif", "png", "bmp", "bmpf", "ico", "cur", "xbm"] - static public func parse(url: URL) throws -> Image { + static public func parse(url: URL, assetTags: [String]?) throws -> Image { let filename = url.lastPathComponent let pathExtension = url.pathExtension guard filename.count > 0 && pathExtension.count > 0 else { @@ -29,6 +29,6 @@ extension Image: SupportedExtensions { let pathExtensionToUse = (pathExtension == "png") ? "" : ".\(pathExtension)" let name = regex.stringByReplacingMatches(in: filename, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: fullFileNameRange, withTemplate: pathExtensionToUse) - return Image(filename: url.lastPathComponent, name: name) + return Image(name: name, onDemandResourceTags: assetTags) } } diff --git a/Sources/RswiftResources/AssetCatalog.swift b/Sources/RswiftResources/AssetCatalog.swift index 89d936c7..0b5aebd0 100644 --- a/Sources/RswiftResources/AssetCatalog.swift +++ b/Sources/RswiftResources/AssetCatalog.swift @@ -20,20 +20,38 @@ public struct AssetCatalog { extension AssetCatalog { public struct Namespace { public var subnamespaces: [String: Namespace] = [:] - public var colors: [String] = [] - public var images: [String] = [] - public var files: [String] = [] + public var colors: [Color] = [] + public var images: [Image] = [] + public var dataAssets: [DataAsset] = [] public init( subnamespaces: [String: Namespace], - colors: [String], - images: [String], - files: [String] + colors: [Color], + images: [Image], + dataAssets: [DataAsset] ) { self.subnamespaces = subnamespaces self.colors = colors self.images = images - self.files = files + self.dataAssets = dataAssets + } + } + + public struct Color { + public let name: String + + public init(name: String) { + self.name = name + } + } + + public struct DataAsset { + public let name: String + public let onDemandResourceTags: [String]? + + public init(name: String, onDemandResourceTags: [String]?) { + self.name = name + self.onDemandResourceTags = onDemandResourceTags } } } diff --git a/Sources/RswiftResources/Image.swift b/Sources/RswiftResources/Image.swift index 1f03e0a4..6ccad254 100644 --- a/Sources/RswiftResources/Image.swift +++ b/Sources/RswiftResources/Image.swift @@ -10,11 +10,11 @@ import Foundation public struct Image { - public let filename: String public let name: String + public let onDemandResourceTags: [String]? - public init(filename: String, name: String) { - self.filename = filename + public init(name: String, onDemandResourceTags: [String]?) { self.name = name + self.onDemandResourceTags = onDemandResourceTags } } From 410276dacc802d07167cf641205b0016fab0efa3 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 10 Jul 2022 20:58:51 +0200 Subject: [PATCH 015/161] Parse nib/storyboard deployment target --- Sources/RswiftParsers/Nib+Parser.swift | 22 +++++++++++++++++ Sources/RswiftParsers/Storyboard+Parser.swift | 8 +++++++ Sources/RswiftResources/Nib.swift | 24 ++++++++++++++++++- Sources/RswiftResources/Storyboard.swift | 14 ++++++++++- 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/Sources/RswiftParsers/Nib+Parser.swift b/Sources/RswiftParsers/Nib+Parser.swift index e8b139cd..1978dad0 100644 --- a/Sources/RswiftParsers/Nib+Parser.swift +++ b/Sources/RswiftParsers/Nib+Parser.swift @@ -32,6 +32,7 @@ extension Nib: SupportedExtensions { return Nib( name: basename, + deploymentTarget: parserDelegate.deploymentTarget, rootViews: parserDelegate.rootViews, reusables: parserDelegate.reusables, usedImageIdentifiers: parserDelegate.usedImageIdentifiers, @@ -51,6 +52,7 @@ private let ElementNameToTypeMapping = [ private class NibParserDelegate: NSObject, XMLParserDelegate { let ignoredRootViewElements = ["placeholder"] + var deploymentTarget: DeploymentTarget? var rootViews: [TypeReference] = [] var reusables: [Reusable] = [] var usedImageIdentifiers: [NameCatalog] = [] @@ -70,6 +72,12 @@ private class NibParserDelegate: NSObject, XMLParserDelegate { } switch elementName { + case "deployment": + let version = attributeDict["version"] + if let platform = attributeDict["identifier"] { + deploymentTarget = DeploymentTarget(version: version.flatMap(parseDeploymentTargetVersion(_:)), platform: platform) + } + case "image": if let imageIdentifier = attributeDict["name"] { usedImageIdentifiers.append(NameCatalog(name: imageIdentifier, catalog: attributeDict["catalog"])) @@ -139,3 +147,17 @@ private class NibParserDelegate: NSObject, XMLParserDelegate { return Reusable(identifier: reuseIdentifier, type: type) } } + +func parseDeploymentTargetVersion(_ str: String) -> DeploymentTarget.Version? { + guard str.count > 2 else { return nil } + guard let i = Int(str) else { return nil } + let s = String(i, radix: 16) + guard + let major = Int(s[.. Date: Sun, 10 Jul 2022 22:12:20 +0200 Subject: [PATCH 016/161] Store namespace in asset name --- Sources/RswiftParsers/AssetCatalog+Parser.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/RswiftParsers/AssetCatalog+Parser.swift b/Sources/RswiftParsers/AssetCatalog+Parser.swift index 40812010..e08b1507 100644 --- a/Sources/RswiftParsers/AssetCatalog+Parser.swift +++ b/Sources/RswiftParsers/AssetCatalog+Parser.swift @@ -105,19 +105,22 @@ extension AssetCatalog: SupportedExtensions { var colors: [AssetCatalog.Color] = [] for fileURL in directory.colors { - colors.append(.init(name: fileURL.filenameWithoutExtension!)) + let name = (path + [fileURL.filenameWithoutExtension!]).joined(separator: "/") + colors.append(.init(name: name)) } var images: [Image] = [] for fileURL in directory.images { + let name = (path + [fileURL.filenameWithoutExtension!]).joined(separator: "/") let tags = onDemandResourceTags(directory: fileURL) - images.append(.init(name: fileURL.filenameWithoutExtension!, onDemandResourceTags: tags)) + images.append(.init(name: name, onDemandResourceTags: tags)) } var dataAssets: [AssetCatalog.DataAsset] = [] for fileURL in directory.dataAssets { + let name = (path + [fileURL.filenameWithoutExtension!]).joined(separator: "/") let tags = onDemandResourceTags(directory: fileURL) - dataAssets.append(.init(name: fileURL.filenameWithoutExtension!, onDemandResourceTags: tags)) + dataAssets.append(.init(name: name, onDemandResourceTags: tags)) } return AssetCatalog.Namespace( From 1c87555272f6599c40d27389ee3c9bdf8fd92c4d Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 10 Jul 2022 22:34:26 +0200 Subject: [PATCH 017/161] Code refactor --- .../ResourceFile+Generator.swift | 2 +- ...Parser.swift => FileResource+Parser.swift} | 6 ++--- Sources/RswiftParsers/Nib+Parser.swift | 14 ------------ .../Shared/DeploymentTarget+Parser.swift | 22 +++++++++++++++++++ ...{ResourceFile.swift => FileResource.swift} | 4 ++-- Sources/RswiftResources/Nib.swift | 12 ---------- .../Shared/DeploymentTarget.swift | 20 +++++++++++++++++ .../RswiftResources/Shared/StringParam.swift | 6 ----- 8 files changed, 48 insertions(+), 38 deletions(-) rename Sources/RswiftParsers/{ResourceFile+Parser.swift => FileResource+Parser.swift} (84%) create mode 100644 Sources/RswiftParsers/Shared/DeploymentTarget+Parser.swift rename Sources/RswiftResources/{ResourceFile.swift => FileResource.swift} (89%) create mode 100644 Sources/RswiftResources/Shared/DeploymentTarget.swift diff --git a/Sources/RswiftGenerators/ResourceFile+Generator.swift b/Sources/RswiftGenerators/ResourceFile+Generator.swift index e6e9ff93..afd9efbc 100644 --- a/Sources/RswiftGenerators/ResourceFile+Generator.swift +++ b/Sources/RswiftGenerators/ResourceFile+Generator.swift @@ -8,7 +8,7 @@ import Foundation import RswiftResources -extension ResourceFile { +extension FileResource { public func generateResourceLetCodeString() -> String { "let \(SwiftIdentifier(name: self.fullname).value) = \(self)" } diff --git a/Sources/RswiftParsers/ResourceFile+Parser.swift b/Sources/RswiftParsers/FileResource+Parser.swift similarity index 84% rename from Sources/RswiftParsers/ResourceFile+Parser.swift rename to Sources/RswiftParsers/FileResource+Parser.swift index f5094345..0978325d 100644 --- a/Sources/RswiftParsers/ResourceFile+Parser.swift +++ b/Sources/RswiftParsers/FileResource+Parser.swift @@ -10,7 +10,7 @@ import Foundation import RswiftResources -extension ResourceFile { +extension FileResource { // These are all extensions of resources that are passed to some special compiler step and not directly available at runtime static let unsupportedExtensions: Set = [ // AssetFolder.supportedExtensions, @@ -20,11 +20,11 @@ extension ResourceFile { ] // .reduce([]) { $0.union($1) } - static public func parse(url: URL) throws -> ResourceFile { + static public func parse(url: URL) throws -> FileResource { guard let basename = url.filenameWithoutExtension else { throw ResourceParsingError("Couldn't extract filename from URL: \(url)") } - return ResourceFile(fullname: url.lastPathComponent, name: basename, pathExtension: url.pathExtension) + return FileResource(fullname: url.lastPathComponent, name: basename, pathExtension: url.pathExtension) } } diff --git a/Sources/RswiftParsers/Nib+Parser.swift b/Sources/RswiftParsers/Nib+Parser.swift index 1978dad0..fb11decb 100644 --- a/Sources/RswiftParsers/Nib+Parser.swift +++ b/Sources/RswiftParsers/Nib+Parser.swift @@ -147,17 +147,3 @@ private class NibParserDelegate: NSObject, XMLParserDelegate { return Reusable(identifier: reuseIdentifier, type: type) } } - -func parseDeploymentTargetVersion(_ str: String) -> DeploymentTarget.Version? { - guard str.count > 2 else { return nil } - guard let i = Int(str) else { return nil } - let s = String(i, radix: 16) - guard - let major = Int(s[.. DeploymentTarget.Version? { + guard str.count > 2 else { return nil } + guard let i = Int(str) else { return nil } + let s = String(i, radix: 16) + guard + let major = Int(s[.. Date: Sun, 10 Jul 2022 23:14:39 +0200 Subject: [PATCH 018/161] Add locale to storyboard, nib parser --- Sources/RswiftParsers/Nib+Parser.swift | 3 +++ Sources/RswiftParsers/Storyboard+Parser.swift | 3 +++ Sources/RswiftResources/Nib.swift | 3 +++ Sources/RswiftResources/Storyboard.swift | 3 +++ 4 files changed, 12 insertions(+) diff --git a/Sources/RswiftParsers/Nib+Parser.swift b/Sources/RswiftParsers/Nib+Parser.swift index fb11decb..08efa778 100644 --- a/Sources/RswiftParsers/Nib+Parser.swift +++ b/Sources/RswiftParsers/Nib+Parser.swift @@ -19,6 +19,8 @@ extension Nib: SupportedExtensions { throw ResourceParsingError("Couldn't extract filename from URL: \(url)") } + let locale = LocaleReference(url: url) + guard let parser = XMLParser(contentsOf: url) else { throw ResourceParsingError("Couldn't load file at: '\(url)'") } @@ -32,6 +34,7 @@ extension Nib: SupportedExtensions { return Nib( name: basename, + locale: locale, deploymentTarget: parserDelegate.deploymentTarget, rootViews: parserDelegate.rootViews, reusables: parserDelegate.reusables, diff --git a/Sources/RswiftParsers/Storyboard+Parser.swift b/Sources/RswiftParsers/Storyboard+Parser.swift index c63b6e76..41e7c86d 100644 --- a/Sources/RswiftParsers/Storyboard+Parser.swift +++ b/Sources/RswiftParsers/Storyboard+Parser.swift @@ -19,6 +19,8 @@ extension Storyboard: SupportedExtensions { throw ResourceParsingError("Couldn't extract filename from URL: \(url)") } + let locale = LocaleReference(url: url) + guard let parser = XMLParser(contentsOf: url) else { throw ResourceParsingError("Couldn't load file at: '\(url)'") } @@ -32,6 +34,7 @@ extension Storyboard: SupportedExtensions { return Storyboard( name: basename, + locale: locale, deploymentTarget: parserDelegate.deploymentTarget, initialViewControllerIdentifier: parserDelegate.initialViewControllerIdentifier, viewControllers: parserDelegate.viewControllers, diff --git a/Sources/RswiftResources/Nib.swift b/Sources/RswiftResources/Nib.swift index 7f6a1904..e9333a6a 100644 --- a/Sources/RswiftResources/Nib.swift +++ b/Sources/RswiftResources/Nib.swift @@ -11,6 +11,7 @@ import Foundation public struct Nib { public let name: String + public let locale: LocaleReference public let deploymentTarget: DeploymentTarget? public let rootViews: [TypeReference] public let reusables: [Reusable] @@ -20,6 +21,7 @@ public struct Nib { public init( name: String, + locale: LocaleReference, deploymentTarget: DeploymentTarget?, rootViews: [TypeReference], reusables: [Reusable], @@ -28,6 +30,7 @@ public struct Nib { usedAccessibilityIdentifiers: [String] ) { self.name = name + self.locale = locale self.deploymentTarget = deploymentTarget self.rootViews = rootViews self.reusables = reusables diff --git a/Sources/RswiftResources/Storyboard.swift b/Sources/RswiftResources/Storyboard.swift index 0aae0a91..3b536697 100644 --- a/Sources/RswiftResources/Storyboard.swift +++ b/Sources/RswiftResources/Storyboard.swift @@ -11,6 +11,7 @@ import Foundation public struct Storyboard { public let name: String + public let locale: LocaleReference public let deploymentTarget: DeploymentTarget? private let initialViewControllerIdentifier: String? public let viewControllers: [ViewController] @@ -28,6 +29,7 @@ public struct Storyboard { public init( name: String, + locale: LocaleReference, deploymentTarget: DeploymentTarget?, initialViewControllerIdentifier: String?, viewControllers: [ViewController], @@ -38,6 +40,7 @@ public struct Storyboard { reusables: [Reusable] ) { self.name = name + self.locale = locale self.deploymentTarget = deploymentTarget self.initialViewControllerIdentifier = initialViewControllerIdentifier self.viewControllers = viewControllers From 9e4505f50b9fe7313abfa5d2cdfa9aa8823c6b66 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 10 Jul 2022 23:28:58 +0200 Subject: [PATCH 019/161] Parse locale --- Sources/RswiftCore/RswiftCore.swift | 14 +++++++------- .../RswiftParsers/AssetCatalog+Parser.swift | 2 +- .../RswiftParsers/FileResource+Parser.swift | 18 +++++++++++------- Sources/RswiftParsers/Image+Parser.swift | 4 +++- Sources/RswiftResources/FileResource.swift | 4 +++- Sources/RswiftResources/Image.swift | 4 +++- 6 files changed, 28 insertions(+), 18 deletions(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 85701bec..df5d9fb3 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -60,15 +60,15 @@ public struct RswiftCore { let start = Date() - let catalogs = try urls - .filter { AssetCatalog.supportedExtensions.contains($0.pathExtension) } -// .filter { $0.lastPathComponent == "Images2.xcassets" } -// .reversed().prefix(1) // DEVELOP - .map { try AssetCatalog.parse(url: $0) } - for catalog in catalogs { - print("RSWIFTCORE", catalog) + let items = try urls +// .filter { FileResource.supportedExtensions.contains($0.pathExtension) } + .filter { !FileResource.unsupportedExtensions.contains($0.pathExtension) } + .map { try FileResource.parse(url: $0) } + for item in items { + print(">>>", item) } + print("TOTAL", Date().timeIntervalSince(start)) print() } diff --git a/Sources/RswiftParsers/AssetCatalog+Parser.swift b/Sources/RswiftParsers/AssetCatalog+Parser.swift index e08b1507..be6b71fc 100644 --- a/Sources/RswiftParsers/AssetCatalog+Parser.swift +++ b/Sources/RswiftParsers/AssetCatalog+Parser.swift @@ -113,7 +113,7 @@ extension AssetCatalog: SupportedExtensions { for fileURL in directory.images { let name = (path + [fileURL.filenameWithoutExtension!]).joined(separator: "/") let tags = onDemandResourceTags(directory: fileURL) - images.append(.init(name: name, onDemandResourceTags: tags)) + images.append(.init(name: name, locale: nil, onDemandResourceTags: tags)) } var dataAssets: [AssetCatalog.DataAsset] = [] diff --git a/Sources/RswiftParsers/FileResource+Parser.swift b/Sources/RswiftParsers/FileResource+Parser.swift index 0978325d..5358e164 100644 --- a/Sources/RswiftParsers/FileResource+Parser.swift +++ b/Sources/RswiftParsers/FileResource+Parser.swift @@ -12,19 +12,23 @@ import RswiftResources extension FileResource { // These are all extensions of resources that are passed to some special compiler step and not directly available at runtime - static let unsupportedExtensions: Set = [ -// AssetFolder.supportedExtensions, -// Storyboard.supportedExtensions, -// Nib.supportedExtensions, -// LocalizableStrings.supportedExtensions, + static public let unsupportedExtensions: Set = [ + AssetCatalog.supportedExtensions, + Font.supportedExtensions, + Image.supportedExtensions, + LocalizableStrings.supportedExtensions, + Nib.supportedExtensions, + Storyboard.supportedExtensions, ] -// .reduce([]) { $0.union($1) } + .reduce([]) { $0.union($1) } static public func parse(url: URL) throws -> FileResource { guard let basename = url.filenameWithoutExtension else { throw ResourceParsingError("Couldn't extract filename from URL: \(url)") } - return FileResource(fullname: url.lastPathComponent, name: basename, pathExtension: url.pathExtension) + let locale = LocaleReference(url: url) + + return FileResource(fullname: url.lastPathComponent, locale: locale, name: basename, pathExtension: url.pathExtension) } } diff --git a/Sources/RswiftParsers/Image+Parser.swift b/Sources/RswiftParsers/Image+Parser.swift index 4f49736d..d23f4ec5 100644 --- a/Sources/RswiftParsers/Image+Parser.swift +++ b/Sources/RswiftParsers/Image+Parser.swift @@ -23,12 +23,14 @@ extension Image: SupportedExtensions { throw ResourceParsingError("Filename and/or extension could not be parsed from URL: \(url.absoluteString)") } + let locale = LocaleReference(url: url) + let extensions = Image.supportedExtensions.joined(separator: "|") let regex = try! NSRegularExpression(pattern: "(~(ipad|iphone))?(@[2,3]x)?\\.(\(extensions))$", options: .caseInsensitive) let fullFileNameRange = NSRange(location: 0, length: filename.count) let pathExtensionToUse = (pathExtension == "png") ? "" : ".\(pathExtension)" let name = regex.stringByReplacingMatches(in: filename, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: fullFileNameRange, withTemplate: pathExtensionToUse) - return Image(name: name, onDemandResourceTags: assetTags) + return Image(name: name, locale: locale, onDemandResourceTags: assetTags) } } diff --git a/Sources/RswiftResources/FileResource.swift b/Sources/RswiftResources/FileResource.swift index 08e57fb7..b2506cae 100644 --- a/Sources/RswiftResources/FileResource.swift +++ b/Sources/RswiftResources/FileResource.swift @@ -11,11 +11,13 @@ import Foundation public struct FileResource { public let fullname: String + public let locale: LocaleReference? public let name: String public let pathExtension: String - public init(fullname: String, name: String, pathExtension: String) { + public init(fullname: String, locale: LocaleReference?, name: String, pathExtension: String) { self.fullname = fullname + self.locale = locale self.name = name self.pathExtension = pathExtension } diff --git a/Sources/RswiftResources/Image.swift b/Sources/RswiftResources/Image.swift index 6ccad254..f471ae58 100644 --- a/Sources/RswiftResources/Image.swift +++ b/Sources/RswiftResources/Image.swift @@ -11,10 +11,12 @@ import Foundation public struct Image { public let name: String + public let locale: LocaleReference? public let onDemandResourceTags: [String]? - public init(name: String, onDemandResourceTags: [String]?) { + public init(name: String, locale: LocaleReference?, onDemandResourceTags: [String]?) { self.name = name + self.locale = locale self.onDemandResourceTags = onDemandResourceTags } } From c92f2c21620a0fd2344e2d49868c1a6849964fb5 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 10 Jul 2022 23:42:56 +0200 Subject: [PATCH 020/161] Refactor --- Sources/RswiftParsers/AssetCatalog+Parser.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Sources/RswiftParsers/AssetCatalog+Parser.swift b/Sources/RswiftParsers/AssetCatalog+Parser.swift index be6b71fc..255978cb 100644 --- a/Sources/RswiftParsers/AssetCatalog+Parser.swift +++ b/Sources/RswiftParsers/AssetCatalog+Parser.swift @@ -82,7 +82,7 @@ extension AssetCatalog: SupportedExtensions { parent.dataAssets.append(fileURL) } else if ignoredExtensions.contains(pathExtension) { directoryEnumerator.skipDescendants() - } else if isDirectory && providesNamespace(directory: fileURL) { + } else if isDirectory && parseProvidesNamespace(directory: fileURL) { let ns = NamespaceDirectory() namespaces[relativeURL] = ns parent.subnamespaces[filename] = ns @@ -96,6 +96,7 @@ extension AssetCatalog: SupportedExtensions { return root } + // Note: ignore any localizations in Contents.json static private func createNamespace(directory: NamespaceDirectory, path: [String]) throws -> Namespace { var subnamespaces: [String: AssetCatalog.Namespace] = [:] @@ -112,14 +113,14 @@ extension AssetCatalog: SupportedExtensions { var images: [Image] = [] for fileURL in directory.images { let name = (path + [fileURL.filenameWithoutExtension!]).joined(separator: "/") - let tags = onDemandResourceTags(directory: fileURL) + let tags = parseOnDemandResourceTags(directory: fileURL) images.append(.init(name: name, locale: nil, onDemandResourceTags: tags)) } var dataAssets: [AssetCatalog.DataAsset] = [] for fileURL in directory.dataAssets { let name = (path + [fileURL.filenameWithoutExtension!]).joined(separator: "/") - let tags = onDemandResourceTags(directory: fileURL) + let tags = parseOnDemandResourceTags(directory: fileURL) dataAssets.append(.init(name: name, onDemandResourceTags: tags)) } @@ -143,7 +144,7 @@ private class NamespaceDirectory: CustomDebugStringConvertible { } } -private func providesNamespace(directory: URL) -> Bool { +private func parseProvidesNamespace(directory: URL) -> Bool { guard let contents = try? ContentsJson.parse(directory: directory), let providesNamespace = contents.properties.providesNamespace @@ -152,7 +153,7 @@ private func providesNamespace(directory: URL) -> Bool { return providesNamespace } -private func onDemandResourceTags(directory: URL) -> [String]? { +private func parseOnDemandResourceTags(directory: URL) -> [String]? { guard let contents = try? ContentsJson.parse(directory: directory) else { return nil } From 830f65aad6bcd1bfddd62bf8187c8ac30529d8dd Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 15 Jul 2022 11:00:57 +0200 Subject: [PATCH 021/161] Parse generated ids --- Sources/RswiftCore/RswiftCore.swift | 1 - Sources/RswiftParsers/Nib+Parser.swift | 6 ++++++ Sources/RswiftParsers/Shared/GeneratedId.swift | 13 +++++++++++++ Sources/RswiftParsers/Storyboard+Parser.swift | 7 ++++++- Sources/RswiftResources/Nib.swift | 3 +++ Sources/RswiftResources/Storyboard.swift | 5 ++++- 6 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 Sources/RswiftParsers/Shared/GeneratedId.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index df5d9fb3..97da59fa 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -58,7 +58,6 @@ public struct RswiftCore { let urls = paths .map { $0.url(with: urlForSourceTreeFolder) } - let start = Date() let items = try urls // .filter { FileResource.supportedExtensions.contains($0.pathExtension) } diff --git a/Sources/RswiftParsers/Nib+Parser.swift b/Sources/RswiftParsers/Nib+Parser.swift index 08efa778..5b8061ae 100644 --- a/Sources/RswiftParsers/Nib+Parser.swift +++ b/Sources/RswiftParsers/Nib+Parser.swift @@ -38,6 +38,7 @@ extension Nib: SupportedExtensions { deploymentTarget: parserDelegate.deploymentTarget, rootViews: parserDelegate.rootViews, reusables: parserDelegate.reusables, + generatedIds: parserDelegate.generatedIds, usedImageIdentifiers: parserDelegate.usedImageIdentifiers, usedColorResources: parserDelegate.usedColorReferences, usedAccessibilityIdentifiers: parserDelegate.usedAccessibilityIdentifiers @@ -58,6 +59,7 @@ private class NibParserDelegate: NSObject, XMLParserDelegate { var deploymentTarget: DeploymentTarget? var rootViews: [TypeReference] = [] var reusables: [Reusable] = [] + var generatedIds: [String] = [] var usedImageIdentifiers: [NameCatalog] = [] var usedColorReferences: [NameCatalog] = [] var usedAccessibilityIdentifiers: [String] = [] @@ -74,6 +76,10 @@ private class NibParserDelegate: NSObject, XMLParserDelegate { isObjectsTagOpened = true } + if let id = attributeDict["id"], isGenerated(id: id) { + generatedIds.append(id) + } + switch elementName { case "deployment": let version = attributeDict["version"] diff --git a/Sources/RswiftParsers/Shared/GeneratedId.swift b/Sources/RswiftParsers/Shared/GeneratedId.swift new file mode 100644 index 00000000..a5e3e4de --- /dev/null +++ b/Sources/RswiftParsers/Shared/GeneratedId.swift @@ -0,0 +1,13 @@ +// +// GeneratedId.swift +// +// +// Created by Tom Lokhorst on 2022-07-15. +// + +import Foundation + +private let generatedIdRegex = try! NSRegularExpression(pattern: #"^\w\w\w-\w\w-\w\w\w$"#) +func isGenerated(id input: String) -> Bool { + generatedIdRegex.firstMatch(in: input, range: NSRange(location: 0, length: input.utf16.count)) != nil +} diff --git a/Sources/RswiftParsers/Storyboard+Parser.swift b/Sources/RswiftParsers/Storyboard+Parser.swift index 41e7c86d..5391cf1c 100644 --- a/Sources/RswiftParsers/Storyboard+Parser.swift +++ b/Sources/RswiftParsers/Storyboard+Parser.swift @@ -39,6 +39,7 @@ extension Storyboard: SupportedExtensions { initialViewControllerIdentifier: parserDelegate.initialViewControllerIdentifier, viewControllers: parserDelegate.viewControllers, viewControllerPlaceholders: parserDelegate.viewControllerPlaceholders, + generatedIds: parserDelegate.generatedIds, usedAccessibilityIdentifiers: parserDelegate.usedAccessibilityIdentifiers, usedImageIdentifiers: parserDelegate.usedImageIdentifiers, usedColorResources: parserDelegate.usedColorReferences, @@ -67,6 +68,7 @@ private class StoryboardParserDelegate: NSObject, XMLParserDelegate { var deploymentTarget: DeploymentTarget? var viewControllers: [Storyboard.ViewController] = [] var viewControllerPlaceholders: [Storyboard.ViewControllerPlaceholder] = [] + var generatedIds: [String] = [] var usedImageIdentifiers: [NameCatalog] = [] var usedColorReferences: [NameCatalog] = [] var usedAccessibilityIdentifiers: [String] = [] @@ -76,6 +78,10 @@ private class StoryboardParserDelegate: NSObject, XMLParserDelegate { var currentViewController: Storyboard.ViewController? @objc func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) { + if let id = attributeDict["id"], isGenerated(id: id) { + generatedIds.append(id) + } + switch elementName { case "deployment": let version = attributeDict["version"] @@ -198,4 +204,3 @@ private class StoryboardParserDelegate: NSObject, XMLParserDelegate { return Reusable(identifier: reuseIdentifier, type: type) } } - diff --git a/Sources/RswiftResources/Nib.swift b/Sources/RswiftResources/Nib.swift index e9333a6a..8d557762 100644 --- a/Sources/RswiftResources/Nib.swift +++ b/Sources/RswiftResources/Nib.swift @@ -15,6 +15,7 @@ public struct Nib { public let deploymentTarget: DeploymentTarget? public let rootViews: [TypeReference] public let reusables: [Reusable] + public let generatedIds: [String] public let usedImageIdentifiers: [NameCatalog] public let usedColorResources: [NameCatalog] public let usedAccessibilityIdentifiers: [String] @@ -25,6 +26,7 @@ public struct Nib { deploymentTarget: DeploymentTarget?, rootViews: [TypeReference], reusables: [Reusable], + generatedIds: [String], usedImageIdentifiers: [NameCatalog], usedColorResources: [NameCatalog], usedAccessibilityIdentifiers: [String] @@ -34,6 +36,7 @@ public struct Nib { self.deploymentTarget = deploymentTarget self.rootViews = rootViews self.reusables = reusables + self.generatedIds = generatedIds self.usedImageIdentifiers = usedImageIdentifiers self.usedColorResources = usedColorResources self.usedAccessibilityIdentifiers = usedAccessibilityIdentifiers diff --git a/Sources/RswiftResources/Storyboard.swift b/Sources/RswiftResources/Storyboard.swift index 3b536697..8d2a53bd 100644 --- a/Sources/RswiftResources/Storyboard.swift +++ b/Sources/RswiftResources/Storyboard.swift @@ -13,9 +13,10 @@ public struct Storyboard { public let name: String public let locale: LocaleReference public let deploymentTarget: DeploymentTarget? - private let initialViewControllerIdentifier: String? + public let initialViewControllerIdentifier: String? public let viewControllers: [ViewController] public let viewControllerPlaceholders: [ViewControllerPlaceholder] + public let generatedIds: [String] public let usedAccessibilityIdentifiers: [String] public let usedImageIdentifiers: [NameCatalog] public let usedColorResources: [NameCatalog] @@ -34,6 +35,7 @@ public struct Storyboard { initialViewControllerIdentifier: String?, viewControllers: [ViewController], viewControllerPlaceholders: [ViewControllerPlaceholder], + generatedIds: [String], usedAccessibilityIdentifiers: [String], usedImageIdentifiers: [NameCatalog], usedColorResources: [NameCatalog], @@ -45,6 +47,7 @@ public struct Storyboard { self.initialViewControllerIdentifier = initialViewControllerIdentifier self.viewControllers = viewControllers self.viewControllerPlaceholders = viewControllerPlaceholders + self.generatedIds = generatedIds self.usedAccessibilityIdentifiers = usedAccessibilityIdentifiers self.usedImageIdentifiers = usedImageIdentifiers self.usedColorResources = usedColorResources From b4a5a05cccef8f0a69a38f1ba1f0cd8f2f6c1527 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 15 Jul 2022 11:14:46 +0200 Subject: [PATCH 022/161] Add Resource suffix to resources --- .../ResourceApp.xcodeproj/project.pbxproj | 2 +- Sources/RswiftCore/RswiftCore.swift | 9 +++++---- ...tor.swift => FileResource+Generator.swift} | 2 +- Sources/RswiftGenerators/Font+Generator.swift | 4 ++-- .../RswiftGenerators/Image+Generator.swift | 6 +++--- Sources/RswiftGenerators/Nib+Generator.swift | 6 +++--- .../Storyboard+Generator.swift | 6 +++--- .../RswiftParsers/AssetCatalog+Parser.swift | 2 +- .../RswiftParsers/FileResource+Parser.swift | 10 +++++----- ...Parser.swift => FontResource+Parser.swift} | 8 ++++---- ...arser.swift => ImageResource+Parser.swift} | 10 +++++----- Sources/RswiftParsers/Nib+Parser.swift | 6 +++--- .../RswiftParsers/PropertyList+Parser.swift | 6 +++--- Sources/RswiftParsers/Storyboard+Parser.swift | 20 +++++++++---------- Sources/RswiftResources/AssetCatalog.swift | 4 ++-- .../{Font.swift => FontResource.swift} | 4 ++-- .../{Image.swift => ImageResource.swift} | 4 ++-- .../{Nib.swift => NibResource.swift} | 4 ++-- ...yList.swift => PropertyListResource.swift} | 4 ++-- ...ryboard.swift => StoryboardResource.swift} | 4 ++-- 20 files changed, 61 insertions(+), 60 deletions(-) rename Sources/RswiftGenerators/{ResourceFile+Generator.swift => FileResource+Generator.swift} (88%) rename Sources/RswiftParsers/{Font+Parser.swift => FontResource+Parser.swift} (74%) rename Sources/RswiftParsers/{Image+Parser.swift => ImageResource+Parser.swift} (83%) rename Sources/RswiftResources/{Font.swift => FontResource.swift} (86%) rename Sources/RswiftResources/{Image.swift => ImageResource.swift} (89%) rename Sources/RswiftResources/{Nib.swift => NibResource.swift} (96%) rename Sources/RswiftResources/{PropertyList.swift => PropertyListResource.swift} (88%) rename Sources/RswiftResources/{Storyboard.swift => StoryboardResource.swift} (98%) diff --git a/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj b/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj index 9b023507..56c9ca35 100644 --- a/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj +++ b/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj @@ -728,7 +728,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#\"$SRCROOT/../../build/Debug/rswift\" generate --generateUITestFile \"$SRCROOT/R.UITest.generated.swift\" --import SWRevealViewController \"$SRCROOT/R.generated.swift\" > \"$SRCROOT/rswift.log\"\n/Users/tom/Library/Developer/Xcode/DerivedData/R-gzdnepbnjmlzctbxokifiegoyhmk/Build/Products/Debug/rswift generate --generateUITestFile \"$SRCROOT/R.UITest.generated.swift\" --import SWRevealViewController \"$SRCROOT/R.generated.swift\" > \"$SRCROOT/rswift.log\"\n"; + shellScript = "# \"$SRCROOT/../../build/Debug/rswift\" generate --generateUITestFile \"$SRCROOT/R.UITest.generated.swift\" --import SWRevealViewController \"$SRCROOT/R.generated.swift\" > \"$SRCROOT/rswift.log\"\n# /Users/tom/Library/Developer/Xcode/DerivedData/R-gzdnepbnjmlzctbxokifiegoyhmk/Build/Products/Debug/rswift generate --generateUITestFile \"$SRCROOT/R.UITest.generated.swift\" --import SWRevealViewController \"$SRCROOT/R.generated.swift\" > \"$SRCROOT/rswift.log\"\n"; }; ED8FCF67313DC003391627B7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 97da59fa..40e50210 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -60,11 +60,12 @@ public struct RswiftCore { let start = Date() let items = try urls -// .filter { FileResource.supportedExtensions.contains($0.pathExtension) } - .filter { !FileResource.unsupportedExtensions.contains($0.pathExtension) } - .map { try FileResource.parse(url: $0) } + .filter { ImageResource.supportedExtensions.contains($0.pathExtension) } +// .filter { !FileResource.unsupportedExtensions.contains($0.pathExtension) } + .map { try ImageResource.parse(url: $0, assetTags: nil) } for item in items { - print(">>>", item) +// print(">>>", item) + print(item.generateResourceLetCodeString()) } diff --git a/Sources/RswiftGenerators/ResourceFile+Generator.swift b/Sources/RswiftGenerators/FileResource+Generator.swift similarity index 88% rename from Sources/RswiftGenerators/ResourceFile+Generator.swift rename to Sources/RswiftGenerators/FileResource+Generator.swift index afd9efbc..681b39ad 100644 --- a/Sources/RswiftGenerators/ResourceFile+Generator.swift +++ b/Sources/RswiftGenerators/FileResource+Generator.swift @@ -1,5 +1,5 @@ // -// ResourceFile+Generator.swift +// FileResource+Generator.swift // // // Created by Tom Lokhorst on 2022-06-24. diff --git a/Sources/RswiftGenerators/Font+Generator.swift b/Sources/RswiftGenerators/Font+Generator.swift index e60c392e..268b16bc 100644 --- a/Sources/RswiftGenerators/Font+Generator.swift +++ b/Sources/RswiftGenerators/Font+Generator.swift @@ -1,5 +1,5 @@ // -// Font+Generator.swift +// FontResource+Generator.swift // rswift // // Created by Tom Lokhorst on 2021-04-18. @@ -8,7 +8,7 @@ import Foundation import RswiftResources -extension Font { +extension FontResource { public func generateResourceLetCodeString() -> String { "let \(SwiftIdentifier(name: self.name).value) = \(self)" } diff --git a/Sources/RswiftGenerators/Image+Generator.swift b/Sources/RswiftGenerators/Image+Generator.swift index 2d610565..df6d60dd 100644 --- a/Sources/RswiftGenerators/Image+Generator.swift +++ b/Sources/RswiftGenerators/Image+Generator.swift @@ -1,5 +1,5 @@ // -// Image+Generator.swift +// ImageResource+Generator.swift // // // Created by Tom Lokhorst on 2022-06-24. @@ -8,9 +8,9 @@ import Foundation import RswiftResources -extension Image { +extension ImageResource { public func generateResourceLetCodeString() -> String { - "let \(SwiftIdentifier(name: self.name).value) = \(self)" + "static let \(SwiftIdentifier(name: self.name).value) = \(self)" } } diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift index 5446dc9f..c7ae0f56 100644 --- a/Sources/RswiftGenerators/Nib+Generator.swift +++ b/Sources/RswiftGenerators/Nib+Generator.swift @@ -1,5 +1,5 @@ // -// Nib+Generator.swift +// NibResource+Generator.swift // // // Created by Tom Lokhorst on 2022-06-24. @@ -8,9 +8,9 @@ import Foundation import RswiftResources -extension Nib { +extension NibResource { public func generateResourceLetCodeString() -> String { - "let \(SwiftIdentifier(name: self.name).value) = \(self)" + "static let \(SwiftIdentifier(name: self.name).value) = \(self)" } } diff --git a/Sources/RswiftGenerators/Storyboard+Generator.swift b/Sources/RswiftGenerators/Storyboard+Generator.swift index 7b0ef2de..6d897fdd 100644 --- a/Sources/RswiftGenerators/Storyboard+Generator.swift +++ b/Sources/RswiftGenerators/Storyboard+Generator.swift @@ -1,5 +1,5 @@ // -// Storyboard+Generator.swift +// StoryboardResource+Generator.swift // // // Created by Tom Lokhorst on 2022-06-24. @@ -8,8 +8,8 @@ import Foundation import RswiftResources -extension Storyboard { +extension StoryboardResource { public func generateResourceLetCodeString() -> String { - "let \(SwiftIdentifier(name: self.name).value) = \(self)" + "static let \(SwiftIdentifier(name: self.name).value) = \(self)" } } diff --git a/Sources/RswiftParsers/AssetCatalog+Parser.swift b/Sources/RswiftParsers/AssetCatalog+Parser.swift index 255978cb..0800780c 100644 --- a/Sources/RswiftParsers/AssetCatalog+Parser.swift +++ b/Sources/RswiftParsers/AssetCatalog+Parser.swift @@ -110,7 +110,7 @@ extension AssetCatalog: SupportedExtensions { colors.append(.init(name: name)) } - var images: [Image] = [] + var images: [ImageResource] = [] for fileURL in directory.images { let name = (path + [fileURL.filenameWithoutExtension!]).joined(separator: "/") let tags = parseOnDemandResourceTags(directory: fileURL) diff --git a/Sources/RswiftParsers/FileResource+Parser.swift b/Sources/RswiftParsers/FileResource+Parser.swift index 5358e164..95f5e56b 100644 --- a/Sources/RswiftParsers/FileResource+Parser.swift +++ b/Sources/RswiftParsers/FileResource+Parser.swift @@ -1,5 +1,5 @@ // -// ResourceFile.swift +// FileResource.swift // R.swift // // Created by Mathijs Kadijk on 09-12-15. @@ -14,11 +14,11 @@ extension FileResource { // These are all extensions of resources that are passed to some special compiler step and not directly available at runtime static public let unsupportedExtensions: Set = [ AssetCatalog.supportedExtensions, - Font.supportedExtensions, - Image.supportedExtensions, + FontResource.supportedExtensions, + ImageResource.supportedExtensions, LocalizableStrings.supportedExtensions, - Nib.supportedExtensions, - Storyboard.supportedExtensions, + NibResource.supportedExtensions, + StoryboardResource.supportedExtensions, ] .reduce([]) { $0.union($1) } diff --git a/Sources/RswiftParsers/Font+Parser.swift b/Sources/RswiftParsers/FontResource+Parser.swift similarity index 74% rename from Sources/RswiftParsers/Font+Parser.swift rename to Sources/RswiftParsers/FontResource+Parser.swift index 7528b50a..53bcc071 100644 --- a/Sources/RswiftParsers/Font+Parser.swift +++ b/Sources/RswiftParsers/FontResource+Parser.swift @@ -1,5 +1,5 @@ // -// Font.swift +// FontResource.swift // R.swift // // Created by Mathijs Kadijk on 09-12-15. @@ -11,10 +11,10 @@ import Foundation import RswiftResources import CoreGraphics -extension Font: SupportedExtensions { +extension FontResource: SupportedExtensions { static public let supportedExtensions: Set = ["otf", "ttf"] - static public func parse(url: URL) throws -> Font { + static public func parse(url: URL) throws -> FontResource { guard let dataProvider = CGDataProvider(url: url as CFURL) else { throw ResourceParsingError("Unable to create data provider for font at \(url)") } @@ -24,6 +24,6 @@ extension Font: SupportedExtensions { throw ResourceParsingError("No postscriptName associated to font at \(url)") } - return Font(filename: url.lastPathComponent, name: postScriptName as String) + return FontResource(filename: url.lastPathComponent, name: postScriptName as String) } } diff --git a/Sources/RswiftParsers/Image+Parser.swift b/Sources/RswiftParsers/ImageResource+Parser.swift similarity index 83% rename from Sources/RswiftParsers/Image+Parser.swift rename to Sources/RswiftParsers/ImageResource+Parser.swift index d23f4ec5..1ff19ea1 100644 --- a/Sources/RswiftParsers/Image+Parser.swift +++ b/Sources/RswiftParsers/ImageResource+Parser.swift @@ -1,5 +1,5 @@ // -// Image.swift +// ImageResource.swift // R.swift // // Created by Mathijs Kadijk on 09-12-15. @@ -12,11 +12,11 @@ import RswiftResources import CoreGraphics -extension Image: SupportedExtensions { +extension ImageResource: SupportedExtensions { // See "Supported Image Formats" on https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/ static public let supportedExtensions: Set = ["tiff", "tif", "jpg", "jpeg", "gif", "png", "bmp", "bmpf", "ico", "cur", "xbm"] - static public func parse(url: URL, assetTags: [String]?) throws -> Image { + static public func parse(url: URL, assetTags: [String]?) throws -> ImageResource { let filename = url.lastPathComponent let pathExtension = url.pathExtension guard filename.count > 0 && pathExtension.count > 0 else { @@ -25,12 +25,12 @@ extension Image: SupportedExtensions { let locale = LocaleReference(url: url) - let extensions = Image.supportedExtensions.joined(separator: "|") + let extensions = ImageResource.supportedExtensions.joined(separator: "|") let regex = try! NSRegularExpression(pattern: "(~(ipad|iphone))?(@[2,3]x)?\\.(\(extensions))$", options: .caseInsensitive) let fullFileNameRange = NSRange(location: 0, length: filename.count) let pathExtensionToUse = (pathExtension == "png") ? "" : ".\(pathExtension)" let name = regex.stringByReplacingMatches(in: filename, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: fullFileNameRange, withTemplate: pathExtensionToUse) - return Image(name: name, locale: locale, onDemandResourceTags: assetTags) + return ImageResource(name: name, locale: locale, onDemandResourceTags: assetTags) } } diff --git a/Sources/RswiftParsers/Nib+Parser.swift b/Sources/RswiftParsers/Nib+Parser.swift index 5b8061ae..b90de42e 100644 --- a/Sources/RswiftParsers/Nib+Parser.swift +++ b/Sources/RswiftParsers/Nib+Parser.swift @@ -11,10 +11,10 @@ import Foundation import RswiftResources -extension Nib: SupportedExtensions { +extension NibResource: SupportedExtensions { static public let supportedExtensions: Set = ["xib"] - static public func parse(url: URL) throws -> Nib { + static public func parse(url: URL) throws -> NibResource { guard let basename = url.filenameWithoutExtension else { throw ResourceParsingError("Couldn't extract filename from URL: \(url)") } @@ -32,7 +32,7 @@ extension Nib: SupportedExtensions { throw ResourceParsingError("Invalid XML in file at: '\(url)'") } - return Nib( + return NibResource( name: basename, locale: locale, deploymentTarget: parserDelegate.deploymentTarget, diff --git a/Sources/RswiftParsers/PropertyList+Parser.swift b/Sources/RswiftParsers/PropertyList+Parser.swift index 9e356c32..343adeda 100644 --- a/Sources/RswiftParsers/PropertyList+Parser.swift +++ b/Sources/RswiftParsers/PropertyList+Parser.swift @@ -10,8 +10,8 @@ import Foundation import RswiftResources -extension PropertyList { - static public func parse(url: URL, buildConfigurationName: String) throws -> PropertyList { +extension PropertyListResource { + static public func parse(url: URL, buildConfigurationName: String) throws -> PropertyListResource { guard let nsDictionary = NSDictionary(contentsOf: url), let dictionary = nsDictionary as? [String: Any] @@ -19,6 +19,6 @@ extension PropertyList { throw ResourceParsingError("File could not be parsed as InfoPlist from URL: \(url.absoluteString)") } - return PropertyList(buildConfigurationName: buildConfigurationName, contents: dictionary, url: url) + return PropertyListResource(buildConfigurationName: buildConfigurationName, contents: dictionary, url: url) } } diff --git a/Sources/RswiftParsers/Storyboard+Parser.swift b/Sources/RswiftParsers/Storyboard+Parser.swift index 5391cf1c..6622cdf6 100644 --- a/Sources/RswiftParsers/Storyboard+Parser.swift +++ b/Sources/RswiftParsers/Storyboard+Parser.swift @@ -11,10 +11,10 @@ import Foundation import RswiftResources -extension Storyboard: SupportedExtensions { +extension StoryboardResource: SupportedExtensions { static public let supportedExtensions: Set = ["storyboard"] - static public func parse(url: URL) throws -> Storyboard { + static public func parse(url: URL) throws -> StoryboardResource { guard let basename = url.filenameWithoutExtension else { throw ResourceParsingError("Couldn't extract filename from URL: \(url)") } @@ -32,7 +32,7 @@ extension Storyboard: SupportedExtensions { throw ResourceParsingError("Invalid XML in file at: '\(url)'") } - return Storyboard( + return StoryboardResource( name: basename, locale: locale, deploymentTarget: parserDelegate.deploymentTarget, @@ -66,8 +66,8 @@ private let ElementNameToTypeMapping: [String: TypeReference] = [ private class StoryboardParserDelegate: NSObject, XMLParserDelegate { var initialViewControllerIdentifier: String? var deploymentTarget: DeploymentTarget? - var viewControllers: [Storyboard.ViewController] = [] - var viewControllerPlaceholders: [Storyboard.ViewControllerPlaceholder] = [] + var viewControllers: [StoryboardResource.ViewController] = [] + var viewControllerPlaceholders: [StoryboardResource.ViewControllerPlaceholder] = [] var generatedIds: [String] = [] var usedImageIdentifiers: [NameCatalog] = [] var usedColorReferences: [NameCatalog] = [] @@ -75,7 +75,7 @@ private class StoryboardParserDelegate: NSObject, XMLParserDelegate { var reusables: [Reusable] = [] // State - var currentViewController: Storyboard.ViewController? + var currentViewController: StoryboardResource.ViewController? @objc func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) { if let id = attributeDict["id"], isGenerated(id: id) { @@ -107,7 +107,7 @@ private class StoryboardParserDelegate: NSObject, XMLParserDelegate { { let type = customType ?? TypeReference._UIStoryboardSegue - let segue = Storyboard.Segue(identifier: segueIdentifier, type: type, destination: destination, kind: kind) + let segue = StoryboardResource.Segue(identifier: segueIdentifier, type: type, destination: destination, kind: kind) currentViewController?.segues.append(segue) } @@ -133,7 +133,7 @@ private class StoryboardParserDelegate: NSObject, XMLParserDelegate { case "viewControllerPlaceholder": if let id = attributeDict["id"] , attributeDict["sceneMemberID"] == "viewController" { - let placeholder = Storyboard.ViewControllerPlaceholder( + let placeholder = StoryboardResource.ViewControllerPlaceholder( id: id, storyboardName: attributeDict["storyboardName"], referencedIdentifier: attributeDict["referencedIdentifier"], @@ -170,7 +170,7 @@ private class StoryboardParserDelegate: NSObject, XMLParserDelegate { } } - func viewControllerFromAttributes(_ attributeDict: [String : String], elementName: String) -> Storyboard.ViewController? { + func viewControllerFromAttributes(_ attributeDict: [String : String], elementName: String) -> StoryboardResource.ViewController? { guard let id = attributeDict["id"] , attributeDict["sceneMemberID"] == "viewController" else { return nil } @@ -185,7 +185,7 @@ private class StoryboardParserDelegate: NSObject, XMLParserDelegate { let type = customType ?? ElementNameToTypeMapping[elementName] ?? TypeReference._UIViewController - return Storyboard.ViewController(id: id, storyboardIdentifier: storyboardIdentifier, type: type, segues: []) + return StoryboardResource.ViewController(id: id, storyboardIdentifier: storyboardIdentifier, type: type, segues: []) } func reusableFromAttributes(_ attributeDict: [String : String], elementName: String) -> Reusable? { diff --git a/Sources/RswiftResources/AssetCatalog.swift b/Sources/RswiftResources/AssetCatalog.swift index 0b5aebd0..e34ea83d 100644 --- a/Sources/RswiftResources/AssetCatalog.swift +++ b/Sources/RswiftResources/AssetCatalog.swift @@ -21,13 +21,13 @@ extension AssetCatalog { public struct Namespace { public var subnamespaces: [String: Namespace] = [:] public var colors: [Color] = [] - public var images: [Image] = [] + public var images: [ImageResource] = [] public var dataAssets: [DataAsset] = [] public init( subnamespaces: [String: Namespace], colors: [Color], - images: [Image], + images: [ImageResource], dataAssets: [DataAsset] ) { self.subnamespaces = subnamespaces diff --git a/Sources/RswiftResources/Font.swift b/Sources/RswiftResources/FontResource.swift similarity index 86% rename from Sources/RswiftResources/Font.swift rename to Sources/RswiftResources/FontResource.swift index 95a886b9..18037350 100644 --- a/Sources/RswiftResources/Font.swift +++ b/Sources/RswiftResources/FontResource.swift @@ -1,5 +1,5 @@ // -// Font.swift +// FontResource.swift // R.swift // // Created by Mathijs Kadijk on 09-12-15. @@ -9,7 +9,7 @@ import Foundation -public struct Font { +public struct FontResource { public let filename: String public let name: String diff --git a/Sources/RswiftResources/Image.swift b/Sources/RswiftResources/ImageResource.swift similarity index 89% rename from Sources/RswiftResources/Image.swift rename to Sources/RswiftResources/ImageResource.swift index f471ae58..431fb682 100644 --- a/Sources/RswiftResources/Image.swift +++ b/Sources/RswiftResources/ImageResource.swift @@ -1,5 +1,5 @@ // -// Image.swift +// ImageResource.swift // R.swift // // Created by Mathijs Kadijk on 09-12-15. @@ -9,7 +9,7 @@ import Foundation -public struct Image { +public struct ImageResource { public let name: String public let locale: LocaleReference? public let onDemandResourceTags: [String]? diff --git a/Sources/RswiftResources/Nib.swift b/Sources/RswiftResources/NibResource.swift similarity index 96% rename from Sources/RswiftResources/Nib.swift rename to Sources/RswiftResources/NibResource.swift index 8d557762..7b615f9b 100644 --- a/Sources/RswiftResources/Nib.swift +++ b/Sources/RswiftResources/NibResource.swift @@ -1,5 +1,5 @@ // -// Nib.swift +// NibResource.swift // R.swift // // Created by Mathijs Kadijk on 09-12-15. @@ -9,7 +9,7 @@ import Foundation -public struct Nib { +public struct NibResource { public let name: String public let locale: LocaleReference public let deploymentTarget: DeploymentTarget? diff --git a/Sources/RswiftResources/PropertyList.swift b/Sources/RswiftResources/PropertyListResource.swift similarity index 88% rename from Sources/RswiftResources/PropertyList.swift rename to Sources/RswiftResources/PropertyListResource.swift index 2df6cc70..26881b24 100644 --- a/Sources/RswiftResources/PropertyList.swift +++ b/Sources/RswiftResources/PropertyListResource.swift @@ -1,5 +1,5 @@ // -// PropertyList.swift +// PropertyListResource.swift // R.swift // // Created by Tom Lokhorst on 2018-07-08. @@ -9,7 +9,7 @@ import Foundation -public struct PropertyList { +public struct PropertyListResource { public typealias Contents = [String: Any] public let buildConfigurationName: String diff --git a/Sources/RswiftResources/Storyboard.swift b/Sources/RswiftResources/StoryboardResource.swift similarity index 98% rename from Sources/RswiftResources/Storyboard.swift rename to Sources/RswiftResources/StoryboardResource.swift index 8d2a53bd..d808aac4 100644 --- a/Sources/RswiftResources/Storyboard.swift +++ b/Sources/RswiftResources/StoryboardResource.swift @@ -1,5 +1,5 @@ // -// Storyboard.swift +// StoryboardResource.swift // R.swift // // Created by Mathijs Kadijk on 09-12-15. @@ -9,7 +9,7 @@ import Foundation -public struct Storyboard { +public struct StoryboardResource { public let name: String public let locale: LocaleReference public let deploymentTarget: DeploymentTarget? From 5d836b0b206f9d81f210780355d14eacaf0025a0 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 15 Jul 2022 12:17:55 +0200 Subject: [PATCH 023/161] Add filename to ImageResource --- .../ResourceApp.xcodeproj/project.pbxproj | 2 +- .../ResourceApp/FirstViewController.swift | 1 + Sources/RswiftCore/RswiftCore.swift | 14 +++++++++++--- Sources/RswiftGenerators/Font+Generator.swift | 3 +-- Sources/RswiftParsers/AssetCatalog+Parser.swift | 2 +- Sources/RswiftParsers/FontResource+Parser.swift | 2 +- Sources/RswiftParsers/ImageResource+Parser.swift | 2 +- Sources/RswiftResources/FontResource.swift | 6 +++--- Sources/RswiftResources/ImageResource.swift | 4 +++- 9 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj b/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj index 56c9ca35..7f735e49 100644 --- a/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj +++ b/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj @@ -728,7 +728,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# \"$SRCROOT/../../build/Debug/rswift\" generate --generateUITestFile \"$SRCROOT/R.UITest.generated.swift\" --import SWRevealViewController \"$SRCROOT/R.generated.swift\" > \"$SRCROOT/rswift.log\"\n# /Users/tom/Library/Developer/Xcode/DerivedData/R-gzdnepbnjmlzctbxokifiegoyhmk/Build/Products/Debug/rswift generate --generateUITestFile \"$SRCROOT/R.UITest.generated.swift\" --import SWRevealViewController \"$SRCROOT/R.generated.swift\" > \"$SRCROOT/rswift.log\"\n"; + shellScript = "# \"$SRCROOT/../../build/rswift\" generate --generateUITestFile \"$SRCROOT/R.UITest.generated.swift\" --import SWRevealViewController \"$SRCROOT/R.generated.swift\" > \"$SRCROOT/rswift.log\"\n# /Users/tom/Library/Developer/Xcode/DerivedData/R-gzdnepbnjmlzctbxokifiegoyhmk/Build/Products/Debug/rswift generate --generateUITestFile \"$SRCROOT/R.UITest.generated.swift\" --import SWRevealViewController \"$SRCROOT/R.generated.swift\" > \"$SRCROOT/rswift.log\"\n"; }; ED8FCF67313DC003391627B7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; diff --git a/Examples/ResourceApp/ResourceApp/FirstViewController.swift b/Examples/ResourceApp/ResourceApp/FirstViewController.swift index 878e9970..56f4f40d 100644 --- a/Examples/ResourceApp/ResourceApp/FirstViewController.swift +++ b/Examples/ResourceApp/ResourceApp/FirstViewController.swift @@ -16,6 +16,7 @@ class FirstViewController: UIViewController { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. titleLabel.font = R.font.averiaLibreBoldItalic(size: 36) + tabBarItem.image = R.image.userWhite.uiImage() } override func didReceiveMemoryWarning() { diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 40e50210..8a992d73 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -59,16 +59,24 @@ public struct RswiftCore { .map { $0.url(with: urlForSourceTreeFolder) } let start = Date() +// let items = try urls +// .filter { ImageResource.supportedExtensions.contains($0.pathExtension) } +//// .filter { !FileResource.unsupportedExtensions.contains($0.pathExtension) } +// .map { try ImageResource.parse(url: $0, assetTags: nil) } +// for item in items { +//// print(">>>", item) +// print(item.generateResourceLetCodeString()) +// } + let items = try urls - .filter { ImageResource.supportedExtensions.contains($0.pathExtension) } + .filter { FontResource.supportedExtensions.contains($0.pathExtension) } // .filter { !FileResource.unsupportedExtensions.contains($0.pathExtension) } - .map { try ImageResource.parse(url: $0, assetTags: nil) } + .map { try FontResource.parse(url: $0) } for item in items { // print(">>>", item) print(item.generateResourceLetCodeString()) } - print("TOTAL", Date().timeIntervalSince(start)) print() } diff --git a/Sources/RswiftGenerators/Font+Generator.swift b/Sources/RswiftGenerators/Font+Generator.swift index 268b16bc..1ad08027 100644 --- a/Sources/RswiftGenerators/Font+Generator.swift +++ b/Sources/RswiftGenerators/Font+Generator.swift @@ -10,7 +10,6 @@ import RswiftResources extension FontResource { public func generateResourceLetCodeString() -> String { - "let \(SwiftIdentifier(name: self.name).value) = \(self)" + "static let \(SwiftIdentifier(name: self.name).value) = \(self)" } } - diff --git a/Sources/RswiftParsers/AssetCatalog+Parser.swift b/Sources/RswiftParsers/AssetCatalog+Parser.swift index 0800780c..abfdc5cc 100644 --- a/Sources/RswiftParsers/AssetCatalog+Parser.swift +++ b/Sources/RswiftParsers/AssetCatalog+Parser.swift @@ -114,7 +114,7 @@ extension AssetCatalog: SupportedExtensions { for fileURL in directory.images { let name = (path + [fileURL.filenameWithoutExtension!]).joined(separator: "/") let tags = parseOnDemandResourceTags(directory: fileURL) - images.append(.init(name: name, locale: nil, onDemandResourceTags: tags)) + images.append(.init(name: name, locale: nil, onDemandResourceTags: tags, filename: fileURL.lastPathComponent)) } var dataAssets: [AssetCatalog.DataAsset] = [] diff --git a/Sources/RswiftParsers/FontResource+Parser.swift b/Sources/RswiftParsers/FontResource+Parser.swift index 53bcc071..3c2b3301 100644 --- a/Sources/RswiftParsers/FontResource+Parser.swift +++ b/Sources/RswiftParsers/FontResource+Parser.swift @@ -24,6 +24,6 @@ extension FontResource: SupportedExtensions { throw ResourceParsingError("No postscriptName associated to font at \(url)") } - return FontResource(filename: url.lastPathComponent, name: postScriptName as String) + return FontResource(name: postScriptName as String, filename: url.lastPathComponent) } } diff --git a/Sources/RswiftParsers/ImageResource+Parser.swift b/Sources/RswiftParsers/ImageResource+Parser.swift index 1ff19ea1..d57be70a 100644 --- a/Sources/RswiftParsers/ImageResource+Parser.swift +++ b/Sources/RswiftParsers/ImageResource+Parser.swift @@ -31,6 +31,6 @@ extension ImageResource: SupportedExtensions { let pathExtensionToUse = (pathExtension == "png") ? "" : ".\(pathExtension)" let name = regex.stringByReplacingMatches(in: filename, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: fullFileNameRange, withTemplate: pathExtensionToUse) - return ImageResource(name: name, locale: locale, onDemandResourceTags: assetTags) + return ImageResource(name: name, locale: locale, onDemandResourceTags: assetTags, filename: filename) } } diff --git a/Sources/RswiftResources/FontResource.swift b/Sources/RswiftResources/FontResource.swift index 18037350..78256fc0 100644 --- a/Sources/RswiftResources/FontResource.swift +++ b/Sources/RswiftResources/FontResource.swift @@ -10,11 +10,11 @@ import Foundation public struct FontResource { - public let filename: String public let name: String + public let filename: String - public init(filename: String, name: String) { - self.filename = filename + public init(name: String, filename: String) { self.name = name + self.filename = filename } } diff --git a/Sources/RswiftResources/ImageResource.swift b/Sources/RswiftResources/ImageResource.swift index 431fb682..6dfd4781 100644 --- a/Sources/RswiftResources/ImageResource.swift +++ b/Sources/RswiftResources/ImageResource.swift @@ -13,10 +13,12 @@ public struct ImageResource { public let name: String public let locale: LocaleReference? public let onDemandResourceTags: [String]? + public let filename: String - public init(name: String, locale: LocaleReference?, onDemandResourceTags: [String]?) { + public init(name: String, locale: LocaleReference?, onDemandResourceTags: [String]?, filename: String) { self.name = name self.locale = locale self.onDemandResourceTags = onDemandResourceTags + self.filename = filename } } From f19f242635042651fc93258a3f1ec34ab58c44f5 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 15 Jul 2022 13:15:30 +0200 Subject: [PATCH 024/161] Development string generated code --- Sources/RswiftCore/RswiftCore.swift | 7 ++--- .../AssetCatalog+Generator.swift | 31 +++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 Sources/RswiftGenerators/AssetCatalog+Generator.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 8a992d73..f1266e6a 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -69,12 +69,11 @@ public struct RswiftCore { // } let items = try urls - .filter { FontResource.supportedExtensions.contains($0.pathExtension) } + .filter { AssetCatalog.supportedExtensions.contains($0.pathExtension) } // .filter { !FileResource.unsupportedExtensions.contains($0.pathExtension) } - .map { try FontResource.parse(url: $0) } + .map { try AssetCatalog.parse(url: $0) } for item in items { -// print(">>>", item) - print(item.generateResourceLetCodeString()) + print(item.generateColorResourceLetCodeString()) } print("TOTAL", Date().timeIntervalSince(start)) diff --git a/Sources/RswiftGenerators/AssetCatalog+Generator.swift b/Sources/RswiftGenerators/AssetCatalog+Generator.swift new file mode 100644 index 00000000..96182b03 --- /dev/null +++ b/Sources/RswiftGenerators/AssetCatalog+Generator.swift @@ -0,0 +1,31 @@ +// +// AssetCatalog+Generator.swift +// +// +// Created by Tom Lokhorst on 2022-07-15. +// + +import Foundation +import RswiftResources + +extension AssetCatalog { + public func generateColorResourceLetCodeString() -> String { + root.generateColorResourceLetsCodeString().joined(separator: "\n") + } +} + +extension AssetCatalog.Namespace { + fileprivate func generateColorResourceLetsCodeString() -> [String] { + var cs = colors.map { color in + "static let \(SwiftIdentifier(name: color.name).value) = AssetCatalog.\(color)" + } + + for (name, namespace) in subnamespaces { + cs.append("struct \(SwiftIdentifier(name: name).value) {") + cs.append(contentsOf: namespace.generateColorResourceLetsCodeString().map { " \($0)" }) + cs.append("}") + } + + return cs + } +} From f5018ac1e650c25365aa076246a5b48c7aadd288 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sat, 16 Jul 2022 21:17:10 +0200 Subject: [PATCH 025/161] Work on code generation --- Package.swift | 7 +- Sources/RswiftCore/RswiftCore.swift | 19 ++- .../RswiftGenerators/SwiftSyntax/Struct.swift | 146 ++++++++++++++++++ 3 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 Sources/RswiftGenerators/SwiftSyntax/Struct.swift diff --git a/Package.swift b/Package.swift index 16d2f0df..dfbab068 100644 --- a/Package.swift +++ b/Package.swift @@ -13,11 +13,16 @@ let package = Package( dependencies: [ .package(url: "https://github.com/kylef/Commander.git", from: "0.8.0"), .package(url: "https://github.com/tomlokhorst/XcodeEdit", from: "2.8.0"), +// .package(url: "https://github.com/apple/swift-syntax.git", .branchItem("swift-5.6-RELEASE")), ], targets: [ .target(name: "RswiftResources"), .target(name: "RswiftParsers", dependencies: ["RswiftResources", "XcodeEdit"]), - .target(name: "RswiftGenerators", dependencies: ["RswiftResources"]), + .target(name: "RswiftGenerators", dependencies: [ + "RswiftResources", +// .product(name: "SwiftSyntax", package: "swift-syntax"), +// .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), + ]), // Core of R.swift, brings all previous parts together .target(name: "RswiftCore", dependencies: ["RswiftParsers", "RswiftGenerators"]), diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index f1266e6a..8e25bc99 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -68,13 +68,18 @@ public struct RswiftCore { // print(item.generateResourceLetCodeString()) // } - let items = try urls - .filter { AssetCatalog.supportedExtensions.contains($0.pathExtension) } -// .filter { !FileResource.unsupportedExtensions.contains($0.pathExtension) } - .map { try AssetCatalog.parse(url: $0) } - for item in items { - print(item.generateColorResourceLetCodeString()) - } +// let items = try urls +// .filter { AssetCatalog.supportedExtensions.contains($0.pathExtension) } +//// .filter { !FileResource.unsupportedExtensions.contains($0.pathExtension) } +// .map { try AssetCatalog.parse(url: $0) } +// for item in items { +// print(item.generateColorResourceLetCodeString()) +// } + +// let xs = try SyntaxParser.parse(source: "struct Hello {}") +// print(xs) + + go() print("TOTAL", Date().timeIntervalSince(start)) print() diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift new file mode 100644 index 00000000..5f598070 --- /dev/null +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -0,0 +1,146 @@ +// +// Struct.swift +// +// +// Created by Tom Lokhorst on 2022-07-16. +// + +import Foundation +import RswiftResources + +public enum AccessControl { + case none + case `public` + + func code() -> String? { + switch self { + case .none: + return nil + case .public: + return "public" + } + } +} + +public struct LetBinding { + public var accessControl = AccessControl.none + public let name: SwiftIdentifier + public let typeReference: TypeReference + + public init(accessControl: AccessControl = AccessControl.none, name: SwiftIdentifier, typeReference: TypeReference) { + self.accessControl = accessControl + self.name = name + self.typeReference = typeReference + } + + func render(_ pp: inout PrettyPrinter) { + pp.append(words: [accessControl.code(), "let", "\(name.value):", typeReference.rawName]) + } +} + + +public typealias StructMembers = ([LetBinding], [Struct]) + +@resultBuilder +public struct StructMembersBuilder { + public static func buildExpression(_ expression: LetBinding) -> StructMembers { + ([expression], []) + } + public static func buildExpression(_ expression: Struct) -> StructMembers { + ([], [expression]) + } + + public static func buildBlock(_ members: StructMembers...) -> StructMembers { + (members.flatMap(\.0), members.flatMap(\.1)) + } +} + +public struct Struct { + public var accessControl = AccessControl.none + public let name: SwiftIdentifier + public var lets: [LetBinding] = [] + public var structs: [Struct] = [] + + public init(accessControl: AccessControl = AccessControl.none, name: SwiftIdentifier, lets: [LetBinding]) { + self.accessControl = accessControl + self.name = name + self.lets = lets + } + + public init(accessControl: AccessControl = AccessControl.none, _ rawName: String, @StructMembersBuilder membersBuilder: () -> StructMembers) { + self.accessControl = accessControl + self.name = SwiftIdentifier(rawValue: rawName) + (self.lets, self.structs) = membersBuilder() + } + + public func prettyPrint() -> String { +// render().joined(separator: "\n") + var pp = PrettyPrinter() + render(&pp) + return pp.render() + } + + func render() -> [String] { + var ls: [String] = [] + ls.append("struct \(name.value) {") +// ls.append(contentsOf: lets.map { " \($0.render())" }) + if !lets.isEmpty && !structs.isEmpty { + ls.append("") + } + ls.append(contentsOf: structs.flatMap { $0.render().map { " \($0)" } }) + ls.append("}") + return ls + } + + func render(_ pp: inout PrettyPrinter) { + pp.append(line: "struct \(name.value) {") + pp.increment() + for letb in lets { + letb.render(&pp) + } + pp.decrement() + + if !lets.isEmpty && !structs.isEmpty { + pp.append(line: "") + } + + pp.increment() + for st in structs { + st.render(&pp) + } + pp.decrement() + + pp.append(line: "}") + } +} + +struct PrettyPrinter { + private var indent = 0 + private var lines: [(Int, String)] = [] + + mutating func increment() { + indent += 1 + } + + mutating func decrement() { + indent -= 1 + } + + mutating func append(line: String) { + lines.append((indent, line)) + } + + mutating func append(words: [String?]) { + let ws = words.compactMap { $0 } + if ws.isEmpty { return } + + append(line: ws.joined(separator: " ")) + } + + func render() -> String { + let ls = lines.map { (indent, line) in + String(repeating: " ", count: indent) + line + } + return ls.joined(separator: "\n") + } +} From a478cfbfd1dc1c793aa2e7275228ce8f05407916 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 17 Jul 2022 13:45:14 +0200 Subject: [PATCH 026/161] Work on code generator --- Sources/RswiftCore/RswiftCore.swift | 22 ++-- .../RswiftGenerators/Image+Generator.swift | 16 --- .../AssetCatalog+Generator.swift | 0 .../FileResource+Generator.swift | 0 .../Font+Generator.swift | 0 .../ResourceGenerators/Image+Generator.swift | 59 ++++++++++ .../LocalizableStrings+Generator.swift | 0 .../Nib+Generator.swift | 0 .../Storyboard+Generator.swift | 0 .../RswiftGenerators/SwiftIdentifier.swift | 2 +- .../RswiftGenerators/SwiftSyntax/Struct.swift | 101 ++++++++++++------ .../RswiftParsers/AssetCatalog+Parser.swift | 2 +- .../RswiftParsers/ImageResource+Parser.swift | 2 +- Sources/RswiftResources/ImageResource.swift | 4 +- 14 files changed, 145 insertions(+), 63 deletions(-) delete mode 100644 Sources/RswiftGenerators/Image+Generator.swift rename Sources/RswiftGenerators/{ => ResourceGenerators}/AssetCatalog+Generator.swift (100%) rename Sources/RswiftGenerators/{ => ResourceGenerators}/FileResource+Generator.swift (100%) rename Sources/RswiftGenerators/{ => ResourceGenerators}/Font+Generator.swift (100%) create mode 100644 Sources/RswiftGenerators/ResourceGenerators/Image+Generator.swift rename Sources/RswiftGenerators/{ => ResourceGenerators}/LocalizableStrings+Generator.swift (100%) rename Sources/RswiftGenerators/{ => ResourceGenerators}/Nib+Generator.swift (100%) rename Sources/RswiftGenerators/{ => ResourceGenerators}/Storyboard+Generator.swift (100%) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 8e25bc99..b94f7718 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -69,17 +69,27 @@ public struct RswiftCore { // } // let items = try urls -// .filter { AssetCatalog.supportedExtensions.contains($0.pathExtension) } +// .filter { StoryboardResource.supportedExtensions.contains($0.pathExtension) } //// .filter { !FileResource.unsupportedExtensions.contains($0.pathExtension) } -// .map { try AssetCatalog.parse(url: $0) } +// .map { try StoryboardResource.parse(url: $0) } // for item in items { -// print(item.generateColorResourceLetCodeString()) +// print(item.name, item.viewControllers.map(\.type.rawName)) // } -// let xs = try SyntaxParser.parse(source: "struct Hello {}") -// print(xs) + let images = try urls + .filter { ImageResource.supportedExtensions.contains($0.pathExtension) } + .map { try ImageResource.parse(url: $0, assetTags: nil) } + let assetCatalogs = try urls + .filter { AssetCatalog.supportedExtensions.contains($0.pathExtension) } + .map { try AssetCatalog.parse(url: $0) } - go() + let structName = SwiftIdentifier(rawValue: "R") + let qualifiedName = structName + let s = Struct(name: structName) { + ImageResource.generateStruct(resources: images, assetCatalogs: assetCatalogs, prefix: qualifiedName) + } + + print(s.prettyPrint()) print("TOTAL", Date().timeIntervalSince(start)) print() diff --git a/Sources/RswiftGenerators/Image+Generator.swift b/Sources/RswiftGenerators/Image+Generator.swift deleted file mode 100644 index df6d60dd..00000000 --- a/Sources/RswiftGenerators/Image+Generator.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// ImageResource+Generator.swift -// -// -// Created by Tom Lokhorst on 2022-06-24. -// - -import Foundation -import RswiftResources - -extension ImageResource { - public func generateResourceLetCodeString() -> String { - "static let \(SwiftIdentifier(name: self.name).value) = \(self)" - } -} - diff --git a/Sources/RswiftGenerators/AssetCatalog+Generator.swift b/Sources/RswiftGenerators/ResourceGenerators/AssetCatalog+Generator.swift similarity index 100% rename from Sources/RswiftGenerators/AssetCatalog+Generator.swift rename to Sources/RswiftGenerators/ResourceGenerators/AssetCatalog+Generator.swift diff --git a/Sources/RswiftGenerators/FileResource+Generator.swift b/Sources/RswiftGenerators/ResourceGenerators/FileResource+Generator.swift similarity index 100% rename from Sources/RswiftGenerators/FileResource+Generator.swift rename to Sources/RswiftGenerators/ResourceGenerators/FileResource+Generator.swift diff --git a/Sources/RswiftGenerators/Font+Generator.swift b/Sources/RswiftGenerators/ResourceGenerators/Font+Generator.swift similarity index 100% rename from Sources/RswiftGenerators/Font+Generator.swift rename to Sources/RswiftGenerators/ResourceGenerators/Font+Generator.swift diff --git a/Sources/RswiftGenerators/ResourceGenerators/Image+Generator.swift b/Sources/RswiftGenerators/ResourceGenerators/Image+Generator.swift new file mode 100644 index 00000000..6f765d56 --- /dev/null +++ b/Sources/RswiftGenerators/ResourceGenerators/Image+Generator.swift @@ -0,0 +1,59 @@ +// +// ImageResource+Generator.swift +// +// +// Created by Tom Lokhorst on 2022-06-24. +// + +import Foundation +import RswiftResources + +extension LocaleReference { + func codeString() -> String { + switch self { + case .none: + return ".none" + case .base: + return ".base" + case .language(let string): + return ".language(\(string))" + } + } +} + +extension ImageResource { + func generateLetBinding() -> LetBinding { + let locs = locale.map { $0.codeString() } ?? "nil" + let code = "ImageResource(name: \"\(name)\", locale: \(locs), onDemandResourceTags: \(String(describing: onDemandResourceTags)))" + return LetBinding( + comments: ["Image `\(name)`."], + isStatic: true, + name: SwiftIdentifier(name: name), + valueCodeString: code) + } + + public static func generateStruct(resources: [ImageResource], assetCatalogs: [AssetCatalog], prefix: SwiftIdentifier) -> Struct { + let structName = SwiftIdentifier(name: "image") + let qualifiedName = prefix + structName + + // Multiple resources can share same name, + // for example: Colors.jpg and Colors@2x.jpg are both named "Colors.jpg" + // Deduplicate these + let namedResources = Dictionary(grouping: resources, by: \.name).values.map(\.first!) + let assetFolderImageResources = assetCatalogs.flatMap(\.root.images) + + let allResources = namedResources + assetFolderImageResources + let groupedResources = allResources.grouped(bySwiftIdentifier: { $0.name }) + + groupedResources.reportWarningsForDuplicatesAndEmpties(source: "image", result: "image") { l in + print("warning:", l) + } + + let imageLets = groupedResources.uniques.map { $0.generateLetBinding() } + + let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(imageLets.count) images."] + return Struct(comments: comments, name: structName) { + imageLets + } + } +} diff --git a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift b/Sources/RswiftGenerators/ResourceGenerators/LocalizableStrings+Generator.swift similarity index 100% rename from Sources/RswiftGenerators/LocalizableStrings+Generator.swift rename to Sources/RswiftGenerators/ResourceGenerators/LocalizableStrings+Generator.swift diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/ResourceGenerators/Nib+Generator.swift similarity index 100% rename from Sources/RswiftGenerators/Nib+Generator.swift rename to Sources/RswiftGenerators/ResourceGenerators/Nib+Generator.swift diff --git a/Sources/RswiftGenerators/Storyboard+Generator.swift b/Sources/RswiftGenerators/ResourceGenerators/Storyboard+Generator.swift similarity index 100% rename from Sources/RswiftGenerators/Storyboard+Generator.swift rename to Sources/RswiftGenerators/ResourceGenerators/Storyboard+Generator.swift diff --git a/Sources/RswiftGenerators/SwiftIdentifier.swift b/Sources/RswiftGenerators/SwiftIdentifier.swift index a91582d7..46d7aa61 100644 --- a/Sources/RswiftGenerators/SwiftIdentifier.swift +++ b/Sources/RswiftGenerators/SwiftIdentifier.swift @@ -74,7 +74,7 @@ struct SwiftNameGroups { let resultPlural = "\(result)s" for (sanitizedName, dups) in duplicates { - warning("Skipping \(dups.count) \(sourcePlural) because symbol '\(sanitizedName)' would be generated for all of these \(resultPlural): \(dups.joined(separator: ", "))") + warning("Skipping \(dups.count) \(sourcePlural) because symbol '\(sanitizedName.value)' would be generated for all of these \(resultPlural): \(dups.joined(separator: ", "))") } if let empty = empties.first , empties.count == 1 { diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index 5f598070..29233711 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -23,18 +23,48 @@ public enum AccessControl { } public struct LetBinding { + public let comments: [String] public var accessControl = AccessControl.none + public let isStatic: Bool public let name: SwiftIdentifier - public let typeReference: TypeReference + public let typeReference: TypeReference? + public let valueCodeString: String? - public init(accessControl: AccessControl = AccessControl.none, name: SwiftIdentifier, typeReference: TypeReference) { + public init(comments: [String] = [], accessControl: AccessControl = AccessControl.none, isStatic: Bool = false, name: SwiftIdentifier, typeReference: TypeReference, valueCodeString: String?) { + self.comments = comments self.accessControl = accessControl + self.isStatic = isStatic self.name = name self.typeReference = typeReference + self.valueCodeString = valueCodeString + } + + public init(comments: [String] = [], accessControl: AccessControl = AccessControl.none, isStatic: Bool = false, name: SwiftIdentifier, valueCodeString: String) { + self.comments = comments + self.accessControl = accessControl + self.isStatic = isStatic + self.name = name + self.typeReference = nil + self.valueCodeString = valueCodeString } func render(_ pp: inout PrettyPrinter) { - pp.append(words: [accessControl.code(), "let", "\(name.value):", typeReference.rawName]) + var words: [String?] = [ + accessControl.code(), + isStatic ? "static" : nil, + "let", + typeReference == nil ? name.value : "\(name.value):", + typeReference?.rawName + ] + if let valueCodeString { + words.append("=") + words.append(valueCodeString) + } + + for c in comments { + pp.append(words: ["///", c == "" ? nil : c]) + } + pp.append(words: words) } } @@ -46,69 +76,72 @@ public struct StructMembersBuilder { public static func buildExpression(_ expression: LetBinding) -> StructMembers { ([expression], []) } + + public static func buildExpression(_ expressions: [LetBinding]) -> StructMembers { + (expressions, []) + } + public static func buildExpression(_ expression: Struct) -> StructMembers { ([], [expression]) } + public static func buildExpression(_ expressions: [Struct]) -> StructMembers { + ([], expressions) + } + + public static func buildArray(_ members: [StructMembers]) -> StructMembers { + (members.flatMap(\.0), members.flatMap(\.1)) + } + public static func buildBlock(_ members: StructMembers...) -> StructMembers { (members.flatMap(\.0), members.flatMap(\.1)) } } public struct Struct { + public let comments: [String] public var accessControl = AccessControl.none public let name: SwiftIdentifier public var lets: [LetBinding] = [] public var structs: [Struct] = [] - public init(accessControl: AccessControl = AccessControl.none, name: SwiftIdentifier, lets: [LetBinding]) { + public init(comments: [String] = [], accessControl: AccessControl = AccessControl.none, name: SwiftIdentifier, @StructMembersBuilder membersBuilder: () -> StructMembers) { + self.comments = comments self.accessControl = accessControl self.name = name - self.lets = lets - } - - public init(accessControl: AccessControl = AccessControl.none, _ rawName: String, @StructMembersBuilder membersBuilder: () -> StructMembers) { - self.accessControl = accessControl - self.name = SwiftIdentifier(rawValue: rawName) (self.lets, self.structs) = membersBuilder() } public func prettyPrint() -> String { -// render().joined(separator: "\n") var pp = PrettyPrinter() render(&pp) return pp.render() } - func render() -> [String] { - var ls: [String] = [] - ls.append("struct \(name.value) {") -// ls.append(contentsOf: lets.map { " \($0.render())" }) - if !lets.isEmpty && !structs.isEmpty { - ls.append("") - } - ls.append(contentsOf: structs.flatMap { $0.render().map { " \($0)" } }) - ls.append("}") - return ls - } - func render(_ pp: inout PrettyPrinter) { + for c in comments { + pp.append(words: ["///", c == "" ? nil : c]) + } pp.append(line: "struct \(name.value) {") - pp.increment() - for letb in lets { - letb.render(&pp) + + pp.indented { pp in + for letb in lets { + if !letb.comments.isEmpty { + pp.append(line: "") + } + letb.render(&pp) + } } - pp.decrement() if !lets.isEmpty && !structs.isEmpty { pp.append(line: "") } - pp.increment() - for st in structs { - st.render(&pp) + pp.indented { pp in + for st in structs { + st.render(&pp) + } } - pp.decrement() pp.append(line: "}") } @@ -118,11 +151,9 @@ struct PrettyPrinter { private var indent = 0 private var lines: [(Int, String)] = [] - mutating func increment() { + mutating func indented(perform: (inout PrettyPrinter) -> Void) { indent += 1 - } - - mutating func decrement() { + perform(&self) indent -= 1 } diff --git a/Sources/RswiftParsers/AssetCatalog+Parser.swift b/Sources/RswiftParsers/AssetCatalog+Parser.swift index abfdc5cc..0800780c 100644 --- a/Sources/RswiftParsers/AssetCatalog+Parser.swift +++ b/Sources/RswiftParsers/AssetCatalog+Parser.swift @@ -114,7 +114,7 @@ extension AssetCatalog: SupportedExtensions { for fileURL in directory.images { let name = (path + [fileURL.filenameWithoutExtension!]).joined(separator: "/") let tags = parseOnDemandResourceTags(directory: fileURL) - images.append(.init(name: name, locale: nil, onDemandResourceTags: tags, filename: fileURL.lastPathComponent)) + images.append(.init(name: name, locale: nil, onDemandResourceTags: tags)) } var dataAssets: [AssetCatalog.DataAsset] = [] diff --git a/Sources/RswiftParsers/ImageResource+Parser.swift b/Sources/RswiftParsers/ImageResource+Parser.swift index d57be70a..1ff19ea1 100644 --- a/Sources/RswiftParsers/ImageResource+Parser.swift +++ b/Sources/RswiftParsers/ImageResource+Parser.swift @@ -31,6 +31,6 @@ extension ImageResource: SupportedExtensions { let pathExtensionToUse = (pathExtension == "png") ? "" : ".\(pathExtension)" let name = regex.stringByReplacingMatches(in: filename, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: fullFileNameRange, withTemplate: pathExtensionToUse) - return ImageResource(name: name, locale: locale, onDemandResourceTags: assetTags, filename: filename) + return ImageResource(name: name, locale: locale, onDemandResourceTags: assetTags) } } diff --git a/Sources/RswiftResources/ImageResource.swift b/Sources/RswiftResources/ImageResource.swift index 6dfd4781..431fb682 100644 --- a/Sources/RswiftResources/ImageResource.swift +++ b/Sources/RswiftResources/ImageResource.swift @@ -13,12 +13,10 @@ public struct ImageResource { public let name: String public let locale: LocaleReference? public let onDemandResourceTags: [String]? - public let filename: String - public init(name: String, locale: LocaleReference?, onDemandResourceTags: [String]?, filename: String) { + public init(name: String, locale: LocaleReference?, onDemandResourceTags: [String]?) { self.name = name self.locale = locale self.onDemandResourceTags = onDemandResourceTags - self.filename = filename } } From da0114d6f68b498203f8b58eeec9f9ada594d08c Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 17 Jul 2022 18:42:23 +0200 Subject: [PATCH 027/161] Add IgnoreFile --- Sources/RswiftCore/IgnoreFile.swift | 69 ++++++ Sources/RswiftCore/RswiftCore.swift | 49 ++-- Sources/RswiftCore/Util/Glob.swift | 218 ++++++++++++++++++ .../Generators/ImageStructGenerator.swift | 2 +- .../ResourceGenerators/Image+Generator.swift | 32 ++- .../RswiftGenerators/SwiftSyntax/Struct.swift | 2 + Sources/rswift/main.swift | 4 +- 7 files changed, 349 insertions(+), 27 deletions(-) create mode 100644 Sources/RswiftCore/IgnoreFile.swift create mode 100644 Sources/RswiftCore/Util/Glob.swift diff --git a/Sources/RswiftCore/IgnoreFile.swift b/Sources/RswiftCore/IgnoreFile.swift new file mode 100644 index 00000000..a6e817f7 --- /dev/null +++ b/Sources/RswiftCore/IgnoreFile.swift @@ -0,0 +1,69 @@ +// +// IgnoreFile.swift +// R.swift +// +// Created by Mathijs Kadijk on 01-10-16. +// Copyright © 2016 Mathijs Kadijk. All rights reserved. +// + +import Foundation + +class IgnoreFile { + let ignoredURLs: [URL] + let explicitlyIncludedURLs: [URL] + + init() { + ignoredURLs = [] + explicitlyIncludedURLs = [] + } + + init(ignoreFileURL: URL) throws { + let workingDirectory = ignoreFileURL.deletingLastPathComponent() + let potentialPatterns = try String(contentsOf: ignoreFileURL).components(separatedBy: .newlines) + + ignoredURLs = potentialPatterns + .filter { IgnoreFile.isPattern(potentialPattern: $0) && !IgnoreFile.isExplicitlyIncludedPattern(potentialPattern: $0) } + .flatMap { IgnoreFile.expandPattern($0, workingDirectory: workingDirectory) } + explicitlyIncludedURLs = potentialPatterns + .filter { IgnoreFile.isPattern(potentialPattern: $0) && IgnoreFile.isExplicitlyIncludedPattern(potentialPattern: $0) } + .map { String($0.dropFirst()) } + .flatMap { IgnoreFile.expandPattern($0, workingDirectory: workingDirectory) } + } + + static private func isPattern(potentialPattern: String) -> Bool { + // Check for empty line + if potentialPattern.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { return false } + + // Check for commented line + if potentialPattern.trimmingCharacters(in: .whitespacesAndNewlines).first == "#" { return false } + + return true + } + + static private func isExplicitlyIncludedPattern(potentialPattern: String) -> Bool { + // Check for explicitly included line + guard potentialPattern.trimmingCharacters(in: .whitespacesAndNewlines).first == "!" else { return false } + + return true + } + + static private func expandPattern(_ pattern: String, workingDirectory: URL) -> [URL] { + let globPattern = workingDirectory.path + "/" + pattern // This is a glob pattern, so we don't use URL here + let filePaths = IgnoreFile.listFilePaths(pattern: globPattern) + let urls = filePaths.map { URL(fileURLWithPath: $0).standardizedFileURL } + + return urls + } + + static private func listFilePaths(pattern: String) -> [String] { + guard !pattern.isEmpty else { + return [] + } + + return Glob(pattern: pattern).paths + } + + func matches(url: URL) -> Bool { + return ignoredURLs.contains(url) && !explicitlyIncludedURLs.contains(url) + } +} diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index b94f7718..e84e9288 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -24,6 +24,8 @@ public struct RswiftCore { let sdkRootURL: URL let platformURL: URL + let rswiftIgnoreURL: URL + public init( xcodeprojURL: URL, targetName: String, @@ -34,22 +36,27 @@ public struct RswiftCore { developerDirURL: URL, sourceRootURL: URL, sdkRootURL: URL, - platformURL: URL + platformURL: URL, + rswiftIgnoreURL: URL ) { self.xcodeprojURL = xcodeprojURL self.targetName = targetName self.productModuleName = productModuleName self.infoPlistFile = infoPlistFile self.codeSignEntitlements = codeSignEntitlements + self.builtProductsDirURL = builtProductsDirURL self.developerDirURL = developerDirURL self.sourceRootURL = sourceRootURL self.sdkRootURL = sdkRootURL self.platformURL = platformURL + + self.rswiftIgnoreURL = rswiftIgnoreURL } // Temporary function for use during development public func developRun() throws { + let ignoreFile = (try? IgnoreFile(ignoreFileURL: rswiftIgnoreURL)) ?? IgnoreFile() let xcodeproj = try! Xcodeproj(url: xcodeprojURL, warning: { print($0) }) let buildConfigurations = try xcodeproj.buildConfigurations(forTarget: targetName) @@ -57,24 +64,25 @@ public struct RswiftCore { let paths = try xcodeproj.resourcePaths(forTarget: targetName) let urls = paths .map { $0.url(with: urlForSourceTreeFolder) } + .filter { !ignoreFile.matches(url: $0) } let start = Date() -// let items = try urls -// .filter { ImageResource.supportedExtensions.contains($0.pathExtension) } -//// .filter { !FileResource.unsupportedExtensions.contains($0.pathExtension) } -// .map { try ImageResource.parse(url: $0, assetTags: nil) } -// for item in items { -//// print(">>>", item) -// print(item.generateResourceLetCodeString()) -// } - -// let items = try urls -// .filter { StoryboardResource.supportedExtensions.contains($0.pathExtension) } -//// .filter { !FileResource.unsupportedExtensions.contains($0.pathExtension) } -// .map { try StoryboardResource.parse(url: $0) } -// for item in items { -// print(item.name, item.viewControllers.map(\.type.rawName)) -// } + // let items = try urls + // .filter { ImageResource.supportedExtensions.contains($0.pathExtension) } + //// .filter { !FileResource.unsupportedExtensions.contains($0.pathExtension) } + // .map { try ImageResource.parse(url: $0, assetTags: nil) } + // for item in items { + //// print(">>>", item) + // print(item.generateResourceLetCodeString()) + // } + + // let items = try urls + // .filter { StoryboardResource.supportedExtensions.contains($0.pathExtension) } + //// .filter { !FileResource.unsupportedExtensions.contains($0.pathExtension) } + // .map { try StoryboardResource.parse(url: $0) } + // for item in items { + // print(item.name, item.viewControllers.map(\.type.rawName)) + // } let images = try urls .filter { ImageResource.supportedExtensions.contains($0.pathExtension) } @@ -86,7 +94,12 @@ public struct RswiftCore { let structName = SwiftIdentifier(rawValue: "R") let qualifiedName = structName let s = Struct(name: structName) { - ImageResource.generateStruct(resources: images, assetCatalogs: assetCatalogs, prefix: qualifiedName) + ImageResource.generateStruct( + resources: images, + namespaces: assetCatalogs.map(\.root), + name: SwiftIdentifier(name: "image"), + prefix: qualifiedName + ) } print(s.prettyPrint()) diff --git a/Sources/RswiftCore/Util/Glob.swift b/Sources/RswiftCore/Util/Glob.swift new file mode 100644 index 00000000..80c90ebc --- /dev/null +++ b/Sources/RswiftCore/Util/Glob.swift @@ -0,0 +1,218 @@ +// +// Created by Eric Firestone on 3/22/16. +// Copyright © 2016 Square, Inc. All rights reserved. +// Released under the Apache v2 License. +// +// Adapted from https://gist.github.com/blakemerryman/76312e1cbf8aec248167 +// Adapted from https://gist.github.com/efirestone/ce01ae109e08772647eb061b3bb387c3 + +import Foundation + +public let GlobBehaviorBashV3 = Glob.Behavior( + supportsGlobstar: false, + includesFilesFromRootOfGlobstar: false, + includesDirectoriesInResults: true, + includesFilesInResultsIfTrailingSlash: false +) +public let GlobBehaviorBashV4 = Glob.Behavior( + supportsGlobstar: true, // Matches Bash v4 with "shopt -s globstar" option + includesFilesFromRootOfGlobstar: true, + includesDirectoriesInResults: true, + includesFilesInResultsIfTrailingSlash: false +) +public let GlobBehaviorGradle = Glob.Behavior( + supportsGlobstar: true, + includesFilesFromRootOfGlobstar: true, + includesDirectoriesInResults: false, + includesFilesInResultsIfTrailingSlash: true +) + +/** + Finds files on the file system using pattern matching. + */ +public class Glob: Collection { + + /** + * Different glob implementations have different behaviors, so the behavior of this + * implementation is customizable. + */ + public struct Behavior { + // If true then a globstar ("**") causes matching to be done recursively in subdirectories. + // If false then "**" is treated the same as "*" + let supportsGlobstar: Bool + + // If true the results from the directory where the globstar is declared will be included as well. + // For example, with the pattern "dir/**/*.ext" the fie "dir/file.ext" would be included if this + // property is true, and would be omitted if it's false. + let includesFilesFromRootOfGlobstar: Bool + + // If false then the results will not include directory entries. This does not affect recursion depth. + let includesDirectoriesInResults: Bool + + // If false and the last characters of the pattern are "**/" then only directories are returned in the results. + let includesFilesInResultsIfTrailingSlash: Bool + } + + public static var defaultBehavior = GlobBehaviorBashV4 + + public static let defaultBlacklistedDirectories = ["node_modules", "Pods"] + + private var isDirectoryCache = [String: Bool]() + + public let behavior: Behavior + public let blacklistedDirectories: [String] + var paths = [String]() + public var startIndex: Int { return paths.startIndex } + public var endIndex: Int { return paths.endIndex } + + /// Initialize a glob + /// + /// - Parameters: + /// - pattern: The pattern to use when building the list of matching directories. + /// - behavior: See individual descriptions on `Glob.Behavior` values. + /// - blacklistedDirectories: An array of directories to ignore at the root level of the project. + public init(pattern: String, behavior: Behavior = Glob.defaultBehavior, blacklistedDirectories: [String] = defaultBlacklistedDirectories) { + + self.behavior = behavior + self.blacklistedDirectories = blacklistedDirectories + + var adjustedPattern = pattern + let hasTrailingGlobstarSlash = pattern.hasSuffix("**/") + var includeFiles = !hasTrailingGlobstarSlash + + if behavior.includesFilesInResultsIfTrailingSlash { + includeFiles = true + if hasTrailingGlobstarSlash { + // Grab the files too. + adjustedPattern += "*" + } + } + + let patterns = behavior.supportsGlobstar ? expandGlobstar(pattern: adjustedPattern) : [adjustedPattern] + + for pattern in patterns { + var gt = glob_t() + if executeGlob(pattern: pattern, gt: >) { + populateFiles(gt: gt, includeFiles: includeFiles) + } + + globfree(>) + } + + paths = Array(Set(paths)).sorted { lhs, rhs in + lhs.compare(rhs) != ComparisonResult.orderedDescending + } + + clearCaches() + } + + // MARK: Subscript Support + + public subscript(i: Int) -> String { + return paths[i] + } + + // MARK: Protocol of IndexableBase + + public func index(after i: Glob.Index) -> Glob.Index { + return i + 1 + } + + // MARK: Private + + private var globalFlags = GLOB_TILDE | GLOB_BRACE | GLOB_MARK + + private func executeGlob(pattern: UnsafePointer, gt: UnsafeMutablePointer) -> Bool { + return 0 == glob(pattern, globalFlags, nil, gt) + } + + private func expandGlobstar(pattern: String) -> [String] { + guard pattern.contains("**") else { + return [pattern] + } + + var results = [String]() + var parts = pattern.components(separatedBy: "**") + let firstPart = parts.removeFirst() + var lastPart = parts.joined(separator: "**") + + let fileManager = FileManager.default + + var directories: [String] + + do { + directories = try fileManager.contentsOfDirectory(atPath: firstPart).compactMap { subpath -> [String]? in + if blacklistedDirectories.contains(subpath) { + return nil + } + let firstLevelPath = NSString(string: firstPart).appendingPathComponent(subpath) + if isDirectory(path: firstLevelPath) { + var subDirs: [String] = try fileManager.subpathsOfDirectory(atPath: firstLevelPath).compactMap { subpath -> String? in + let fullPath = NSString(string: firstLevelPath).appendingPathComponent(subpath) + return isDirectory(path: fullPath) ? fullPath : nil + } + subDirs.append(firstLevelPath) + return subDirs + } else { + return nil + } + }.joined().map { $0 } + } catch { + directories = [] + print("Error parsing file system item: \(error)") + } + + if behavior.includesFilesFromRootOfGlobstar { + // Check the base directory for the glob star as well. + directories.insert(firstPart, at: 0) + + // Include the globstar root directory ("dir/") in a pattern like "dir/**" or "dir/**/" + if lastPart.isEmpty { + results.append(firstPart) + } + } + + if lastPart.isEmpty { + lastPart = "*" + } + for directory in directories { + let partiallyResolvedPattern = NSString(string: directory).appendingPathComponent(lastPart) + results.append(contentsOf: expandGlobstar(pattern: partiallyResolvedPattern)) + } + + return results + } + + private func isDirectory(path: String) -> Bool { + if let isDirectory = isDirectoryCache[path] { + return isDirectory + } + + var isDirectoryBool = ObjCBool(false) + let isDirectory = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectoryBool) && isDirectoryBool.boolValue + isDirectoryCache[path] = isDirectory + + return isDirectory + } + + private func clearCaches() { + isDirectoryCache.removeAll() + } + + private func populateFiles(gt: glob_t, includeFiles: Bool) { + let includeDirectories = behavior.includesDirectoriesInResults + + for i in 0.. Struct { + func generatedImageStruct(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Struct { let allFunctions = imageAssets let groupedFunctions = allFunctions.grouped(bySwiftIdentifier: { $0 }) diff --git a/Sources/RswiftGenerators/ResourceGenerators/Image+Generator.swift b/Sources/RswiftGenerators/ResourceGenerators/Image+Generator.swift index 6f765d56..1da2f4d7 100644 --- a/Sources/RswiftGenerators/ResourceGenerators/Image+Generator.swift +++ b/Sources/RswiftGenerators/ResourceGenerators/Image+Generator.swift @@ -24,7 +24,8 @@ extension LocaleReference { extension ImageResource { func generateLetBinding() -> LetBinding { let locs = locale.map { $0.codeString() } ?? "nil" - let code = "ImageResource(name: \"\(name)\", locale: \(locs), onDemandResourceTags: \(String(describing: onDemandResourceTags)))" + let odrt = onDemandResourceTags?.debugDescription ?? "nil" + let code = "ImageResource(name: \"\(name)\", locale: \(locs), onDemandResourceTags: \(odrt))" return LetBinding( comments: ["Image `\(name)`."], isStatic: true, @@ -32,15 +33,16 @@ extension ImageResource { valueCodeString: code) } - public static func generateStruct(resources: [ImageResource], assetCatalogs: [AssetCatalog], prefix: SwiftIdentifier) -> Struct { - let structName = SwiftIdentifier(name: "image") + public static func generateStruct(resources: [ImageResource], namespaces: [AssetCatalog.Namespace], name: SwiftIdentifier, prefix: SwiftIdentifier) -> Struct { + let structName = name let qualifiedName = prefix + structName // Multiple resources can share same name, // for example: Colors.jpg and Colors@2x.jpg are both named "Colors.jpg" // Deduplicate these let namedResources = Dictionary(grouping: resources, by: \.name).values.map(\.first!) - let assetFolderImageResources = assetCatalogs.flatMap(\.root.images) + + let assetFolderImageResources = namespaces.flatMap(\.images) let allResources = namedResources + assetFolderImageResources let groupedResources = allResources.grouped(bySwiftIdentifier: { $0.name }) @@ -49,11 +51,27 @@ extension ImageResource { print("warning:", l) } - let imageLets = groupedResources.uniques.map { $0.generateLetBinding() } + let letbindings = groupedResources.uniques.map { $0.generateLetBinding() } + + let structs = namespaces.flatMap(\.subnamespaces) + .sorted { $0.key < $1.key } + .map { (name, namespace) in + ImageResource.generateStruct( + resources: [], + namespaces: [namespace], + name: SwiftIdentifier(name: name), + prefix: qualifiedName + ) + } + .filter { !$0.isEmpty } + + + // TODO group structs by name - let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(imageLets.count) images."] + let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) images."] return Struct(comments: comments, name: structName) { - imageLets + letbindings + structs } } } diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index 29233711..9e310bb2 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -105,6 +105,8 @@ public struct Struct { public var lets: [LetBinding] = [] public var structs: [Struct] = [] + public var isEmpty: Bool { lets.isEmpty && structs.isEmpty } + public init(comments: [String] = [], accessControl: AccessControl = AccessControl.none, name: SwiftIdentifier, @StructMembersBuilder membersBuilder: () -> StructMembers) { self.comments = comments self.accessControl = accessControl diff --git a/Sources/rswift/main.swift b/Sources/rswift/main.swift index 0bc8c293..a7204353 100644 --- a/Sources/rswift/main.swift +++ b/Sources/rswift/main.swift @@ -34,6 +34,7 @@ let targetName = processInfo.environment[EnvironmentKeys.target] ?? "ResourceApp let xcodeprojURL = URL(fileURLWithPath: processInfo.environment[EnvironmentKeys.xcodeproj] ?? "/Users/tom/Projects/R.swift/Examples/ResourceApp/ResourceApp.xcodeproj") let sourceRootURL = xcodeprojURL.deletingLastPathComponent() +let rswiftIgnoreURL = sourceRootURL.appendingPathComponent(".rswiftignore") let fakeURL = URL(fileURLWithPath: "/FAKE") let core = RswiftCore( @@ -46,7 +47,8 @@ let core = RswiftCore( developerDirURL: fakeURL, sourceRootURL: sourceRootURL, sdkRootURL: fakeURL, - platformURL: fakeURL + platformURL: fakeURL, + rswiftIgnoreURL: rswiftIgnoreURL ) print("Start") From 7ac266292b9dac2e45e65535bc024bc8a6d50344 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 17 Jul 2022 22:12:06 +0200 Subject: [PATCH 028/161] Work on AssetCatalogSubfolers --- .../ResourceApp/FirstViewController.swift | 2 +- .../AssetCatalog+Generator.swift | 5 +-- .../AssetCatalogSubfolders.swift | 35 +++++++++++++++++++ .../ResourceGenerators/Image+Generator.swift | 20 +++++++---- .../RswiftParsers/AssetCatalog+Parser.swift | 10 +++--- Sources/RswiftResources/AssetCatalog.swift | 16 +++++++-- 6 files changed, 72 insertions(+), 16 deletions(-) create mode 100644 Sources/RswiftGenerators/ResourceGenerators/AssetCatalogSubfolders.swift diff --git a/Examples/ResourceApp/ResourceApp/FirstViewController.swift b/Examples/ResourceApp/ResourceApp/FirstViewController.swift index 56f4f40d..32eb7f45 100644 --- a/Examples/ResourceApp/ResourceApp/FirstViewController.swift +++ b/Examples/ResourceApp/ResourceApp/FirstViewController.swift @@ -16,7 +16,7 @@ class FirstViewController: UIViewController { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. titleLabel.font = R.font.averiaLibreBoldItalic(size: 36) - tabBarItem.image = R.image.userWhite.uiImage() + tabBarItem.image = R.image.userWhite() } override func didReceiveMemoryWarning() { diff --git a/Sources/RswiftGenerators/ResourceGenerators/AssetCatalog+Generator.swift b/Sources/RswiftGenerators/ResourceGenerators/AssetCatalog+Generator.swift index 96182b03..3aa05e62 100644 --- a/Sources/RswiftGenerators/ResourceGenerators/AssetCatalog+Generator.swift +++ b/Sources/RswiftGenerators/ResourceGenerators/AssetCatalog+Generator.swift @@ -20,12 +20,13 @@ extension AssetCatalog.Namespace { "static let \(SwiftIdentifier(name: color.name).value) = AssetCatalog.\(color)" } - for (name, namespace) in subnamespaces { - cs.append("struct \(SwiftIdentifier(name: name).value) {") + for namespace in subnamespaces { + cs.append("struct \(SwiftIdentifier(name: namespace.name).value) {") cs.append(contentsOf: namespace.generateColorResourceLetsCodeString().map { " \($0)" }) cs.append("}") } return cs } + } diff --git a/Sources/RswiftGenerators/ResourceGenerators/AssetCatalogSubfolders.swift b/Sources/RswiftGenerators/ResourceGenerators/AssetCatalogSubfolders.swift new file mode 100644 index 00000000..27cf57ec --- /dev/null +++ b/Sources/RswiftGenerators/ResourceGenerators/AssetCatalogSubfolders.swift @@ -0,0 +1,35 @@ +// +// AssetCatalogSubfolders.swift +// R.swift +// +// Created by Tom Lokhorst on 2017-06-06. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation +import RswiftResources + +struct AssetCatalogSubfolders { + let folders: [AssetCatalog.Namespace] + let duplicates: [AssetCatalog.Namespace] + + init(all subfolders: [AssetCatalog.Namespace], assetIdentifiers: [SwiftIdentifier]) { + var dict: [SwiftIdentifier: AssetCatalog.Namespace] = [:] + + for subfolder in subfolders { + let name = SwiftIdentifier(name: subfolder.name) + dict[name] = dict[name]?.merging(subfolder) ?? subfolder + } + + self.folders = dict.values.filter { !assetIdentifiers.contains(SwiftIdentifier(name: $0.name)) } + self.duplicates = dict.values.filter { assetIdentifiers.contains(SwiftIdentifier(name: $0.name)) } + } + + func printWarningsForDuplicates(warning: (String) -> Void) { + for subfolder in duplicates { + warning("Skipping asset subfolder because symbol '\(subfolder.name)' would conflict with image: \(subfolder.name)") + } + } +} + diff --git a/Sources/RswiftGenerators/ResourceGenerators/Image+Generator.swift b/Sources/RswiftGenerators/ResourceGenerators/Image+Generator.swift index 1da2f4d7..587b072a 100644 --- a/Sources/RswiftGenerators/ResourceGenerators/Image+Generator.swift +++ b/Sources/RswiftGenerators/ResourceGenerators/Image+Generator.swift @@ -53,21 +53,27 @@ extension ImageResource { let letbindings = groupedResources.uniques.map { $0.generateLetBinding() } - let structs = namespaces.flatMap(\.subnamespaces) - .sorted { $0.key < $1.key } - .map { (name, namespace) in + let allNamespaces = namespaces.flatMap(\.subnamespaces) + let assetSubfolders = AssetCatalogSubfolders( + all: allNamespaces, + assetIdentifiers: allResources.map { SwiftIdentifier(name: $0.name) }) + + assetSubfolders.printWarningsForDuplicates { l in + print("warning:", l) + } + + let structs = assetSubfolders.folders.flatMap(\.subnamespaces) + .sorted { $0.name < $1.name } + .map { namespace in ImageResource.generateStruct( resources: [], namespaces: [namespace], - name: SwiftIdentifier(name: name), + name: SwiftIdentifier(name: namespace.name), prefix: qualifiedName ) } .filter { !$0.isEmpty } - - // TODO group structs by name - let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) images."] return Struct(comments: comments, name: structName) { letbindings diff --git a/Sources/RswiftParsers/AssetCatalog+Parser.swift b/Sources/RswiftParsers/AssetCatalog+Parser.swift index 0800780c..46507d80 100644 --- a/Sources/RswiftParsers/AssetCatalog+Parser.swift +++ b/Sources/RswiftParsers/AssetCatalog+Parser.swift @@ -30,7 +30,7 @@ extension AssetCatalog: SupportedExtensions { } let directory = try parseDirectory(catalogURL: url) - let namespace = try createNamespace(directory: directory, path: []) + let namespace = try createNamespace(name: "image", directory: directory, path: []) return AssetCatalog(filename: basename, root: namespace) } @@ -97,11 +97,12 @@ extension AssetCatalog: SupportedExtensions { } // Note: ignore any localizations in Contents.json - static private func createNamespace(directory: NamespaceDirectory, path: [String]) throws -> Namespace { + static private func createNamespace(name: String, directory: NamespaceDirectory, path: [String]) throws -> Namespace { - var subnamespaces: [String: AssetCatalog.Namespace] = [:] + var subnamespaces: [AssetCatalog.Namespace] = [] for (name, directory) in directory.subnamespaces { - subnamespaces[name] = try createNamespace(directory: directory, path: path + [name]) + let namespace = try createNamespace(name: name, directory: directory, path: path + [name]) + subnamespaces.append(namespace) } var colors: [AssetCatalog.Color] = [] @@ -125,6 +126,7 @@ extension AssetCatalog: SupportedExtensions { } return AssetCatalog.Namespace( + name: name, subnamespaces: subnamespaces, colors: colors, images: images, diff --git a/Sources/RswiftResources/AssetCatalog.swift b/Sources/RswiftResources/AssetCatalog.swift index e34ea83d..ba815ae1 100644 --- a/Sources/RswiftResources/AssetCatalog.swift +++ b/Sources/RswiftResources/AssetCatalog.swift @@ -19,22 +19,34 @@ public struct AssetCatalog { extension AssetCatalog { public struct Namespace { - public var subnamespaces: [String: Namespace] = [:] + public let name: String + public var subnamespaces: [Namespace] = [] public var colors: [Color] = [] public var images: [ImageResource] = [] public var dataAssets: [DataAsset] = [] public init( - subnamespaces: [String: Namespace], + name: String, + subnamespaces: [Namespace], colors: [Color], images: [ImageResource], dataAssets: [DataAsset] ) { + self.name = name self.subnamespaces = subnamespaces self.colors = colors self.images = images self.dataAssets = dataAssets } + + public func merging(_ other: Namespace) -> Namespace { + var new = self + new.subnamespaces += other.subnamespaces + new.colors += other.colors + new.images += other.images + new.dataAssets += other.dataAssets + return new + } } public struct Color { From 923e5ff355e8f8fcca06ec0fb3063a7b1c4ea5f2 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 22 Jul 2022 11:50:42 +0200 Subject: [PATCH 029/161] RenameAssetCatalogMergedNamespaces --- .../ResourceApp.xcodeproj/project.pbxproj | 4 +++ .../AssetCatalog+Generator.swift | 0 .../FileResource+Generator.swift | 0 .../Font+Generator.swift | 0 .../Image+Generator.swift | 18 ++++++---- .../LocalizableStrings+Generator.swift | 0 .../Nib+Generator.swift | 0 .../AssetCatalogSubfolders.swift | 35 ------------------- .../Shared/AssetCatalogMergedNamespaces.swift | 35 +++++++++++++++++++ .../{ => Shared}/SwiftIdentifier.swift | 6 ++++ .../Storyboard+Generator.swift | 0 11 files changed, 56 insertions(+), 42 deletions(-) rename Sources/RswiftGenerators/{ResourceGenerators => }/AssetCatalog+Generator.swift (100%) rename Sources/RswiftGenerators/{ResourceGenerators => }/FileResource+Generator.swift (100%) rename Sources/RswiftGenerators/{ResourceGenerators => }/Font+Generator.swift (100%) rename Sources/RswiftGenerators/{ResourceGenerators => }/Image+Generator.swift (79%) rename Sources/RswiftGenerators/{ResourceGenerators => }/LocalizableStrings+Generator.swift (100%) rename Sources/RswiftGenerators/{ResourceGenerators => }/Nib+Generator.swift (100%) delete mode 100644 Sources/RswiftGenerators/ResourceGenerators/AssetCatalogSubfolders.swift create mode 100644 Sources/RswiftGenerators/Shared/AssetCatalogMergedNamespaces.swift rename Sources/RswiftGenerators/{ => Shared}/SwiftIdentifier.swift (95%) rename Sources/RswiftGenerators/{ResourceGenerators => }/Storyboard+Generator.swift (100%) diff --git a/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj b/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj index 7f735e49..022c63f9 100644 --- a/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj +++ b/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj @@ -515,6 +515,10 @@ D55C6CB01B5D757300301B0D /* Project object */ = { isa = PBXProject; attributes = { + KnownAssetTags = ( + one, + two, + ); LastSwiftMigration = 0700; LastSwiftUpdateCheck = 1000; LastUpgradeCheck = 1210; diff --git a/Sources/RswiftGenerators/ResourceGenerators/AssetCatalog+Generator.swift b/Sources/RswiftGenerators/AssetCatalog+Generator.swift similarity index 100% rename from Sources/RswiftGenerators/ResourceGenerators/AssetCatalog+Generator.swift rename to Sources/RswiftGenerators/AssetCatalog+Generator.swift diff --git a/Sources/RswiftGenerators/ResourceGenerators/FileResource+Generator.swift b/Sources/RswiftGenerators/FileResource+Generator.swift similarity index 100% rename from Sources/RswiftGenerators/ResourceGenerators/FileResource+Generator.swift rename to Sources/RswiftGenerators/FileResource+Generator.swift diff --git a/Sources/RswiftGenerators/ResourceGenerators/Font+Generator.swift b/Sources/RswiftGenerators/Font+Generator.swift similarity index 100% rename from Sources/RswiftGenerators/ResourceGenerators/Font+Generator.swift rename to Sources/RswiftGenerators/Font+Generator.swift diff --git a/Sources/RswiftGenerators/ResourceGenerators/Image+Generator.swift b/Sources/RswiftGenerators/Image+Generator.swift similarity index 79% rename from Sources/RswiftGenerators/ResourceGenerators/Image+Generator.swift rename to Sources/RswiftGenerators/Image+Generator.swift index 587b072a..11aa70cd 100644 --- a/Sources/RswiftGenerators/ResourceGenerators/Image+Generator.swift +++ b/Sources/RswiftGenerators/Image+Generator.swift @@ -54,15 +54,14 @@ extension ImageResource { let letbindings = groupedResources.uniques.map { $0.generateLetBinding() } let allNamespaces = namespaces.flatMap(\.subnamespaces) - let assetSubfolders = AssetCatalogSubfolders( - all: allNamespaces, - assetIdentifiers: allResources.map { SwiftIdentifier(name: $0.name) }) + let otherIdentifiers = groupedResources.uniques.map { SwiftIdentifier(name: $0.name) } + let assetSubfolders = AssetCatalogMergedNamespaces(all: allNamespaces, otherIdentifiers: otherIdentifiers) - assetSubfolders.printWarningsForDuplicates { l in + assetSubfolders.printWarningsForDuplicates(result: "image") { l in print("warning:", l) } - let structs = assetSubfolders.folders.flatMap(\.subnamespaces) + let structs = assetSubfolders.namespaces .sorted { $0.name < $1.name } .map { namespace in ImageResource.generateStruct( @@ -73,8 +72,13 @@ extension ImageResource { ) } .filter { !$0.isEmpty } - - let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) images."] + + let comment = [ + "This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) images", + structs.isEmpty ? "" : ", and \(structs.count) namespaces", + "." + ].joined() + let comments = [comment] return Struct(comments: comments, name: structName) { letbindings structs diff --git a/Sources/RswiftGenerators/ResourceGenerators/LocalizableStrings+Generator.swift b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift similarity index 100% rename from Sources/RswiftGenerators/ResourceGenerators/LocalizableStrings+Generator.swift rename to Sources/RswiftGenerators/LocalizableStrings+Generator.swift diff --git a/Sources/RswiftGenerators/ResourceGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift similarity index 100% rename from Sources/RswiftGenerators/ResourceGenerators/Nib+Generator.swift rename to Sources/RswiftGenerators/Nib+Generator.swift diff --git a/Sources/RswiftGenerators/ResourceGenerators/AssetCatalogSubfolders.swift b/Sources/RswiftGenerators/ResourceGenerators/AssetCatalogSubfolders.swift deleted file mode 100644 index 27cf57ec..00000000 --- a/Sources/RswiftGenerators/ResourceGenerators/AssetCatalogSubfolders.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// AssetCatalogSubfolders.swift -// R.swift -// -// Created by Tom Lokhorst on 2017-06-06. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation -import RswiftResources - -struct AssetCatalogSubfolders { - let folders: [AssetCatalog.Namespace] - let duplicates: [AssetCatalog.Namespace] - - init(all subfolders: [AssetCatalog.Namespace], assetIdentifiers: [SwiftIdentifier]) { - var dict: [SwiftIdentifier: AssetCatalog.Namespace] = [:] - - for subfolder in subfolders { - let name = SwiftIdentifier(name: subfolder.name) - dict[name] = dict[name]?.merging(subfolder) ?? subfolder - } - - self.folders = dict.values.filter { !assetIdentifiers.contains(SwiftIdentifier(name: $0.name)) } - self.duplicates = dict.values.filter { assetIdentifiers.contains(SwiftIdentifier(name: $0.name)) } - } - - func printWarningsForDuplicates(warning: (String) -> Void) { - for subfolder in duplicates { - warning("Skipping asset subfolder because symbol '\(subfolder.name)' would conflict with image: \(subfolder.name)") - } - } -} - diff --git a/Sources/RswiftGenerators/Shared/AssetCatalogMergedNamespaces.swift b/Sources/RswiftGenerators/Shared/AssetCatalogMergedNamespaces.swift new file mode 100644 index 00000000..7cba8f8e --- /dev/null +++ b/Sources/RswiftGenerators/Shared/AssetCatalogMergedNamespaces.swift @@ -0,0 +1,35 @@ +// +// AssetCatalogMergedNamespaces.swift +// R.swift +// +// Created by Tom Lokhorst on 2017-06-06. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation +import RswiftResources + +struct AssetCatalogMergedNamespaces { + let namespaces: [AssetCatalog.Namespace] + let duplicates: [(SwiftIdentifier, String)] + + init(all namespaces: [AssetCatalog.Namespace], otherIdentifiers: [SwiftIdentifier]) { + var dict: [SwiftIdentifier: AssetCatalog.Namespace] = [:] + + for namespace in namespaces { + let id = SwiftIdentifier(name: namespace.name) + dict[id] = dict[id]?.merging(namespace) ?? namespace + } + + self.namespaces = dict.compactMap { (id, ns) in !otherIdentifiers.contains(id) ? ns : nil } + self.duplicates = dict.compactMap { (id, ns) in otherIdentifiers.contains(id) ? (id, ns.name) : nil } + } + + func printWarningsForDuplicates(result: String, warning: (String) -> Void) { + for (name, identifier) in duplicates { + warning("Skipping asset namespace '\(name)' because symbol '\(identifier)' would conflict with \(result): \(identifier)") + } + } +} + diff --git a/Sources/RswiftGenerators/SwiftIdentifier.swift b/Sources/RswiftGenerators/Shared/SwiftIdentifier.swift similarity index 95% rename from Sources/RswiftGenerators/SwiftIdentifier.swift rename to Sources/RswiftGenerators/Shared/SwiftIdentifier.swift index 46d7aa61..3e47dd03 100644 --- a/Sources/RswiftGenerators/SwiftIdentifier.swift +++ b/Sources/RswiftGenerators/Shared/SwiftIdentifier.swift @@ -65,6 +65,12 @@ struct SwiftNameGroups { let duplicates: [(SwiftIdentifier, [String])] // Identifiers that result in duplicate Swift names let empties: [String] // Identifiers (wrapped in quotes) that result in empty swift names + // Example: + // source: "xib", container: nil, result: "file" + // "Skipping 1 xib, because ... for all these files" + // + // source: "segue", container: "for MyViewController", result: "segue" + // "Skipping 2 segues for MyViewController, because ... for all these segues" func reportWarningsForDuplicatesAndEmpties(source: String, container: String? = nil, result: String, warning: (String) -> Void) { let sourceSingular = [source, container].compactMap { $0 }.joined(separator: " ") diff --git a/Sources/RswiftGenerators/ResourceGenerators/Storyboard+Generator.swift b/Sources/RswiftGenerators/Storyboard+Generator.swift similarity index 100% rename from Sources/RswiftGenerators/ResourceGenerators/Storyboard+Generator.swift rename to Sources/RswiftGenerators/Storyboard+Generator.swift From b1d04e5df08ec5e4cf0e956611d416cabf2d32b6 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 22 Jul 2022 12:44:25 +0200 Subject: [PATCH 030/161] Generate full path for image name --- Sources/RswiftGenerators/Image+Generator.swift | 14 ++++++++++---- Sources/RswiftParsers/AssetCatalog+Parser.swift | 7 ++++--- Sources/RswiftResources/AssetCatalog.swift | 3 +++ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Sources/RswiftGenerators/Image+Generator.swift b/Sources/RswiftGenerators/Image+Generator.swift index 11aa70cd..4bf041b4 100644 --- a/Sources/RswiftGenerators/Image+Generator.swift +++ b/Sources/RswiftGenerators/Image+Generator.swift @@ -22,18 +22,23 @@ extension LocaleReference { } extension ImageResource { - func generateLetBinding() -> LetBinding { + func generateLetBinding(path: [String]) -> LetBinding { let locs = locale.map { $0.codeString() } ?? "nil" let odrt = onDemandResourceTags?.debugDescription ?? "nil" - let code = "ImageResource(name: \"\(name)\", locale: \(locs), onDemandResourceTags: \(odrt))" + let fullname = (path + [name]).joined(separator: "/") + let code = "ImageResource(name: \"\(fullname)\", locale: \(locs), onDemandResourceTags: \(odrt))" return LetBinding( - comments: ["Image `\(name)`."], + comments: ["Image `\(fullname)`."], isStatic: true, name: SwiftIdentifier(name: name), valueCodeString: code) } public static func generateStruct(resources: [ImageResource], namespaces: [AssetCatalog.Namespace], name: SwiftIdentifier, prefix: SwiftIdentifier) -> Struct { + generateStruct(resources: resources, namespaces: namespaces, path: [], name: name, prefix: prefix) + } + + public static func generateStruct(resources: [ImageResource], namespaces: [AssetCatalog.Namespace], path: [String], name: SwiftIdentifier, prefix: SwiftIdentifier) -> Struct { let structName = name let qualifiedName = prefix + structName @@ -51,7 +56,7 @@ extension ImageResource { print("warning:", l) } - let letbindings = groupedResources.uniques.map { $0.generateLetBinding() } + let letbindings = groupedResources.uniques.map { $0.generateLetBinding(path: path) } let allNamespaces = namespaces.flatMap(\.subnamespaces) let otherIdentifiers = groupedResources.uniques.map { SwiftIdentifier(name: $0.name) } @@ -67,6 +72,7 @@ extension ImageResource { ImageResource.generateStruct( resources: [], namespaces: [namespace], + path: namespace.path, name: SwiftIdentifier(name: namespace.name), prefix: qualifiedName ) diff --git a/Sources/RswiftParsers/AssetCatalog+Parser.swift b/Sources/RswiftParsers/AssetCatalog+Parser.swift index 46507d80..d3b9d172 100644 --- a/Sources/RswiftParsers/AssetCatalog+Parser.swift +++ b/Sources/RswiftParsers/AssetCatalog+Parser.swift @@ -107,26 +107,27 @@ extension AssetCatalog: SupportedExtensions { var colors: [AssetCatalog.Color] = [] for fileURL in directory.colors { - let name = (path + [fileURL.filenameWithoutExtension!]).joined(separator: "/") + let name = fileURL.filenameWithoutExtension! colors.append(.init(name: name)) } var images: [ImageResource] = [] for fileURL in directory.images { - let name = (path + [fileURL.filenameWithoutExtension!]).joined(separator: "/") + let name = fileURL.filenameWithoutExtension! let tags = parseOnDemandResourceTags(directory: fileURL) images.append(.init(name: name, locale: nil, onDemandResourceTags: tags)) } var dataAssets: [AssetCatalog.DataAsset] = [] for fileURL in directory.dataAssets { - let name = (path + [fileURL.filenameWithoutExtension!]).joined(separator: "/") + let name = fileURL.filenameWithoutExtension! let tags = parseOnDemandResourceTags(directory: fileURL) dataAssets.append(.init(name: name, onDemandResourceTags: tags)) } return AssetCatalog.Namespace( name: name, + path: path, subnamespaces: subnamespaces, colors: colors, images: images, diff --git a/Sources/RswiftResources/AssetCatalog.swift b/Sources/RswiftResources/AssetCatalog.swift index ba815ae1..81c88ae2 100644 --- a/Sources/RswiftResources/AssetCatalog.swift +++ b/Sources/RswiftResources/AssetCatalog.swift @@ -20,6 +20,7 @@ public struct AssetCatalog { extension AssetCatalog { public struct Namespace { public let name: String + public let path: [String] public var subnamespaces: [Namespace] = [] public var colors: [Color] = [] public var images: [ImageResource] = [] @@ -27,12 +28,14 @@ extension AssetCatalog { public init( name: String, + path: [String], subnamespaces: [Namespace], colors: [Color], images: [ImageResource], dataAssets: [DataAsset] ) { self.name = name + self.path = path self.subnamespaces = subnamespaces self.colors = colors self.images = images From c545d2667a6e4286e12e84466819081d0c5e40b4 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 22 Jul 2022 14:06:49 +0200 Subject: [PATCH 031/161] Update AssetCatalog example --- .../Images2.xcassets/Contents.json | 6 ++-- .../{Namespace => Namespace 1}/Contents.json | 0 .../Namespace 1/Inner/Contents.json | 9 +++++ .../Inner/Namespace 2}/Contents.json | 0 .../Inner/Namespace 2}/Folder/Contents.json | 0 .../Folder/first.imageset/Contents.json | 0 .../Folder/first.imageset/first.pdf | Bin .../Second.imageset/Contents.json | 0 .../Second.imageset/second.pdf | Bin .../third.imageset/Contents.json | 0 .../third.imageset/first.pdf | Bin .../Namespace => Namespace-}/Contents.json | 0 .../Namespace-/Inner/Contents.json | 9 +++++ .../Namespace-/Inner/Namespace/Contents.json | 9 +++++ .../Inner/Namespace/Folder/Contents.json | 6 ++++ .../Folder/first.imageset/Contents.json | 12 +++++++ .../Namespace/Folder/first.imageset/first.pdf | Bin 0 -> 2465 bytes .../Namespace-/Second.imageset/Contents.json | 12 +++++++ .../Namespace-/Second.imageset/second.pdf | Bin 0 -> 2423 bytes .../Namespace-/third.imageset/Contents.json | 12 +++++++ .../Namespace-/third.imageset/first.pdf | Bin 0 -> 2465 bytes .../AssetCatalog+Generator.swift | 32 ------------------ 22 files changed, 72 insertions(+), 35 deletions(-) rename Examples/ResourceApp/ResourceApp/Images2.xcassets/{Namespace => Namespace 1}/Contents.json (100%) create mode 100644 Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/Inner/Contents.json rename Examples/ResourceApp/ResourceApp/Images2.xcassets/{Namespace/Inner => Namespace 1/Inner/Namespace 2}/Contents.json (100%) rename Examples/ResourceApp/ResourceApp/Images2.xcassets/{Namespace/Inner/Namespace => Namespace 1/Inner/Namespace 2}/Folder/Contents.json (100%) rename Examples/ResourceApp/ResourceApp/Images2.xcassets/{Namespace/Inner/Namespace => Namespace 1/Inner/Namespace 2}/Folder/first.imageset/Contents.json (100%) rename Examples/ResourceApp/ResourceApp/Images2.xcassets/{Namespace/Inner/Namespace => Namespace 1/Inner/Namespace 2}/Folder/first.imageset/first.pdf (100%) rename Examples/ResourceApp/ResourceApp/Images2.xcassets/{Namespace => Namespace 1}/Second.imageset/Contents.json (100%) rename Examples/ResourceApp/ResourceApp/Images2.xcassets/{Namespace => Namespace 1}/Second.imageset/second.pdf (100%) rename Examples/ResourceApp/ResourceApp/Images2.xcassets/{Namespace => Namespace 1}/third.imageset/Contents.json (100%) rename Examples/ResourceApp/ResourceApp/Images2.xcassets/{Namespace => Namespace 1}/third.imageset/first.pdf (100%) rename Examples/ResourceApp/ResourceApp/Images2.xcassets/{Namespace/Inner/Namespace => Namespace-}/Contents.json (100%) create mode 100644 Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Contents.json create mode 100644 Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Namespace/Contents.json create mode 100644 Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Namespace/Folder/Contents.json create mode 100644 Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Namespace/Folder/first.imageset/Contents.json create mode 100644 Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Namespace/Folder/first.imageset/first.pdf create mode 100644 Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Second.imageset/Contents.json create mode 100644 Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Second.imageset/second.pdf create mode 100644 Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/third.imageset/Contents.json create mode 100644 Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/third.imageset/first.pdf delete mode 100644 Sources/RswiftGenerators/AssetCatalog+Generator.swift diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Contents.json b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Contents.json index da4a164c..73c00596 100644 --- a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Contents.json +++ b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/Contents.json b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/Contents.json similarity index 100% rename from Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/Contents.json rename to Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/Contents.json diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/Inner/Contents.json b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/Inner/Contents.json new file mode 100644 index 00000000..6e965652 --- /dev/null +++ b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/Inner/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "provides-namespace" : true + } +} diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/Inner/Contents.json b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/Inner/Namespace 2/Contents.json similarity index 100% rename from Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/Inner/Contents.json rename to Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/Inner/Namespace 2/Contents.json diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/Inner/Namespace/Folder/Contents.json b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/Inner/Namespace 2/Folder/Contents.json similarity index 100% rename from Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/Inner/Namespace/Folder/Contents.json rename to Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/Inner/Namespace 2/Folder/Contents.json diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/Inner/Namespace/Folder/first.imageset/Contents.json b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/Inner/Namespace 2/Folder/first.imageset/Contents.json similarity index 100% rename from Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/Inner/Namespace/Folder/first.imageset/Contents.json rename to Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/Inner/Namespace 2/Folder/first.imageset/Contents.json diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/Inner/Namespace/Folder/first.imageset/first.pdf b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/Inner/Namespace 2/Folder/first.imageset/first.pdf similarity index 100% rename from Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/Inner/Namespace/Folder/first.imageset/first.pdf rename to Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/Inner/Namespace 2/Folder/first.imageset/first.pdf diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/Second.imageset/Contents.json b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/Second.imageset/Contents.json similarity index 100% rename from Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/Second.imageset/Contents.json rename to Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/Second.imageset/Contents.json diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/Second.imageset/second.pdf b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/Second.imageset/second.pdf similarity index 100% rename from Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/Second.imageset/second.pdf rename to Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/Second.imageset/second.pdf diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/third.imageset/Contents.json b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/third.imageset/Contents.json similarity index 100% rename from Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/third.imageset/Contents.json rename to Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/third.imageset/Contents.json diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/third.imageset/first.pdf b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/third.imageset/first.pdf similarity index 100% rename from Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/third.imageset/first.pdf rename to Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace 1/third.imageset/first.pdf diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/Inner/Namespace/Contents.json b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Contents.json similarity index 100% rename from Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace/Inner/Namespace/Contents.json rename to Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Contents.json diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Contents.json b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Contents.json new file mode 100644 index 00000000..38f0c81f --- /dev/null +++ b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "provides-namespace" : true + } +} \ No newline at end of file diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Namespace/Contents.json b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Namespace/Contents.json new file mode 100644 index 00000000..38f0c81f --- /dev/null +++ b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Namespace/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "provides-namespace" : true + } +} \ No newline at end of file diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Namespace/Folder/Contents.json b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Namespace/Folder/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Namespace/Folder/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Namespace/Folder/first.imageset/Contents.json b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Namespace/Folder/first.imageset/Contents.json new file mode 100644 index 00000000..33a74510 --- /dev/null +++ b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Namespace/Folder/first.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "first.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Namespace/Folder/first.imageset/first.pdf b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Inner/Namespace/Folder/first.imageset/first.pdf new file mode 100644 index 0000000000000000000000000000000000000000..47d911dea647d55983671ead4d08b6f6b3600715 GIT binary patch literal 2465 zcmai03se(l7FLvisY+L#f-EBsK`11Xkc1=%MIujmB|xQ!>5vQ%APFP`Mlf5nSPOKE z2-^C>7DZkM!J;Da5{kfrC?Kvp3J41-3shFXvhuJqKo*oer*r0H{{P-P_x0cTzLzjD!bE z0qT;#Q7l4Gy%fMoXJaKT`@{5#R(MOqJPwQifv8iK6A%Ot9L14h2`38T!2s4PM=1!< zmL06}VYAA|ay#jZRs>HpA%X+eQW4rufWU%d1w5GTy!X#LeZsF_+~ccZmn3Fi)v^Z; zIG;?uU*yLLEYs61tjD>gXOFvSWsh{48xJvPNqKrIJtMdCz2cA2aC7TF?b@K`V!Lw- zE;zpH&ApqhoRjAHt}gK}>(qAc8dvrkD31*`<5<%C(&4_LqhQ3-GOf8~n^leG?+-@@^pjPa$J2gW@O)!b9hdTJ zTauyIJ&~rqeEZC1p9dWgx7{_WRc2=drMO=wcT7B{Zd58z`d)r_36r4V<6PGkCjpGfbnVMtm@;b}F!T?)Qav$_y7H5T;Mf!T}M zWxP9TNqrV?e5;b|pWd3^u2KAatk&3aDB@0 zZBaQNdCZ2#fblzYZnRCCjQ-GQWb-s8bX&<)?SxnUGdDYVFVk`xIf7@g3~$DX`H71_;r5OSZz1p`$_8y_9 z{;MJJ+n=>7Ewg;GnGHoz)&ID0z@F2!e$F7cWQ?d6s(!VY)_Gw})xCyMvsD={5i&H* zAIr_ACo8;Se6<*!-mm9Am79Iz^RVlc?%S5sg|E*SyIV{dd9{Mpf#d3cih5WKt=%ps zBEo)bt8EjmeCFYJRYU|b7d`p+-V|X2wOCYtyLP6t=!WH-kgdf0A};ytPfZiCwVPx{ z`g;zpe{8a4RQxQUwVU02<4X3w|9h;}XjhGWquqn{vVqq8g{*}BQF9=ybe_TM*!M=rFs1pN)yzctIXAoicsCe6>fit>wgZ#vp^hZOY0`J`rx zwdSK?GwR_xm9;5XjH|vf{O+Yg-)z;s*xt>;-vU`D-_SK<=~4eEVQkV~57{vyH6ebu>DqwE~Mx_A31Lg~$ zy`i52ew?GEluE%AN>ju*5z~dx2QQLP-G+E>o|En2+s&<^4*d zWD+<8PXs_Jp7_5Dpi(Gg3J?M;%Rn0{dcxQRh!ip+iHJ_e`!bM1MGxwI8Iedr+x>wI zq@&04fsADHfBccq^gpm8Q_u-~Uj|Z$YwSR(^_mzUb!}Xb2Cj`uqphg}v_Y%?Fs=;= zWq3s{hy)Tu!ji@RJsd$C+G{F6V2j1*Xk+7xo|LO7M2yb)(wc=%x-AclP9pLsbTF7m tf?*P!2EjJu5MD57P32Q4pdJ3tO_shCDT2=K;$ebx8ymcd$v!V9{2$sQiO>K5 literal 0 HcmV?d00001 diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Second.imageset/Contents.json b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Second.imageset/Contents.json new file mode 100644 index 00000000..03bd9c92 --- /dev/null +++ b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Second.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "second.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Second.imageset/second.pdf b/Examples/ResourceApp/ResourceApp/Images2.xcassets/Namespace-/Second.imageset/second.pdf new file mode 100644 index 0000000000000000000000000000000000000000..401614e288b4b160471c2776bed6f09762af3e1c GIT binary patch literal 2423 zcmai02~-nz8dqq7rWU#)x2z)&iMWtV?gK$2a)?|BP!TbkV}J-Gl1wmy+3G9S0=uOY zwCjai6uBOPMMdNiN`VDYKwLQr2#W^`R93*Ua=0^)EGYY4=grIf|KIohziYnv{elnR zdN_hk6db<(+3RPcxs!iut8c(j0TLh=9mY931H^rhEE0|aXo!dh5Iv+ZFr+}9F+v#P zLSnfD0$f~hN>~92V{z)WJA&2)5rW!|I?;o?oI*%zejJiuY>hFLQwKlAa9O+PnPh#% zJp~JAfs-De6u@;{cREMs}D-sGib_4>50m8_|Uj-zC0m@Qx zJV+^5Da4Qxpf3sBtKZs<_aI$Cb1T+h<->yL^rfiGNelYkqgC3#SqVP_iy*_RPcbjH)xYf zvYdC-)D_t8c{=5%;$VJQxs_#61J*kuXMC?|&PbPo-7uSxoNr~&J94|gKk+03JGXXM zx4w~=^VUzWqQveu_ilRgPdgNNdnLZCH`=;w91}cP5ihdm*B_jI+rk=Pz2Lk!)XUu| zkls3P5&YHJ!t0kF+MUVBHOAx)a zy|JxHNd37z*~b5#%*0w_F|#4?erNP;pG`Fevoq2%?9Y>%Cmzv1(ks;aPIGaiy1mhU zr{7lB^Mem&`QK{b{uiDC zLD8muY(FNk{YdV|zO#o_VB%);8|~Bh(Z4p?GM}g-HN2;q7~YNl-lD~HZ$taYw$Fzbn6c?c5eo=@`i>()el#qwB)hI-gp0 ztsw@dh-I5NJkk)RCoBC`L8ffKfY)=oG=#4LA9TO6eA}9~@a6dupB+=S{%s&NY}^0GY`hAqod)uxXHitrN%bWOJohV>t?!&Z#d4y^7J@=Oh`HV zee**4j>xRUd)|D0^I-B#<`m1-5PO{)!v(q?ecFYqr_8Q{+z7#n0uFbq%_?hZ-Ck(6NRn=vDS7Nb1Xxx_)9`!i- zGR>av*PJ+P_AGO*a_$FbtCCISg5PK44p&W#t$)+HVcxpR9LAL%_Pv$p`0tO*sYZD0 zCG3-ipr4WoVbjpw%Nf^r^rnWcDcjumgBPEjO;NM6QtZ8wU#0rjHx5zs%CahF4$W*U zxu!q8kYP+>v!BNPVIRJUGIWU@7qVvx2VZDw4gV8%z%bjpF#aF(W|PmliwlakuRq>= zhaCRc^R!7T)#Twb0{!Tns=CxGcGX|leErg`f3|v0eBXxHU&6Sj-_>zOL!!d+j5Xt* zPS;rWd^zEF{5d5%)nNK=)zFJ8Uc=aQKD%UK)_TNes1Gh{SD*Mb_@O` z7*zs9Uq~Vq?v|^8FcM-yLGMKzR3YFB0z@CFM2X-dio%Gu6&MJj=I?uS7sA3AdE^p8 zE49$PjJzPIJwfn;jso7X2#jynr3dO@p5QAK1O9wqKLC=PkQjj~p#n|-5O`ihbTzzu zL{}2QEe28pJYfVZRW56FL`LvO&|C^1Th{0}hQMW$Ng$QPpnzlwnMnf=kVy8(y==L% zLMa>r0gELdT5vQ%APFP`Mlf5nSPOKE z2-^C>7DZkM!J;Da5{kfrC?Kvp3J41-3shFXvhuJqKo*oer*r0H{{P-P_x0cTzLzjD!bE z0qT;#Q7l4Gy%fMoXJaKT`@{5#R(MOqJPwQifv8iK6A%Ot9L14h2`38T!2s4PM=1!< zmL06}VYAA|ay#jZRs>HpA%X+eQW4rufWU%d1w5GTy!X#LeZsF_+~ccZmn3Fi)v^Z; zIG;?uU*yLLEYs61tjD>gXOFvSWsh{48xJvPNqKrIJtMdCz2cA2aC7TF?b@K`V!Lw- zE;zpH&ApqhoRjAHt}gK}>(qAc8dvrkD31*`<5<%C(&4_LqhQ3-GOf8~n^leG?+-@@^pjPa$J2gW@O)!b9hdTJ zTauyIJ&~rqeEZC1p9dWgx7{_WRc2=drMO=wcT7B{Zd58z`d)r_36r4V<6PGkCjpGfbnVMtm@;b}F!T?)Qav$_y7H5T;Mf!T}M zWxP9TNqrV?e5;b|pWd3^u2KAatk&3aDB@0 zZBaQNdCZ2#fblzYZnRCCjQ-GQWb-s8bX&<)?SxnUGdDYVFVk`xIf7@g3~$DX`H71_;r5OSZz1p`$_8y_9 z{;MJJ+n=>7Ewg;GnGHoz)&ID0z@F2!e$F7cWQ?d6s(!VY)_Gw})xCyMvsD={5i&H* zAIr_ACo8;Se6<*!-mm9Am79Iz^RVlc?%S5sg|E*SyIV{dd9{Mpf#d3cih5WKt=%ps zBEo)bt8EjmeCFYJRYU|b7d`p+-V|X2wOCYtyLP6t=!WH-kgdf0A};ytPfZiCwVPx{ z`g;zpe{8a4RQxQUwVU02<4X3w|9h;}XjhGWquqn{vVqq8g{*}BQF9=ybe_TM*!M=rFs1pN)yzctIXAoicsCe6>fit>wgZ#vp^hZOY0`J`rx zwdSK?GwR_xm9;5XjH|vf{O+Yg-)z;s*xt>;-vU`D-_SK<=~4eEVQkV~57{vyH6ebu>DqwE~Mx_A31Lg~$ zy`i52ew?GEluE%AN>ju*5z~dx2QQLP-G+E>o|En2+s&<^4*d zWD+<8PXs_Jp7_5Dpi(Gg3J?M;%Rn0{dcxQRh!ip+iHJ_e`!bM1MGxwI8Iedr+x>wI zq@&04fsADHfBccq^gpm8Q_u-~Uj|Z$YwSR(^_mzUb!}Xb2Cj`uqphg}v_Y%?Fs=;= zWq3s{hy)Tu!ji@RJsd$C+G{F6V2j1*Xk+7xo|LO7M2yb)(wc=%x-AclP9pLsbTF7m tf?*P!2EjJu5MD57P32Q4pdJ3tO_shCDT2=K;$ebx8ymcd$v!V9{2$sQiO>K5 literal 0 HcmV?d00001 diff --git a/Sources/RswiftGenerators/AssetCatalog+Generator.swift b/Sources/RswiftGenerators/AssetCatalog+Generator.swift deleted file mode 100644 index 3aa05e62..00000000 --- a/Sources/RswiftGenerators/AssetCatalog+Generator.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// AssetCatalog+Generator.swift -// -// -// Created by Tom Lokhorst on 2022-07-15. -// - -import Foundation -import RswiftResources - -extension AssetCatalog { - public func generateColorResourceLetCodeString() -> String { - root.generateColorResourceLetsCodeString().joined(separator: "\n") - } -} - -extension AssetCatalog.Namespace { - fileprivate func generateColorResourceLetsCodeString() -> [String] { - var cs = colors.map { color in - "static let \(SwiftIdentifier(name: color.name).value) = AssetCatalog.\(color)" - } - - for namespace in subnamespaces { - cs.append("struct \(SwiftIdentifier(name: namespace.name).value) {") - cs.append(contentsOf: namespace.generateColorResourceLetsCodeString().map { " \($0)" }) - cs.append("}") - } - - return cs - } - -} From 440872accd10b53253524908b4b50174195b3264 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 22 Jul 2022 14:17:31 +0200 Subject: [PATCH 032/161] Generate path in ImageResource name --- .../RswiftGenerators/Image+Generator.swift | 23 +++++++---------- .../Shared/AssetCatalogMergedNamespaces.swift | 25 +++++++++---------- .../Shared/SwiftIdentifier.swift | 6 ++++- .../RswiftParsers/AssetCatalog+Parser.swift | 14 +++++------ .../RswiftParsers/ImageResource+Parser.swift | 2 +- Sources/RswiftResources/AssetCatalog.swift | 25 ++++++++++--------- Sources/RswiftResources/ImageResource.swift | 4 ++- 7 files changed, 49 insertions(+), 50 deletions(-) diff --git a/Sources/RswiftGenerators/Image+Generator.swift b/Sources/RswiftGenerators/Image+Generator.swift index 4bf041b4..581dbdfa 100644 --- a/Sources/RswiftGenerators/Image+Generator.swift +++ b/Sources/RswiftGenerators/Image+Generator.swift @@ -22,7 +22,7 @@ extension LocaleReference { } extension ImageResource { - func generateLetBinding(path: [String]) -> LetBinding { + func generateLetBinding() -> LetBinding { let locs = locale.map { $0.codeString() } ?? "nil" let odrt = onDemandResourceTags?.debugDescription ?? "nil" let fullname = (path + [name]).joined(separator: "/") @@ -35,10 +35,6 @@ extension ImageResource { } public static func generateStruct(resources: [ImageResource], namespaces: [AssetCatalog.Namespace], name: SwiftIdentifier, prefix: SwiftIdentifier) -> Struct { - generateStruct(resources: resources, namespaces: namespaces, path: [], name: name, prefix: prefix) - } - - public static func generateStruct(resources: [ImageResource], namespaces: [AssetCatalog.Namespace], path: [String], name: SwiftIdentifier, prefix: SwiftIdentifier) -> Struct { let structName = name let qualifiedName = prefix + structName @@ -56,24 +52,23 @@ extension ImageResource { print("warning:", l) } - let letbindings = groupedResources.uniques.map { $0.generateLetBinding(path: path) } + let letbindings = groupedResources.uniques.map { $0.generateLetBinding() } - let allNamespaces = namespaces.flatMap(\.subnamespaces) + let allNamespaces = Dictionary(namespaces.flatMap(\.subnamespaces)) { $0.merging($1) } let otherIdentifiers = groupedResources.uniques.map { SwiftIdentifier(name: $0.name) } - let assetSubfolders = AssetCatalogMergedNamespaces(all: allNamespaces, otherIdentifiers: otherIdentifiers) + let mergedNamespaces = AssetCatalogMergedNamespaces(all: allNamespaces, otherIdentifiers: otherIdentifiers) - assetSubfolders.printWarningsForDuplicates(result: "image") { l in + mergedNamespaces.printWarningsForDuplicates(result: "image") { l in print("warning:", l) } - let structs = assetSubfolders.namespaces - .sorted { $0.name < $1.name } - .map { namespace in + let structs = mergedNamespaces.namespaces + .sorted { $0.key < $1.key } + .map { (name, namespace) in ImageResource.generateStruct( resources: [], namespaces: [namespace], - path: namespace.path, - name: SwiftIdentifier(name: namespace.name), + name: name, prefix: qualifiedName ) } diff --git a/Sources/RswiftGenerators/Shared/AssetCatalogMergedNamespaces.swift b/Sources/RswiftGenerators/Shared/AssetCatalogMergedNamespaces.swift index 7cba8f8e..678629f6 100644 --- a/Sources/RswiftGenerators/Shared/AssetCatalogMergedNamespaces.swift +++ b/Sources/RswiftGenerators/Shared/AssetCatalogMergedNamespaces.swift @@ -11,24 +11,23 @@ import Foundation import RswiftResources struct AssetCatalogMergedNamespaces { - let namespaces: [AssetCatalog.Namespace] - let duplicates: [(SwiftIdentifier, String)] + var namespaces: [SwiftIdentifier: AssetCatalog.Namespace] = [:] + var duplicates: [(SwiftIdentifier, String)] = [] - init(all namespaces: [AssetCatalog.Namespace], otherIdentifiers: [SwiftIdentifier]) { - var dict: [SwiftIdentifier: AssetCatalog.Namespace] = [:] - - for namespace in namespaces { - let id = SwiftIdentifier(name: namespace.name) - dict[id] = dict[id]?.merging(namespace) ?? namespace + init(all: [String: AssetCatalog.Namespace], otherIdentifiers: [SwiftIdentifier]) { + for (name, namespace) in all { + let id = SwiftIdentifier(name: name) + if otherIdentifiers.contains(id) { + duplicates.append((id, name)) + } else { + namespaces[id, default: .init()].merge(namespace) + } } - - self.namespaces = dict.compactMap { (id, ns) in !otherIdentifiers.contains(id) ? ns : nil } - self.duplicates = dict.compactMap { (id, ns) in otherIdentifiers.contains(id) ? (id, ns.name) : nil } } func printWarningsForDuplicates(result: String, warning: (String) -> Void) { - for (name, identifier) in duplicates { - warning("Skipping asset namespace '\(name)' because symbol '\(identifier)' would conflict with \(result): \(identifier)") + for (identifier, name) in duplicates { + warning("Skipping asset namespace '\(name)' because symbol '\(identifier.value)' would conflict with \(result): \(identifier.value)") } } } diff --git a/Sources/RswiftGenerators/Shared/SwiftIdentifier.swift b/Sources/RswiftGenerators/Shared/SwiftIdentifier.swift index 3e47dd03..133b05e5 100644 --- a/Sources/RswiftGenerators/Shared/SwiftIdentifier.swift +++ b/Sources/RswiftGenerators/Shared/SwiftIdentifier.swift @@ -16,7 +16,7 @@ private let upperCasedPrefixRegex = try! NSRegularExpression(pattern: "^([A-Z]+) Disallowed characters: whitespace, mathematical symbols, arrows, private-use and invalid Unicode points, line- and boxdrawing characters Special rules: Can't begin with a number */ -public struct SwiftIdentifier : Hashable { +public struct SwiftIdentifier: Hashable, Comparable { public let value: String public init(name: String, lowercaseStartingCharacters: Bool = true) { @@ -57,6 +57,10 @@ public struct SwiftIdentifier : Hashable { static func +(lhs: SwiftIdentifier, rhs: SwiftIdentifier) -> SwiftIdentifier { return SwiftIdentifier(rawValue: "\(lhs.value).\(rhs.value)") } + + public static func < (lhs: SwiftIdentifier, rhs: SwiftIdentifier) -> Bool { + lhs.value < rhs.value + } } diff --git a/Sources/RswiftParsers/AssetCatalog+Parser.swift b/Sources/RswiftParsers/AssetCatalog+Parser.swift index d3b9d172..a5123d61 100644 --- a/Sources/RswiftParsers/AssetCatalog+Parser.swift +++ b/Sources/RswiftParsers/AssetCatalog+Parser.swift @@ -30,7 +30,7 @@ extension AssetCatalog: SupportedExtensions { } let directory = try parseDirectory(catalogURL: url) - let namespace = try createNamespace(name: "image", directory: directory, path: []) + let namespace = try createNamespace(directory: directory, path: []) return AssetCatalog(filename: basename, root: namespace) } @@ -97,12 +97,12 @@ extension AssetCatalog: SupportedExtensions { } // Note: ignore any localizations in Contents.json - static private func createNamespace(name: String, directory: NamespaceDirectory, path: [String]) throws -> Namespace { + static private func createNamespace(directory: NamespaceDirectory, path: [String]) throws -> Namespace { - var subnamespaces: [AssetCatalog.Namespace] = [] + var subnamespaces: [String: AssetCatalog.Namespace] = [:] for (name, directory) in directory.subnamespaces { - let namespace = try createNamespace(name: name, directory: directory, path: path + [name]) - subnamespaces.append(namespace) + let namespace = try createNamespace(directory: directory, path: path + [name]) + subnamespaces[name] = namespace } var colors: [AssetCatalog.Color] = [] @@ -115,7 +115,7 @@ extension AssetCatalog: SupportedExtensions { for fileURL in directory.images { let name = fileURL.filenameWithoutExtension! let tags = parseOnDemandResourceTags(directory: fileURL) - images.append(.init(name: name, locale: nil, onDemandResourceTags: tags)) + images.append(.init(name: name, path: path, locale: nil, onDemandResourceTags: tags)) } var dataAssets: [AssetCatalog.DataAsset] = [] @@ -126,8 +126,6 @@ extension AssetCatalog: SupportedExtensions { } return AssetCatalog.Namespace( - name: name, - path: path, subnamespaces: subnamespaces, colors: colors, images: images, diff --git a/Sources/RswiftParsers/ImageResource+Parser.swift b/Sources/RswiftParsers/ImageResource+Parser.swift index 1ff19ea1..99092689 100644 --- a/Sources/RswiftParsers/ImageResource+Parser.swift +++ b/Sources/RswiftParsers/ImageResource+Parser.swift @@ -31,6 +31,6 @@ extension ImageResource: SupportedExtensions { let pathExtensionToUse = (pathExtension == "png") ? "" : ".\(pathExtension)" let name = regex.stringByReplacingMatches(in: filename, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: fullFileNameRange, withTemplate: pathExtensionToUse) - return ImageResource(name: name, locale: locale, onDemandResourceTags: assetTags) + return ImageResource(name: name, path: [], locale: locale, onDemandResourceTags: assetTags) } } diff --git a/Sources/RswiftResources/AssetCatalog.swift b/Sources/RswiftResources/AssetCatalog.swift index 81c88ae2..0dd523dc 100644 --- a/Sources/RswiftResources/AssetCatalog.swift +++ b/Sources/RswiftResources/AssetCatalog.swift @@ -19,35 +19,36 @@ public struct AssetCatalog { extension AssetCatalog { public struct Namespace { - public let name: String - public let path: [String] - public var subnamespaces: [Namespace] = [] + public var subnamespaces: [String: Namespace] = [:] public var colors: [Color] = [] public var images: [ImageResource] = [] public var dataAssets: [DataAsset] = [] + public init() { + } + public init( - name: String, - path: [String], - subnamespaces: [Namespace], + subnamespaces: [String: Namespace], colors: [Color], images: [ImageResource], dataAssets: [DataAsset] ) { - self.name = name - self.path = path self.subnamespaces = subnamespaces self.colors = colors self.images = images self.dataAssets = dataAssets } + public mutating func merge(_ other: Namespace) { + self.subnamespaces = self.subnamespaces.merging(other.subnamespaces) { $0.merging($1) } + self.colors += other.colors + self.images += other.images + self.dataAssets += other.dataAssets + } + public func merging(_ other: Namespace) -> Namespace { var new = self - new.subnamespaces += other.subnamespaces - new.colors += other.colors - new.images += other.images - new.dataAssets += other.dataAssets + new.merge(other) return new } } diff --git a/Sources/RswiftResources/ImageResource.swift b/Sources/RswiftResources/ImageResource.swift index 431fb682..567ffebb 100644 --- a/Sources/RswiftResources/ImageResource.swift +++ b/Sources/RswiftResources/ImageResource.swift @@ -11,11 +11,13 @@ import Foundation public struct ImageResource { public let name: String + public let path: [String] public let locale: LocaleReference? public let onDemandResourceTags: [String]? - public init(name: String, locale: LocaleReference?, onDemandResourceTags: [String]?) { + public init(name: String, path: [String], locale: LocaleReference?, onDemandResourceTags: [String]?) { self.name = name + self.path = path self.locale = locale self.onDemandResourceTags = onDemandResourceTags } From f0e90b4359b9f78c995080493acf134196068404 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 22 Jul 2022 14:33:37 +0200 Subject: [PATCH 033/161] Update struct generation --- Sources/RswiftCore/RswiftCore.swift | 2 +- .../RswiftGenerators/Image+Generator.swift | 25 +++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index e84e9288..f8783aee 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -96,7 +96,7 @@ public struct RswiftCore { let s = Struct(name: structName) { ImageResource.generateStruct( resources: images, - namespaces: assetCatalogs.map(\.root), + catalogs: assetCatalogs, name: SwiftIdentifier(name: "image"), prefix: qualifiedName ) diff --git a/Sources/RswiftGenerators/Image+Generator.swift b/Sources/RswiftGenerators/Image+Generator.swift index 581dbdfa..1ce66df0 100644 --- a/Sources/RswiftGenerators/Image+Generator.swift +++ b/Sources/RswiftGenerators/Image+Generator.swift @@ -34,19 +34,23 @@ extension ImageResource { valueCodeString: code) } - public static func generateStruct(resources: [ImageResource], namespaces: [AssetCatalog.Namespace], name: SwiftIdentifier, prefix: SwiftIdentifier) -> Struct { - let structName = name - let qualifiedName = prefix + structName - + public static func generateStruct(resources: [ImageResource], catalogs: [AssetCatalog], name: SwiftIdentifier, prefix: SwiftIdentifier) -> Struct { // Multiple resources can share same name, // for example: Colors.jpg and Colors@2x.jpg are both named "Colors.jpg" // Deduplicate these let namedResources = Dictionary(grouping: resources, by: \.name).values.map(\.first!) - let assetFolderImageResources = namespaces.flatMap(\.images) + var merged: AssetCatalog.Namespace = catalogs.map(\.root).reduce(.init(), { $0.merging($1) }) + merged.images += namedResources - let allResources = namedResources + assetFolderImageResources - let groupedResources = allResources.grouped(bySwiftIdentifier: { $0.name }) + return generateStruct(namespace: merged, name: name, prefix: prefix) + } + + public static func generateStruct(namespace: AssetCatalog.Namespace, name: SwiftIdentifier, prefix: SwiftIdentifier) -> Struct { + let structName = name + let qualifiedName = prefix + structName + + let groupedResources = namespace.images.grouped(bySwiftIdentifier: { $0.name }) groupedResources.reportWarningsForDuplicatesAndEmpties(source: "image", result: "image") { l in print("warning:", l) @@ -54,9 +58,8 @@ extension ImageResource { let letbindings = groupedResources.uniques.map { $0.generateLetBinding() } - let allNamespaces = Dictionary(namespaces.flatMap(\.subnamespaces)) { $0.merging($1) } let otherIdentifiers = groupedResources.uniques.map { SwiftIdentifier(name: $0.name) } - let mergedNamespaces = AssetCatalogMergedNamespaces(all: allNamespaces, otherIdentifiers: otherIdentifiers) + let mergedNamespaces = AssetCatalogMergedNamespaces(all: namespace.subnamespaces, otherIdentifiers: otherIdentifiers) mergedNamespaces.printWarningsForDuplicates(result: "image") { l in print("warning:", l) @@ -66,8 +69,7 @@ extension ImageResource { .sorted { $0.key < $1.key } .map { (name, namespace) in ImageResource.generateStruct( - resources: [], - namespaces: [namespace], + namespace: namespace, name: name, prefix: qualifiedName ) @@ -79,6 +81,7 @@ extension ImageResource { structs.isEmpty ? "" : ", and \(structs.count) namespaces", "." ].joined() + let comments = [comment] return Struct(comments: comments, name: structName) { letbindings From 1065a1d4410ad6cff6c31352a67b328b28378385 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 22 Jul 2022 14:49:19 +0200 Subject: [PATCH 034/161] Add Font generation --- Sources/RswiftCore/RswiftCore.swift | 24 +++++++++--- Sources/RswiftGenerators/Font+Generator.swift | 15 ------- .../FontResource+Generator.swift | 39 +++++++++++++++++++ ...or.swift => ImageResource+Generator.swift} | 13 ------- .../Shared/LocaleReference+Generator.swift | 21 ++++++++++ 5 files changed, 78 insertions(+), 34 deletions(-) delete mode 100644 Sources/RswiftGenerators/Font+Generator.swift create mode 100644 Sources/RswiftGenerators/FontResource+Generator.swift rename Sources/RswiftGenerators/{Image+Generator.swift => ImageResource+Generator.swift} (91%) create mode 100644 Sources/RswiftGenerators/Shared/LocaleReference+Generator.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index f8783aee..2d48bc84 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -84,6 +84,11 @@ public struct RswiftCore { // print(item.name, item.viewControllers.map(\.type.rawName)) // } + + let fonts = try urls + .filter { FontResource.supportedExtensions.contains($0.pathExtension) } + .map { try FontResource.parse(url: $0) } + let images = try urls .filter { ImageResource.supportedExtensions.contains($0.pathExtension) } .map { try ImageResource.parse(url: $0, assetTags: nil) } @@ -91,15 +96,22 @@ public struct RswiftCore { .filter { AssetCatalog.supportedExtensions.contains($0.pathExtension) } .map { try AssetCatalog.parse(url: $0) } + let structName = SwiftIdentifier(rawValue: "R") let qualifiedName = structName + + let imageStruct = ImageResource.generateStruct( + resources: images, + catalogs: assetCatalogs, + name: SwiftIdentifier(name: "image"), + prefix: qualifiedName + ) + + let fontStruct = FontResource.generateStruct(resources: fonts, prefix: qualifiedName) + let s = Struct(name: structName) { - ImageResource.generateStruct( - resources: images, - catalogs: assetCatalogs, - name: SwiftIdentifier(name: "image"), - prefix: qualifiedName - ) +// imageStruct + fontStruct } print(s.prettyPrint()) diff --git a/Sources/RswiftGenerators/Font+Generator.swift b/Sources/RswiftGenerators/Font+Generator.swift deleted file mode 100644 index 1ad08027..00000000 --- a/Sources/RswiftGenerators/Font+Generator.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// FontResource+Generator.swift -// rswift -// -// Created by Tom Lokhorst on 2021-04-18. -// - -import Foundation -import RswiftResources - -extension FontResource { - public func generateResourceLetCodeString() -> String { - "static let \(SwiftIdentifier(name: self.name).value) = \(self)" - } -} diff --git a/Sources/RswiftGenerators/FontResource+Generator.swift b/Sources/RswiftGenerators/FontResource+Generator.swift new file mode 100644 index 00000000..154da637 --- /dev/null +++ b/Sources/RswiftGenerators/FontResource+Generator.swift @@ -0,0 +1,39 @@ +// +// FontResource+Generator.swift +// rswift +// +// Created by Tom Lokhorst on 2021-04-18. +// + +import Foundation +import RswiftResources + +extension FontResource { + func generateLetBinding() -> LetBinding { + let code = "FontResource(name: \"\(name)\", filename: \"\(filename)\")" + return LetBinding( + comments: ["Font `\(name)`."], + isStatic: true, + name: SwiftIdentifier(name: name), + valueCodeString: code) + } + + public static func generateStruct(resources: [FontResource], prefix: SwiftIdentifier) -> Struct { + let structName = SwiftIdentifier(name: "font") + let qualifiedName = prefix + structName + + let groupedResources = resources.grouped(bySwiftIdentifier: { $0.name }) + + groupedResources.reportWarningsForDuplicatesAndEmpties(source: "font resource", result: "font") { l in + print("warning:", l) + } + + let letbindings = groupedResources.uniques.map { $0.generateLetBinding() } + + let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) fonts."] + + return Struct(comments: comments, name: structName) { + letbindings + } + } +} diff --git a/Sources/RswiftGenerators/Image+Generator.swift b/Sources/RswiftGenerators/ImageResource+Generator.swift similarity index 91% rename from Sources/RswiftGenerators/Image+Generator.swift rename to Sources/RswiftGenerators/ImageResource+Generator.swift index 1ce66df0..1276612c 100644 --- a/Sources/RswiftGenerators/Image+Generator.swift +++ b/Sources/RswiftGenerators/ImageResource+Generator.swift @@ -8,19 +8,6 @@ import Foundation import RswiftResources -extension LocaleReference { - func codeString() -> String { - switch self { - case .none: - return ".none" - case .base: - return ".base" - case .language(let string): - return ".language(\(string))" - } - } -} - extension ImageResource { func generateLetBinding() -> LetBinding { let locs = locale.map { $0.codeString() } ?? "nil" diff --git a/Sources/RswiftGenerators/Shared/LocaleReference+Generator.swift b/Sources/RswiftGenerators/Shared/LocaleReference+Generator.swift new file mode 100644 index 00000000..09c54bc1 --- /dev/null +++ b/Sources/RswiftGenerators/Shared/LocaleReference+Generator.swift @@ -0,0 +1,21 @@ +// +// File.swift +// +// +// Created by Tom Lokhorst on 2022-07-22. +// + +import RswiftResources + +extension LocaleReference { + func codeString() -> String { + switch self { + case .none: + return ".none" + case .base: + return ".base" + case .language(let string): + return ".language(\(string))" + } + } +} From 8a2b206bfb939d3726290c9abec8d01135ca7b5c Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 22 Jul 2022 17:10:32 +0200 Subject: [PATCH 035/161] Generate segues --- Sources/RswiftCore/RswiftCore.swift | 27 ++- .../FontResource+Generator.swift | 6 +- .../ImageResource+Generator.swift | 17 +- .../RswiftGenerators/Segue+Generator.swift | 172 ++++++++++++++++++ Sources/RswiftResources/SegueIdentifier.swift | 16 ++ 5 files changed, 208 insertions(+), 30 deletions(-) create mode 100644 Sources/RswiftGenerators/Segue+Generator.swift create mode 100644 Sources/RswiftResources/SegueIdentifier.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 2d48bc84..f0a46f5d 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -76,14 +76,9 @@ public struct RswiftCore { // print(item.generateResourceLetCodeString()) // } - // let items = try urls - // .filter { StoryboardResource.supportedExtensions.contains($0.pathExtension) } - //// .filter { !FileResource.unsupportedExtensions.contains($0.pathExtension) } - // .map { try StoryboardResource.parse(url: $0) } - // for item in items { - // print(item.name, item.viewControllers.map(\.type.rawName)) - // } - + let storyboards = try urls + .filter { StoryboardResource.supportedExtensions.contains($0.pathExtension) } + .map { try StoryboardResource.parse(url: $0) } let fonts = try urls .filter { FontResource.supportedExtensions.contains($0.pathExtension) } @@ -100,18 +95,20 @@ public struct RswiftCore { let structName = SwiftIdentifier(rawValue: "R") let qualifiedName = structName - let imageStruct = ImageResource.generateStruct( - resources: images, - catalogs: assetCatalogs, - name: SwiftIdentifier(name: "image"), - prefix: qualifiedName - ) + let segueStruct = Segue.generateStruct(storyboards: storyboards, prefix: qualifiedName) + +// let imageStruct = ImageResource.generateStruct( +// resources: images, +// catalogs: assetCatalogs, +// prefix: qualifiedName +// ) let fontStruct = FontResource.generateStruct(resources: fonts, prefix: qualifiedName) let s = Struct(name: structName) { // imageStruct - fontStruct +// fontStruct + segueStruct } print(s.prettyPrint()) diff --git a/Sources/RswiftGenerators/FontResource+Generator.swift b/Sources/RswiftGenerators/FontResource+Generator.swift index 154da637..0993cda3 100644 --- a/Sources/RswiftGenerators/FontResource+Generator.swift +++ b/Sources/RswiftGenerators/FontResource+Generator.swift @@ -21,12 +21,10 @@ extension FontResource { public static func generateStruct(resources: [FontResource], prefix: SwiftIdentifier) -> Struct { let structName = SwiftIdentifier(name: "font") let qualifiedName = prefix + structName + let warning: (String) -> Void = { print("warning:", $0) } let groupedResources = resources.grouped(bySwiftIdentifier: { $0.name }) - - groupedResources.reportWarningsForDuplicatesAndEmpties(source: "font resource", result: "font") { l in - print("warning:", l) - } + groupedResources.reportWarningsForDuplicatesAndEmpties(source: "font resource", result: "font", warning: warning) let letbindings = groupedResources.uniques.map { $0.generateLetBinding() } diff --git a/Sources/RswiftGenerators/ImageResource+Generator.swift b/Sources/RswiftGenerators/ImageResource+Generator.swift index 1276612c..f63fe0b9 100644 --- a/Sources/RswiftGenerators/ImageResource+Generator.swift +++ b/Sources/RswiftGenerators/ImageResource+Generator.swift @@ -21,7 +21,7 @@ extension ImageResource { valueCodeString: code) } - public static func generateStruct(resources: [ImageResource], catalogs: [AssetCatalog], name: SwiftIdentifier, prefix: SwiftIdentifier) -> Struct { + public static func generateStruct(resources: [ImageResource], catalogs: [AssetCatalog], prefix: SwiftIdentifier) -> Struct { // Multiple resources can share same name, // for example: Colors.jpg and Colors@2x.jpg are both named "Colors.jpg" // Deduplicate these @@ -30,27 +30,22 @@ extension ImageResource { var merged: AssetCatalog.Namespace = catalogs.map(\.root).reduce(.init(), { $0.merging($1) }) merged.images += namedResources - return generateStruct(namespace: merged, name: name, prefix: prefix) + return generateStruct(namespace: merged, name: SwiftIdentifier(name: "image"), prefix: prefix) } public static func generateStruct(namespace: AssetCatalog.Namespace, name: SwiftIdentifier, prefix: SwiftIdentifier) -> Struct { let structName = name let qualifiedName = prefix + structName + let warning: (String) -> Void = { print("warning:", $0) } let groupedResources = namespace.images.grouped(bySwiftIdentifier: { $0.name }) - - groupedResources.reportWarningsForDuplicatesAndEmpties(source: "image", result: "image") { l in - print("warning:", l) - } + groupedResources.reportWarningsForDuplicatesAndEmpties(source: "image", result: "image", warning: warning) let letbindings = groupedResources.uniques.map { $0.generateLetBinding() } - let otherIdentifiers = groupedResources.uniques.map { SwiftIdentifier(name: $0.name) } - let mergedNamespaces = AssetCatalogMergedNamespaces(all: namespace.subnamespaces, otherIdentifiers: otherIdentifiers) - mergedNamespaces.printWarningsForDuplicates(result: "image") { l in - print("warning:", l) - } + let mergedNamespaces = AssetCatalogMergedNamespaces(all: namespace.subnamespaces, otherIdentifiers: otherIdentifiers) + mergedNamespaces.printWarningsForDuplicates(result: "image", warning: warning) let structs = mergedNamespaces.namespaces .sorted { $0.key < $1.key } diff --git a/Sources/RswiftGenerators/Segue+Generator.swift b/Sources/RswiftGenerators/Segue+Generator.swift new file mode 100644 index 00000000..27bac75d --- /dev/null +++ b/Sources/RswiftGenerators/Segue+Generator.swift @@ -0,0 +1,172 @@ +// +// Segue+Generator.swift +// +// +// Created by Tom Lokhorst on 2022-07-22. +// + +import Foundation +import RswiftResources + +public struct Segue { + public static func generateStruct(storyboards: [StoryboardResource], prefix: SwiftIdentifier) -> Struct { + let structName = SwiftIdentifier(name: "segue") + let qualifiedName = prefix + structName + + let warning: (String) -> Void = { print("warning:", $0) } + + let allSegues = allSegueInfos(storyboards: storyboards, warning: warning) + let viewControllers = viewControllers(segues: allSegues, warning: warning) + let structs = viewControllers + .map { generateStruct(sourceType: $0.key, segues: $0.value) } + .sorted { $0.name < $1.name } + + let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(structs.count) view controllers."] + + return Struct(comments: comments, name: structName) { + structs + } + } + + private static func generateStruct(sourceType: TypeReference, segues: [SegueWithInfo]) -> Struct { + let comments = ["This struct is generated for `\(sourceType.rawName)`, and contains static references to \(segues.count) segues."] + return Struct(comments: comments, name: SwiftIdentifier(name: sourceType.rawName)) { + segues.map { $0.generateLetBinding() } + } + } + + private static func viewControllers(segues: [SegueWithInfo], warning: (String) -> Void) -> [TypeReference: [SegueWithInfo]] { + var result: [TypeReference: [SegueWithInfo]] = [:] + + let grouped = Dictionary(grouping: segues, by: \.sourceType) + for (sourceType, seguesBySourceType) in grouped { + let segues = seguesBySourceType.grouped(bySwiftIdentifier: { $0.segue.identifier }) + segues.reportWarningsForDuplicatesAndEmpties(source: "segue", container: "for '\(sourceType.rawName)'", result: "segue", warning: warning) + + result[sourceType] = segues.uniques + } + + return result + } + + private static func allSegueInfos(storyboards: [StoryboardResource], warning: (String) -> Void) -> [SegueWithInfo] { + let allSegues = storyboards.flatMap { storyboard in + storyboard.viewControllers.flatMap { viewController in + viewController.segues.compactMap { segue -> SegueWithInfo? in + guard let destinationType = resolveDestinationType( + for:segue, + inViewController: viewController, + inStoryboard: storyboard, + allStoryboards: storyboards) + else + { + warning("Destination view controller with id \(segue.destination) for segue \(segue.identifier) in \(viewController.type.rawName) not found in storyboard \(storyboard.name). Is this storyboard corrupt?") + return nil + } + + guard !segue.identifier.isEmpty else { + return nil + } + + return SegueWithInfo( + segue: segue, + sourceType: viewController.type, + destinationType: destinationType + ) + } + } + } + + let deduplicatedSeguesWithInfo = Dictionary(grouping: allSegues, by: \.groupKey) + .values + .compactMap { $0.first } + + return deduplicatedSeguesWithInfo + } + + private static func resolveDestinationType(for segue: StoryboardResource.Segue, inViewController: StoryboardResource.ViewController, inStoryboard storyboard: StoryboardResource, allStoryboards storyboards: [StoryboardResource]) -> TypeReference? { + let uiViewController = TypeReference(module: .custom(name: "UIKit"), rawName: "UIViewController") + + if segue.kind == "unwind" { + return uiViewController + } + + let destinationViewControllerType = storyboard.viewControllers + .filter { $0.id == segue.destination } + .first? + .type + + let destinationViewControllerPlaceholderType = storyboard.viewControllerPlaceholders + .filter { $0.id == segue.destination } + .first + .flatMap { storyboard -> TypeReference? in + switch storyboard.resolveWithStoryboards(storyboards) { + case .customBundle: + return uiViewController // Not supported, fallback to UIViewController + case let .resolved(vc): + return vc?.type + } + } + + return destinationViewControllerType ?? destinationViewControllerPlaceholderType + } +} + +private extension StoryboardResource.ViewControllerPlaceholder { + enum ResolvedResult { + case customBundle + case resolved(StoryboardResource.ViewController?) + } + + func resolveWithStoryboards(_ storyboards: [StoryboardResource]) -> ResolvedResult { + if nil != bundleIdentifier { + // Can't resolve storyboard in other bundles + return .customBundle + } + + guard let storyboardName = storyboardName else { + // Storyboard reference without a storyboard defined?! + return .resolved(nil) + } + + let storyboard = storyboards + .filter { $0.name == storyboardName } + + guard let referencedIdentifier = referencedIdentifier else { + return .resolved(storyboard.first?.initialViewController) + } + + return .resolved(storyboard + .flatMap { + $0.viewControllers.filter { $0.storyboardIdentifier == referencedIdentifier } + } + .first + ) + } +} + +struct SegueWithInfo { + let segue: StoryboardResource.Segue + let sourceType: TypeReference + let destinationType: TypeReference + + var groupKey: String { + "\(segue.identifier)|\(segue.type)|\(sourceType)|\(destinationType)" + } + + var genericTypeReference: TypeReference { + TypeReference( + module: .host, + rawName: "SegueIdentifier<\(segue.type.rawName), \(sourceType.rawName), \(destinationType.rawName)>") + } + + func generateLetBinding() -> LetBinding { + return LetBinding( + comments: ["Segue identifier `\(segue.identifier)`."], + isStatic: true, + name: SwiftIdentifier(name: segue.identifier), + typeReference: genericTypeReference, + valueCodeString: "SegueIdentifier(identifier: \"\(segue.identifier)\")" + ) + } +} diff --git a/Sources/RswiftResources/SegueIdentifier.swift b/Sources/RswiftResources/SegueIdentifier.swift new file mode 100644 index 00000000..58e45e2f --- /dev/null +++ b/Sources/RswiftResources/SegueIdentifier.swift @@ -0,0 +1,16 @@ +// +// SegueIdentifier.swift +// +// +// Created by Tom Lokhorst on 2022-07-22. +// + +import Foundation + +public struct SegueIdentifier { + public let identifier: String + + public init(identifier: String) { + self.identifier = identifier + } +} From 1e43541ea37b535bfb92c4a7c1d1753db8e468d5 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 22 Jul 2022 17:41:22 +0200 Subject: [PATCH 036/161] Start work on storyboards --- Sources/RswiftCore/RswiftCore.swift | 10 ++-- .../ImageResource+Generator.swift | 26 +++++----- .../Storyboard+Generator.swift | 52 ++++++++++++++++++- .../RswiftGenerators/SwiftSyntax/Struct.swift | 15 +++++- .../RswiftResources/StoryboardResource.swift | 4 ++ 5 files changed, 88 insertions(+), 19 deletions(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index f0a46f5d..a8386a91 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -95,7 +95,7 @@ public struct RswiftCore { let structName = SwiftIdentifier(rawValue: "R") let qualifiedName = structName - let segueStruct = Segue.generateStruct(storyboards: storyboards, prefix: qualifiedName) +// let segueStruct = Segue.generateStruct(storyboards: storyboards, prefix: qualifiedName) // let imageStruct = ImageResource.generateStruct( // resources: images, @@ -103,12 +103,16 @@ public struct RswiftCore { // prefix: qualifiedName // ) - let fontStruct = FontResource.generateStruct(resources: fonts, prefix: qualifiedName) +// let fontStruct = FontResource.generateStruct(resources: fonts, prefix: qualifiedName) + + let storyboardStruct = StoryboardResource.generateStruct(storyboards: storyboards, prefix: qualifiedName) let s = Struct(name: structName) { // imageStruct // fontStruct - segueStruct +// segueStruct + + storyboardStruct } print(s.prettyPrint()) diff --git a/Sources/RswiftGenerators/ImageResource+Generator.swift b/Sources/RswiftGenerators/ImageResource+Generator.swift index f63fe0b9..223f5984 100644 --- a/Sources/RswiftGenerators/ImageResource+Generator.swift +++ b/Sources/RswiftGenerators/ImageResource+Generator.swift @@ -9,18 +9,6 @@ import Foundation import RswiftResources extension ImageResource { - func generateLetBinding() -> LetBinding { - let locs = locale.map { $0.codeString() } ?? "nil" - let odrt = onDemandResourceTags?.debugDescription ?? "nil" - let fullname = (path + [name]).joined(separator: "/") - let code = "ImageResource(name: \"\(fullname)\", locale: \(locs), onDemandResourceTags: \(odrt))" - return LetBinding( - comments: ["Image `\(fullname)`."], - isStatic: true, - name: SwiftIdentifier(name: name), - valueCodeString: code) - } - public static func generateStruct(resources: [ImageResource], catalogs: [AssetCatalog], prefix: SwiftIdentifier) -> Struct { // Multiple resources can share same name, // for example: Colors.jpg and Colors@2x.jpg are both named "Colors.jpg" @@ -71,3 +59,17 @@ extension ImageResource { } } } + +extension ImageResource { + func generateLetBinding() -> LetBinding { + let locs = locale.map { $0.codeString() } ?? "nil" + let odrt = onDemandResourceTags?.debugDescription ?? "nil" + let fullname = (path + [name]).joined(separator: "/") + let code = "ImageResource(name: \"\(fullname)\", locale: \(locs), onDemandResourceTags: \(odrt))" + return LetBinding( + comments: ["Image `\(fullname)`."], + isStatic: true, + name: SwiftIdentifier(name: name), + valueCodeString: code) + } +} diff --git a/Sources/RswiftGenerators/Storyboard+Generator.swift b/Sources/RswiftGenerators/Storyboard+Generator.swift index 6d897fdd..aa18a7b8 100644 --- a/Sources/RswiftGenerators/Storyboard+Generator.swift +++ b/Sources/RswiftGenerators/Storyboard+Generator.swift @@ -9,7 +9,55 @@ import Foundation import RswiftResources extension StoryboardResource { - public func generateResourceLetCodeString() -> String { - "static let \(SwiftIdentifier(name: self.name).value) = \(self)" + public static func generateStruct(storyboards: [StoryboardResource], prefix: SwiftIdentifier) -> Struct { + let structName = SwiftIdentifier(name: "storyboard") + let qualifiedName = prefix + structName + + let warning: (String) -> Void = { print("warning:", $0) } + + let groupedStoryboards = storyboards.grouped(bySwiftIdentifier: { $0.name }) + groupedStoryboards.reportWarningsForDuplicatesAndEmpties(source: "storyboard", result: "storyboard", warning: warning) + + let structs = groupedStoryboards.uniques + .map { $0.generateStruct(prefix: qualifiedName, warning: warning) } + .sorted { $0.name < $1.name } + + let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(structs.count) storyboards."] + + return Struct(comments: comments, name: structName) { + structs + } + } +} + +extension StoryboardResource { + + func generateStruct(prefix: SwiftIdentifier, warning: (String) -> Void) -> Struct { + // TODO filter lets with name `identifier` + + let letIdentifier = LetBinding( + isStatic: true, + name: SwiftIdentifier(name: "identifier"), + valueCodeString: "\"\(name)\"") + + let identifier = SwiftIdentifier(rawValue: name) + let storyboardIdentifier = TypeReference(module: .host, rawName: "StoryboardIdentifier") + + return Struct( + comments: ["Storyboard `\(name)`."], + name: identifier, + protocols: [storyboardIdentifier] + ) { + letIdentifier + } + } + + func generateLetBinding() -> LetBinding { + let code = "\"\(name)\"" + return LetBinding( + comments: ["Storyboard `\(name)`."], + isStatic: true, + name: SwiftIdentifier(name: name), + valueCodeString: code) } } diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index 9e310bb2..6d3c8440 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -102,15 +102,23 @@ public struct Struct { public let comments: [String] public var accessControl = AccessControl.none public let name: SwiftIdentifier + public var protocols: [TypeReference] = [] public var lets: [LetBinding] = [] public var structs: [Struct] = [] public var isEmpty: Bool { lets.isEmpty && structs.isEmpty } - public init(comments: [String] = [], accessControl: AccessControl = AccessControl.none, name: SwiftIdentifier, @StructMembersBuilder membersBuilder: () -> StructMembers) { + public init( + comments: [String] = [], + accessControl: AccessControl = AccessControl.none, + name: SwiftIdentifier, + protocols: [TypeReference] = [], + @StructMembersBuilder membersBuilder: () -> StructMembers + ) { self.comments = comments self.accessControl = accessControl self.name = name + self.protocols = protocols (self.lets, self.structs) = membersBuilder() } @@ -124,7 +132,10 @@ public struct Struct { for c in comments { pp.append(words: ["///", c == "" ? nil : c]) } - pp.append(line: "struct \(name.value) {") + + let ps = protocols.map(\.rawName).joined(separator: ", ") + let implements = ps.isEmpty ? "" : ": \(ps)" + pp.append(line: "struct \(name.value)\(implements) {") pp.indented { pp in for letb in lets { diff --git a/Sources/RswiftResources/StoryboardResource.swift b/Sources/RswiftResources/StoryboardResource.swift index d808aac4..96b34a64 100644 --- a/Sources/RswiftResources/StoryboardResource.swift +++ b/Sources/RswiftResources/StoryboardResource.swift @@ -9,6 +9,10 @@ import Foundation +public protocol StoryboardIdentifier { + static var identifier: String { get } +} + public struct StoryboardResource { public let name: String public let locale: LocaleReference From 1bca47e5a502532563b157722641139c3db31a77 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 22 Jul 2022 23:44:22 +0200 Subject: [PATCH 037/161] Generate storyboards --- .../RswiftGenerators/Segue+Generator.swift | 1 + .../Storyboard+Generator.swift | 46 +++++++++++++++---- Sources/RswiftResources/SegueIdentifier.swift | 16 ------- .../StoryboardIdentifier.swift | 28 +++++++++++ .../RswiftResources/StoryboardResource.swift | 4 -- 5 files changed, 66 insertions(+), 29 deletions(-) delete mode 100644 Sources/RswiftResources/SegueIdentifier.swift create mode 100644 Sources/RswiftResources/StoryboardIdentifier.swift diff --git a/Sources/RswiftGenerators/Segue+Generator.swift b/Sources/RswiftGenerators/Segue+Generator.swift index 27bac75d..1058c5c1 100644 --- a/Sources/RswiftGenerators/Segue+Generator.swift +++ b/Sources/RswiftGenerators/Segue+Generator.swift @@ -77,6 +77,7 @@ public struct Segue { } } + // Deduplicate segues that are identical let deduplicatedSeguesWithInfo = Dictionary(grouping: allSegues, by: \.groupKey) .values .compactMap { $0.first } diff --git a/Sources/RswiftGenerators/Storyboard+Generator.swift b/Sources/RswiftGenerators/Storyboard+Generator.swift index aa18a7b8..9602efa8 100644 --- a/Sources/RswiftGenerators/Storyboard+Generator.swift +++ b/Sources/RswiftGenerators/Storyboard+Generator.swift @@ -33,14 +33,36 @@ extension StoryboardResource { extension StoryboardResource { func generateStruct(prefix: SwiftIdentifier, warning: (String) -> Void) -> Struct { - // TODO filter lets with name `identifier` + let nameIdentifier = SwiftIdentifier(rawValue: "name") - let letIdentifier = LetBinding( + // View controllers with identifiers + let grouped = viewControllers + .compactMap { (vc) -> (identifier: String, vc: StoryboardResource.ViewController)? in + guard let storyboardIdentifier = vc.storyboardIdentifier else { return nil } + return (storyboardIdentifier, vc) + } + .grouped(bySwiftIdentifier: { $0.identifier }) + + grouped.reportWarningsForDuplicatesAndEmpties(source: "view controller", result: "view controller identifier", warning: warning) + + let skip = grouped.uniques + .map(\.identifier) + .first { SwiftIdentifier(rawValue: $0) == nameIdentifier } + if let skip = skip { + warning("Skipping 1 view controller because symbol '\(skip)' conflicts with reserved name '\(nameIdentifier.value)'") + } + + let letbindings = grouped.uniques + .filter { (id, _) in SwiftIdentifier(rawValue: id) != nameIdentifier } + .map { (id, vc) in vc.generateLetBinding(identifier: id) } + .sorted { $0.name < $1.name } + + let letName = LetBinding( isStatic: true, - name: SwiftIdentifier(name: "identifier"), + name: nameIdentifier, valueCodeString: "\"\(name)\"") - let identifier = SwiftIdentifier(rawValue: name) + let identifier = SwiftIdentifier(name: name) let storyboardIdentifier = TypeReference(module: .host, rawName: "StoryboardIdentifier") return Struct( @@ -48,16 +70,22 @@ extension StoryboardResource { name: identifier, protocols: [storyboardIdentifier] ) { - letIdentifier + letName + + letbindings } } +} - func generateLetBinding() -> LetBinding { - let code = "\"\(name)\"" +extension StoryboardResource.ViewController { + + func generateLetBinding(identifier: String) -> LetBinding { + let type = TypeReference(module: .host, rawName: "ViewControllerIdentifier<\(self.type.rawName)>") + let code = #"ViewControllerIdentifier(identifier: "\#(identifier)")"# return LetBinding( - comments: ["Storyboard `\(name)`."], isStatic: true, - name: SwiftIdentifier(name: name), + name: SwiftIdentifier(name: identifier), + typeReference: type, valueCodeString: code) } } diff --git a/Sources/RswiftResources/SegueIdentifier.swift b/Sources/RswiftResources/SegueIdentifier.swift deleted file mode 100644 index 58e45e2f..00000000 --- a/Sources/RswiftResources/SegueIdentifier.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// SegueIdentifier.swift -// -// -// Created by Tom Lokhorst on 2022-07-22. -// - -import Foundation - -public struct SegueIdentifier { - public let identifier: String - - public init(identifier: String) { - self.identifier = identifier - } -} diff --git a/Sources/RswiftResources/StoryboardIdentifier.swift b/Sources/RswiftResources/StoryboardIdentifier.swift new file mode 100644 index 00000000..2ad75dba --- /dev/null +++ b/Sources/RswiftResources/StoryboardIdentifier.swift @@ -0,0 +1,28 @@ +// +// StoryboardIdentifier.swift +// +// +// Created by Tom Lokhorst on 2022-07-22. +// + +import Foundation + +public protocol StoryboardIdentifier { + static var identifier: String { get } +} + +public struct ViewControllerIdentifier { + public let identifier: String + + public init(identifier: String) { + self.identifier = identifier + } +} + +public struct SegueIdentifier { + public let identifier: String + + public init(identifier: String) { + self.identifier = identifier + } +} diff --git a/Sources/RswiftResources/StoryboardResource.swift b/Sources/RswiftResources/StoryboardResource.swift index 96b34a64..d808aac4 100644 --- a/Sources/RswiftResources/StoryboardResource.swift +++ b/Sources/RswiftResources/StoryboardResource.swift @@ -9,10 +9,6 @@ import Foundation -public protocol StoryboardIdentifier { - static var identifier: String { get } -} - public struct StoryboardResource { public let name: String public let locale: LocaleReference From 610c33a155a41ebe6a5b6bb3f0afaac2b3ca9e31 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sat, 23 Jul 2022 00:14:09 +0200 Subject: [PATCH 038/161] Move Color and Asset to top level --- Sources/RswiftCore/RswiftCore.swift | 16 ++++++------ .../ImageResource+Generator.swift | 2 +- .../RswiftParsers/AssetCatalog+Parser.swift | 8 +++--- Sources/RswiftResources/AssetCatalog.swift | 26 +++---------------- Sources/RswiftResources/ColorResource.swift | 18 +++++++++++++ .../RswiftResources/DataAssetResource.swift | 20 ++++++++++++++ 6 files changed, 55 insertions(+), 35 deletions(-) create mode 100644 Sources/RswiftResources/ColorResource.swift create mode 100644 Sources/RswiftResources/DataAssetResource.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index a8386a91..119a3ff6 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -97,22 +97,22 @@ public struct RswiftCore { // let segueStruct = Segue.generateStruct(storyboards: storyboards, prefix: qualifiedName) -// let imageStruct = ImageResource.generateStruct( -// resources: images, -// catalogs: assetCatalogs, -// prefix: qualifiedName -// ) + let imageStruct = ImageResource.generateStruct( + catalogs: assetCatalogs, + resources: images, + prefix: qualifiedName + ) // let fontStruct = FontResource.generateStruct(resources: fonts, prefix: qualifiedName) - let storyboardStruct = StoryboardResource.generateStruct(storyboards: storyboards, prefix: qualifiedName) +// let storyboardStruct = StoryboardResource.generateStruct(storyboards: storyboards, prefix: qualifiedName) let s = Struct(name: structName) { -// imageStruct + imageStruct // fontStruct // segueStruct - storyboardStruct +// storyboardStruct } print(s.prettyPrint()) diff --git a/Sources/RswiftGenerators/ImageResource+Generator.swift b/Sources/RswiftGenerators/ImageResource+Generator.swift index 223f5984..ecdce6fc 100644 --- a/Sources/RswiftGenerators/ImageResource+Generator.swift +++ b/Sources/RswiftGenerators/ImageResource+Generator.swift @@ -9,7 +9,7 @@ import Foundation import RswiftResources extension ImageResource { - public static func generateStruct(resources: [ImageResource], catalogs: [AssetCatalog], prefix: SwiftIdentifier) -> Struct { + public static func generateStruct(catalogs: [AssetCatalog], resources: [ImageResource], prefix: SwiftIdentifier) -> Struct { // Multiple resources can share same name, // for example: Colors.jpg and Colors@2x.jpg are both named "Colors.jpg" // Deduplicate these diff --git a/Sources/RswiftParsers/AssetCatalog+Parser.swift b/Sources/RswiftParsers/AssetCatalog+Parser.swift index a5123d61..ceb5bf4f 100644 --- a/Sources/RswiftParsers/AssetCatalog+Parser.swift +++ b/Sources/RswiftParsers/AssetCatalog+Parser.swift @@ -105,10 +105,10 @@ extension AssetCatalog: SupportedExtensions { subnamespaces[name] = namespace } - var colors: [AssetCatalog.Color] = [] + var colors: [ColorResource] = [] for fileURL in directory.colors { let name = fileURL.filenameWithoutExtension! - colors.append(.init(name: name)) + colors.append(.init(name: name, path: path)) } var images: [ImageResource] = [] @@ -118,11 +118,11 @@ extension AssetCatalog: SupportedExtensions { images.append(.init(name: name, path: path, locale: nil, onDemandResourceTags: tags)) } - var dataAssets: [AssetCatalog.DataAsset] = [] + var dataAssets: [DataAssetResource] = [] for fileURL in directory.dataAssets { let name = fileURL.filenameWithoutExtension! let tags = parseOnDemandResourceTags(directory: fileURL) - dataAssets.append(.init(name: name, onDemandResourceTags: tags)) + dataAssets.append(.init(name: name, path: path, onDemandResourceTags: tags)) } return AssetCatalog.Namespace( diff --git a/Sources/RswiftResources/AssetCatalog.swift b/Sources/RswiftResources/AssetCatalog.swift index 0dd523dc..06f14c5f 100644 --- a/Sources/RswiftResources/AssetCatalog.swift +++ b/Sources/RswiftResources/AssetCatalog.swift @@ -20,18 +20,18 @@ public struct AssetCatalog { extension AssetCatalog { public struct Namespace { public var subnamespaces: [String: Namespace] = [:] - public var colors: [Color] = [] + public var colors: [ColorResource] = [] public var images: [ImageResource] = [] - public var dataAssets: [DataAsset] = [] + public var dataAssets: [DataAssetResource] = [] public init() { } public init( subnamespaces: [String: Namespace], - colors: [Color], + colors: [ColorResource], images: [ImageResource], - dataAssets: [DataAsset] + dataAssets: [DataAssetResource] ) { self.subnamespaces = subnamespaces self.colors = colors @@ -52,22 +52,4 @@ extension AssetCatalog { return new } } - - public struct Color { - public let name: String - - public init(name: String) { - self.name = name - } - } - - public struct DataAsset { - public let name: String - public let onDemandResourceTags: [String]? - - public init(name: String, onDemandResourceTags: [String]?) { - self.name = name - self.onDemandResourceTags = onDemandResourceTags - } - } } diff --git a/Sources/RswiftResources/ColorResource.swift b/Sources/RswiftResources/ColorResource.swift new file mode 100644 index 00000000..d5cb90c3 --- /dev/null +++ b/Sources/RswiftResources/ColorResource.swift @@ -0,0 +1,18 @@ +// +// ColorResource.swift +// +// +// Created by Tom Lokhorst on 2022-07-23. +// + +import Foundation + +public struct ColorResource { + public let name: String + public let path: [String] + + public init(name: String, path: [String]) { + self.name = name + self.path = path + } +} diff --git a/Sources/RswiftResources/DataAssetResource.swift b/Sources/RswiftResources/DataAssetResource.swift new file mode 100644 index 00000000..6ab9a181 --- /dev/null +++ b/Sources/RswiftResources/DataAssetResource.swift @@ -0,0 +1,20 @@ +// +// DataAsset.swift +// +// +// Created by Tom Lokhorst on 2022-07-23. +// + +import Foundation + +public struct DataAssetResource { + public let name: String + public let path: [String] + public let onDemandResourceTags: [String]? + + public init(name: String, path: [String], onDemandResourceTags: [String]?) { + self.name = name + self.path = path + self.onDemandResourceTags = onDemandResourceTags + } +} From 0319cbfbfe406e8556470b482723c2c6ba259d2b Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sat, 23 Jul 2022 10:43:30 +0200 Subject: [PATCH 039/161] Add Color and Data resource generator --- Sources/RswiftCore/RswiftCore.swift | 16 ++- .../AssetCatalog+Generator.swift | 130 ++++++++++++++++++ .../ImageResource+Generator.swift | 75 ---------- .../RswiftParsers/AssetCatalog+Parser.swift | 2 +- Sources/RswiftResources/AssetCatalog.swift | 4 +- ...AssetResource.swift => DataResource.swift} | 4 +- 6 files changed, 148 insertions(+), 83 deletions(-) create mode 100644 Sources/RswiftGenerators/AssetCatalog+Generator.swift delete mode 100644 Sources/RswiftGenerators/ImageResource+Generator.swift rename Sources/RswiftResources/{DataAssetResource.swift => DataResource.swift} (87%) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 119a3ff6..c519c787 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -97,9 +97,17 @@ public struct RswiftCore { // let segueStruct = Segue.generateStruct(storyboards: storyboards, prefix: qualifiedName) - let imageStruct = ImageResource.generateStruct( +// let imageStruct = ImageResource.generateStruct( +// catalogs: assetCatalogs, +// resources: images, +// prefix: qualifiedName +// ) +// let colorStruct = ColorResource.generateStruct( +// catalogs: assetCatalogs, +// prefix: qualifiedName +// ) + let dataStruct = DataResource.generateStruct( catalogs: assetCatalogs, - resources: images, prefix: qualifiedName ) @@ -108,7 +116,9 @@ public struct RswiftCore { // let storyboardStruct = StoryboardResource.generateStruct(storyboards: storyboards, prefix: qualifiedName) let s = Struct(name: structName) { - imageStruct +// imageStruct +// colorStruct +// dataStruct // fontStruct // segueStruct diff --git a/Sources/RswiftGenerators/AssetCatalog+Generator.swift b/Sources/RswiftGenerators/AssetCatalog+Generator.swift new file mode 100644 index 00000000..6cc28cda --- /dev/null +++ b/Sources/RswiftGenerators/AssetCatalog+Generator.swift @@ -0,0 +1,130 @@ +// +// AssetCatalog+Generator.swift +// +// +// Created by Tom Lokhorst on 2022-07-23. +// + +import Foundation +import RswiftResources + +public protocol AssetCatalogContent { + var name: String { get } + func generateLetBinding() -> LetBinding +} + +extension DataResource: AssetCatalogContent {} +extension ColorResource: AssetCatalogContent {} +extension ImageResource: AssetCatalogContent {} + +extension ColorResource { + public static func generateStruct(catalogs: [AssetCatalog], prefix: SwiftIdentifier) -> Struct { + let merged: AssetCatalog.Namespace = catalogs.map(\.root).reduce(.init(), { $0.merging($1) }) + + return merged.generateStruct(resourceName: "color", resourcesSelector: { $0.colors }, prefix: prefix) + } +} + +extension DataResource { + public static func generateStruct(catalogs: [AssetCatalog], prefix: SwiftIdentifier) -> Struct { + let merged: AssetCatalog.Namespace = catalogs.map(\.root).reduce(.init(), { $0.merging($1) }) + + return merged.generateStruct(resourceName: "data", resourcesSelector: { $0.dataAssets }, prefix: prefix) + } +} + +extension ImageResource { + public static func generateStruct(catalogs: [AssetCatalog], resources: [ImageResource], prefix: SwiftIdentifier) -> Struct { + // Multiple resources can share same name, + // for example: Colors.jpg and Colors@2x.jpg are both named "Colors.jpg" + // Deduplicate these + let namedResources = Dictionary(grouping: resources, by: \.name).values.map(\.first!) + + var merged: AssetCatalog.Namespace = catalogs.map(\.root).reduce(.init(), { $0.merging($1) }) + merged.images += namedResources + + return merged.generateStruct(resourceName: "image", resourcesSelector: { $0.images }, prefix: prefix) + } +} + +extension AssetCatalog.Namespace { + public func generateStruct(resourceName: String, resourcesSelector: (Self) -> [AssetCatalogContent], prefix: SwiftIdentifier) -> Struct { + let structName = SwiftIdentifier(name: resourceName) + let qualifiedName = prefix + structName + let warning: (String) -> Void = { print("warning:", $0) } + + let allResources = resourcesSelector(self) + let groupedResources = allResources.grouped(bySwiftIdentifier: { $0.name }) + groupedResources.reportWarningsForDuplicatesAndEmpties(source: resourceName, result: resourceName, warning: warning) + + let letbindings = groupedResources.uniques.map { $0.generateLetBinding() } + let otherIdentifiers = groupedResources.uniques.map { SwiftIdentifier(name: $0.name) } + + let mergedNamespaces = AssetCatalogMergedNamespaces(all: subnamespaces, otherIdentifiers: otherIdentifiers) + mergedNamespaces.printWarningsForDuplicates(result: resourceName, warning: warning) + + let structs = mergedNamespaces.namespaces + .sorted { $0.key < $1.key } + .map { (name, namespace) in + namespace.generateStruct( + resourceName: resourceName, + resourcesSelector: resourcesSelector, + prefix: qualifiedName + ) + } + .filter { !$0.isEmpty } + + let comment = [ + "This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) \(resourceName)s", + structs.isEmpty ? "" : ", and \(structs.count) namespaces", + "." + ].joined() + + let comments = [comment] + return Struct(comments: comments, name: structName) { + letbindings + structs + } + } +} + +extension ColorResource { + public func generateLetBinding() -> LetBinding { + let fullname = (path + [name]).joined(separator: "/") + let code = "ColorResource(name: \"\(fullname)\")" + return LetBinding( + comments: ["Color `\(fullname)`."], + isStatic: true, + name: SwiftIdentifier(name: name), + valueCodeString: code + ) + } +} + +extension DataResource { + public func generateLetBinding() -> LetBinding { + let fullname = (path + [name]).joined(separator: "/") + let code = "DataResource(name: \"\(fullname)\")" + return LetBinding( + comments: ["Data asset `\(fullname)`."], + isStatic: true, + name: SwiftIdentifier(name: name), + valueCodeString: code + ) + } +} + +extension ImageResource { + public func generateLetBinding() -> LetBinding { + let locs = locale.map { $0.codeString() } ?? "nil" + let odrt = onDemandResourceTags?.debugDescription ?? "nil" + let fullname = (path + [name]).joined(separator: "/") + let code = "ImageResource(name: \"\(fullname)\", locale: \(locs), onDemandResourceTags: \(odrt))" + return LetBinding( + comments: ["Image `\(fullname)`."], + isStatic: true, + name: SwiftIdentifier(name: name), + valueCodeString: code + ) + } +} diff --git a/Sources/RswiftGenerators/ImageResource+Generator.swift b/Sources/RswiftGenerators/ImageResource+Generator.swift deleted file mode 100644 index ecdce6fc..00000000 --- a/Sources/RswiftGenerators/ImageResource+Generator.swift +++ /dev/null @@ -1,75 +0,0 @@ -// -// ImageResource+Generator.swift -// -// -// Created by Tom Lokhorst on 2022-06-24. -// - -import Foundation -import RswiftResources - -extension ImageResource { - public static func generateStruct(catalogs: [AssetCatalog], resources: [ImageResource], prefix: SwiftIdentifier) -> Struct { - // Multiple resources can share same name, - // for example: Colors.jpg and Colors@2x.jpg are both named "Colors.jpg" - // Deduplicate these - let namedResources = Dictionary(grouping: resources, by: \.name).values.map(\.first!) - - var merged: AssetCatalog.Namespace = catalogs.map(\.root).reduce(.init(), { $0.merging($1) }) - merged.images += namedResources - - return generateStruct(namespace: merged, name: SwiftIdentifier(name: "image"), prefix: prefix) - } - - public static func generateStruct(namespace: AssetCatalog.Namespace, name: SwiftIdentifier, prefix: SwiftIdentifier) -> Struct { - let structName = name - let qualifiedName = prefix + structName - let warning: (String) -> Void = { print("warning:", $0) } - - let groupedResources = namespace.images.grouped(bySwiftIdentifier: { $0.name }) - groupedResources.reportWarningsForDuplicatesAndEmpties(source: "image", result: "image", warning: warning) - - let letbindings = groupedResources.uniques.map { $0.generateLetBinding() } - let otherIdentifiers = groupedResources.uniques.map { SwiftIdentifier(name: $0.name) } - - let mergedNamespaces = AssetCatalogMergedNamespaces(all: namespace.subnamespaces, otherIdentifiers: otherIdentifiers) - mergedNamespaces.printWarningsForDuplicates(result: "image", warning: warning) - - let structs = mergedNamespaces.namespaces - .sorted { $0.key < $1.key } - .map { (name, namespace) in - ImageResource.generateStruct( - namespace: namespace, - name: name, - prefix: qualifiedName - ) - } - .filter { !$0.isEmpty } - - let comment = [ - "This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) images", - structs.isEmpty ? "" : ", and \(structs.count) namespaces", - "." - ].joined() - - let comments = [comment] - return Struct(comments: comments, name: structName) { - letbindings - structs - } - } -} - -extension ImageResource { - func generateLetBinding() -> LetBinding { - let locs = locale.map { $0.codeString() } ?? "nil" - let odrt = onDemandResourceTags?.debugDescription ?? "nil" - let fullname = (path + [name]).joined(separator: "/") - let code = "ImageResource(name: \"\(fullname)\", locale: \(locs), onDemandResourceTags: \(odrt))" - return LetBinding( - comments: ["Image `\(fullname)`."], - isStatic: true, - name: SwiftIdentifier(name: name), - valueCodeString: code) - } -} diff --git a/Sources/RswiftParsers/AssetCatalog+Parser.swift b/Sources/RswiftParsers/AssetCatalog+Parser.swift index ceb5bf4f..b58d7bdd 100644 --- a/Sources/RswiftParsers/AssetCatalog+Parser.swift +++ b/Sources/RswiftParsers/AssetCatalog+Parser.swift @@ -118,7 +118,7 @@ extension AssetCatalog: SupportedExtensions { images.append(.init(name: name, path: path, locale: nil, onDemandResourceTags: tags)) } - var dataAssets: [DataAssetResource] = [] + var dataAssets: [DataResource] = [] for fileURL in directory.dataAssets { let name = fileURL.filenameWithoutExtension! let tags = parseOnDemandResourceTags(directory: fileURL) diff --git a/Sources/RswiftResources/AssetCatalog.swift b/Sources/RswiftResources/AssetCatalog.swift index 06f14c5f..6e80f7c8 100644 --- a/Sources/RswiftResources/AssetCatalog.swift +++ b/Sources/RswiftResources/AssetCatalog.swift @@ -22,7 +22,7 @@ extension AssetCatalog { public var subnamespaces: [String: Namespace] = [:] public var colors: [ColorResource] = [] public var images: [ImageResource] = [] - public var dataAssets: [DataAssetResource] = [] + public var dataAssets: [DataResource] = [] public init() { } @@ -31,7 +31,7 @@ extension AssetCatalog { subnamespaces: [String: Namespace], colors: [ColorResource], images: [ImageResource], - dataAssets: [DataAssetResource] + dataAssets: [DataResource] ) { self.subnamespaces = subnamespaces self.colors = colors diff --git a/Sources/RswiftResources/DataAssetResource.swift b/Sources/RswiftResources/DataResource.swift similarity index 87% rename from Sources/RswiftResources/DataAssetResource.swift rename to Sources/RswiftResources/DataResource.swift index 6ab9a181..b19e2926 100644 --- a/Sources/RswiftResources/DataAssetResource.swift +++ b/Sources/RswiftResources/DataResource.swift @@ -1,5 +1,5 @@ // -// DataAsset.swift +// DataResource.swift // // // Created by Tom Lokhorst on 2022-07-23. @@ -7,7 +7,7 @@ import Foundation -public struct DataAssetResource { +public struct DataResource { public let name: String public let path: [String] public let onDemandResourceTags: [String]? From e67b62edadf15aa99ea464790b7f7920f647d766 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sat, 23 Jul 2022 11:23:19 +0200 Subject: [PATCH 040/161] Work on file generator --- Sources/RswiftCore/RswiftCore.swift | 26 ++++++++------ .../FileResource+Generator.swift | 34 +++++++++++++++++-- .../FontResource+Generator.swift | 20 ++++++----- .../RswiftParsers/FileResource+Parser.swift | 2 -- 4 files changed, 59 insertions(+), 23 deletions(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index c519c787..5f5693ae 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -67,14 +67,14 @@ public struct RswiftCore { .filter { !ignoreFile.matches(url: $0) } let start = Date() - // let items = try urls - // .filter { ImageResource.supportedExtensions.contains($0.pathExtension) } - //// .filter { !FileResource.unsupportedExtensions.contains($0.pathExtension) } - // .map { try ImageResource.parse(url: $0, assetTags: nil) } - // for item in items { - //// print(">>>", item) - // print(item.generateResourceLetCodeString()) - // } + + let dontGenerateFileForFonts = false + let dontGenerateFileForImages = false + let files = try urls + .filter { !FileResource.unsupportedExtensions.contains($0.pathExtension) } + .filter { !(dontGenerateFileForFonts && FontResource.supportedExtensions.contains($0.pathExtension)) } + .filter { !(dontGenerateFileForImages && ImageResource.supportedExtensions.contains($0.pathExtension)) } + .map { try FileResource.parse(url: $0) } let storyboards = try urls .filter { StoryboardResource.supportedExtensions.contains($0.pathExtension) } @@ -106,8 +106,13 @@ public struct RswiftCore { // catalogs: assetCatalogs, // prefix: qualifiedName // ) - let dataStruct = DataResource.generateStruct( - catalogs: assetCatalogs, +// let dataStruct = DataResource.generateStruct( +// catalogs: assetCatalogs, +// prefix: qualifiedName +// ) + + let fileStruct = FileResource.generateStruct( + resources: files, prefix: qualifiedName ) @@ -121,6 +126,7 @@ public struct RswiftCore { // dataStruct // fontStruct // segueStruct + fileStruct // storyboardStruct } diff --git a/Sources/RswiftGenerators/FileResource+Generator.swift b/Sources/RswiftGenerators/FileResource+Generator.swift index 681b39ad..73b65bc9 100644 --- a/Sources/RswiftGenerators/FileResource+Generator.swift +++ b/Sources/RswiftGenerators/FileResource+Generator.swift @@ -9,7 +9,37 @@ import Foundation import RswiftResources extension FileResource { - public func generateResourceLetCodeString() -> String { - "let \(SwiftIdentifier(name: self.fullname).value) = \(self)" + public static func generateStruct(resources: [FileResource], prefix: SwiftIdentifier) -> Struct { + let structName = SwiftIdentifier(name: "file") + let qualifiedName = prefix + structName + let warning: (String) -> Void = { print("warning:", $0) } + + let localized = Dictionary(grouping: resources, by: \.fullname) + let groupedLocalized = localized.grouped(bySwiftIdentifier: { $0.0 }) + + groupedLocalized.reportWarningsForDuplicatesAndEmpties(source: "resource file", result: "file", warning: warning) + + // For resource files, the contents of the different locales don't matter, so we just use the first one + let firstLocales = groupedLocalized.uniques.map { $0.1.first! } + let letbindings = firstLocales.map { $0.generateLetBinding() } +// .sorted { $0.name < $1.name } + + let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) resource files."] + + return Struct(comments: comments, name: structName) { + letbindings + } + } +} + +extension FileResource { + func generateLetBinding() -> LetBinding { + let code = "FileResource(name: \"\(name)\", filename: \"\(fullname)\")" + return LetBinding( + comments: ["Resource file `\(fullname)`."], + isStatic: true, + name: SwiftIdentifier(name: fullname), + valueCodeString: code + ) } } diff --git a/Sources/RswiftGenerators/FontResource+Generator.swift b/Sources/RswiftGenerators/FontResource+Generator.swift index 0993cda3..03d64ffb 100644 --- a/Sources/RswiftGenerators/FontResource+Generator.swift +++ b/Sources/RswiftGenerators/FontResource+Generator.swift @@ -9,15 +9,6 @@ import Foundation import RswiftResources extension FontResource { - func generateLetBinding() -> LetBinding { - let code = "FontResource(name: \"\(name)\", filename: \"\(filename)\")" - return LetBinding( - comments: ["Font `\(name)`."], - isStatic: true, - name: SwiftIdentifier(name: name), - valueCodeString: code) - } - public static func generateStruct(resources: [FontResource], prefix: SwiftIdentifier) -> Struct { let structName = SwiftIdentifier(name: "font") let qualifiedName = prefix + structName @@ -35,3 +26,14 @@ extension FontResource { } } } + +extension FontResource { + func generateLetBinding() -> LetBinding { + let code = "FontResource(name: \"\(name)\", filename: \"\(filename)\")" + return LetBinding( + comments: ["Font `\(name)`."], + isStatic: true, + name: SwiftIdentifier(name: name), + valueCodeString: code) + } +} diff --git a/Sources/RswiftParsers/FileResource+Parser.swift b/Sources/RswiftParsers/FileResource+Parser.swift index 95f5e56b..617dc3b8 100644 --- a/Sources/RswiftParsers/FileResource+Parser.swift +++ b/Sources/RswiftParsers/FileResource+Parser.swift @@ -14,8 +14,6 @@ extension FileResource { // These are all extensions of resources that are passed to some special compiler step and not directly available at runtime static public let unsupportedExtensions: Set = [ AssetCatalog.supportedExtensions, - FontResource.supportedExtensions, - ImageResource.supportedExtensions, LocalizableStrings.supportedExtensions, NibResource.supportedExtensions, StoryboardResource.supportedExtensions, From 103360de6bd99edf688ee7257a6a02b86ae2cdb5 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sat, 23 Jul 2022 11:33:33 +0200 Subject: [PATCH 041/161] Cleanup --- Sources/RswiftGenerators/AssetCatalog+Generator.swift | 10 +++------- Sources/RswiftGenerators/SwiftSyntax/Struct.swift | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Sources/RswiftGenerators/AssetCatalog+Generator.swift b/Sources/RswiftGenerators/AssetCatalog+Generator.swift index 6cc28cda..12d411ec 100644 --- a/Sources/RswiftGenerators/AssetCatalog+Generator.swift +++ b/Sources/RswiftGenerators/AssetCatalog+Generator.swift @@ -13,10 +13,6 @@ public protocol AssetCatalogContent { func generateLetBinding() -> LetBinding } -extension DataResource: AssetCatalogContent {} -extension ColorResource: AssetCatalogContent {} -extension ImageResource: AssetCatalogContent {} - extension ColorResource { public static func generateStruct(catalogs: [AssetCatalog], prefix: SwiftIdentifier) -> Struct { let merged: AssetCatalog.Namespace = catalogs.map(\.root).reduce(.init(), { $0.merging($1) }) @@ -88,7 +84,7 @@ extension AssetCatalog.Namespace { } } -extension ColorResource { +extension ColorResource: AssetCatalogContent { public func generateLetBinding() -> LetBinding { let fullname = (path + [name]).joined(separator: "/") let code = "ColorResource(name: \"\(fullname)\")" @@ -101,7 +97,7 @@ extension ColorResource { } } -extension DataResource { +extension DataResource: AssetCatalogContent { public func generateLetBinding() -> LetBinding { let fullname = (path + [name]).joined(separator: "/") let code = "DataResource(name: \"\(fullname)\")" @@ -114,7 +110,7 @@ extension DataResource { } } -extension ImageResource { +extension ImageResource: AssetCatalogContent { public func generateLetBinding() -> LetBinding { let locs = locale.map { $0.codeString() } ?? "nil" let odrt = onDemandResourceTags?.debugDescription ?? "nil" diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index 6d3c8440..b45aa9f5 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -56,7 +56,7 @@ public struct LetBinding { typeReference == nil ? name.value : "\(name.value):", typeReference?.rawName ] - if let valueCodeString { + if let valueCodeString = valueCodeString { words.append("=") words.append(valueCodeString) } From 8c3940bed6c69a394b06952159d74080247d7d8f Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sat, 23 Jul 2022 15:42:27 +0200 Subject: [PATCH 042/161] Add accessibility identifier generator --- Sources/RswiftCore/RswiftCore.swift | 20 ++++-- .../AccessibilityIdentifier+Generator.swift | 68 +++++++++++++++++++ .../AssetCatalog+Generator.swift | 2 +- 3 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 Sources/RswiftGenerators/AccessibilityIdentifier+Generator.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 5f5693ae..8ae99969 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -80,6 +80,10 @@ public struct RswiftCore { .filter { StoryboardResource.supportedExtensions.contains($0.pathExtension) } .map { try StoryboardResource.parse(url: $0) } + let nibs = try urls + .filter { NibResource.supportedExtensions.contains($0.pathExtension) } + .map { try NibResource.parse(url: $0) } + let fonts = try urls .filter { FontResource.supportedExtensions.contains($0.pathExtension) } .map { try FontResource.parse(url: $0) } @@ -99,7 +103,7 @@ public struct RswiftCore { // let imageStruct = ImageResource.generateStruct( // catalogs: assetCatalogs, -// resources: images, +// toplevel: images, // prefix: qualifiedName // ) // let colorStruct = ColorResource.generateStruct( @@ -111,10 +115,12 @@ public struct RswiftCore { // prefix: qualifiedName // ) - let fileStruct = FileResource.generateStruct( - resources: files, - prefix: qualifiedName - ) +// let fileStruct = FileResource.generateStruct( +// resources: files, +// prefix: qualifiedName +// ) + + let idStruct = AccessibilityIdentifier.generateStruct(nibs: nibs, storyboards: storyboards, prefix: qualifiedName) // let fontStruct = FontResource.generateStruct(resources: fonts, prefix: qualifiedName) @@ -126,7 +132,9 @@ public struct RswiftCore { // dataStruct // fontStruct // segueStruct - fileStruct +// fileStruct + + idStruct // storyboardStruct } diff --git a/Sources/RswiftGenerators/AccessibilityIdentifier+Generator.swift b/Sources/RswiftGenerators/AccessibilityIdentifier+Generator.swift new file mode 100644 index 00000000..8362b650 --- /dev/null +++ b/Sources/RswiftGenerators/AccessibilityIdentifier+Generator.swift @@ -0,0 +1,68 @@ +// +// AccessibilityIdentifier+Generator.swift +// +// +// Created by Tom Lokhorst on 2022-07-23. +// + +import Foundation +import RswiftResources + +private protocol AccessibilityIdentifierContainer { + var name: String { get } + var usedAccessibilityIdentifiers: [String] { get } +} + +extension NibResource: AccessibilityIdentifierContainer {} +extension StoryboardResource: AccessibilityIdentifierContainer {} + +public struct AccessibilityIdentifier { + public static func generateStruct(nibs: [NibResource], storyboards: [StoryboardResource], prefix: SwiftIdentifier) -> Struct { + let structName = SwiftIdentifier(name: "id") + let qualifiedName = prefix + structName + +// let warning: (String) -> Void = { print("warning:", $0) } + + let containers: [AccessibilityIdentifierContainer] = nibs + storyboards + let mergedContainers = Dictionary(grouping: containers, by: \.name) + .mapValues { $0.flatMap(\.usedAccessibilityIdentifiers) } + .filter { $0.value.count > 0 } + + let structs = mergedContainers + .map { (name, ids) in + generateStruct( + viewControllerName: name, + usedAccessibilityIdentifiers: ids, + prefix: qualifiedName + ) + } + .sorted { $0.name < $1.name } + + let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(structs.count) accessibility identifiers."] + + return Struct(comments: comments, name: structName) { + structs + } + } + + static func generateStruct(viewControllerName: String, usedAccessibilityIdentifiers: [String], prefix: SwiftIdentifier) -> Struct { + let structName = SwiftIdentifier(name: viewControllerName) + let qualifiedName = prefix + structName + + let letbindings = usedAccessibilityIdentifiers + .map { id in + LetBinding( + comments: ["Accessibility identifier `\(id)`."], + isStatic: true, + name: SwiftIdentifier(name: id), + valueCodeString: "\"\(id)\"" + ) + } + + let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) accessibility identifiers."] + + return Struct(comments: comments, name: structName) { + letbindings + } + } +} diff --git a/Sources/RswiftGenerators/AssetCatalog+Generator.swift b/Sources/RswiftGenerators/AssetCatalog+Generator.swift index 12d411ec..f0f3d64d 100644 --- a/Sources/RswiftGenerators/AssetCatalog+Generator.swift +++ b/Sources/RswiftGenerators/AssetCatalog+Generator.swift @@ -30,7 +30,7 @@ extension DataResource { } extension ImageResource { - public static func generateStruct(catalogs: [AssetCatalog], resources: [ImageResource], prefix: SwiftIdentifier) -> Struct { + public static func generateStruct(catalogs: [AssetCatalog], toplevel resources: [ImageResource], prefix: SwiftIdentifier) -> Struct { // Multiple resources can share same name, // for example: Colors.jpg and Colors@2x.jpg are both named "Colors.jpg" // Deduplicate these From ac015a94d42e7ef75db24f3056f9eaac0a5d7956 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 24 Jul 2022 11:04:12 +0200 Subject: [PATCH 043/161] Add plist generator --- Sources/RswiftCore/RswiftCore.swift | 49 ++++++-- .../PropertyListResource+Generator.swift | 115 ++++++++++++++++++ .../RswiftGenerators/SwiftSyntax/Struct.swift | 50 ++++++-- 3 files changed, 193 insertions(+), 21 deletions(-) create mode 100644 Sources/RswiftGenerators/PropertyListResource+Generator.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 8ae99969..20ac0843 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -96,6 +96,14 @@ public struct RswiftCore { .map { try AssetCatalog.parse(url: $0) } + let infoplist = URL(fileURLWithPath: "/Users/tom/Projects/R.swift/Examples/ResourceApp/ResourceApp/Info.plist") + let entitlementsURL = URL(fileURLWithPath: "/Users/tom/Projects/R.swift/Examples/ResourceApp/ResourceApp/ResourceApp.entitlements") + let plist = try PropertyListResource.parse(url: infoplist, buildConfigurationName: "ResourceApp") + let infoPlistWhitelist = ["UIApplicationShortcutItems", "UIApplicationSceneManifest", "NSUserActivityTypes", "NSExtension"] + +// let entitlements = try PropertyListResource.parse(url: entitlementsURL, buildConfigurationName: "ResourceApp") + + let structName = SwiftIdentifier(rawValue: "R") let qualifiedName = structName @@ -120,23 +128,38 @@ public struct RswiftCore { // prefix: qualifiedName // ) - let idStruct = AccessibilityIdentifier.generateStruct(nibs: nibs, storyboards: storyboards, prefix: qualifiedName) - -// let fontStruct = FontResource.generateStruct(resources: fonts, prefix: qualifiedName) +// let idStruct = AccessibilityIdentifier.generateStruct( +// nibs: nibs, +// storyboards: storyboards, +// prefix: qualifiedName +// ) -// let storyboardStruct = StoryboardResource.generateStruct(storyboards: storyboards, prefix: qualifiedName) +// let fontStruct = FontResource.generateStruct( +// resources: fonts, +// prefix: qualifiedName +// ) - let s = Struct(name: structName) { -// imageStruct -// colorStruct -// dataStruct -// fontStruct -// segueStruct -// fileStruct +// let storyboardStruct = StoryboardResource.generateStruct( +// storyboards: storyboards, +// prefix: qualifiedName +// ) - idStruct + let infoStruct = PropertyListResource.generateStruct( + resourceName: "info", + plists: [plist], + toplevelKeysWhitelist: infoPlistWhitelist, + prefix: qualifiedName + ) + +// let entitlementsStruct = PropertyListResource.generateStruct( +// resourceName: "entitlements", +// plists: [entitlements], +// toplevelKeysWhitelist: nil, +// prefix: qualifiedName +// ) -// storyboardStruct + let s = Struct(name: structName) { + infoStruct } print(s.prettyPrint()) diff --git a/Sources/RswiftGenerators/PropertyListResource+Generator.swift b/Sources/RswiftGenerators/PropertyListResource+Generator.swift new file mode 100644 index 00000000..b2738266 --- /dev/null +++ b/Sources/RswiftGenerators/PropertyListResource+Generator.swift @@ -0,0 +1,115 @@ +// +// PropertyListResource+Generator.swift +// +// +// Created by Tom Lokhorst on 2022-07-23. +// + +import Foundation +import RswiftResources + +extension PropertyListResource { + public static func generateStruct(resourceName: String, plists: [PropertyListResource], toplevelKeysWhitelist: [String]?, prefix: SwiftIdentifier) -> Struct { + let structName = SwiftIdentifier(name: resourceName) + let qualifiedName = prefix + structName + let warning: (String) -> Void = { print("warning:", $0) } + + guard let plist = plists.first else { return .empty } + + guard plists.allSatisfy({ $0.url == plist.url }) else { + let configs = plists.map { $0.buildConfigurationName } + warning("Build configurations \(configs) use different \(resourceName) files, this is not yet supported") + return .empty + } + + let contents: PropertyListResource.Contents + if let whitelist = toplevelKeysWhitelist { + contents = plist.contents.filter { (key, _) in whitelist.contains(key) } + } else { + contents = plist.contents + } + + let members = contents.generateMembers(resourceName: resourceName, path: [], warning: warning) + .sorted() + let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(members.structs.count) properties."] + + return Struct(comments: comments, name: structName) { + members + } + } +} + +extension PropertyListResource.Contents { + @StructMembersBuilder func generateMembers(resourceName: String, path: [String], warning: (String) -> Void) -> StructMembers { + let groupedContents = self.grouped(bySwiftIdentifier: { $0.key }) + groupedContents.reportWarningsForDuplicatesAndEmpties(source: resourceName, result: resourceName, warning: warning) + + for (key, value) in groupedContents.uniques { + let newPath = path + [key] + + switch value { + case let value as Bool: + LetBinding( + isStatic: true, + name: SwiftIdentifier(name: key), + valueCodeString: "\(value)" + ) + + case let value as String: + let keyPath = (path + [key.escapedStringLiteral]).joined(separator: ".") + LetBinding( + isStatic: true, + name: SwiftIdentifier(name: key), + valueCodeString: "\"\(value.escapedStringLiteral)\"" // "NSDictionary().value(forKeyPath: \"\(keyPath)\") ?? \"\(value.escapedStringLiteral)\"" + ) + + case let duplicateArray as [String]: + let groupedArray = duplicateArray.grouped(bySwiftIdentifier: { $0 }) + groupedArray.reportWarningsForDuplicatesAndEmpties(source: resourceName, result: resourceName, warning: warning) + let dicts = Dictionary(groupedArray.uniques.map { ($0, $0) }, uniquingKeysWith: { l, r in l }) + + Struct(name: SwiftIdentifier(name: key)) { + dicts + .generateMembers(resourceName: resourceName, path: newPath, warning: warning) + .sorted() + } + + case var dict as [String: Any]: + dict["_key"] = key + + Struct(name: SwiftIdentifier(name: key)) { + dict.generateMembers(resourceName: resourceName, path: newPath, warning: warning) + .sorted() + } + + case let dicts as [[String: Any]] where arrayOfDictionariesPrimaryKeys.keys.contains(key): + + Struct(name: SwiftIdentifier(name: key)) { + for dict in dicts { + if let primaryKey = arrayOfDictionariesPrimaryKeys[key], + let primary = dict[primaryKey] as? String { + Struct(name: SwiftIdentifier(name: primary)) { + dict.generateMembers(resourceName: resourceName, path: path, warning: warning) + .sorted() + } + } + } + } + + default: + do {} + } + } + } + +} + +// For arrays of dictionaries we need a primary key. +// This key will be used as a name for the struct in the generated code. +private let arrayOfDictionariesPrimaryKeys: [String: String] = [ + "UIWindowSceneSessionRoleExternalDisplay": "UISceneConfigurationName", + "UIWindowSceneSessionRoleApplication": "UISceneConfigurationName", + "UIApplicationShortcutItems": "UIApplicationShortcutItemType", + "CFBundleDocumentTypes": "CFBundleTypeName", + "CFBundleURLTypes": "CFBundleURLName" +] diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index b45aa9f5..08f00471 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -69,32 +69,62 @@ public struct LetBinding { } -public typealias StructMembers = ([LetBinding], [Struct]) +public struct StructMembers { + var lets: [LetBinding] = [] + var structs: [Struct] = [] + + func sorted() -> StructMembers { + var new = self + new.lets.sort { $0.name < $1.name } + new.structs.sort { $0.name < $1.name } + return new + } +} @resultBuilder public struct StructMembersBuilder { public static func buildExpression(_ expression: LetBinding) -> StructMembers { - ([expression], []) + StructMembers(lets: [expression]) } public static func buildExpression(_ expressions: [LetBinding]) -> StructMembers { - (expressions, []) + StructMembers(lets: expressions) } public static func buildExpression(_ expression: Struct) -> StructMembers { - ([], [expression]) + StructMembers(structs: [expression]) } public static func buildExpression(_ expressions: [Struct]) -> StructMembers { - ([], expressions) + StructMembers(structs: expressions) + } + + public static func buildExpression(_ members: StructMembers) -> StructMembers { + members + } + + public static func buildExpression(_ members: Void) -> StructMembers { + StructMembers() } public static func buildArray(_ members: [StructMembers]) -> StructMembers { - (members.flatMap(\.0), members.flatMap(\.1)) + StructMembers(lets: members.flatMap(\.lets), structs: members.flatMap(\.structs)) + } + + public static func buildEither(first component: StructMembers) -> StructMembers { + component + } + + public static func buildEither(second component: StructMembers) -> StructMembers { + component + } + + public static func buildOptional(_ component: StructMembers?) -> StructMembers { + component ?? StructMembers() } public static func buildBlock(_ members: StructMembers...) -> StructMembers { - (members.flatMap(\.0), members.flatMap(\.1)) + StructMembers(lets: members.flatMap(\.lets), structs: members.flatMap(\.structs)) } } @@ -108,6 +138,8 @@ public struct Struct { public var isEmpty: Bool { lets.isEmpty && structs.isEmpty } + public static var empty: Struct = Struct(name: SwiftIdentifier(name: "empty"), membersBuilder: {}) + public init( comments: [String] = [], accessControl: AccessControl = AccessControl.none, @@ -119,7 +151,9 @@ public struct Struct { self.accessControl = accessControl self.name = name self.protocols = protocols - (self.lets, self.structs) = membersBuilder() + let members = membersBuilder() + self.lets = members.lets + self.structs = members.structs } public func prettyPrint() -> String { From 28aa8758038024918f709e016c34f0cb6ff08e81 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 24 Jul 2022 12:18:10 +0200 Subject: [PATCH 044/161] Add reuse identifier generator --- Sources/RswiftCore/RswiftCore.swift | 20 ++++--- .../ReuseIdentifier+Generator.swift | 52 +++++++++++++++++++ .../RswiftGenerators/Segue+Generator.swift | 10 ++-- .../Shared/TypeReference+Generator.swift | 28 ++++++++++ .../Storyboard+Generator.swift | 19 ++++--- .../StoryboardIdentifier.swift | 8 +++ 6 files changed, 120 insertions(+), 17 deletions(-) create mode 100644 Sources/RswiftGenerators/ReuseIdentifier+Generator.swift create mode 100644 Sources/RswiftGenerators/Shared/TypeReference+Generator.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 20ac0843..9e01c4e2 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -144,12 +144,12 @@ public struct RswiftCore { // prefix: qualifiedName // ) - let infoStruct = PropertyListResource.generateStruct( - resourceName: "info", - plists: [plist], - toplevelKeysWhitelist: infoPlistWhitelist, - prefix: qualifiedName - ) +// let infoStruct = PropertyListResource.generateStruct( +// resourceName: "info", +// plists: [plist], +// toplevelKeysWhitelist: infoPlistWhitelist, +// prefix: qualifiedName +// ) // let entitlementsStruct = PropertyListResource.generateStruct( // resourceName: "entitlements", @@ -158,8 +158,14 @@ public struct RswiftCore { // prefix: qualifiedName // ) + let reuseIdentifierStruct = Reusable.generateStruct( + nibs: nibs, + storyboards: storyboards, + prefix: qualifiedName + ) + let s = Struct(name: structName) { - infoStruct + reuseIdentifierStruct } print(s.prettyPrint()) diff --git a/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift b/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift new file mode 100644 index 00000000..cdab5ad3 --- /dev/null +++ b/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift @@ -0,0 +1,52 @@ +// +// ReuseIdentifier+Generator.swift +// +// +// Created by Tom Lokhorst on 2022-07-24. +// + +import Foundation +import RswiftResources + +extension Reusable { + public static func generateStruct(nibs: [NibResource], storyboards: [StoryboardResource], prefix: SwiftIdentifier) -> Struct { + let structName = SwiftIdentifier(name: "reuseableIdentifier") + let qualifiedName = prefix + structName + + let warning: (String) -> Void = { print("warning:", $0) } + + let reusables = nibs.flatMap(\.reusables) + storyboards.flatMap(\.reusables) + let deduplicatedReusables = Dictionary(grouping: reusables, by: \.hashValue) + .values.compactMap(\.first) + + let groupedReusables = deduplicatedReusables.grouped(bySwiftIdentifier: \.identifier) + groupedReusables.reportWarningsForDuplicatesAndEmpties(source: "reuseIdentifier", result: "reuse identifier", warning: warning) + + let letbindings = groupedReusables.uniques + .map { $0.generateLetBinding() } + .sorted { $0.name < $1.name } + + let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) reuse identifiers."] + + return Struct(comments: comments, name: structName) { + letbindings + } + } + +} + +extension Reusable { + var genericTypeReference: TypeReference { + TypeReference(module: .rswift, name: "ReusableIdentifier", genericArgs: [type]) + } + + func generateLetBinding() -> LetBinding { + LetBinding( + comments: ["Reuseable identifier `\(identifier)`."], + isStatic: true, + name: SwiftIdentifier(name: identifier), + typeReference: genericTypeReference, + valueCodeString: "ReuseableIdentifier(identifier: \"\(identifier)\")" + ) + } +} diff --git a/Sources/RswiftGenerators/Segue+Generator.swift b/Sources/RswiftGenerators/Segue+Generator.swift index 1058c5c1..fd7ae0fe 100644 --- a/Sources/RswiftGenerators/Segue+Generator.swift +++ b/Sources/RswiftGenerators/Segue+Generator.swift @@ -86,7 +86,7 @@ public struct Segue { } private static func resolveDestinationType(for segue: StoryboardResource.Segue, inViewController: StoryboardResource.ViewController, inStoryboard storyboard: StoryboardResource, allStoryboards storyboards: [StoryboardResource]) -> TypeReference? { - let uiViewController = TypeReference(module: .custom(name: "UIKit"), rawName: "UIViewController") + let uiViewController = TypeReference(module: .uiKit, rawName: "UIViewController") if segue.kind == "unwind" { return uiViewController @@ -157,12 +157,14 @@ struct SegueWithInfo { var genericTypeReference: TypeReference { TypeReference( - module: .host, - rawName: "SegueIdentifier<\(segue.type.rawName), \(sourceType.rawName), \(destinationType.rawName)>") + module: .rswift, + name: "SegueIdentifier", + genericArgs: [segue.type, sourceType, destinationType] + ) } func generateLetBinding() -> LetBinding { - return LetBinding( + LetBinding( comments: ["Segue identifier `\(segue.identifier)`."], isStatic: true, name: SwiftIdentifier(name: segue.identifier), diff --git a/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift b/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift new file mode 100644 index 00000000..5d269de6 --- /dev/null +++ b/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift @@ -0,0 +1,28 @@ +// +// File.swift +// +// +// Created by Tom Lokhorst on 2022-07-24. +// + +import Foundation +import RswiftResources + +extension TypeReference { + init(module: ModuleReference, name: String, genericArgs: [TypeReference]) { + let args = genericArgs.map { $0.codeString() }.joined(separator: ", ") + let rawName = args.isEmpty ? name : "\(name)<\(args)>" + + self.init(module: module, rawName: rawName) + } +} + +extension TypeReference { + func codeString() -> String { + if case .custom(let module) = module { + return "\(module).\(rawName)" + } else { + return rawName + } + } +} diff --git a/Sources/RswiftGenerators/Storyboard+Generator.swift b/Sources/RswiftGenerators/Storyboard+Generator.swift index 9602efa8..ed463068 100644 --- a/Sources/RswiftGenerators/Storyboard+Generator.swift +++ b/Sources/RswiftGenerators/Storyboard+Generator.swift @@ -63,7 +63,7 @@ extension StoryboardResource { valueCodeString: "\"\(name)\"") let identifier = SwiftIdentifier(name: name) - let storyboardIdentifier = TypeReference(module: .host, rawName: "StoryboardIdentifier") + let storyboardIdentifier = TypeReference(module: .rswift, rawName: "StoryboardIdentifier") return Struct( comments: ["Storyboard `\(name)`."], @@ -79,13 +79,20 @@ extension StoryboardResource { extension StoryboardResource.ViewController { + var genericTypeReference: TypeReference { + TypeReference( + module: .rswift, + name: "ViewControllerIdentifier", + genericArgs: [self.type] + ) + } + func generateLetBinding(identifier: String) -> LetBinding { - let type = TypeReference(module: .host, rawName: "ViewControllerIdentifier<\(self.type.rawName)>") - let code = #"ViewControllerIdentifier(identifier: "\#(identifier)")"# - return LetBinding( + LetBinding( isStatic: true, name: SwiftIdentifier(name: identifier), - typeReference: type, - valueCodeString: code) + typeReference: genericTypeReference, + valueCodeString: #"ViewControllerIdentifier(identifier: "\#(identifier)")"# + ) } } diff --git a/Sources/RswiftResources/StoryboardIdentifier.swift b/Sources/RswiftResources/StoryboardIdentifier.swift index 2ad75dba..bf6de2cc 100644 --- a/Sources/RswiftResources/StoryboardIdentifier.swift +++ b/Sources/RswiftResources/StoryboardIdentifier.swift @@ -19,6 +19,14 @@ public struct ViewControllerIdentifier { } } +public struct ReuseIdentifier { + public let identifier: String + + public init(identifier: String) { + self.identifier = identifier + } +} + public struct SegueIdentifier { public let identifier: String From 6cf0da95e087a4c9c4a28030971fe6e4e988094e Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 24 Jul 2022 13:56:23 +0200 Subject: [PATCH 045/161] Add nib generator --- Sources/RswiftCore/RswiftCore.swift | 11 +++-- .../FileResource+Generator.swift | 12 +++--- Sources/RswiftGenerators/Nib+Generator.swift | 43 ++++++++++++++++++- .../RswiftGenerators/Segue+Generator.swift | 2 +- .../Shared/TypeReference+Generator.swift | 3 ++ .../StoryboardIdentifier.swift | 14 ++++-- 6 files changed, 70 insertions(+), 15 deletions(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 9e01c4e2..b2234cb6 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -158,14 +158,19 @@ public struct RswiftCore { // prefix: qualifiedName // ) - let reuseIdentifierStruct = Reusable.generateStruct( +// let reuseIdentifierStruct = Reusable.generateStruct( +// nibs: nibs, +// storyboards: storyboards, +// prefix: qualifiedName +// ) + + let nibStruct = NibResource.generateStruct( nibs: nibs, - storyboards: storyboards, prefix: qualifiedName ) let s = Struct(name: structName) { - reuseIdentifierStruct + nibStruct } print(s.prettyPrint()) diff --git a/Sources/RswiftGenerators/FileResource+Generator.swift b/Sources/RswiftGenerators/FileResource+Generator.swift index 73b65bc9..ed0d318a 100644 --- a/Sources/RswiftGenerators/FileResource+Generator.swift +++ b/Sources/RswiftGenerators/FileResource+Generator.swift @@ -14,14 +14,14 @@ extension FileResource { let qualifiedName = prefix + structName let warning: (String) -> Void = { print("warning:", $0) } - let localized = Dictionary(grouping: resources, by: \.fullname) - let groupedLocalized = localized.grouped(bySwiftIdentifier: { $0.0 }) + // For resource files, the contents of the different locales don't matter, so we just use the first one + let firstLocales = Dictionary(grouping: resources, by: \.fullname) + .values.map(\.first!) + let groupedFiles = firstLocales.grouped(bySwiftIdentifier: \.fullname) - groupedLocalized.reportWarningsForDuplicatesAndEmpties(source: "resource file", result: "file", warning: warning) + groupedFiles.reportWarningsForDuplicatesAndEmpties(source: "resource file", result: "file", warning: warning) - // For resource files, the contents of the different locales don't matter, so we just use the first one - let firstLocales = groupedLocalized.uniques.map { $0.1.first! } - let letbindings = firstLocales.map { $0.generateLetBinding() } + let letbindings = groupedFiles.uniques.map { $0.generateLetBinding() } // .sorted { $0.name < $1.name } let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) resource files."] diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift index c7ae0f56..2cdf6380 100644 --- a/Sources/RswiftGenerators/Nib+Generator.swift +++ b/Sources/RswiftGenerators/Nib+Generator.swift @@ -9,8 +9,47 @@ import Foundation import RswiftResources extension NibResource { - public func generateResourceLetCodeString() -> String { - "static let \(SwiftIdentifier(name: self.name).value) = \(self)" + public static func generateStruct(nibs: [NibResource], prefix: SwiftIdentifier) -> Struct { + let structName = SwiftIdentifier(name: "nib") + let qualifiedName = prefix + structName + + let warning: (String) -> Void = { print("warning:", $0) } + + // TODO: Generate warnings for mismatched identifier/root view in different locales +// let firstLocales = Dictionary(grouping: nibs, by: \.name) +// .values.map(\.first!) + let groupedNibs = nibs.grouped(bySwiftIdentifier: \.name) + groupedNibs.reportWarningsForDuplicatesAndEmpties(source: "nib", result: "nib", warning: warning) + + let letbindings = groupedNibs.uniques + .map { $0.generateLetBinding() } + .sorted { $0.name < $1.name } + + let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) nibs."] + + return Struct(comments: comments, name: structName) { + letbindings + } } + } +extension NibResource { + var genericTypeReference: TypeReference { + TypeReference( + module: .rswift, + name: "NibReference", + genericArgs: [rootViews.first ?? TypeReference.uiView] + ) + } + + func generateLetBinding() -> LetBinding { + LetBinding( + comments: ["Nib `\(name)`."], + isStatic: true, + name: SwiftIdentifier(name: name), + typeReference: genericTypeReference, + valueCodeString: "NibReference(name: \"\(name)\")" + ) + } +} diff --git a/Sources/RswiftGenerators/Segue+Generator.swift b/Sources/RswiftGenerators/Segue+Generator.swift index fd7ae0fe..3a2aed57 100644 --- a/Sources/RswiftGenerators/Segue+Generator.swift +++ b/Sources/RswiftGenerators/Segue+Generator.swift @@ -86,7 +86,7 @@ public struct Segue { } private static func resolveDestinationType(for segue: StoryboardResource.Segue, inViewController: StoryboardResource.ViewController, inStoryboard storyboard: StoryboardResource, allStoryboards storyboards: [StoryboardResource]) -> TypeReference? { - let uiViewController = TypeReference(module: .uiKit, rawName: "UIViewController") + let uiViewController = TypeReference.uiViewController if segue.kind == "unwind" { return uiViewController diff --git a/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift b/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift index 5d269de6..094e82f6 100644 --- a/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift +++ b/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift @@ -25,4 +25,7 @@ extension TypeReference { return rawName } } + + static var uiView: TypeReference = .init(module: .uiKit, rawName: "UIView") + static var uiViewController: TypeReference = .init(module: .uiKit, rawName: "UIViewController") } diff --git a/Sources/RswiftResources/StoryboardIdentifier.swift b/Sources/RswiftResources/StoryboardIdentifier.swift index bf6de2cc..a727c8d8 100644 --- a/Sources/RswiftResources/StoryboardIdentifier.swift +++ b/Sources/RswiftResources/StoryboardIdentifier.swift @@ -11,7 +11,15 @@ public protocol StoryboardIdentifier { static var identifier: String { get } } -public struct ViewControllerIdentifier { +public struct NibReference { + public let name: String + + public init(name: String) { + self.name = name + } +} + +public struct ReuseIdentifier { public let identifier: String public init(identifier: String) { @@ -19,7 +27,7 @@ public struct ViewControllerIdentifier { } } -public struct ReuseIdentifier { +public struct SegueIdentifier { public let identifier: String public init(identifier: String) { @@ -27,7 +35,7 @@ public struct ReuseIdentifier { } } -public struct SegueIdentifier { +public struct ViewControllerIdentifier { public let identifier: String public init(identifier: String) { From 69a77339255aa6539c900f0459933f5d16349f99 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 24 Jul 2022 22:11:41 +0200 Subject: [PATCH 046/161] Work on string generator --- Sources/RswiftCore/RswiftCore.swift | 16 +- .../LocalizableStrings+Generator.swift | 235 +++++++++++++++++- .../LocalizableStrings+Parser.swift | 21 +- Sources/RswiftParsers/Nib+Parser.swift | 14 +- ...ions.swift => FormatPart+Extensions.swift} | 72 +----- .../Shared/TypeReference+Extensions.swift | 33 +-- Sources/RswiftParsers/Shared/Xcodeproj.swift | 2 +- Sources/RswiftParsers/Storyboard+Parser.swift | 8 +- .../RswiftResources/LocalizableStrings.swift | 6 +- .../Shared/LocaleReference.swift | 11 + .../Shared/StringParam+Unifiable.swift | 70 ++++++ .../Shared/StringResource.swift | 20 ++ .../Shared/Unifiable.swift | 0 13 files changed, 381 insertions(+), 127 deletions(-) rename Sources/RswiftParsers/Shared/{StringParam+Extensions.swift => FormatPart+Extensions.swift} (80%) create mode 100644 Sources/RswiftResources/Shared/StringParam+Unifiable.swift create mode 100644 Sources/RswiftResources/Shared/StringResource.swift rename Sources/{RswiftParsers => RswiftResources}/Shared/Unifiable.swift (100%) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index b2234cb6..b20a0469 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -95,6 +95,10 @@ public struct RswiftCore { .filter { AssetCatalog.supportedExtensions.contains($0.pathExtension) } .map { try AssetCatalog.parse(url: $0) } + let localizableStrings = try urls + .filter { LocalizableStrings.supportedExtensions.contains($0.pathExtension) } + .map { try LocalizableStrings.parse(url: $0) } + let infoplist = URL(fileURLWithPath: "/Users/tom/Projects/R.swift/Examples/ResourceApp/ResourceApp/Info.plist") let entitlementsURL = URL(fileURLWithPath: "/Users/tom/Projects/R.swift/Examples/ResourceApp/ResourceApp/ResourceApp.entitlements") @@ -164,13 +168,19 @@ public struct RswiftCore { // prefix: qualifiedName // ) - let nibStruct = NibResource.generateStruct( - nibs: nibs, +// let nibStruct = NibResource.generateStruct( +// nibs: nibs, +// prefix: qualifiedName +// ) + + let stringStruct = LocalizableStrings.generateStruct( + resources: localizableStrings, + developmentLanguage: xcodeproj.developmentLanguage, prefix: qualifiedName ) let s = Struct(name: structName) { - nibStruct + stringStruct } print(s.prettyPrint()) diff --git a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift index ab8f9d85..82efe2ca 100644 --- a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift +++ b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift @@ -9,7 +9,238 @@ import Foundation import RswiftResources extension LocalizableStrings { - public func generateResourceLetCodeString() -> String { - "let \(SwiftIdentifier(name: self.filename).value) = \(self)" + public static func generateStruct(resources: [LocalizableStrings], developmentLanguage: String, prefix: SwiftIdentifier) -> Struct { + let structName = SwiftIdentifier(name: "string") + let qualifiedName = prefix + structName + let warning: (String) -> Void = { print("warning:", $0) } + + let localized = Dictionary(grouping: resources, by: \.filename) + let groupedLocalized = localized.grouped(bySwiftIdentifier: \.key) + + groupedLocalized.reportWarningsForDuplicatesAndEmpties(source: "strings file", result: "file", warning: warning) + + let structs = groupedLocalized.uniques + .compactMap { (filename, resources) -> Struct? in + generateStruct( + filename: filename, + resources: resources, + developmentLanguage: developmentLanguage, + prefix: qualifiedName, + warning: warning + ) + } + + let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(groupedLocalized.uniques.count) localization tables."] + + return Struct(comments: comments, name: structName) { + structs + } + } + + private static func generateStruct(filename: String, resources: [LocalizableStrings], developmentLanguage: String, prefix: SwiftIdentifier, warning: (String) -> Void) -> Struct? { + + let structName = SwiftIdentifier(name: filename) + let qualifiedName = prefix + structName + + let strings = computeStringsWithParams(filename: filename, resources: resources, developmentLanguage: developmentLanguage, warning: warning) + let letbindings = strings.map { $0.generateLetBinding() } + + let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) localization keys."] + + return Struct(comments: comments, name: structName) { + letbindings + } + } + + // Ahem, this code is a bit of a mess. It might need cleaning up... ;-) + private static func computeStringsWithParams(filename: String, resources: [LocalizableStrings], developmentLanguage: String, warning: (String) -> Void) -> [StringWithParams] { + + var allParams: [String: [(LocaleReference, String, [StringParam])]] = [:] + let primaryLanguage: String + let primaryKeys: Set? + let bases = resources.filter { $0.locale.isBase } + let developments = resources.filter { $0.locale.language == developmentLanguage } + + if !bases.isEmpty { + primaryKeys = Set(bases.flatMap { $0.dictionary.keys }) + primaryLanguage = "Base" + } else if !developments.isEmpty { + primaryKeys = Set(developments.flatMap { $0.dictionary.keys }) + primaryLanguage = developmentLanguage + } else { + primaryKeys = nil + primaryLanguage = developmentLanguage + } + + // Warnings about duplicates and empties + for ls in resources { + let filenameLocale = ls.locale.debugDescription(filename: filename) + let groupedKeys = ls.dictionary.keys.grouped(bySwiftIdentifier: { $0 }) + + groupedKeys.reportWarningsForDuplicatesAndEmpties(source: "string", container: "in \(filenameLocale)", result: "key", warning: warning) + + // Save uniques + for key in groupedKeys.uniques { + if let value = ls.dictionary[key] { + if let _ = allParams[key] { + allParams[key]?.append((ls.locale, value.originalValue, value.params)) + } + else { + allParams[key] = [(ls.locale, value.originalValue, value.params)] + } + } + } + } + + // Warnings about missing translations + for (locale, lss) in Dictionary(grouping: resources, by: \.locale) { + let filenameLocale = locale.debugDescription(filename: filename) + let sourceKeys = primaryKeys ?? Set(allParams.keys) + + let missing = sourceKeys.subtracting(lss.flatMap { $0.dictionary.keys }) + + if missing.isEmpty { + continue + } + + let paddedKeys = missing.sorted().map { "'\($0)'" } + let paddedKeysString = paddedKeys.joined(separator: ", ") + + warning("Strings file \(filenameLocale) is missing translations for keys: \(paddedKeysString)") + } + + // Warnings about extra translations + for (locale, lss) in Dictionary(grouping: resources, by: \.locale) { + let filenameLocale = locale.debugDescription(filename: filename) + let sourceKeys = primaryKeys ?? Set(allParams.keys) + + let usedKeys = Set(lss.flatMap { $0.dictionary.keys }) + let extra = usedKeys.subtracting(sourceKeys) + + if extra.isEmpty { + continue + } + + let paddedKeys = extra.sorted().map { "'\($0)'" } + let paddedKeysString = paddedKeys.joined(separator: ", ") + + warning("Strings file \(filenameLocale) has extra translations (not in \(primaryLanguage)) for keys: \(paddedKeysString)") + } + + // Only include translation if it exists in the primary language + func includeTranslation(_ key: String) -> Bool { + if let primaryKeys = primaryKeys { + return primaryKeys.contains(key) + } + + return true + } + + var results: [StringWithParams] = [] + var badFormatSpecifiersKeys = Set() + + let filteredSortedParams = allParams + .map { $0 } + .filter { includeTranslation($0.0) } + .sorted(by: { $0.0 < $1.0 }) + + // Unify format specifiers + for (key, keyParams) in filteredSortedParams { + var params: [StringParam] = [] + var areCorrectFormatSpecifiers = true + + for (locale, _, ps) in keyParams { + if ps.contains(where: { $0.spec == FormatSpecifier.topType }) { + let name = locale.debugDescription(filename: filename) + warning("Skipping string \(key) in \(name), not all format specifiers are consecutive") + + areCorrectFormatSpecifiers = false + } + } + + if !areCorrectFormatSpecifiers { continue } + + for (_, _, ps) in keyParams { + if let unified = params.unify(ps) { + params = unified + } + else { + badFormatSpecifiersKeys.insert(key) + + areCorrectFormatSpecifiers = false + } + } + + if !areCorrectFormatSpecifiers { continue } + + let vals = keyParams.map { ($0.0, $0.1) } + let values = StringWithParams(key: key, params: params, tableName: filename, values: vals, developmentLanguage: developmentLanguage) + results.append(values) + } + + for badKey in badFormatSpecifiersKeys.sorted() { + let fewParams = allParams.filter { $0.0 == badKey }.map { $0.1 } + + if let params = fewParams.first { + let locales = params.compactMap { $0.0.localeDescription }.joined(separator: ", ") + warning("Skipping string for key \(badKey) (\(filename)), format specifiers don't match for all locales: \(locales)") + } + } + + return results + } + +} + +private struct StringWithParams { + let key: String + let params: [StringParam] + let tableName: String + let values: [(LocaleReference, String)] + let developmentLanguage: String + + func generateLetBinding() -> LetBinding { + LetBinding( + comments: self.comments, + name: SwiftIdentifier(name: key), + valueCodeString: valueCodeString + ) + } + + private var valueCodeString: String { + #"StringResource(key: "\#(key.escapedStringLiteral)", tableName: "\#(tableName)", locales: "\#(values.compactMap(\.0.language))""# + } + + private var primaryLanguageValues: [(LocaleReference, String)] { + return values.filter { $0.0.isBase } + values.filter { $0.0.language == developmentLanguage } + } + + private var comments: [String] { + var results: [String] = [] + + let anyNone = values.contains { $0.0.isNone } + let vs = primaryLanguageValues + values + + if let (locale, value) = vs.first { + if let localeDescription = locale.localeDescription { + let str = "\(localeDescription) translation: \(value)".commentString + results.append(str) + } + else { + let str = "Value: \(value)".commentString + results.append(str) + } + } + + if !anyNone { + if !results.isEmpty { + results.append("") + } + + let locales = values.compactMap { $0.0.localeDescription } + results.append("Locales: \(locales.joined(separator: ", "))") + } + + return results } } diff --git a/Sources/RswiftParsers/LocalizableStrings+Parser.swift b/Sources/RswiftParsers/LocalizableStrings+Parser.swift index 42bad2ef..2a0640b0 100644 --- a/Sources/RswiftParsers/LocalizableStrings+Parser.swift +++ b/Sources/RswiftParsers/LocalizableStrings+Parser.swift @@ -30,9 +30,9 @@ extension LocalizableStrings: SupportedExtensions { let dictionary: [LocalizableStrings.Key: LocalizableStrings.Value] switch url.pathExtension { case "strings": - dictionary = try parseStrings(nsDictionary, source: locale.withFilename("\(basename).strings")) + dictionary = try parseStrings(nsDictionary, source: locale.debugDescription(filename: "\(basename).strings")) case "stringsdict": - dictionary = try parseStringsdict(nsDictionary, source: locale.withFilename("\(basename).stringsdict")) + dictionary = try parseStringsdict(nsDictionary, source: locale.debugDescription(filename: "\(basename).stringsdict")) default: throw ResourceParsingError("File could not be parsed as a strings file: \(url.absoluteString)") } @@ -41,19 +41,6 @@ extension LocalizableStrings: SupportedExtensions { } } -private extension LocaleReference { - func withFilename(_ filename: String) -> String { - switch self { - case .none: - return "'\(filename)'" - case .base: - return "'\(filename)' (Base)" - case .language(let language): - return "'\(filename)' (\(language))" - } - } -} - private func parseStrings(_ nsDictionary: NSDictionary, source: String) throws -> [LocalizableStrings.Key: LocalizableStrings.Value] { var dictionary: [LocalizableStrings.Key: LocalizableStrings.Value] = [:] @@ -75,7 +62,7 @@ private func parseStrings(_ nsDictionary: NSDictionary, source: String) throws - } - dictionary[key] = .init(params: params, commentValue: val) + dictionary[key] = .init(params: params, originalValue: val) } else { throw ResourceParsingError("Non-string value in \(source): \(key) = \(obj)") @@ -99,7 +86,7 @@ private func parseStringsdict(_ nsDictionary: NSDictionary, source: String) thro do { let params = try parseStringsdictParams(localizedFormat, dict: dict) - dictionary[key] = .init(params: params, commentValue: localizedFormat) + dictionary[key] = .init(params: params, originalValue: localizedFormat) } catch let error as ResourceParsingError { // TODO: Log warning // warn("\(error) in '\(key)' \(source)") diff --git a/Sources/RswiftParsers/Nib+Parser.swift b/Sources/RswiftParsers/Nib+Parser.swift index b90de42e..ff59be70 100644 --- a/Sources/RswiftParsers/Nib+Parser.swift +++ b/Sources/RswiftParsers/Nib+Parser.swift @@ -46,12 +46,12 @@ extension NibResource: SupportedExtensions { } } -private let ElementNameToTypeMapping = [ +private let ElementNameToTypeMapping: [String: TypeReference] = [ // TODO: Should contain all standard view elements, like button -> UIButton, view -> UIView etc - "view": TypeReference._UIView, - "tableViewCell": TypeReference._UITableViewCell, - "collectionViewCell": TypeReference._UICollectionViewCell, - "collectionReusableView": TypeReference._UICollectionReusableView + "view": TypeReference.uiView, + "tableViewCell": TypeReference(module: .uiKit, rawName: "UITableViewCell"), + "collectionViewCell": TypeReference(module: .uiKit, rawName: "UICollectionViewCell"), + "collectionReusableView": TypeReference(module: .uiKit, rawName: "UICollectionReusableView"), ] private class NibParserDelegate: NSObject, XMLParserDelegate { @@ -137,7 +137,7 @@ private class NibParserDelegate: NSObject, XMLParserDelegate { let customType = customClass .map { TypeReference(module: ModuleReference(name: customModule), rawName: $0) } - return customType ?? ElementNameToTypeMapping[elementName] ?? TypeReference._UIView + return customType ?? ElementNameToTypeMapping[elementName] ?? TypeReference.uiView } func reusableFromAttributes(_ attributeDict: [String : String], elementName: String) -> Reusable? { @@ -151,7 +151,7 @@ private class NibParserDelegate: NSObject, XMLParserDelegate { let customType = customClass .map { TypeReference(module: ModuleReference(name: customModule), rawName: $0) } - let type = customType ?? ElementNameToTypeMapping[elementName] ?? TypeReference._UIView + let type = customType ?? ElementNameToTypeMapping[elementName] ?? TypeReference.uiView return Reusable(identifier: reuseIdentifier, type: type) } diff --git a/Sources/RswiftParsers/Shared/StringParam+Extensions.swift b/Sources/RswiftParsers/Shared/FormatPart+Extensions.swift similarity index 80% rename from Sources/RswiftParsers/Shared/StringParam+Extensions.swift rename to Sources/RswiftParsers/Shared/FormatPart+Extensions.swift index b316de5c..a550e404 100644 --- a/Sources/RswiftParsers/Shared/StringParam+Extensions.swift +++ b/Sources/RswiftParsers/Shared/FormatPart+Extensions.swift @@ -1,5 +1,5 @@ // -// StringParam.swift +// FormatPart.swift // R.swift // // Created by Tom Lokhorst on 2016-04-18. @@ -17,69 +17,37 @@ import Foundation import RswiftResources -extension StringParam: Unifiable { - public func unify(_ other: StringParam) -> StringParam? { - if let name = name, let otherName = other.name , name != otherName { - return nil - } - - if let spec = spec.unify(other.spec) { - return StringParam(name: name ?? other.name, spec: spec) - } - - return nil - } -} - -extension FormatPart: Unifiable { +extension FormatPart { static public func formatParts(formatString: String) -> [FormatPart] { createFormatParts(formatString) } - - public func unify(_ other: FormatPart) -> FormatPart? { - switch (self, other) { - case let (.spec(l), .spec(r)): - if let spec = l.unify(r) { - return .spec(spec) - } - else { - return nil - } - - case let (.reference(l), .reference(r)) where l == r: - return .reference(l) - - default: - return nil - } - } } extension FormatSpecifier { var type: TypeReference { switch self { case .object: - return ._String + return TypeReference(module: .stdLib, rawName: "String") case .double: - return ._Double + return TypeReference(module: .stdLib, rawName: "Double") case .int: - return ._Int + return TypeReference(module: .stdLib, rawName: "Int") case .uInt: - return ._UInt + return TypeReference(module: .stdLib, rawName: "UInt") case .character: - return ._Character + return TypeReference(module: .stdLib, rawName: "Character") case .cStringPointer: - return ._CStringPointer + return TypeReference(module: .stdLib, rawName: "UnsafePointer") case .voidPointer: - return ._VoidPointer + return TypeReference(module: .stdLib, rawName: "UnsafePointer") case .topType: - return ._Any + return TypeReference(module: .stdLib, rawName: "Any") } } } -extension FormatSpecifier : Unifiable { - +// https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html#//apple_ref/doc/uid/TP40004265-SW1 +extension FormatSpecifier { // Convenience initializer, uses last character of string, // ignoring lengt modifiers, e.g. "lld" public init?(formatString string: String) { @@ -111,22 +79,6 @@ extension FormatSpecifier : Unifiable { return nil } } - - public func unify(_ other: FormatSpecifier) -> FormatSpecifier? { - if self == .topType { - return other - } - - if other == .topType { - return self - } - - if self == other { - return self - } - - return nil - } } private let referenceRegEx: NSRegularExpression = { diff --git a/Sources/RswiftParsers/Shared/TypeReference+Extensions.swift b/Sources/RswiftParsers/Shared/TypeReference+Extensions.swift index 30c5f4d0..80c9bad5 100644 --- a/Sources/RswiftParsers/Shared/TypeReference+Extensions.swift +++ b/Sources/RswiftParsers/Shared/TypeReference+Extensions.swift @@ -9,34 +9,7 @@ import Foundation import RswiftResources extension TypeReference { - static let _Void = TypeReference(module: .stdLib, rawName: "Void") - static let _Any = TypeReference(module: .stdLib, rawName: "Any") - static let _AnyObject = TypeReference(module: .stdLib, rawName: "AnyObject") - static let _String = TypeReference(module: .stdLib, rawName: "String") - static let _Bool = TypeReference(module: .stdLib, rawName: "Bool") - static let _Array = TypeReference(module: .stdLib, rawName: "Array") - static let _Tuple = TypeReference(module: .stdLib, rawName: "_TUPLE_") - static let _Int = TypeReference(module: .stdLib, rawName: "Int") - static let _UInt = TypeReference(module: .stdLib, rawName: "UInt") - static let _Double = TypeReference(module: .stdLib, rawName: "Double") - static let _Character = TypeReference(module: .stdLib, rawName: "Character") - static let _CStringPointer = TypeReference(module: .stdLib, rawName: "UnsafePointer") - static let _VoidPointer = TypeReference(module: .stdLib, rawName: "UnsafePointer") - static let _URL = TypeReference(module: .foundation, rawName: "URL") - static let _Bundle = TypeReference(module: .foundation, rawName: "Bundle") - static let _Locale = TypeReference(module: .foundation, rawName: "Locale") - static let _UINib = TypeReference(module: .uiKit, rawName: "UINib") - static let _UIView = TypeReference(module: .uiKit, rawName: "UIView") - static let _UIImage = TypeReference(module: .uiKit, rawName: "UIImage") - static let _UIStoryboard = TypeReference(module: .uiKit, rawName: "UIStoryboard") - static let _UITableViewCell = TypeReference(module: .uiKit, rawName: "UITableViewCell") - static let _UICollectionViewCell = TypeReference(module: .uiKit, rawName: "UICollectionViewCell") - static let _UICollectionReusableView = TypeReference(module: .uiKit, rawName: "UICollectionReusableView") - static let _UIStoryboardSegue = TypeReference(module: .uiKit, rawName: "UIStoryboardSegue") - static let _UITraitCollection = TypeReference(module: .uiKit, rawName: "UITraitCollection") - static let _UIViewController = TypeReference(module: .uiKit, rawName: "UIViewController") - static let _UIFont = TypeReference(module: .uiKit, rawName: "UIFont") - static let _UIColor = TypeReference(module: .uiKit, rawName: "UIColor") - static let _CGFloat = TypeReference(module: .stdLib, rawName: "CGFloat") - static let _CVarArgType = TypeReference(module: .stdLib, rawName: "CVarArgType...") + static let uiView = TypeReference(module: .uiKit, rawName: "UIView") + static let uiViewController = TypeReference(module: .uiKit, rawName: "UIViewController") + static let uiStoryboardSegue = TypeReference(module: .uiKit, rawName: "UIStoryboardSegue") } diff --git a/Sources/RswiftParsers/Shared/Xcodeproj.swift b/Sources/RswiftParsers/Shared/Xcodeproj.swift index 156731e1..da0aafdc 100644 --- a/Sources/RswiftParsers/Shared/Xcodeproj.swift +++ b/Sources/RswiftParsers/Shared/Xcodeproj.swift @@ -15,7 +15,7 @@ public struct Xcodeproj: SupportedExtensions { private let projectFile: XCProjectFile - let developmentLanguage: String + public let developmentLanguage: String public init(url: URL, warning: (String) -> Void) throws { try Xcodeproj.throwIfUnsupportedExtension(url) diff --git a/Sources/RswiftParsers/Storyboard+Parser.swift b/Sources/RswiftParsers/Storyboard+Parser.swift index 6622cdf6..6311cf78 100644 --- a/Sources/RswiftParsers/Storyboard+Parser.swift +++ b/Sources/RswiftParsers/Storyboard+Parser.swift @@ -49,7 +49,7 @@ extension StoryboardResource: SupportedExtensions { } private let ElementNameToTypeMapping: [String: TypeReference] = [ - "viewController": TypeReference._UIViewController, + "viewController": TypeReference(module: .uiKit, rawName: "UIViewController"), "tableViewCell": TypeReference(module: .uiKit, rawName: "UITableViewCell"), "tabBarController": TypeReference(module: .uiKit, rawName: "UITabBarController"), "glkViewController": TypeReference(module: .custom(name: "GLKit"), rawName: "GLKViewController"), @@ -105,7 +105,7 @@ private class StoryboardParserDelegate: NSObject, XMLParserDelegate { let destination = attributeDict["destination"], let kind = attributeDict["kind"] { - let type = customType ?? TypeReference._UIStoryboardSegue + let type = customType ?? TypeReference.uiStoryboardSegue let segue = StoryboardResource.Segue(identifier: segueIdentifier, type: type, destination: destination, kind: kind) currentViewController?.segues.append(segue) @@ -183,7 +183,7 @@ private class StoryboardParserDelegate: NSObject, XMLParserDelegate { let customType = customClass .map { TypeReference(module: ModuleReference(name: customModule), rawName: $0) } - let type = customType ?? ElementNameToTypeMapping[elementName] ?? TypeReference._UIViewController + let type = customType ?? ElementNameToTypeMapping[elementName] ?? TypeReference.uiViewController return StoryboardResource.ViewController(id: id, storyboardIdentifier: storyboardIdentifier, type: type, segues: []) } @@ -199,7 +199,7 @@ private class StoryboardParserDelegate: NSObject, XMLParserDelegate { let customType = customClass .map { TypeReference(module: ModuleReference(name: customModule), rawName: $0) } - let type = customType ?? ElementNameToTypeMapping[elementName] ?? TypeReference._UIView + let type = customType ?? ElementNameToTypeMapping[elementName] ?? TypeReference.uiView return Reusable(identifier: reuseIdentifier, type: type) } diff --git a/Sources/RswiftResources/LocalizableStrings.swift b/Sources/RswiftResources/LocalizableStrings.swift index 1e79cf1a..4be552f4 100644 --- a/Sources/RswiftResources/LocalizableStrings.swift +++ b/Sources/RswiftResources/LocalizableStrings.swift @@ -13,11 +13,11 @@ public struct LocalizableStrings { public typealias Key = String public struct Value { public let params: [StringParam] - public let commentValue: String + public let originalValue: String - public init(params: [StringParam], commentValue: String) { + public init(params: [StringParam], originalValue: String) { self.params = params - self.commentValue = commentValue + self.originalValue = originalValue } } diff --git a/Sources/RswiftResources/Shared/LocaleReference.swift b/Sources/RswiftResources/Shared/LocaleReference.swift index e1022cb9..bc0c4f66 100644 --- a/Sources/RswiftResources/Shared/LocaleReference.swift +++ b/Sources/RswiftResources/Shared/LocaleReference.swift @@ -67,4 +67,15 @@ extension LocaleReference { return language } } + + public func debugDescription(filename: String) -> String { + switch self { + case .none: + return "'\(filename)'" + case .base: + return "'\(filename)' (Base)" + case .language(let language): + return "'\(filename)' (\(language))" + } + } } diff --git a/Sources/RswiftResources/Shared/StringParam+Unifiable.swift b/Sources/RswiftResources/Shared/StringParam+Unifiable.swift new file mode 100644 index 00000000..5d538ff0 --- /dev/null +++ b/Sources/RswiftResources/Shared/StringParam+Unifiable.swift @@ -0,0 +1,70 @@ +// +// StringParam.swift +// R.swift +// +// Created by Tom Lokhorst on 2016-04-18. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// +// Parts of the content of this file are loosly based on StringsFileParser.swift from SwiftGen/GenumKit. +// We don't feel this is a "substantial portion of the Software" so are not including their MIT license, +// eventhough we would like to give credit where credit is due by referring to SwiftGen thanking Olivier +// Halligon for creating SwiftGen and GenumKit. +// +// See: https://github.com/AliSoftware/SwiftGen/blob/master/GenumKit/Parsers/StringsFileParser.swift +// + +import Foundation + +extension StringParam: Unifiable { + public func unify(_ other: StringParam) -> StringParam? { + if let name = name, let otherName = other.name , name != otherName { + return nil + } + + if let spec = spec.unify(other.spec) { + return StringParam(name: name ?? other.name, spec: spec) + } + + return nil + } +} + +extension FormatPart: Unifiable { + public func unify(_ other: FormatPart) -> FormatPart? { + switch (self, other) { + case let (.spec(l), .spec(r)): + if let spec = l.unify(r) { + return .spec(spec) + } + else { + return nil + } + + case let (.reference(l), .reference(r)) where l == r: + return .reference(l) + + default: + return nil + } + } +} + +extension FormatSpecifier: Unifiable { + + public func unify(_ other: FormatSpecifier) -> FormatSpecifier? { + if self == .topType { + return other + } + + if other == .topType { + return self + } + + if self == other { + return self + } + + return nil + } +} diff --git a/Sources/RswiftResources/Shared/StringResource.swift b/Sources/RswiftResources/Shared/StringResource.swift new file mode 100644 index 00000000..ebd7a682 --- /dev/null +++ b/Sources/RswiftResources/Shared/StringResource.swift @@ -0,0 +1,20 @@ +// +// File.swift +// +// +// Created by Tom Lokhorst on 2022-07-24. +// + +import Foundation + +public struct StringResource { + public let key: String + public let tableName: String + public let locales: [String] + + public init(key: String, tableName: String, locales: [String]) { + self.key = key + self.tableName = tableName + self.locales = locales + } +} diff --git a/Sources/RswiftParsers/Shared/Unifiable.swift b/Sources/RswiftResources/Shared/Unifiable.swift similarity index 100% rename from Sources/RswiftParsers/Shared/Unifiable.swift rename to Sources/RswiftResources/Shared/Unifiable.swift From c3ad3feb5d733c8f57e952555d33d86af306cd6b Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 24 Jul 2022 22:43:30 +0200 Subject: [PATCH 047/161] Add callAsFunction examples for StringResources --- Sources/RswiftCore/RswiftCore.swift | 2 +- .../LocalizableStrings+Generator.swift | 11 ++++- .../RswiftGenerators/SwiftSyntax/Struct.swift | 3 ++ .../Shared/FormatPart+Extensions.swift | 23 ---------- ...ble.swift => StringParam+Extensions.swift} | 24 +++++++++- .../Shared/StringResource.swift | 46 ++++++++++++++++++- 6 files changed, 82 insertions(+), 27 deletions(-) rename Sources/RswiftResources/Shared/{StringParam+Unifiable.swift => StringParam+Extensions.swift} (67%) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index b20a0469..aebe3e89 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -59,7 +59,7 @@ public struct RswiftCore { let ignoreFile = (try? IgnoreFile(ignoreFileURL: rswiftIgnoreURL)) ?? IgnoreFile() let xcodeproj = try! Xcodeproj(url: xcodeprojURL, warning: { print($0) }) - let buildConfigurations = try xcodeproj.buildConfigurations(forTarget: targetName) + let buildConfigurations = try xcodeproj.buildConfigurations(forTarget: targetName) let paths = try xcodeproj.resourcePaths(forTarget: targetName) let urls = paths diff --git a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift index 82efe2ca..dfd557f5 100644 --- a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift +++ b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift @@ -208,7 +208,16 @@ private struct StringWithParams { } private var valueCodeString: String { - #"StringResource(key: "\#(key.escapedStringLiteral)", tableName: "\#(tableName)", locales: "\#(values.compactMap(\.0.language))""# + #"\#(typeName)(key: "\#(key.escapedStringLiteral)", tableName: "\#(tableName)", developmentValue: "\#(developmentValue)", locales: \#(values.compactMap(\.0.language))"# + } + + private var typeName: String { + "StringResource" + + (params.isEmpty ? "" : "\(params.count)<\(params.map(\.spec.typeReference.rawName).joined(separator: ", "))>") + } + + private var developmentValue: String { + values.first(where: { $0.0.localeDescription == developmentLanguage })?.1 ?? "" } private var primaryLanguageValues: [(LocaleReference, String)] { diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index 08f00471..9ccb14df 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -186,6 +186,9 @@ public struct Struct { pp.indented { pp in for st in structs { + if !st.comments.isEmpty { + pp.append(line: "") + } st.render(&pp) } } diff --git a/Sources/RswiftParsers/Shared/FormatPart+Extensions.swift b/Sources/RswiftParsers/Shared/FormatPart+Extensions.swift index a550e404..1adec849 100644 --- a/Sources/RswiftParsers/Shared/FormatPart+Extensions.swift +++ b/Sources/RswiftParsers/Shared/FormatPart+Extensions.swift @@ -23,29 +23,6 @@ extension FormatPart { } } -extension FormatSpecifier { - var type: TypeReference { - switch self { - case .object: - return TypeReference(module: .stdLib, rawName: "String") - case .double: - return TypeReference(module: .stdLib, rawName: "Double") - case .int: - return TypeReference(module: .stdLib, rawName: "Int") - case .uInt: - return TypeReference(module: .stdLib, rawName: "UInt") - case .character: - return TypeReference(module: .stdLib, rawName: "Character") - case .cStringPointer: - return TypeReference(module: .stdLib, rawName: "UnsafePointer") - case .voidPointer: - return TypeReference(module: .stdLib, rawName: "UnsafePointer") - case .topType: - return TypeReference(module: .stdLib, rawName: "Any") - } - } -} - // https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html#//apple_ref/doc/uid/TP40004265-SW1 extension FormatSpecifier { // Convenience initializer, uses last character of string, diff --git a/Sources/RswiftResources/Shared/StringParam+Unifiable.swift b/Sources/RswiftResources/Shared/StringParam+Extensions.swift similarity index 67% rename from Sources/RswiftResources/Shared/StringParam+Unifiable.swift rename to Sources/RswiftResources/Shared/StringParam+Extensions.swift index 5d538ff0..337738c3 100644 --- a/Sources/RswiftResources/Shared/StringParam+Unifiable.swift +++ b/Sources/RswiftResources/Shared/StringParam+Extensions.swift @@ -51,7 +51,6 @@ extension FormatPart: Unifiable { } extension FormatSpecifier: Unifiable { - public func unify(_ other: FormatSpecifier) -> FormatSpecifier? { if self == .topType { return other @@ -68,3 +67,26 @@ extension FormatSpecifier: Unifiable { return nil } } + +extension FormatSpecifier { + public var typeReference: TypeReference { + switch self { + case .object: + return TypeReference(module: .stdLib, rawName: "String") + case .double: + return TypeReference(module: .stdLib, rawName: "Double") + case .int: + return TypeReference(module: .stdLib, rawName: "Int") + case .uInt: + return TypeReference(module: .stdLib, rawName: "UInt") + case .character: + return TypeReference(module: .stdLib, rawName: "Character") + case .cStringPointer: + return TypeReference(module: .stdLib, rawName: "UnsafePointer") + case .voidPointer: + return TypeReference(module: .stdLib, rawName: "UnsafePointer") + case .topType: + return TypeReference(module: .stdLib, rawName: "Any") + } + } +} diff --git a/Sources/RswiftResources/Shared/StringResource.swift b/Sources/RswiftResources/Shared/StringResource.swift index ebd7a682..2113c85b 100644 --- a/Sources/RswiftResources/Shared/StringResource.swift +++ b/Sources/RswiftResources/Shared/StringResource.swift @@ -11,10 +11,54 @@ public struct StringResource { public let key: String public let tableName: String public let locales: [String] + public let developmentValue: String - public init(key: String, tableName: String, locales: [String]) { + public init(key: String, tableName: String, locales: [String], developmentValue: String) { self.key = key self.tableName = tableName self.locales = locales + self.developmentValue = developmentValue + } + + public func callAsFunction() -> String { + NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") + } +} + +public struct StringResource1 { + public let key: String + public let tableName: String + public let locales: [String] + public let developmentValue: String + + public init(key: String, tableName: String, locales: [String], developmentValue: String) { + self.key = key + self.tableName = tableName + self.locales = locales + self.developmentValue = developmentValue + } + + public func callAsFunction(_ arg1: Arg1) -> String { + let format = NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") + return String(format: format, arguments: [arg1]) + } +} + +public struct StringResource2 { + public let key: String + public let tableName: String + public let locales: [String] + public let developmentValue: String + + public init(key: String, tableName: String, locales: [String], developmentValue: String) { + self.key = key + self.tableName = tableName + self.locales = locales + self.developmentValue = developmentValue + } + + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2) -> String { + let format = NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") + return String(format: format, arguments: [arg1, arg2]) } } From e69fca6fe0a6c602e03e665e8fff752c43e9a857 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Mon, 25 Jul 2022 09:18:13 +0200 Subject: [PATCH 048/161] Add project parsing --- Sources/RswiftCore/RswiftCore.swift | 10 +++++++++- Sources/RswiftParsers/Shared/Xcodeproj.swift | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index aebe3e89..79f86be2 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -179,8 +179,16 @@ public struct RswiftCore { prefix: qualifiedName ) + let projectStruct = Struct(name: SwiftIdentifier(name: "project")) { + LetBinding(name: SwiftIdentifier(name: "developmentLanguage"), valueCodeString: #""\#(xcodeproj.developmentLanguage)""#) + + if let knownAssetTags = xcodeproj.knownAssetTags { + LetBinding(name: SwiftIdentifier(name: "knownAssetTags"), valueCodeString: "\(knownAssetTags)") + } + } + let s = Struct(name: structName) { - stringStruct + projectStruct } print(s.prettyPrint()) diff --git a/Sources/RswiftParsers/Shared/Xcodeproj.swift b/Sources/RswiftParsers/Shared/Xcodeproj.swift index da0aafdc..39ddf984 100644 --- a/Sources/RswiftParsers/Shared/Xcodeproj.swift +++ b/Sources/RswiftParsers/Shared/Xcodeproj.swift @@ -16,6 +16,7 @@ public struct Xcodeproj: SupportedExtensions { private let projectFile: XCProjectFile public let developmentLanguage: String + public let knownAssetTags: [String]? public init(url: URL, warning: (String) -> Void) throws { try Xcodeproj.throwIfUnsupportedExtension(url) @@ -38,6 +39,7 @@ public struct Xcodeproj: SupportedExtensions { self.projectFile = projectFile self.developmentLanguage = projectFile.project.developmentRegion + self.knownAssetTags = projectFile.project.knownAssetTags } private func findTarget(name: String) throws -> PBXTarget { From 0a043f1c7b662767de98abe05a5da44e6a67090b Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 29 Jul 2022 11:12:01 +0200 Subject: [PATCH 049/161] Add Project struct in Parsers --- Sources/RswiftCore/RswiftCore.swift | 103 +++--------------- Sources/RswiftParsers/Project.swift | 101 +++++++++++++++++ .../{ => Resources}/AssetCatalog+Parser.swift | 0 .../{ => Resources}/FileResource+Parser.swift | 0 .../{ => Resources}/FontResource+Parser.swift | 0 .../ImageResource+Parser.swift | 0 .../LocalizableStrings+Parser.swift | 0 .../{ => Resources}/Nib+Parser.swift | 0 .../{ => Resources}/PropertyList+Parser.swift | 0 .../{ => Resources}/Storyboard+Parser.swift | 0 .../Util => RswiftParsers/Shared}/Glob.swift | 0 .../Shared}/IgnoreFile.swift | 18 +-- .../RswiftParsers/Shared/SourceTreeURLs.swift | 40 +++++++ Sources/rswift/main.swift | 17 ++- 14 files changed, 179 insertions(+), 100 deletions(-) create mode 100644 Sources/RswiftParsers/Project.swift rename Sources/RswiftParsers/{ => Resources}/AssetCatalog+Parser.swift (100%) rename Sources/RswiftParsers/{ => Resources}/FileResource+Parser.swift (100%) rename Sources/RswiftParsers/{ => Resources}/FontResource+Parser.swift (100%) rename Sources/RswiftParsers/{ => Resources}/ImageResource+Parser.swift (100%) rename Sources/RswiftParsers/{ => Resources}/LocalizableStrings+Parser.swift (100%) rename Sources/RswiftParsers/{ => Resources}/Nib+Parser.swift (100%) rename Sources/RswiftParsers/{ => Resources}/PropertyList+Parser.swift (100%) rename Sources/RswiftParsers/{ => Resources}/Storyboard+Parser.swift (100%) rename Sources/{RswiftCore/Util => RswiftParsers/Shared}/Glob.swift (100%) rename Sources/{RswiftCore => RswiftParsers/Shared}/IgnoreFile.swift (91%) create mode 100644 Sources/RswiftParsers/Shared/SourceTreeURLs.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 79f86be2..b872f38d 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -18,11 +18,7 @@ public struct RswiftCore { let infoPlistFile: URL? let codeSignEntitlements: URL? - let builtProductsDirURL: URL - let developerDirURL: URL - let sourceRootURL: URL - let sdkRootURL: URL - let platformURL: URL + let sourceTreeURLs: SourceTreeURLs let rswiftIgnoreURL: URL @@ -32,12 +28,8 @@ public struct RswiftCore { productModuleName: String?, infoPlistFile: URL?, codeSignEntitlements: URL?, - builtProductsDirURL: URL, - developerDirURL: URL, - sourceRootURL: URL, - sdkRootURL: URL, - platformURL: URL, - rswiftIgnoreURL: URL + rswiftIgnoreURL: URL, + sourceTreeURLs: SourceTreeURLs ) { self.xcodeprojURL = xcodeprojURL self.targetName = targetName @@ -45,68 +37,24 @@ public struct RswiftCore { self.infoPlistFile = infoPlistFile self.codeSignEntitlements = codeSignEntitlements - self.builtProductsDirURL = builtProductsDirURL - self.developerDirURL = developerDirURL - self.sourceRootURL = sourceRootURL - self.sdkRootURL = sdkRootURL - self.platformURL = platformURL - self.rswiftIgnoreURL = rswiftIgnoreURL + + self.sourceTreeURLs = sourceTreeURLs } // Temporary function for use during development public func developRun() throws { - let ignoreFile = (try? IgnoreFile(ignoreFileURL: rswiftIgnoreURL)) ?? IgnoreFile() - let xcodeproj = try! Xcodeproj(url: xcodeprojURL, warning: { print($0) }) - - let buildConfigurations = try xcodeproj.buildConfigurations(forTarget: targetName) - - let paths = try xcodeproj.resourcePaths(forTarget: targetName) - let urls = paths - .map { $0.url(with: urlForSourceTreeFolder) } - .filter { !ignoreFile.matches(url: $0) } - let start = Date() - let dontGenerateFileForFonts = false - let dontGenerateFileForImages = false - let files = try urls - .filter { !FileResource.unsupportedExtensions.contains($0.pathExtension) } - .filter { !(dontGenerateFileForFonts && FontResource.supportedExtensions.contains($0.pathExtension)) } - .filter { !(dontGenerateFileForImages && ImageResource.supportedExtensions.contains($0.pathExtension)) } - .map { try FileResource.parse(url: $0) } - - let storyboards = try urls - .filter { StoryboardResource.supportedExtensions.contains($0.pathExtension) } - .map { try StoryboardResource.parse(url: $0) } - - let nibs = try urls - .filter { NibResource.supportedExtensions.contains($0.pathExtension) } - .map { try NibResource.parse(url: $0) } - - let fonts = try urls - .filter { FontResource.supportedExtensions.contains($0.pathExtension) } - .map { try FontResource.parse(url: $0) } - - let images = try urls - .filter { ImageResource.supportedExtensions.contains($0.pathExtension) } - .map { try ImageResource.parse(url: $0, assetTags: nil) } - let assetCatalogs = try urls - .filter { AssetCatalog.supportedExtensions.contains($0.pathExtension) } - .map { try AssetCatalog.parse(url: $0) } - - let localizableStrings = try urls - .filter { LocalizableStrings.supportedExtensions.contains($0.pathExtension) } - .map { try LocalizableStrings.parse(url: $0) } - - - let infoplist = URL(fileURLWithPath: "/Users/tom/Projects/R.swift/Examples/ResourceApp/ResourceApp/Info.plist") - let entitlementsURL = URL(fileURLWithPath: "/Users/tom/Projects/R.swift/Examples/ResourceApp/ResourceApp/ResourceApp.entitlements") - let plist = try PropertyListResource.parse(url: infoplist, buildConfigurationName: "ResourceApp") - let infoPlistWhitelist = ["UIApplicationShortcutItems", "UIApplicationSceneManifest", "NSUserActivityTypes", "NSExtension"] - -// let entitlements = try PropertyListResource.parse(url: entitlementsURL, buildConfigurationName: "ResourceApp") - + let project = try Project.parseTarget( + name: targetName, + xcodeprojURL: xcodeprojURL, + rswiftIgnoreURL: rswiftIgnoreURL, + infoPlistFile: infoPlistFile, + codeSignEntitlements: codeSignEntitlements, + sourceTreeURLs: sourceTreeURLs, + warning: { print("[warning]", $0) } + ) let structName = SwiftIdentifier(rawValue: "R") let qualifiedName = structName @@ -174,15 +122,15 @@ public struct RswiftCore { // ) let stringStruct = LocalizableStrings.generateStruct( - resources: localizableStrings, - developmentLanguage: xcodeproj.developmentLanguage, + resources: project.localizableStrings, + developmentLanguage: project.xcodeproj.developmentLanguage, prefix: qualifiedName ) let projectStruct = Struct(name: SwiftIdentifier(name: "project")) { - LetBinding(name: SwiftIdentifier(name: "developmentLanguage"), valueCodeString: #""\#(xcodeproj.developmentLanguage)""#) + LetBinding(name: SwiftIdentifier(name: "developmentLanguage"), valueCodeString: #""\#(project.xcodeproj.developmentLanguage)""#) - if let knownAssetTags = xcodeproj.knownAssetTags { + if let knownAssetTags = project.xcodeproj.knownAssetTags { LetBinding(name: SwiftIdentifier(name: "knownAssetTags"), valueCodeString: "\(knownAssetTags)") } } @@ -196,19 +144,4 @@ public struct RswiftCore { print("TOTAL", Date().timeIntervalSince(start)) print() } - - private func urlForSourceTreeFolder(_ sourceTreeFolder: SourceTreeFolder) -> URL { - switch sourceTreeFolder { - case .buildProductsDir: - return builtProductsDirURL - case .developerDir: - return developerDirURL - case .sdkRoot: - return sdkRootURL - case .sourceRoot: - return sourceRootURL - case .platformDir: - return platformURL - } - } } diff --git a/Sources/RswiftParsers/Project.swift b/Sources/RswiftParsers/Project.swift new file mode 100644 index 00000000..11b1b33e --- /dev/null +++ b/Sources/RswiftParsers/Project.swift @@ -0,0 +1,101 @@ +// +// File.swift +// +// +// Created by Tom Lokhorst on 2022-07-29. +// + +import Foundation +import XcodeEdit +import RswiftResources + +public struct Project { + public let assetCatalogs: [AssetCatalog] + public let files: [FileResource] + public let fonts: [FontResource] + public let images: [ImageResource] + public let localizableStrings: [LocalizableStrings] + public let nibs: [NibResource] + public let storyboards: [StoryboardResource] + public let infoPlists: [PropertyListResource] + public let codeSignEntitlements: [PropertyListResource] + public let xcodeproj: Xcodeproj + + public static func parseTarget( + name targetName: String, + xcodeprojURL: URL, + rswiftIgnoreURL: URL?, + infoPlistFile: URL?, + codeSignEntitlements: URL?, + sourceTreeURLs: SourceTreeURLs, + parseFontsAsFiles: Bool = true, + parseImagesAsFiles: Bool = true, + warning: (String) -> Void + ) throws -> Project { + let xcodeproj = try Xcodeproj(url: xcodeprojURL, warning: warning) + let ignoreFile = rswiftIgnoreURL.flatMap { try? IgnoreFile(ignoreFileURL: $0) } ?? IgnoreFile() + + let buildConfigurations = try xcodeproj.buildConfigurations(forTarget: targetName) + + let paths = try xcodeproj.resourcePaths(forTarget: targetName) + let urls = paths + .map { $0.url(with: sourceTreeURLs.url(for:)) } + .filter { !ignoreFile.matches(url: $0) } + + + let assetCatalogs = try urls + .filter { AssetCatalog.supportedExtensions.contains($0.pathExtension) } + .map { try AssetCatalog.parse(url: $0) } + + let dontParseFileForFonts = !parseFontsAsFiles + let dontParseFileForImages = !parseImagesAsFiles + let files = try urls + .filter { !FileResource.unsupportedExtensions.contains($0.pathExtension) } + .filter { !(dontParseFileForFonts && FontResource.supportedExtensions.contains($0.pathExtension)) } + .filter { !(dontParseFileForImages && ImageResource.supportedExtensions.contains($0.pathExtension)) } + .map { try FileResource.parse(url: $0) } + + let fonts = try urls + .filter { FontResource.supportedExtensions.contains($0.pathExtension) } + .map { try FontResource.parse(url: $0) } + + let images = try urls + .filter { ImageResource.supportedExtensions.contains($0.pathExtension) } + .map { try ImageResource.parse(url: $0, assetTags: nil) } + + let localizableStrings = try urls + .filter { LocalizableStrings.supportedExtensions.contains($0.pathExtension) } + .map { try LocalizableStrings.parse(url: $0) } + + let nibs = try urls + .filter { NibResource.supportedExtensions.contains($0.pathExtension) } + .map { try NibResource.parse(url: $0) } + + let storyboards = try urls + .filter { StoryboardResource.supportedExtensions.contains($0.pathExtension) } + .map { try StoryboardResource.parse(url: $0) } + + let infoPlists = try buildConfigurations.compactMap { config -> PropertyListResource? in + guard let url = infoPlistFile else { return nil } + return try PropertyListResource.parse(url: url, buildConfigurationName: config.name) + } + + let codeSignEntitlements = try buildConfigurations.compactMap { config -> PropertyListResource? in + guard let url = codeSignEntitlements else { return nil } + return try PropertyListResource.parse(url: url, buildConfigurationName: config.name) + } + + return Project( + assetCatalogs: assetCatalogs, + files: files, + fonts: fonts, + images: images, + localizableStrings: localizableStrings, + nibs: nibs, + storyboards: storyboards, + infoPlists: infoPlists, + codeSignEntitlements: codeSignEntitlements, + xcodeproj: xcodeproj + ) + } +} diff --git a/Sources/RswiftParsers/AssetCatalog+Parser.swift b/Sources/RswiftParsers/Resources/AssetCatalog+Parser.swift similarity index 100% rename from Sources/RswiftParsers/AssetCatalog+Parser.swift rename to Sources/RswiftParsers/Resources/AssetCatalog+Parser.swift diff --git a/Sources/RswiftParsers/FileResource+Parser.swift b/Sources/RswiftParsers/Resources/FileResource+Parser.swift similarity index 100% rename from Sources/RswiftParsers/FileResource+Parser.swift rename to Sources/RswiftParsers/Resources/FileResource+Parser.swift diff --git a/Sources/RswiftParsers/FontResource+Parser.swift b/Sources/RswiftParsers/Resources/FontResource+Parser.swift similarity index 100% rename from Sources/RswiftParsers/FontResource+Parser.swift rename to Sources/RswiftParsers/Resources/FontResource+Parser.swift diff --git a/Sources/RswiftParsers/ImageResource+Parser.swift b/Sources/RswiftParsers/Resources/ImageResource+Parser.swift similarity index 100% rename from Sources/RswiftParsers/ImageResource+Parser.swift rename to Sources/RswiftParsers/Resources/ImageResource+Parser.swift diff --git a/Sources/RswiftParsers/LocalizableStrings+Parser.swift b/Sources/RswiftParsers/Resources/LocalizableStrings+Parser.swift similarity index 100% rename from Sources/RswiftParsers/LocalizableStrings+Parser.swift rename to Sources/RswiftParsers/Resources/LocalizableStrings+Parser.swift diff --git a/Sources/RswiftParsers/Nib+Parser.swift b/Sources/RswiftParsers/Resources/Nib+Parser.swift similarity index 100% rename from Sources/RswiftParsers/Nib+Parser.swift rename to Sources/RswiftParsers/Resources/Nib+Parser.swift diff --git a/Sources/RswiftParsers/PropertyList+Parser.swift b/Sources/RswiftParsers/Resources/PropertyList+Parser.swift similarity index 100% rename from Sources/RswiftParsers/PropertyList+Parser.swift rename to Sources/RswiftParsers/Resources/PropertyList+Parser.swift diff --git a/Sources/RswiftParsers/Storyboard+Parser.swift b/Sources/RswiftParsers/Resources/Storyboard+Parser.swift similarity index 100% rename from Sources/RswiftParsers/Storyboard+Parser.swift rename to Sources/RswiftParsers/Resources/Storyboard+Parser.swift diff --git a/Sources/RswiftCore/Util/Glob.swift b/Sources/RswiftParsers/Shared/Glob.swift similarity index 100% rename from Sources/RswiftCore/Util/Glob.swift rename to Sources/RswiftParsers/Shared/Glob.swift diff --git a/Sources/RswiftCore/IgnoreFile.swift b/Sources/RswiftParsers/Shared/IgnoreFile.swift similarity index 91% rename from Sources/RswiftCore/IgnoreFile.swift rename to Sources/RswiftParsers/Shared/IgnoreFile.swift index a6e817f7..85a3b925 100644 --- a/Sources/RswiftCore/IgnoreFile.swift +++ b/Sources/RswiftParsers/Shared/IgnoreFile.swift @@ -8,16 +8,16 @@ import Foundation -class IgnoreFile { - let ignoredURLs: [URL] - let explicitlyIncludedURLs: [URL] +public class IgnoreFile { + public let ignoredURLs: [URL] + public let explicitlyIncludedURLs: [URL] - init() { + public init() { ignoredURLs = [] explicitlyIncludedURLs = [] } - init(ignoreFileURL: URL) throws { + public init(ignoreFileURL: URL) throws { let workingDirectory = ignoreFileURL.deletingLastPathComponent() let potentialPatterns = try String(contentsOf: ignoreFileURL).components(separatedBy: .newlines) @@ -30,6 +30,10 @@ class IgnoreFile { .flatMap { IgnoreFile.expandPattern($0, workingDirectory: workingDirectory) } } + public func matches(url: URL) -> Bool { + return ignoredURLs.contains(url) && !explicitlyIncludedURLs.contains(url) + } + static private func isPattern(potentialPattern: String) -> Bool { // Check for empty line if potentialPattern.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { return false } @@ -62,8 +66,4 @@ class IgnoreFile { return Glob(pattern: pattern).paths } - - func matches(url: URL) -> Bool { - return ignoredURLs.contains(url) && !explicitlyIncludedURLs.contains(url) - } } diff --git a/Sources/RswiftParsers/Shared/SourceTreeURLs.swift b/Sources/RswiftParsers/Shared/SourceTreeURLs.swift new file mode 100644 index 00000000..bcfe2509 --- /dev/null +++ b/Sources/RswiftParsers/Shared/SourceTreeURLs.swift @@ -0,0 +1,40 @@ +// +// SourceTreeURLs.swift +// +// +// Created by Tom Lokhorst on 2022-07-29. +// + +import Foundation +import XcodeEdit + +public struct SourceTreeURLs { + public let builtProductsDirURL: URL + public let developerDirURL: URL + public let sourceRootURL: URL + public let sdkRootURL: URL + public let platformURL: URL + + public init(builtProductsDirURL: URL, developerDirURL: URL, sourceRootURL: URL, sdkRootURL: URL, platformURL: URL) { + self.builtProductsDirURL = builtProductsDirURL + self.developerDirURL = developerDirURL + self.sourceRootURL = sourceRootURL + self.sdkRootURL = sdkRootURL + self.platformURL = platformURL + } + + public func url(for sourceTreeFolder: SourceTreeFolder) -> URL { + switch sourceTreeFolder { + case .buildProductsDir: + return builtProductsDirURL + case .developerDir: + return developerDirURL + case .sdkRoot: + return sdkRootURL + case .sourceRoot: + return sourceRootURL + case .platformDir: + return platformURL + } + } +} diff --git a/Sources/rswift/main.swift b/Sources/rswift/main.swift index a7204353..99d2bf56 100644 --- a/Sources/rswift/main.swift +++ b/Sources/rswift/main.swift @@ -7,6 +7,7 @@ import Foundation import RswiftCore +import RswiftParsers import XcodeEdit @@ -37,18 +38,22 @@ let sourceRootURL = xcodeprojURL.deletingLastPathComponent() let rswiftIgnoreURL = sourceRootURL.appendingPathComponent(".rswiftignore") let fakeURL = URL(fileURLWithPath: "/FAKE") +let sourceTreeURLs = SourceTreeURLs( + builtProductsDirURL: fakeURL, + developerDirURL: fakeURL, + sourceRootURL: sourceRootURL, + sdkRootURL: fakeURL, + platformURL: fakeURL +) + let core = RswiftCore( xcodeprojURL: xcodeprojURL, targetName: targetName, productModuleName: processInfo.environment[EnvironmentKeys.productModuleName], infoPlistFile: processInfo.environment[EnvironmentKeys.infoPlistFile].map { URL(fileURLWithPath: $0) }, codeSignEntitlements: processInfo.environment[EnvironmentKeys.codeSignEntitlements].map { URL(fileURLWithPath: $0) }, - builtProductsDirURL: fakeURL, - developerDirURL: fakeURL, - sourceRootURL: sourceRootURL, - sdkRootURL: fakeURL, - platformURL: fakeURL, - rswiftIgnoreURL: rswiftIgnoreURL + rswiftIgnoreURL: rswiftIgnoreURL, + sourceTreeURLs: sourceTreeURLs ) print("Start") From 19a3590eac9271490147eba34d4afe9c991658f3 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 29 Jul 2022 14:12:13 +0200 Subject: [PATCH 050/161] Work on string generation --- Package.swift | 4 +- Sources/RswiftCore/RswiftCore.swift | 1 + .../LocalizableStrings+Generator.swift | 35 ++++- .../Shared/TypeReference+Generator.swift | 1 + .../RswiftGenerators/SwiftSyntax/Struct.swift | 131 ++++++++++------- .../SwiftSyntax/StructMembersBuilder.swift | 85 +++++++++++ .../Shared/StringResource.swift | 133 ++++++++++++++++++ 7 files changed, 332 insertions(+), 58 deletions(-) create mode 100644 Sources/RswiftGenerators/SwiftSyntax/StructMembersBuilder.swift diff --git a/Package.swift b/Package.swift index dfbab068..d94987b7 100644 --- a/Package.swift +++ b/Package.swift @@ -4,11 +4,13 @@ import PackageDescription let package = Package( name: "rswift", platforms: [ - .macOS(.v10_15) + .macOS(.v10_15), + .iOS(.v11), ], products: [ .executable(name: "rswift", targets: ["rswift"]), .executable(name: "rswift-legacy", targets: ["rswift-legacy"]), + .library(name: "RswiftCombined", targets: ["RswiftResources", "RswiftGenerators"]) ], dependencies: [ .package(url: "https://github.com/kylef/Commander.git", from: "0.8.0"), diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index b872f38d..75f1416c 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -137,6 +137,7 @@ public struct RswiftCore { let s = Struct(name: structName) { projectStruct + stringStruct } print(s.prettyPrint()) diff --git a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift index dfd557f5..5d3e276c 100644 --- a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift +++ b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift @@ -44,11 +44,15 @@ extension LocalizableStrings { let strings = computeStringsWithParams(filename: filename, resources: resources, developmentLanguage: developmentLanguage, warning: warning) let letbindings = strings.map { $0.generateLetBinding() } + let functions = strings + .filter { $0.params.count > 0 } + .map { $0.generateFunction() } let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) localization keys."] return Struct(comments: comments, name: structName) { letbindings + functions } } @@ -202,13 +206,38 @@ private struct StringWithParams { func generateLetBinding() -> LetBinding { LetBinding( comments: self.comments, + isStatic: true, name: SwiftIdentifier(name: key), - valueCodeString: valueCodeString + valueCodeString: letValueCodeString ) } - private var valueCodeString: String { - #"\#(typeName)(key: "\#(key.escapedStringLiteral)", tableName: "\#(tableName)", developmentValue: "\#(developmentValue)", locales: \#(values.compactMap(\.0.language))"# + func generateFunction() -> Function { + Function( + comments: self.comments, + isStatic: true, + name: SwiftIdentifier(name: key), + params: zip(params.indices, params).map { (ix, p) in + .init(name: p.name ?? "_", localName: "value\(ix + 1)", typeReference: p.spec.typeReference, defaultValue: nil) + }, + returnType: .string, + valueCodeString: funcBodyCodeString + ) + } + + private var letValueCodeString: String { + #"\#(typeName)(key: "\#(key.escapedStringLiteral)", tableName: "\#(tableName)", locales: \#(values.compactMap(\.0.language)), developmentValue: "\#(developmentValue)")"# + } + + private var funcBodyCodeString: String { + + var args = params.indices.map { "value\($0 + 1)" } + args.insert("format: format", at: 0) + + return """ + let format = NSLocalizedString("\(key.escapedStringLiteral)", tableName: "\(tableName)", comment: "") + return String(\(args.joined(separator: ", "))) + """ } private var typeName: String { diff --git a/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift b/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift index 094e82f6..002494f5 100644 --- a/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift +++ b/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift @@ -26,6 +26,7 @@ extension TypeReference { } } + static var string: TypeReference = .init(module: .stdLib, rawName: "String") static var uiView: TypeReference = .init(module: .uiKit, rawName: "UIView") static var uiViewController: TypeReference = .init(module: .uiKit, rawName: "UIViewController") } diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index 9ccb14df..48229f45 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -54,7 +54,7 @@ public struct LetBinding { isStatic ? "static" : nil, "let", typeReference == nil ? name.value : "\(name.value):", - typeReference?.rawName + typeReference?.codeString() ] if let valueCodeString = valueCodeString { words.append("=") @@ -69,62 +69,65 @@ public struct LetBinding { } -public struct StructMembers { - var lets: [LetBinding] = [] - var structs: [Struct] = [] - - func sorted() -> StructMembers { - var new = self - new.lets.sort { $0.name < $1.name } - new.structs.sort { $0.name < $1.name } - return new - } -} - -@resultBuilder -public struct StructMembersBuilder { - public static func buildExpression(_ expression: LetBinding) -> StructMembers { - StructMembers(lets: [expression]) - } - - public static func buildExpression(_ expressions: [LetBinding]) -> StructMembers { - StructMembers(lets: expressions) - } - - public static func buildExpression(_ expression: Struct) -> StructMembers { - StructMembers(structs: [expression]) - } - - public static func buildExpression(_ expressions: [Struct]) -> StructMembers { - StructMembers(structs: expressions) - } - - public static func buildExpression(_ members: StructMembers) -> StructMembers { - members - } +public struct Function { + public let comments: [String] + public var accessControl = AccessControl.none + public let isStatic: Bool + public let name: SwiftIdentifier + public let params: [Parameter] + public let returnType: TypeReference + public let valueCodeString: String - public static func buildExpression(_ members: Void) -> StructMembers { - StructMembers() + public init(comments: [String], accessControl: AccessControl = AccessControl.none, isStatic: Bool, name: SwiftIdentifier, params: [Parameter], returnType: TypeReference, valueCodeString: String) { + self.comments = comments + self.accessControl = accessControl + self.isStatic = isStatic + self.name = name + self.params = params + self.returnType = returnType + self.valueCodeString = valueCodeString } - public static func buildArray(_ members: [StructMembers]) -> StructMembers { - StructMembers(lets: members.flatMap(\.lets), structs: members.flatMap(\.structs)) - } + public struct Parameter { + public let name: String + public let localName: String? + public let typeReference: TypeReference + public let defaultValue: String? - public static func buildEither(first component: StructMembers) -> StructMembers { - component - } + func codeString() -> String { + var result = name + if let localName { + result += " \(localName)" + } + result += ": \(typeReference.codeString())" + if let defaultValue { + result += " = \(defaultValue)" + } - public static func buildEither(second component: StructMembers) -> StructMembers { - component + return result + } } - public static func buildOptional(_ component: StructMembers?) -> StructMembers { - component ?? StructMembers() - } + func render(_ pp: inout PrettyPrinter) { + let prs = params.map { $0.codeString() }.joined(separator: ", ") + let words: [String?] = [ + accessControl.code(), + isStatic ? "static" : nil, + "func", + "\(name.value)(\(prs))", + "->", + returnType.codeString(), + "{" + ] - public static func buildBlock(_ members: StructMembers...) -> StructMembers { - StructMembers(lets: members.flatMap(\.lets), structs: members.flatMap(\.structs)) + for c in comments { + pp.append(words: ["///", c == "" ? nil : c]) + } + pp.append(words: words) + pp.indented { pp in + pp.append(line: valueCodeString) + } + pp.append(line: "}") } } @@ -134,9 +137,10 @@ public struct Struct { public let name: SwiftIdentifier public var protocols: [TypeReference] = [] public var lets: [LetBinding] = [] + public var funcs: [Function] = [] public var structs: [Struct] = [] - public var isEmpty: Bool { lets.isEmpty && structs.isEmpty } + public var isEmpty: Bool { lets.isEmpty && funcs.isEmpty && structs.isEmpty } public static var empty: Struct = Struct(name: SwiftIdentifier(name: "empty"), membersBuilder: {}) @@ -153,6 +157,7 @@ public struct Struct { self.protocols = protocols let members = membersBuilder() self.lets = members.lets + self.funcs = members.funcs self.structs = members.structs } @@ -167,7 +172,7 @@ public struct Struct { pp.append(words: ["///", c == "" ? nil : c]) } - let ps = protocols.map(\.rawName).joined(separator: ", ") + let ps = protocols.map { $0.codeString() }.joined(separator: ", ") let implements = ps.isEmpty ? "" : ": \(ps)" pp.append(line: "struct \(name.value)\(implements) {") @@ -180,7 +185,20 @@ public struct Struct { } } - if !lets.isEmpty && !structs.isEmpty { + if !lets.isEmpty && !funcs.isEmpty { + pp.append(line: "") + } + + pp.indented { pp in + for fun in funcs { + if !fun.comments.isEmpty { + pp.append(line: "") + } + fun.render(&pp) + } + } + + if !funcs.isEmpty && !structs.isEmpty { pp.append(line: "") } @@ -207,8 +225,13 @@ struct PrettyPrinter { indent -= 1 } - mutating func append(line: String) { - lines.append((indent, line)) + mutating func append(line str: String) { + if str.isEmpty { + lines.append((indent, "")) + } + for line in str.split(separator: "\n") { + lines.append((indent, String(line))) + } } mutating func append(words: [String?]) { diff --git a/Sources/RswiftGenerators/SwiftSyntax/StructMembersBuilder.swift b/Sources/RswiftGenerators/SwiftSyntax/StructMembersBuilder.swift new file mode 100644 index 00000000..7c9d1a49 --- /dev/null +++ b/Sources/RswiftGenerators/SwiftSyntax/StructMembersBuilder.swift @@ -0,0 +1,85 @@ +// +// StructMembersBuilder.swift +// +// +// Created by Tom Lokhorst on 2022-07-29. +// + +import Foundation + +public struct StructMembers { + var lets: [LetBinding] = [] + var funcs: [Function] = [] + var structs: [Struct] = [] + + func sorted() -> StructMembers { + var new = self + new.lets.sort { $0.name < $1.name } + new.funcs.sort { $0.name < $1.name } + new.structs.sort { $0.name < $1.name } + return new + } +} + +@resultBuilder +public struct StructMembersBuilder { + public static func buildExpression(_ members: Void) -> StructMembers { + StructMembers() + } + + public static func buildExpression(_ expression: LetBinding) -> StructMembers { + StructMembers(lets: [expression]) + } + + public static func buildExpression(_ expressions: [LetBinding]) -> StructMembers { + StructMembers(lets: expressions) + } + + public static func buildExpression(_ expression: Function) -> StructMembers { + StructMembers(funcs: [expression]) + } + + public static func buildExpression(_ expressions: [Function]) -> StructMembers { + StructMembers(funcs: expressions) + } + + public static func buildExpression(_ expression: Struct) -> StructMembers { + StructMembers(structs: [expression]) + } + + public static func buildExpression(_ expressions: [Struct]) -> StructMembers { + StructMembers(structs: expressions) + } + + public static func buildExpression(_ members: StructMembers) -> StructMembers { + members + } + + public static func buildArray(_ members: [StructMembers]) -> StructMembers { + StructMembers( + lets: members.flatMap(\.lets), + funcs: members.flatMap(\.funcs), + structs: members.flatMap(\.structs) + ) + } + + public static func buildBlock(_ members: StructMembers...) -> StructMembers { + StructMembers( + lets: members.flatMap(\.lets), + funcs: members.flatMap(\.funcs), + structs: members.flatMap(\.structs) + ) + } + + public static func buildEither(first component: StructMembers) -> StructMembers { + component + } + + public static func buildEither(second component: StructMembers) -> StructMembers { + component + } + + public static func buildOptional(_ component: StructMembers?) -> StructMembers { + component ?? StructMembers() + } +} diff --git a/Sources/RswiftResources/Shared/StringResource.swift b/Sources/RswiftResources/Shared/StringResource.swift index 2113c85b..147ab466 100644 --- a/Sources/RswiftResources/Shared/StringResource.swift +++ b/Sources/RswiftResources/Shared/StringResource.swift @@ -62,3 +62,136 @@ public struct StringResource2 { return String(format: format, arguments: [arg1, arg2]) } } + +public struct StringResource3 { + public let key: String + public let tableName: String + public let locales: [String] + public let developmentValue: String + + public init(key: String, tableName: String, locales: [String], developmentValue: String) { + self.key = key + self.tableName = tableName + self.locales = locales + self.developmentValue = developmentValue + } + + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) -> String { + let format = NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") + return String(format: format, arguments: [arg1, arg2, arg3]) + } +} + +public struct StringResource4 { + public let key: String + public let tableName: String + public let locales: [String] + public let developmentValue: String + + public init(key: String, tableName: String, locales: [String], developmentValue: String) { + self.key = key + self.tableName = tableName + self.locales = locales + self.developmentValue = developmentValue + } + + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4) -> String { + let format = NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") + return String(format: format, arguments: [arg1, arg2, arg3, arg4]) + } +} + +public struct StringResource5 { + public let key: String + public let tableName: String + public let locales: [String] + public let developmentValue: String + + public init(key: String, tableName: String, locales: [String], developmentValue: String) { + self.key = key + self.tableName = tableName + self.locales = locales + self.developmentValue = developmentValue + } + + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5) -> String { + let format = NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") + return String(format: format, arguments: [arg1, arg2, arg3, arg4, arg5]) + } +} + +public struct StringResource6 { + public let key: String + public let tableName: String + public let locales: [String] + public let developmentValue: String + + public init(key: String, tableName: String, locales: [String], developmentValue: String) { + self.key = key + self.tableName = tableName + self.locales = locales + self.developmentValue = developmentValue + } + + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) -> String { + let format = NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") + return String(format: format, arguments: [arg1, arg2, arg3, arg4, arg5, arg6]) + } +} + +public struct StringResource7 { + public let key: String + public let tableName: String + public let locales: [String] + public let developmentValue: String + + public init(key: String, tableName: String, locales: [String], developmentValue: String) { + self.key = key + self.tableName = tableName + self.locales = locales + self.developmentValue = developmentValue + } + + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7) -> String { + let format = NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") + return String(format: format, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7]) + } +} + +public struct StringResource8 { + public let key: String + public let tableName: String + public let locales: [String] + public let developmentValue: String + + public init(key: String, tableName: String, locales: [String], developmentValue: String) { + self.key = key + self.tableName = tableName + self.locales = locales + self.developmentValue = developmentValue + } + + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8) -> String { + let format = NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") + return String(format: format, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8]) + } +} + +public struct StringResource9 { + public let key: String + public let tableName: String + public let locales: [String] + public let developmentValue: String + + public init(key: String, tableName: String, locales: [String], developmentValue: String) { + self.key = key + self.tableName = tableName + self.locales = locales + self.developmentValue = developmentValue + } + + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8, _ arg9: Arg9) -> String { + let format = NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") + return String(format: format, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9]) + } +} From 8bbaec8b7e308fdabe127ee8ccb6cd9a61f591a7 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 29 Jul 2022 14:28:30 +0200 Subject: [PATCH 051/161] Use localeDescription instead of language --- .../LocalizableStrings+Generator.swift | 10 ++++------ Sources/RswiftResources/Shared/LocaleReference.swift | 8 -------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift index 5d3e276c..e4a4521f 100644 --- a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift +++ b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift @@ -44,9 +44,7 @@ extension LocalizableStrings { let strings = computeStringsWithParams(filename: filename, resources: resources, developmentLanguage: developmentLanguage, warning: warning) let letbindings = strings.map { $0.generateLetBinding() } - let functions = strings - .filter { $0.params.count > 0 } - .map { $0.generateFunction() } + let functions = strings.filter { $0.params.count > 0 }.map { $0.generateFunction() } let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) localization keys."] @@ -63,7 +61,7 @@ extension LocalizableStrings { let primaryLanguage: String let primaryKeys: Set? let bases = resources.filter { $0.locale.isBase } - let developments = resources.filter { $0.locale.language == developmentLanguage } + let developments = resources.filter { $0.locale.localeDescription == developmentLanguage } if !bases.isEmpty { primaryKeys = Set(bases.flatMap { $0.dictionary.keys }) @@ -226,7 +224,7 @@ private struct StringWithParams { } private var letValueCodeString: String { - #"\#(typeName)(key: "\#(key.escapedStringLiteral)", tableName: "\#(tableName)", locales: \#(values.compactMap(\.0.language)), developmentValue: "\#(developmentValue)")"# + #"\#(typeName)(key: "\#(key.escapedStringLiteral)", tableName: "\#(tableName)", locales: \#(values.compactMap(\.0.localeDescription)), developmentValue: "\#(developmentValue)")"# } private var funcBodyCodeString: String { @@ -250,7 +248,7 @@ private struct StringWithParams { } private var primaryLanguageValues: [(LocaleReference, String)] { - return values.filter { $0.0.isBase } + values.filter { $0.0.language == developmentLanguage } + return values.filter { $0.0.isBase } + values.filter { $0.0.localeDescription == developmentLanguage } } private var comments: [String] { diff --git a/Sources/RswiftResources/Shared/LocaleReference.swift b/Sources/RswiftResources/Shared/LocaleReference.swift index bc0c4f66..8caf66ff 100644 --- a/Sources/RswiftResources/Shared/LocaleReference.swift +++ b/Sources/RswiftResources/Shared/LocaleReference.swift @@ -29,14 +29,6 @@ public enum LocaleReference: Hashable { return false } - - public var language: String? { - if case .language(let language) = self { - return language - } - - return nil - } } extension LocaleReference { From 5200f3c0b1489b75e20ca73aa7ad2b9cc7ebb20e Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 29 Jul 2022 21:57:08 +0200 Subject: [PATCH 052/161] Work on String generation --- Sources/RswiftCore/RswiftCore.swift | 4 +- .../LocalizableStrings+Generator.swift | 48 ++-- .../RswiftGenerators/SwiftSyntax/Struct.swift | 55 ++++- .../SwiftSyntax/StructMembersBuilder.swift | 12 + Sources/RswiftParsers/Shared/Xcodeproj.swift | 4 +- .../Shared/StringResource.swift | 206 +++++++++++------- 6 files changed, 225 insertions(+), 104 deletions(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 75f1416c..01d5cf67 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -123,12 +123,12 @@ public struct RswiftCore { let stringStruct = LocalizableStrings.generateStruct( resources: project.localizableStrings, - developmentLanguage: project.xcodeproj.developmentLanguage, + developmentLanguage: project.xcodeproj.developmentRegion, prefix: qualifiedName ) let projectStruct = Struct(name: SwiftIdentifier(name: "project")) { - LetBinding(name: SwiftIdentifier(name: "developmentLanguage"), valueCodeString: #""\#(project.xcodeproj.developmentLanguage)""#) + LetBinding(name: SwiftIdentifier(name: "developmentRegion"), valueCodeString: #""\#(project.xcodeproj.developmentRegion)""#) if let knownAssetTags = project.xcodeproj.knownAssetTags { LetBinding(name: SwiftIdentifier(name: "knownAssetTags"), valueCodeString: "\(knownAssetTags)") diff --git a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift index e4a4521f..70d4584c 100644 --- a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift +++ b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift @@ -43,13 +43,13 @@ extension LocalizableStrings { let qualifiedName = prefix + structName let strings = computeStringsWithParams(filename: filename, resources: resources, developmentLanguage: developmentLanguage, warning: warning) - let letbindings = strings.map { $0.generateLetBinding() } + let vargetters = strings.map { $0.generateVarGetter() } let functions = strings.filter { $0.params.count > 0 }.map { $0.generateFunction() } - let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) localization keys."] + let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(vargetters.count) localization keys."] return Struct(comments: comments, name: structName) { - letbindings + vargetters functions } } @@ -201,19 +201,19 @@ private struct StringWithParams { let values: [(LocaleReference, String)] let developmentLanguage: String - func generateLetBinding() -> LetBinding { - LetBinding( + func generateVarGetter() -> VarGetter { + VarGetter( comments: self.comments, - isStatic: true, + isLazy: true, name: SwiftIdentifier(name: key), - valueCodeString: letValueCodeString + typeReference: typeReference, + valueCodeString: varValueCodeString ) } func generateFunction() -> Function { Function( comments: self.comments, - isStatic: true, name: SwiftIdentifier(name: key), params: zip(params.indices, params).map { (ix, p) in .init(name: p.name ?? "_", localName: "value\(ix + 1)", typeReference: p.spec.typeReference, defaultValue: nil) @@ -223,32 +223,36 @@ private struct StringWithParams { ) } - private var letValueCodeString: String { - #"\#(typeName)(key: "\#(key.escapedStringLiteral)", tableName: "\#(tableName)", locales: \#(values.compactMap(\.0.localeDescription)), developmentValue: "\#(developmentValue)")"# + + private var varValueCodeString: String { + #".init(key: "\#(key.escapedStringLiteral)", tableName: "\#(tableName)", bundle: _bundle, locale: _locale, defaultValue: "\#(fallbackValue.escapedStringLiteral)", comment: nil)"# } private var funcBodyCodeString: String { - - var args = params.indices.map { "value\($0 + 1)" } - args.insert("format: format", at: 0) + let ps = params.indices.map { "value\($0 + 1)" } + let args = ["format: format", "locale: _locale"] + ps return """ - let format = NSLocalizedString("\(key.escapedStringLiteral)", tableName: "\(tableName)", comment: "") + let format = NSLocalizedString("\(key.escapedStringLiteral)", tableName: "\(tableName)", bundle: _bundle, value: "\(fallbackValue.escapedStringLiteral)", comment: "") return String(\(args.joined(separator: ", "))) """ } + private var typeReference: TypeReference { + TypeReference(module: .host, name: "StringResource\(params.isEmpty ? "" : "\(params.count)")", genericArgs: params.map(\.spec.typeReference)) + } + private var typeName: String { "StringResource" + (params.isEmpty ? "" : "\(params.count)<\(params.map(\.spec.typeReference.rawName).joined(separator: ", "))>") } - private var developmentValue: String { - values.first(where: { $0.0.localeDescription == developmentLanguage })?.1 ?? "" + private var primaryLanguageValues: [(LocaleReference, String)] { + values.filter { $0.0.isBase } + values.filter { $0.0.localeDescription == developmentLanguage } } - private var primaryLanguageValues: [(LocaleReference, String)] { - return values.filter { $0.0.isBase } + values.filter { $0.0.localeDescription == developmentLanguage } + private var fallbackValue: String { + (primaryLanguageValues + values).first?.1 ?? "" } private var comments: [String] { @@ -257,6 +261,7 @@ private struct StringWithParams { let anyNone = values.contains { $0.0.isNone } let vs = primaryLanguageValues + values + // Value if let (locale, value) = vs.first { if let localeDescription = locale.localeDescription { let str = "\(localeDescription) translation: \(value)".commentString @@ -268,6 +273,13 @@ private struct StringWithParams { } } + // Key + if !results.isEmpty { + results.append("") + } + results.append("Key: \(key)".commentString) + + // Locales if !anyNone { if !results.isEmpty { results.append("") diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index 48229f45..fa8d920a 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -68,6 +68,41 @@ public struct LetBinding { } } +public struct VarGetter { + public let comments: [String] + public var accessControl = AccessControl.none + public let name: SwiftIdentifier + public let typeReference: TypeReference + public let valueCodeString: String + + public init(comments: [String] = [], accessControl: AccessControl = AccessControl.none, isLazy: Bool = false, name: SwiftIdentifier, typeReference: TypeReference, valueCodeString: String) { + self.comments = comments + self.accessControl = accessControl + self.name = name + self.typeReference = typeReference + self.valueCodeString = valueCodeString + } + + func render(_ pp: inout PrettyPrinter) { + let words: [String?] = [ + accessControl.code(), + "var", + "\(name.value):", + typeReference.codeString(), + "{", + ] + + for c in comments { + pp.append(words: ["///", c == "" ? nil : c]) + } + pp.append(words: words) + pp.indented { pp in + pp.append(line: valueCodeString) + } + pp.append(line: "}") + } +} + public struct Function { public let comments: [String] @@ -78,7 +113,7 @@ public struct Function { public let returnType: TypeReference public let valueCodeString: String - public init(comments: [String], accessControl: AccessControl = AccessControl.none, isStatic: Bool, name: SwiftIdentifier, params: [Parameter], returnType: TypeReference, valueCodeString: String) { + public init(comments: [String], accessControl: AccessControl = AccessControl.none, isStatic: Bool = false, name: SwiftIdentifier, params: [Parameter], returnType: TypeReference, valueCodeString: String) { self.comments = comments self.accessControl = accessControl self.isStatic = isStatic @@ -137,6 +172,7 @@ public struct Struct { public let name: SwiftIdentifier public var protocols: [TypeReference] = [] public var lets: [LetBinding] = [] + public var vars: [VarGetter] = [] public var funcs: [Function] = [] public var structs: [Struct] = [] @@ -155,8 +191,10 @@ public struct Struct { self.accessControl = accessControl self.name = name self.protocols = protocols + let members = membersBuilder() self.lets = members.lets + self.vars = members.vars self.funcs = members.funcs self.structs = members.structs } @@ -185,7 +223,20 @@ public struct Struct { } } - if !lets.isEmpty && !funcs.isEmpty { + if !lets.isEmpty && !vars.isEmpty { + pp.append(line: "") + } + + pp.indented { pp in + for varb in vars { + if !varb.comments.isEmpty { + pp.append(line: "") + } + varb.render(&pp) + } + } + + if !vars.isEmpty && !funcs.isEmpty { pp.append(line: "") } diff --git a/Sources/RswiftGenerators/SwiftSyntax/StructMembersBuilder.swift b/Sources/RswiftGenerators/SwiftSyntax/StructMembersBuilder.swift index 7c9d1a49..f24bbbf1 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/StructMembersBuilder.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/StructMembersBuilder.swift @@ -9,12 +9,14 @@ import Foundation public struct StructMembers { var lets: [LetBinding] = [] + var vars: [VarGetter] = [] var funcs: [Function] = [] var structs: [Struct] = [] func sorted() -> StructMembers { var new = self new.lets.sort { $0.name < $1.name } + new.vars.sort { $0.name < $1.name } new.funcs.sort { $0.name < $1.name } new.structs.sort { $0.name < $1.name } return new @@ -35,6 +37,14 @@ public struct StructMembersBuilder { StructMembers(lets: expressions) } + public static func buildExpression(_ expression: VarGetter) -> StructMembers { + StructMembers(vars: [expression]) + } + + public static func buildExpression(_ expressions: [VarGetter]) -> StructMembers { + StructMembers(vars: expressions) + } + public static func buildExpression(_ expression: Function) -> StructMembers { StructMembers(funcs: [expression]) } @@ -58,6 +68,7 @@ public struct StructMembersBuilder { public static func buildArray(_ members: [StructMembers]) -> StructMembers { StructMembers( lets: members.flatMap(\.lets), + vars: members.flatMap(\.vars), funcs: members.flatMap(\.funcs), structs: members.flatMap(\.structs) ) @@ -66,6 +77,7 @@ public struct StructMembersBuilder { public static func buildBlock(_ members: StructMembers...) -> StructMembers { StructMembers( lets: members.flatMap(\.lets), + vars: members.flatMap(\.vars), funcs: members.flatMap(\.funcs), structs: members.flatMap(\.structs) ) diff --git a/Sources/RswiftParsers/Shared/Xcodeproj.swift b/Sources/RswiftParsers/Shared/Xcodeproj.swift index 39ddf984..eb9dd5d8 100644 --- a/Sources/RswiftParsers/Shared/Xcodeproj.swift +++ b/Sources/RswiftParsers/Shared/Xcodeproj.swift @@ -15,7 +15,7 @@ public struct Xcodeproj: SupportedExtensions { private let projectFile: XCProjectFile - public let developmentLanguage: String + public let developmentRegion: String public let knownAssetTags: [String]? public init(url: URL, warning: (String) -> Void) throws { @@ -38,7 +38,7 @@ public struct Xcodeproj: SupportedExtensions { } self.projectFile = projectFile - self.developmentLanguage = projectFile.project.developmentRegion + self.developmentRegion = projectFile.project.developmentRegion self.knownAssetTags = projectFile.project.knownAssetTags } diff --git a/Sources/RswiftResources/Shared/StringResource.swift b/Sources/RswiftResources/Shared/StringResource.swift index 147ab466..b8b96a9a 100644 --- a/Sources/RswiftResources/Shared/StringResource.swift +++ b/Sources/RswiftResources/Shared/StringResource.swift @@ -1,5 +1,5 @@ // -// File.swift +// StringResource.swift // // // Created by Tom Lokhorst on 2022-07-24. @@ -8,190 +8,236 @@ import Foundation public struct StringResource { - public let key: String + public let key: StaticString public let tableName: String - public let locales: [String] - public let developmentValue: String + public let bundle: Bundle + public let locale: Locale + public let defaultValue: String + public let comment: StaticString? - public init(key: String, tableName: String, locales: [String], developmentValue: String) { + public init(key: StaticString, tableName: String, bundle: Bundle, locale: Locale, defaultValue: String, comment: StaticString?) { self.key = key self.tableName = tableName - self.locales = locales - self.developmentValue = developmentValue + self.bundle = bundle + self.locale = locale + self.defaultValue = defaultValue + self.comment = comment } public func callAsFunction() -> String { - NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") +// bundle.localizedString(forKey: key, value: defaultValue, table: table) + NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + } + + @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) + public var localizedStringResource: LocalizedStringResource { + LocalizedStringResource(key, defaultValue: String.LocalizationValue(stringLiteral: defaultValue), bundle: bundle == .main ? .main : .atURL(bundle.bundleURL), comment: comment) } } public struct StringResource1 { - public let key: String + public let key: StaticString public let tableName: String - public let locales: [String] - public let developmentValue: String + public let bundle: Bundle + public let locale: Locale + public let defaultValue: String + public let comment: StaticString? - public init(key: String, tableName: String, locales: [String], developmentValue: String) { + public init(key: StaticString, tableName: String, bundle: Bundle, locale: Locale, defaultValue: String, comment: StaticString?) { self.key = key self.tableName = tableName - self.locales = locales - self.developmentValue = developmentValue + self.bundle = bundle + self.locale = locale + self.defaultValue = defaultValue + self.comment = comment } public func callAsFunction(_ arg1: Arg1) -> String { - let format = NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") - return String(format: format, arguments: [arg1]) + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1]) } } public struct StringResource2 { - public let key: String + public let key: StaticString public let tableName: String - public let locales: [String] - public let developmentValue: String + public let bundle: Bundle + public let locale: Locale + public let defaultValue: String + public let comment: StaticString? - public init(key: String, tableName: String, locales: [String], developmentValue: String) { + public init(key: StaticString, tableName: String, bundle: Bundle, locale: Locale, defaultValue: String, comment: StaticString?) { self.key = key self.tableName = tableName - self.locales = locales - self.developmentValue = developmentValue + self.bundle = bundle + self.locale = locale + self.defaultValue = defaultValue + self.comment = comment } public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2) -> String { - let format = NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") - return String(format: format, arguments: [arg1, arg2]) + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2]) } } public struct StringResource3 { - public let key: String + public let key: StaticString public let tableName: String - public let locales: [String] - public let developmentValue: String + public let bundle: Bundle + public let locale: Locale + public let defaultValue: String + public let comment: StaticString? - public init(key: String, tableName: String, locales: [String], developmentValue: String) { + public init(key: StaticString, tableName: String, bundle: Bundle, locale: Locale, defaultValue: String, comment: StaticString?) { self.key = key self.tableName = tableName - self.locales = locales - self.developmentValue = developmentValue + self.bundle = bundle + self.locale = locale + self.defaultValue = defaultValue + self.comment = comment } public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) -> String { - let format = NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") - return String(format: format, arguments: [arg1, arg2, arg3]) + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3]) } } public struct StringResource4 { - public let key: String + public let key: StaticString public let tableName: String - public let locales: [String] - public let developmentValue: String + public let bundle: Bundle + public let locale: Locale + public let defaultValue: String + public let comment: StaticString? - public init(key: String, tableName: String, locales: [String], developmentValue: String) { + public init(key: StaticString, tableName: String, bundle: Bundle, locale: Locale, defaultValue: String, comment: StaticString?) { self.key = key self.tableName = tableName - self.locales = locales - self.developmentValue = developmentValue + self.bundle = bundle + self.locale = locale + self.defaultValue = defaultValue + self.comment = comment } public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4) -> String { - let format = NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") - return String(format: format, arguments: [arg1, arg2, arg3, arg4]) + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4]) } } public struct StringResource5 { - public let key: String + public let key: StaticString public let tableName: String - public let locales: [String] - public let developmentValue: String + public let bundle: Bundle + public let locale: Locale + public let defaultValue: String + public let comment: StaticString? - public init(key: String, tableName: String, locales: [String], developmentValue: String) { + public init(key: StaticString, tableName: String, bundle: Bundle, locale: Locale, defaultValue: String, comment: StaticString?) { self.key = key self.tableName = tableName - self.locales = locales - self.developmentValue = developmentValue + self.bundle = bundle + self.locale = locale + self.defaultValue = defaultValue + self.comment = comment } public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5) -> String { - let format = NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") - return String(format: format, arguments: [arg1, arg2, arg3, arg4, arg5]) + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5]) } } public struct StringResource6 { - public let key: String + public let key: StaticString public let tableName: String - public let locales: [String] - public let developmentValue: String + public let bundle: Bundle + public let locale: Locale + public let defaultValue: String + public let comment: StaticString? - public init(key: String, tableName: String, locales: [String], developmentValue: String) { + public init(key: StaticString, tableName: String, bundle: Bundle, locale: Locale, defaultValue: String, comment: StaticString?) { self.key = key self.tableName = tableName - self.locales = locales - self.developmentValue = developmentValue + self.bundle = bundle + self.locale = locale + self.defaultValue = defaultValue + self.comment = comment } public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) -> String { - let format = NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") - return String(format: format, arguments: [arg1, arg2, arg3, arg4, arg5, arg6]) + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6]) } } public struct StringResource7 { - public let key: String + public let key: StaticString public let tableName: String - public let locales: [String] - public let developmentValue: String + public let bundle: Bundle + public let locale: Locale + public let defaultValue: String + public let comment: StaticString? - public init(key: String, tableName: String, locales: [String], developmentValue: String) { + public init(key: StaticString, tableName: String, bundle: Bundle, locale: Locale, defaultValue: String, comment: StaticString?) { self.key = key self.tableName = tableName - self.locales = locales - self.developmentValue = developmentValue + self.bundle = bundle + self.locale = locale + self.defaultValue = defaultValue + self.comment = comment } public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7) -> String { - let format = NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") - return String(format: format, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7]) + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7]) } } public struct StringResource8 { - public let key: String + public let key: StaticString public let tableName: String - public let locales: [String] - public let developmentValue: String + public let bundle: Bundle + public let locale: Locale + public let defaultValue: String + public let comment: StaticString? - public init(key: String, tableName: String, locales: [String], developmentValue: String) { + public init(key: StaticString, tableName: String, bundle: Bundle, locale: Locale, defaultValue: String, comment: StaticString?) { self.key = key self.tableName = tableName - self.locales = locales - self.developmentValue = developmentValue + self.bundle = bundle + self.locale = locale + self.defaultValue = defaultValue + self.comment = comment } public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8) -> String { - let format = NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") - return String(format: format, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8]) + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8]) } } public struct StringResource9 { - public let key: String + public let key: StaticString public let tableName: String - public let locales: [String] - public let developmentValue: String + public let bundle: Bundle + public let locale: Locale + public let defaultValue: String + public let comment: StaticString? - public init(key: String, tableName: String, locales: [String], developmentValue: String) { + public init(key: StaticString, tableName: String, bundle: Bundle, locale: Locale, defaultValue: String, comment: StaticString?) { self.key = key self.tableName = tableName - self.locales = locales - self.developmentValue = developmentValue + self.bundle = bundle + self.locale = locale + self.defaultValue = defaultValue + self.comment = comment } public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8, _ arg9: Arg9) -> String { - let format = NSLocalizedString(key, tableName: tableName, bundle: Bundle.main, value: developmentValue, comment: "Comment") - return String(format: format, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9]) + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9]) } } From 7edea66037a4b2a8dee1cedc908549c28aac151a Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 29 Jul 2022 23:54:19 +0200 Subject: [PATCH 053/161] Work on custom bundle --- Sources/RswiftCore/RswiftCore.swift | 14 +- .../LocalizableStrings+Generator.swift | 48 ++++++- .../Shared/TypeReference+Generator.swift | 2 + .../RswiftGenerators/SwiftSyntax/Struct.swift | 135 ++++++++++++++++++ .../SwiftSyntax/StructMembersBuilder.swift | 11 ++ 5 files changed, 208 insertions(+), 2 deletions(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 01d5cf67..45fe1d9b 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -56,7 +56,7 @@ public struct RswiftCore { warning: { print("[warning]", $0) } ) - let structName = SwiftIdentifier(rawValue: "R") + let structName = SwiftIdentifier(rawValue: "_S") let qualifiedName = structName // let segueStruct = Segue.generateStruct(storyboards: storyboards, prefix: qualifiedName) @@ -136,12 +136,24 @@ public struct RswiftCore { } let s = Struct(name: structName) { + Init.bundle projectStruct + + stringStruct.generateBundleVarGetter(name: "string") + stringStruct.generateBundleFunction(name: "string") stringStruct } print(s.prettyPrint()) + print() + + print("let S = _S(bundle: Bundle.main)") + print("") + print("extension R {") + print(" static let string = S.string") + print("}") + print("TOTAL", Date().timeIntervalSince(start)) print() } diff --git a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift index 70d4584c..07664788 100644 --- a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift +++ b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift @@ -10,7 +10,7 @@ import RswiftResources extension LocalizableStrings { public static func generateStruct(resources: [LocalizableStrings], developmentLanguage: String, prefix: SwiftIdentifier) -> Struct { - let structName = SwiftIdentifier(name: "string") + let structName = SwiftIdentifier(name: "string", lowercaseStartingCharacters: false) let qualifiedName = prefix + structName let warning: (String) -> Void = { print("warning:", $0) } @@ -33,6 +33,12 @@ extension LocalizableStrings { let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(groupedLocalized.uniques.count) localization tables."] return Struct(comments: comments, name: structName) { + Init.bundle + for name in groupedLocalized.uniques.map(\.0) { + generateBundleLocaleVarGetter(name: SwiftIdentifier(name: name)) + generateBundleLocaleFunction(name: SwiftIdentifier(name: name)) + generatePreferredLanguagesFunction(name: SwiftIdentifier(name: name), tableName: name) + } structs } } @@ -49,11 +55,51 @@ extension LocalizableStrings { let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(vargetters.count) localization keys."] return Struct(comments: comments, name: structName) { + Init.bundleLocale vargetters functions } } + public static func generateBundleLocaleVarGetter(name: SwiftIdentifier) -> VarGetter { + VarGetter( + name: name, + typeReference: TypeReference(module: .host, rawName: name.value), + valueCodeString: ".init(bundle: _bundle, locale: _bundle.firstPreferredLocale)" + ) + } + + public static func generateBundleLocaleFunction(name: SwiftIdentifier) -> Function { + Function( + comments: [], + name: name, + params: [ + .init(name: "bundle", localName: nil, typeReference: .bundle, defaultValue: nil), + .init(name: "locale", localName: nil, typeReference: .locale, defaultValue: nil), + ], + returnType: TypeReference(module: .host, rawName: name.value), + valueCodeString: ".init(bundle: bundle, locale: locale)" + ) + } + + public static func generatePreferredLanguagesFunction(name: SwiftIdentifier, tableName: String) -> Function { + Function( + comments: [], + name: name, + params: [ + .init(name: "preferredLanguages", localName: nil, typeReference: TypeReference(module: .stdLib, rawName: "[String]"), defaultValue: nil), + ], + returnType: TypeReference(module: .host, rawName: name.value), + valueCodeString: """ + if let (bundle, locale) = _bundle.firstBundleAndLocale(tableName: "\(tableName.escapedStringLiteral)", preferredLanguages: preferredLanguages) { + return .init(bundle: bundle, locale: locale) + } else { + return .init(bundle: _bundle, locale: _bundle.firstPreferredLocale) + } + """ + ) + } + // Ahem, this code is a bit of a mess. It might need cleaning up... ;-) private static func computeStringsWithParams(filename: String, resources: [LocalizableStrings], developmentLanguage: String, warning: (String) -> Void) -> [StringWithParams] { diff --git a/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift b/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift index 002494f5..5485529b 100644 --- a/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift +++ b/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift @@ -26,6 +26,8 @@ extension TypeReference { } } + static var bundle: TypeReference = .init(module: .foundation, rawName: "Bundle") + static var locale: TypeReference = .init(module: .foundation, rawName: "Locale") static var string: TypeReference = .init(module: .stdLib, rawName: "String") static var uiView: TypeReference = .init(module: .uiKit, rawName: "UIView") static var uiViewController: TypeReference = .init(module: .uiKit, rawName: "UIViewController") diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index fa8d920a..3b80f6e8 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -129,6 +129,13 @@ public struct Function { public let typeReference: TypeReference public let defaultValue: String? + public init(name: String, localName: String?, typeReference: TypeReference, defaultValue: String?) { + self.name = name + self.localName = localName + self.typeReference = typeReference + self.defaultValue = defaultValue + } + func codeString() -> String { var result = name if let localName { @@ -166,6 +173,98 @@ public struct Function { } } + +public struct Init { + public let comments: [String] + public var accessControl = AccessControl.none + public let params: [Parameter] + public let valueCodeString: String + + public init(comments: [String], accessControl: AccessControl = AccessControl.none, params: [Parameter], valueCodeString: String) { + self.comments = comments + self.accessControl = accessControl + self.params = params + self.valueCodeString = valueCodeString + } + + public struct Parameter { + public let name: String + public let localName: String? + public let typeReference: TypeReference + public let defaultValue: String? + + func codeString() -> String { + var result = name + if let localName { + result += " \(localName)" + } + result += ": \(typeReference.codeString())" + if let defaultValue { + result += " = \(defaultValue)" + } + + return result + } + + var privateLetCodeString: String { + let words: [String] = [ + "private", + "let", + "\(localName ?? name):", + typeReference.codeString() + ] + + return words.joined(separator: " ") + } + } + + func render(_ pp: inout PrettyPrinter) { + + for param in params { + pp.append(line: param.privateLetCodeString) + } + + for c in comments { + pp.append(words: ["///", c == "" ? nil : c]) + } + + let prs = params.map { $0.codeString() }.joined(separator: ", ") + let words: [String?] = [ + accessControl.code(), + "init(\(prs))", + "{" + ] + + pp.append(words: words) + pp.indented { pp in + pp.append(line: valueCodeString) + } + pp.append(line: "}") + } + + public static var bundle: Init { + Init( + comments: [], + params: [Parameter(name: "bundle", localName: "_bundle", typeReference: .bundle, defaultValue: nil),], + valueCodeString: "self._bundle = _bundle" + ) + } + + public static var bundleLocale: Init { + Init( + comments: [], + params: [ + Parameter(name: "bundle", localName: "_bundle", typeReference: .bundle, defaultValue: nil), + Parameter(name: "locale", localName: "_locale", typeReference: .locale, defaultValue: nil), + ], + valueCodeString: """ + self._bundle = _bundle + self._locale = _locale + """ + ) + } +} + public struct Struct { public let comments: [String] public var accessControl = AccessControl.none @@ -173,6 +272,7 @@ public struct Struct { public var protocols: [TypeReference] = [] public var lets: [LetBinding] = [] public var vars: [VarGetter] = [] + public var inits: [Init] = [] public var funcs: [Function] = [] public var structs: [Struct] = [] @@ -195,6 +295,7 @@ public struct Struct { let members = membersBuilder() self.lets = members.lets self.vars = members.vars + self.inits = members.inits self.funcs = members.funcs self.structs = members.structs } @@ -214,6 +315,19 @@ public struct Struct { let implements = ps.isEmpty ? "" : ": \(ps)" pp.append(line: "struct \(name.value)\(implements) {") + pp.indented { pp in + for inib in inits { + if !inib.comments.isEmpty { + pp.append(line: "") + } + inib.render(&pp) + } + } + + if !inits.isEmpty && !lets.isEmpty { + pp.append(line: "") + } + pp.indented { pp in for letb in lets { if !letb.comments.isEmpty { @@ -299,3 +413,24 @@ struct PrettyPrinter { return ls.joined(separator: "\n") } } + + +extension Struct { + public func generateBundleVarGetter(name: String) -> VarGetter { + VarGetter( + name: SwiftIdentifier(name: name), + typeReference: TypeReference(module: .host, rawName: self.name.value), + valueCodeString: ".init(bundle: _bundle)" + ) + } + + public func generateBundleFunction(name: String) -> Function { + Function( + comments: [], + name: SwiftIdentifier(name: name), + params: [.init(name: "bundle", localName: nil, typeReference: .bundle, defaultValue: nil)], + returnType: TypeReference(module: .host, rawName: self.name.value), + valueCodeString: ".init(bundle: bundle)" + ) + } +} diff --git a/Sources/RswiftGenerators/SwiftSyntax/StructMembersBuilder.swift b/Sources/RswiftGenerators/SwiftSyntax/StructMembersBuilder.swift index f24bbbf1..1554bb6e 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/StructMembersBuilder.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/StructMembersBuilder.swift @@ -10,6 +10,7 @@ import Foundation public struct StructMembers { var lets: [LetBinding] = [] var vars: [VarGetter] = [] + var inits: [Init] = [] var funcs: [Function] = [] var structs: [Struct] = [] @@ -45,6 +46,14 @@ public struct StructMembersBuilder { StructMembers(vars: expressions) } + public static func buildExpression(_ expression: Init) -> StructMembers { + StructMembers(inits: [expression]) + } + + public static func buildExpression(_ expressions: [Init]) -> StructMembers { + StructMembers(inits: expressions) + } + public static func buildExpression(_ expression: Function) -> StructMembers { StructMembers(funcs: [expression]) } @@ -69,6 +78,7 @@ public struct StructMembersBuilder { StructMembers( lets: members.flatMap(\.lets), vars: members.flatMap(\.vars), + inits: members.flatMap(\.inits), funcs: members.flatMap(\.funcs), structs: members.flatMap(\.structs) ) @@ -78,6 +88,7 @@ public struct StructMembersBuilder { StructMembers( lets: members.flatMap(\.lets), vars: members.flatMap(\.vars), + inits: members.flatMap(\.inits), funcs: members.flatMap(\.funcs), structs: members.flatMap(\.structs) ) From dc9c81c0bba2e157c56c5d22d03ce3e848870b32 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sat, 30 Jul 2022 18:08:29 +0200 Subject: [PATCH 054/161] Add integrations for Image/String --- Package.swift | 11 +- Sources/RswiftCore/RswiftCore.swift | 29 ++-- .../AssetCatalog+Generator.swift | 8 +- .../Shared/LocaleReference+Generator.swift | 2 +- .../Resources/AssetCatalog+Parser.swift | 10 +- .../Resources/ImageResource+Parser.swift | 2 +- Sources/RswiftResources/ImageResource.swift | 4 +- .../ImageResource+Integrations.swift | 73 ++++++++++ .../StringResource+Integrations.swift | 134 ++++++++++++++++++ .../Shared/StringResource.swift | 55 ------- 10 files changed, 242 insertions(+), 86 deletions(-) create mode 100644 Sources/RswiftResources/Integrations/ImageResource+Integrations.swift create mode 100644 Sources/RswiftResources/Integrations/StringResource+Integrations.swift diff --git a/Package.swift b/Package.swift index d94987b7..5b6c8752 100644 --- a/Package.swift +++ b/Package.swift @@ -6,25 +6,22 @@ let package = Package( platforms: [ .macOS(.v10_15), .iOS(.v11), + .tvOS(.v11), + .watchOS(.v4), ], products: [ .executable(name: "rswift", targets: ["rswift"]), .executable(name: "rswift-legacy", targets: ["rswift-legacy"]), - .library(name: "RswiftCombined", targets: ["RswiftResources", "RswiftGenerators"]) + .library(name: "RswiftCombined", targets: ["RswiftResources"]) ], dependencies: [ .package(url: "https://github.com/kylef/Commander.git", from: "0.8.0"), .package(url: "https://github.com/tomlokhorst/XcodeEdit", from: "2.8.0"), -// .package(url: "https://github.com/apple/swift-syntax.git", .branchItem("swift-5.6-RELEASE")), ], targets: [ .target(name: "RswiftResources"), + .target(name: "RswiftGenerators", dependencies: ["RswiftResources"]), .target(name: "RswiftParsers", dependencies: ["RswiftResources", "XcodeEdit"]), - .target(name: "RswiftGenerators", dependencies: [ - "RswiftResources", -// .product(name: "SwiftSyntax", package: "swift-syntax"), -// .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), - ]), // Core of R.swift, brings all previous parts together .target(name: "RswiftCore", dependencies: ["RswiftParsers", "RswiftGenerators"]), diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 45fe1d9b..03fb43e7 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -61,11 +61,11 @@ public struct RswiftCore { // let segueStruct = Segue.generateStruct(storyboards: storyboards, prefix: qualifiedName) -// let imageStruct = ImageResource.generateStruct( -// catalogs: assetCatalogs, -// toplevel: images, -// prefix: qualifiedName -// ) + let imageStruct = ImageResource.generateStruct( + catalogs: project.assetCatalogs, + toplevel: project.images, + prefix: qualifiedName + ) // let colorStruct = ColorResource.generateStruct( // catalogs: assetCatalogs, // prefix: qualifiedName @@ -121,11 +121,11 @@ public struct RswiftCore { // prefix: qualifiedName // ) - let stringStruct = LocalizableStrings.generateStruct( - resources: project.localizableStrings, - developmentLanguage: project.xcodeproj.developmentRegion, - prefix: qualifiedName - ) +// let stringStruct = LocalizableStrings.generateStruct( +// resources: project.localizableStrings, +// developmentLanguage: project.xcodeproj.developmentRegion, +// prefix: qualifiedName +// ) let projectStruct = Struct(name: SwiftIdentifier(name: "project")) { LetBinding(name: SwiftIdentifier(name: "developmentRegion"), valueCodeString: #""\#(project.xcodeproj.developmentRegion)""#) @@ -139,9 +139,9 @@ public struct RswiftCore { Init.bundle projectStruct - stringStruct.generateBundleVarGetter(name: "string") - stringStruct.generateBundleFunction(name: "string") - stringStruct + imageStruct.generateBundleVarGetter(name: "image") + imageStruct.generateBundleFunction(name: "image") + imageStruct } print(s.prettyPrint()) @@ -151,7 +151,8 @@ public struct RswiftCore { print("let S = _S(bundle: Bundle.main)") print("") print("extension R {") - print(" static let string = S.string") +// print(" static let string = S.string") + print(" static let image = S.image") print("}") print("TOTAL", Date().timeIntervalSince(start)) diff --git a/Sources/RswiftGenerators/AssetCatalog+Generator.swift b/Sources/RswiftGenerators/AssetCatalog+Generator.swift index f0f3d64d..f285e8d4 100644 --- a/Sources/RswiftGenerators/AssetCatalog+Generator.swift +++ b/Sources/RswiftGenerators/AssetCatalog+Generator.swift @@ -63,7 +63,7 @@ extension AssetCatalog.Namespace { .sorted { $0.key < $1.key } .map { (name, namespace) in namespace.generateStruct( - resourceName: resourceName, + resourceName: name.value, resourcesSelector: resourcesSelector, prefix: qualifiedName ) @@ -78,6 +78,7 @@ extension AssetCatalog.Namespace { let comments = [comment] return Struct(comments: comments, name: structName) { + Init.bundle letbindings structs } @@ -90,7 +91,6 @@ extension ColorResource: AssetCatalogContent { let code = "ColorResource(name: \"\(fullname)\")" return LetBinding( comments: ["Color `\(fullname)`."], - isStatic: true, name: SwiftIdentifier(name: name), valueCodeString: code ) @@ -103,7 +103,6 @@ extension DataResource: AssetCatalogContent { let code = "DataResource(name: \"\(fullname)\")" return LetBinding( comments: ["Data asset `\(fullname)`."], - isStatic: true, name: SwiftIdentifier(name: name), valueCodeString: code ) @@ -115,10 +114,9 @@ extension ImageResource: AssetCatalogContent { let locs = locale.map { $0.codeString() } ?? "nil" let odrt = onDemandResourceTags?.debugDescription ?? "nil" let fullname = (path + [name]).joined(separator: "/") - let code = "ImageResource(name: \"\(fullname)\", locale: \(locs), onDemandResourceTags: \(odrt))" + let code = "ImageResource(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: nil, locale: \(locs), onDemandResourceTags: \(odrt))" return LetBinding( comments: ["Image `\(fullname)`."], - isStatic: true, name: SwiftIdentifier(name: name), valueCodeString: code ) diff --git a/Sources/RswiftGenerators/Shared/LocaleReference+Generator.swift b/Sources/RswiftGenerators/Shared/LocaleReference+Generator.swift index 09c54bc1..e5fab516 100644 --- a/Sources/RswiftGenerators/Shared/LocaleReference+Generator.swift +++ b/Sources/RswiftGenerators/Shared/LocaleReference+Generator.swift @@ -11,7 +11,7 @@ extension LocaleReference { func codeString() -> String { switch self { case .none: - return ".none" + return "LocaleReference.none" // Plain `.none` is ambiguous whith Optional case .base: return ".base" case .language(let string): diff --git a/Sources/RswiftParsers/Resources/AssetCatalog+Parser.swift b/Sources/RswiftParsers/Resources/AssetCatalog+Parser.swift index b58d7bdd..2a34a807 100644 --- a/Sources/RswiftParsers/Resources/AssetCatalog+Parser.swift +++ b/Sources/RswiftParsers/Resources/AssetCatalog+Parser.swift @@ -41,7 +41,13 @@ extension AssetCatalog: SupportedExtensions { assertionFailure((error as NSError).debugDescription) return true } - guard let directoryEnumerator = fileManager.enumerator(at: catalogURL, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles, .producesRelativePathURLs], errorHandler: errorHandler) else { + let options: FileManager.DirectoryEnumerationOptions + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) { + options = [.skipsHiddenFiles, .producesRelativePathURLs] + } else { + options = [.skipsHiddenFiles] + } + guard let directoryEnumerator = fileManager.enumerator(at: catalogURL, includingPropertiesForKeys: [.isDirectoryKey], options: options, errorHandler: errorHandler) else { throw ResourceParsingError("Supposed AssetCatalog \(catalogURL) can't be enumerated") } @@ -115,7 +121,7 @@ extension AssetCatalog: SupportedExtensions { for fileURL in directory.images { let name = fileURL.filenameWithoutExtension! let tags = parseOnDemandResourceTags(directory: fileURL) - images.append(.init(name: name, path: path, locale: nil, onDemandResourceTags: tags)) + images.append(.init(name: name, path: path, bundle: nil, locale: nil, onDemandResourceTags: tags)) } var dataAssets: [DataResource] = [] diff --git a/Sources/RswiftParsers/Resources/ImageResource+Parser.swift b/Sources/RswiftParsers/Resources/ImageResource+Parser.swift index 99092689..6c5649d8 100644 --- a/Sources/RswiftParsers/Resources/ImageResource+Parser.swift +++ b/Sources/RswiftParsers/Resources/ImageResource+Parser.swift @@ -31,6 +31,6 @@ extension ImageResource: SupportedExtensions { let pathExtensionToUse = (pathExtension == "png") ? "" : ".\(pathExtension)" let name = regex.stringByReplacingMatches(in: filename, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: fullFileNameRange, withTemplate: pathExtensionToUse) - return ImageResource(name: name, path: [], locale: locale, onDemandResourceTags: assetTags) + return ImageResource(name: name, path: [], bundle: nil, locale: locale, onDemandResourceTags: assetTags) } } diff --git a/Sources/RswiftResources/ImageResource.swift b/Sources/RswiftResources/ImageResource.swift index 567ffebb..f62b88b7 100644 --- a/Sources/RswiftResources/ImageResource.swift +++ b/Sources/RswiftResources/ImageResource.swift @@ -12,12 +12,14 @@ import Foundation public struct ImageResource { public let name: String public let path: [String] + public let bundle: Bundle? public let locale: LocaleReference? public let onDemandResourceTags: [String]? - public init(name: String, path: [String], locale: LocaleReference?, onDemandResourceTags: [String]?) { + public init(name: String, path: [String], bundle: Bundle?, locale: LocaleReference?, onDemandResourceTags: [String]?) { self.name = name self.path = path + self.bundle = bundle self.locale = locale self.onDemandResourceTags = onDemandResourceTags } diff --git a/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift b/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift new file mode 100644 index 00000000..6da9260f --- /dev/null +++ b/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift @@ -0,0 +1,73 @@ +// +// ImageResource+Integrations.swift +// +// +// Created by Tom Lokhorst on 2022-07-30. +// + +import Foundation +import SwiftUI + +#if canImport(UIKit) +extension ImageResource { + + @available(*, deprecated, message: "Use UIImage(resource:) initializer instead") + public func callAsFunction(compatibleWith traitCollection: UITraitCollection? = nil) -> UIImage? { + UIImage(named: name, in: bundle, compatibleWith: traitCollection) + } +} +#endif + + +@available(iOS 13, tvOS 13, watchOS 6, *) +extension Image { + + public init(_ resource: ImageResource) { + self.init(resource.name, bundle: resource.bundle) + } + + @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) + public init(_ resource: ImageResource, variableValue: Double?) { + self.init(resource.name, variableValue: variableValue, bundle: resource.bundle) + } + + public init(_ resource: ImageResource, label: Text) { + self.init(resource.name, bundle: resource.bundle, label: label) + } + + @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) + public init(_ resource: ImageResource, variableValue: Double?, label: Text) { + self.init(resource.name, variableValue: variableValue, bundle: resource.bundle, label: label) + } + + public init(decorative resource: ImageResource) { + self.init(decorative: resource.name, bundle: resource.bundle) + } + + @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) + public init(decorative resource: ImageResource, variableValue: Double?) { + self.init(decorative: resource.name, variableValue: variableValue, bundle: resource.bundle) + } +} + + +#if canImport(UIKit) +import UIKit + +extension UIImage { + + public convenience init?(resource: ImageResource, compatibleWith traitCollection: UITraitCollection? = nil) { + self.init(named: resource.name, in: resource.bundle, compatibleWith: traitCollection) + } + + @available(iOS 13, tvOS 13, watchOS 6, *) + public convenience init?(resource: ImageResource, with configuration: UIImage.Configuration?) { + self.init(named: resource.name, in: resource.bundle, with: configuration) + } + + @available(iOS 16, tvOS 16, watchOS 9, *) + public convenience init?(resource: ImageResource, variableValue: Double, with configuration: UIImage.Configuration? = nil) { + self.init(named: resource.name, in: resource.bundle, variableValue: variableValue, configuration: configuration) + } +} +#endif diff --git a/Sources/RswiftResources/Integrations/StringResource+Integrations.swift b/Sources/RswiftResources/Integrations/StringResource+Integrations.swift new file mode 100644 index 00000000..65216703 --- /dev/null +++ b/Sources/RswiftResources/Integrations/StringResource+Integrations.swift @@ -0,0 +1,134 @@ +// +// StringResource+Integrations.swift +// +// +// Created by Tom Lokhorst on 2022-07-30. +// + +import Foundation +import SwiftUI + +extension String { + public init(resource: StringResource) { + self = resource.callAsFunction() + } +} + + +@available(macOS 10, iOS 13, tvOS 13, watchOS 6, *) +extension Text { + public init(_ resource: StringResource) { + self.init(resource.callAsFunction()) + } + + public init(_ resource: StringResource1, _ arg1: Arg1) { + self.init(resource.callAsFunction(arg1)) + } + + public init(_ resource: StringResource2, _ arg1: Arg1, _ arg2: Arg2) { + self.init(resource.callAsFunction(arg1, arg2)) + } + + public init(_ resource: StringResource3, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) { + self.init(resource.callAsFunction(arg1, arg2, arg3)) + } + + public init(_ resource: StringResource4, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4) { + self.init(resource.callAsFunction(arg1, arg2, arg3, arg4)) + } + + public init(_ resource: StringResource5, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5) { + self.init(resource.callAsFunction(arg1, arg2, arg3, arg4, arg5)) + } + + public init(_ resource: StringResource6, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) { + self.init(resource.callAsFunction(arg1, arg2, arg3, arg4, arg5, arg6)) + } + + public init(_ resource: StringResource7, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7) { + self.init(resource.callAsFunction(arg1, arg2, arg3, arg4, arg5, arg6, arg7)) + } + + public init(_ resource: StringResource8, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8) { + self.init(resource.callAsFunction(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)) + } + + public init(_ resource: StringResource9, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8, _ arg9: Arg9) { + self.init(resource.callAsFunction(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)) + } +} + +extension StringResource { + public func callAsFunction() -> String { +// bundle.localizedString(forKey: key, value: defaultValue, table: table) + NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + } + + @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) + public var localizedStringResource: LocalizedStringResource { + LocalizedStringResource(key, defaultValue: String.LocalizationValue(stringLiteral: defaultValue), bundle: bundle == .main ? .main : .atURL(bundle.bundleURL), comment: comment) + } +} + +extension StringResource1 { + public func callAsFunction(_ arg1: Arg1) -> String { + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1]) + } +} + +extension StringResource2 { + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2) -> String { + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2]) + } +} + +extension StringResource3 { + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) -> String { + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3]) + } +} + +extension StringResource4 { + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4) -> String { + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4]) + } +} + +extension StringResource5 { + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5) -> String { + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5]) + } +} + +extension StringResource6 { + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) -> String { + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6]) + } +} + +extension StringResource7 { + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7) -> String { + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7]) + } +} + +extension StringResource8 { + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8) -> String { + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8]) + } +} + +extension StringResource9 { + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8, _ arg9: Arg9) -> String { + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9]) + } +} diff --git a/Sources/RswiftResources/Shared/StringResource.swift b/Sources/RswiftResources/Shared/StringResource.swift index b8b96a9a..bb50f035 100644 --- a/Sources/RswiftResources/Shared/StringResource.swift +++ b/Sources/RswiftResources/Shared/StringResource.swift @@ -23,16 +23,6 @@ public struct StringResource { self.defaultValue = defaultValue self.comment = comment } - - public func callAsFunction() -> String { -// bundle.localizedString(forKey: key, value: defaultValue, table: table) - NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") - } - - @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) - public var localizedStringResource: LocalizedStringResource { - LocalizedStringResource(key, defaultValue: String.LocalizationValue(stringLiteral: defaultValue), bundle: bundle == .main ? .main : .atURL(bundle.bundleURL), comment: comment) - } } public struct StringResource1 { @@ -51,11 +41,6 @@ public struct StringResource1 { self.defaultValue = defaultValue self.comment = comment } - - public func callAsFunction(_ arg1: Arg1) -> String { - let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") - return String(format: format, locale: locale, arguments: [arg1]) - } } public struct StringResource2 { @@ -74,11 +59,6 @@ public struct StringResource2 { self.defaultValue = defaultValue self.comment = comment } - - public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2) -> String { - let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") - return String(format: format, locale: locale, arguments: [arg1, arg2]) - } } public struct StringResource3 { @@ -97,11 +77,6 @@ public struct StringResource3 { self.defaultValue = defaultValue self.comment = comment } - - public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) -> String { - let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") - return String(format: format, locale: locale, arguments: [arg1, arg2, arg3]) - } } public struct StringResource4 { @@ -120,11 +95,6 @@ public struct StringResource4 String { - let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") - return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4]) - } } public struct StringResource5 { @@ -143,11 +113,6 @@ public struct StringResource5 String { - let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") - return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5]) - } } public struct StringResource6 { @@ -166,11 +131,6 @@ public struct StringResource6 String { - let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") - return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6]) - } } public struct StringResource7 { @@ -189,11 +149,6 @@ public struct StringResource7 String { - let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") - return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7]) - } } public struct StringResource8 { @@ -212,11 +167,6 @@ public struct StringResource8 String { - let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") - return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8]) - } } public struct StringResource9 { @@ -235,9 +185,4 @@ public struct StringResource9 String { - let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") - return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9]) - } } From b521e71ac4a3cd3db86a06a7cc5b3912088931d8 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sat, 30 Jul 2022 21:08:55 +0200 Subject: [PATCH 055/161] Add Segue integrations --- Sources/RswiftCore/RswiftCore.swift | 15 ++-- .../RswiftGenerators/Segue+Generator.swift | 7 +- .../RswiftGenerators/SwiftSyntax/Struct.swift | 7 ++ .../Integrations/Bundle+Extensions.swift | 78 +++++++++++++++++++ .../SegueIdentifier+Integrations.swift | 65 ++++++++++++++++ .../StoryboardIdentifier.swift | 73 ++++++++++++++++- 6 files changed, 235 insertions(+), 10 deletions(-) create mode 100644 Sources/RswiftResources/Integrations/Bundle+Extensions.swift create mode 100644 Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 03fb43e7..b2d248be 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -59,7 +59,10 @@ public struct RswiftCore { let structName = SwiftIdentifier(rawValue: "_S") let qualifiedName = structName -// let segueStruct = Segue.generateStruct(storyboards: storyboards, prefix: qualifiedName) + let segueStruct = Segue.generateStruct( + storyboards: project.storyboards, + prefix: qualifiedName + ) let imageStruct = ImageResource.generateStruct( catalogs: project.assetCatalogs, @@ -139,9 +142,10 @@ public struct RswiftCore { Init.bundle projectStruct - imageStruct.generateBundleVarGetter(name: "image") - imageStruct.generateBundleFunction(name: "image") - imageStruct +// imageStruct.generateBundleVarGetter(name: "segue") +// imageStruct.generateBundleFunction(name: "segue") + segueStruct.generateLetBinding() + segueStruct } print(s.prettyPrint()) @@ -152,7 +156,8 @@ public struct RswiftCore { print("") print("extension R {") // print(" static let string = S.string") - print(" static let image = S.image") +// print(" static let image = S.image") + print(" static let segue = S.segue") print("}") print("TOTAL", Date().timeIntervalSince(start)) diff --git a/Sources/RswiftGenerators/Segue+Generator.swift b/Sources/RswiftGenerators/Segue+Generator.swift index 3a2aed57..0ddfcb34 100644 --- a/Sources/RswiftGenerators/Segue+Generator.swift +++ b/Sources/RswiftGenerators/Segue+Generator.swift @@ -24,6 +24,10 @@ public struct Segue { let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(structs.count) view controllers."] return Struct(comments: comments, name: structName) { + for s in structs { + s.generateLetBinding() + } + structs } } @@ -157,7 +161,7 @@ struct SegueWithInfo { var genericTypeReference: TypeReference { TypeReference( - module: .rswift, + module: .host, name: "SegueIdentifier", genericArgs: [segue.type, sourceType, destinationType] ) @@ -166,7 +170,6 @@ struct SegueWithInfo { func generateLetBinding() -> LetBinding { LetBinding( comments: ["Segue identifier `\(segue.identifier)`."], - isStatic: true, name: SwiftIdentifier(name: segue.identifier), typeReference: genericTypeReference, valueCodeString: "SegueIdentifier(identifier: \"\(segue.identifier)\")" diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index 3b80f6e8..cbb170fe 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -416,6 +416,13 @@ struct PrettyPrinter { extension Struct { + public func generateLetBinding() -> LetBinding { + LetBinding( + name: name, + valueCodeString: "\(name.value)()" + ) + } + public func generateBundleVarGetter(name: String) -> VarGetter { VarGetter( name: SwiftIdentifier(name: name), diff --git a/Sources/RswiftResources/Integrations/Bundle+Extensions.swift b/Sources/RswiftResources/Integrations/Bundle+Extensions.swift new file mode 100644 index 00000000..117a6347 --- /dev/null +++ b/Sources/RswiftResources/Integrations/Bundle+Extensions.swift @@ -0,0 +1,78 @@ +// +// Bundle+Extensions.swift +// +// +// Created by Tom Lokhorst on 2022-07-30. +// + +import Foundation + +extension Bundle { + public var firstPreferredLocale: Foundation.Locale { + self.preferredLocalizations.first.flatMap { Foundation.Locale(identifier: $0) } ?? Foundation.Locale.current + } + + /// Find first bundle and locale for which the table exists + public func firstBundleAndLocale(tableName: String, preferredLanguages: [String]) -> (bundle: Foundation.Bundle, locale: Foundation.Locale)? { + let hostingBundle = self + + // Filter preferredLanguages to localizations, use first locale + var languages = preferredLanguages + .map { Foundation.Locale(identifier: $0) } + .prefix(1) + .flatMap { locale -> [String] in + if hostingBundle.localizations.contains(locale.identifier) { + if let language = locale.languageCode, hostingBundle.localizations.contains(language) { + return [locale.identifier, language] + } else { + return [locale.identifier] + } + } else if let language = locale.languageCode, hostingBundle.localizations.contains(language) { + return [language] + } else { + return [] + } + } + + if languages.isEmpty { + // If there's no languages, use development language as backstop + if let developmentLocalization = hostingBundle.developmentLocalization { + languages = [developmentLocalization] + } + } else { + // Insert Base as second item (between locale identifier and languageCode) + languages.insert("Base", at: 1) + + // Add development language as backstop + if let developmentLocalization = hostingBundle.developmentLocalization { + languages.append(developmentLocalization) + } + } + + // Find first language for which table exists + // Note: key might not exist in chosen language (in that case, key will be shown) + for language in languages { + if let lproj = hostingBundle.url(forResource: language, withExtension: "lproj"), + let lbundle = Bundle(url: lproj) + { + let strings = lbundle.url(forResource: tableName, withExtension: "strings") + let stringsdict = lbundle.url(forResource: tableName, withExtension: "stringsdict") + + if strings != nil || stringsdict != nil { + return (lbundle, Foundation.Locale(identifier: language)) + } + } + } + + // If table is available in main bundle, don't look for localized resources + let strings = hostingBundle.url(forResource: tableName, withExtension: "strings", subdirectory: nil, localization: nil) + let stringsdict = hostingBundle.url(forResource: tableName, withExtension: "stringsdict", subdirectory: nil, localization: nil) + + if strings != nil || stringsdict != nil { + return (hostingBundle, hostingBundle.firstPreferredLocale) + } + + // If table is not found for requested languages, key will be shown + return nil + } +} diff --git a/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift b/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift new file mode 100644 index 00000000..fe361363 --- /dev/null +++ b/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift @@ -0,0 +1,65 @@ +// +// UIViewController+StoryboardSegueIdentifierProtocol.swift +// R.swift Library +// +// Created by Mathijs Kadijk on 06-12-15. +// From: https://github.com/mac-cain13/R.swift.Library +// License: MIT License +// + +import Foundation + +#if canImport(UIKit) +import UIKit + +public protocol SeguePerformer { + func performSegue(withIdentifier identifier: String, sender: Any?) +} + +extension UIViewController: SeguePerformer {} + +extension SeguePerformer { + /** + Initiates the segue with the specified identifier (R.segue.*) from the current view controller's storyboard file. + - parameter identifier: The R.segue.* that identifies the triggered segue. + - parameter sender: The object that you want to use to initiate the segue. This object is made available for informational purposes during the actual segue. + - SeeAlso: Library for typed block based segues: [tomlokhorst/SegueManager](https://github.com/tomlokhorst/SegueManager) + */ + public func performSegue(withIdentifier identifier: SegueIdentifier, sender: Any?) { + performSegue(withIdentifier: identifier.identifier, sender: sender) + } +} + +extension SegueIdentifier where Segue: UIStoryboardSegue { + /// Optionally returns a typed version of the segue. + /// Returns nil if either the segue identifier, the source, destination, or segue types don't match. + /// For use inside `prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)`. + public func callAsFunction(segue: Segue) -> TypedSegue? { + TypedSegue(segueIdentifier: self, uiStoryboardSegue: segue) + } +} + +extension TypedSegue { + /** + Returns typed information about the given segue, fails if the segue types don't exactly match types. + + - returns: A newly initialized TypedSegue object or nil. + */ + public init?(segueIdentifier: SegueIdentifier, uiStoryboardSegue: StoryboardSegue) { + guard + let identifier = uiStoryboardSegue.identifier, + let source = uiStoryboardSegue.source as? Source, + let destination = uiStoryboardSegue.destination as? Destination, + let segue = uiStoryboardSegue as? Segue, + identifier == segueIdentifier.identifier + else { + return nil + } + + self.segue = segue + self.identifier = identifier + self.source = source + self.destination = destination + } +} +#endif diff --git a/Sources/RswiftResources/StoryboardIdentifier.swift b/Sources/RswiftResources/StoryboardIdentifier.swift index a727c8d8..94ca8ca8 100644 --- a/Sources/RswiftResources/StoryboardIdentifier.swift +++ b/Sources/RswiftResources/StoryboardIdentifier.swift @@ -1,44 +1,111 @@ // -// StoryboardIdentifier.swift -// +// StoryboardSegueIdentifierProtocol.swift +// R.swift Library // -// Created by Tom Lokhorst on 2022-07-22. +// Created by Mathijs Kadijk on 06-12-15. +// From: https://github.com/mac-cain13/R.swift.Library +// License: MIT License // import Foundation +/// Storyboard identifier public protocol StoryboardIdentifier { + /// Storyboard identifier of this view controller static var identifier: String { get } } +/// Nib reference public struct NibReference { + + /// String name of this nib public let name: String + /** + Create a new NibRefence based on the string name + - parameter name: The string name for this nib + - returns: A new NibReference + */ public init(name: String) { self.name = name } } +/// Reuse identifier public struct ReuseIdentifier { + + /// String identifier of this reusable public let identifier: String + /** + Create a new ReuseIdentifier based on the string identifier + - parameter identifier: The string identifier for this reusable + - returns: A new ReuseIdentifier + */ public init(identifier: String) { self.identifier = identifier } } +/// Segue identifier public struct SegueIdentifier { + + /// Identifier string of this segue public let identifier: String + /** + Create a new SegueIdentifier based on the identifier string + - parameter identifier: The string identifier for this segue + - returns: A new SegueIdentifier + */ public init(identifier: String) { self.identifier = identifier } } +/// View controller identifier public struct ViewControllerIdentifier { + + /// Identifier string of this view controller public let identifier: String + /** + Create a new ViewControllerIdentifier based on the identifier string + - parameter identifier: The string identifier for this view controller + - returns: A new ViewControllerIdentifier + */ public init(identifier: String) { self.identifier = identifier } } + +/// Typed segue information +public struct TypedSegue { + + /// The original segue + public let segue: Segue + + /// Segue source view controller + public let source: Source + + /// Segue destination view controller + public let destination: Destination + + /// Segue identifier + public let identifier: String + + /** + Create a new TypedSegue based on the original segue + - parameter segue: The original segue + - parameter source: Segue source view controller + - parameter destination: Segue destination view controller + - parameter identifier: The string identifier for this segue + - returns: A new TypedSegue + */ + public init(segue: Segue, source: Source, destination: Destination, identifier: String) { + self.segue = segue + self.source = source + self.destination = destination + self.identifier = identifier + } +} From a3eec6c5f652337e4e4d72e3a9b62d73be8ef0df Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sat, 30 Jul 2022 22:54:06 +0200 Subject: [PATCH 056/161] Work on storyboard integrations --- Sources/RswiftCore/RswiftCore.swift | 13 ++-- Sources/RswiftGenerators/Nib+Generator.swift | 2 +- .../ReuseIdentifier+Generator.swift | 6 +- .../RswiftGenerators/Segue+Generator.swift | 2 +- .../Storyboard+Generator.swift | 25 +++++-- .../RswiftGenerators/SwiftSyntax/Struct.swift | 46 +++++++++++++ .../SwiftSyntax/StructMembersBuilder.swift | 16 ++++- .../StoryboardReference+Integrations.swift | 51 ++++++++++++++ .../Shared/ModuleReference.swift | 2 +- .../StoryboardReference.swift} | 68 +++++++++++++------ 10 files changed, 195 insertions(+), 36 deletions(-) create mode 100644 Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift rename Sources/RswiftResources/{StoryboardIdentifier.swift => Shared/StoryboardReference.swift} (75%) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index b2d248be..16550e84 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -94,10 +94,10 @@ public struct RswiftCore { // prefix: qualifiedName // ) -// let storyboardStruct = StoryboardResource.generateStruct( -// storyboards: storyboards, -// prefix: qualifiedName -// ) + let storyboardStruct = StoryboardResource.generateStruct( + storyboards: project.storyboards, + prefix: qualifiedName + ) // let infoStruct = PropertyListResource.generateStruct( // resourceName: "info", @@ -146,6 +146,10 @@ public struct RswiftCore { // imageStruct.generateBundleFunction(name: "segue") segueStruct.generateLetBinding() segueStruct + + storyboardStruct.generateBundleVarGetter(name: "storyboard") + storyboardStruct.generateBundleFunction(name: "storyboard") + storyboardStruct } print(s.prettyPrint()) @@ -158,6 +162,7 @@ public struct RswiftCore { // print(" static let string = S.string") // print(" static let image = S.image") print(" static let segue = S.segue") + print(" static let storyboard = S.storyboard") print("}") print("TOTAL", Date().timeIntervalSince(start)) diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift index 2cdf6380..61114cc9 100644 --- a/Sources/RswiftGenerators/Nib+Generator.swift +++ b/Sources/RswiftGenerators/Nib+Generator.swift @@ -37,7 +37,7 @@ extension NibResource { extension NibResource { var genericTypeReference: TypeReference { TypeReference( - module: .rswift, + module: .rswiftResources, name: "NibReference", genericArgs: [rootViews.first ?? TypeReference.uiView] ) diff --git a/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift b/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift index cdab5ad3..928e6180 100644 --- a/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift +++ b/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift @@ -37,7 +37,11 @@ extension Reusable { extension Reusable { var genericTypeReference: TypeReference { - TypeReference(module: .rswift, name: "ReusableIdentifier", genericArgs: [type]) + TypeReference( + module: .rswiftResources, + name: "ReusableIdentifier", + genericArgs: [type] + ) } func generateLetBinding() -> LetBinding { diff --git a/Sources/RswiftGenerators/Segue+Generator.swift b/Sources/RswiftGenerators/Segue+Generator.swift index 0ddfcb34..d50bbb20 100644 --- a/Sources/RswiftGenerators/Segue+Generator.swift +++ b/Sources/RswiftGenerators/Segue+Generator.swift @@ -161,7 +161,7 @@ struct SegueWithInfo { var genericTypeReference: TypeReference { TypeReference( - module: .host, + module: .rswiftResources, name: "SegueIdentifier", genericArgs: [segue.type, sourceType, destinationType] ) diff --git a/Sources/RswiftGenerators/Storyboard+Generator.swift b/Sources/RswiftGenerators/Storyboard+Generator.swift index ed463068..e326aea5 100644 --- a/Sources/RswiftGenerators/Storyboard+Generator.swift +++ b/Sources/RswiftGenerators/Storyboard+Generator.swift @@ -25,6 +25,13 @@ extension StoryboardResource { let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(structs.count) storyboards."] return Struct(comments: comments, name: structName) { + Init.bundle + + for s in structs { + s.generateBundleVarGetter(name: s.name.value) + s.generateBundleFunction(name: s.name.value) + } + structs } } @@ -58,18 +65,27 @@ extension StoryboardResource { .sorted { $0.name < $1.name } let letName = LetBinding( - isStatic: true, name: nameIdentifier, valueCodeString: "\"\(name)\"") + let varBundle = VarGetter( + name: SwiftIdentifier(name: "bundle"), + typeReference: TypeReference.bundle, + valueCodeString: "_bundle") let identifier = SwiftIdentifier(name: name) - let storyboardIdentifier = TypeReference(module: .rswift, rawName: "StoryboardIdentifier") + let storyboardReference = TypeReference(module: .rswiftResources, rawName: "StoryboardReference") + let initialContainer = initialViewController == nil ? nil : TypeReference(module: .rswiftResources, rawName: "InitialControllerContainer") return Struct( comments: ["Storyboard `\(name)`."], name: identifier, - protocols: [storyboardIdentifier] + protocols: [storyboardReference, initialContainer].compactMap { $0 } ) { + if let initialViewController = initialViewController { + TypeAlias(name: "InitialController", value: initialViewController.type) + } + Init.bundle + varBundle letName letbindings @@ -81,7 +97,7 @@ extension StoryboardResource.ViewController { var genericTypeReference: TypeReference { TypeReference( - module: .rswift, + module: .rswiftResources, name: "ViewControllerIdentifier", genericArgs: [self.type] ) @@ -89,7 +105,6 @@ extension StoryboardResource.ViewController { func generateLetBinding(identifier: String) -> LetBinding { LetBinding( - isStatic: true, name: SwiftIdentifier(name: identifier), typeReference: genericTypeReference, valueCodeString: #"ViewControllerIdentifier(identifier: "\#(identifier)")"# diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index cbb170fe..3bd71fbe 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -265,6 +265,37 @@ public struct Init { } } +public struct TypeAlias { + public let comments: [String] + public var accessControl = AccessControl.none + public let name: String + public let value: TypeReference + + public init(comments: [String] = [], accessControl: AccessControl = AccessControl.none, name: String, value: TypeReference) { + self.comments = comments + self.accessControl = accessControl + self.name = name + self.value = value + } + + func render(_ pp: inout PrettyPrinter) { + + for c in comments { + pp.append(words: ["///", c == "" ? nil : c]) + } + + let words: [String?] = [ + accessControl.code(), + "typealias", + name, + "=", + value.codeString() + ] + + pp.append(words: words) + } +} + public struct Struct { public let comments: [String] public var accessControl = AccessControl.none @@ -275,6 +306,7 @@ public struct Struct { public var inits: [Init] = [] public var funcs: [Function] = [] public var structs: [Struct] = [] + public var typealiasses: [TypeAlias] = [] public var isEmpty: Bool { lets.isEmpty && funcs.isEmpty && structs.isEmpty } @@ -298,6 +330,7 @@ public struct Struct { self.inits = members.inits self.funcs = members.funcs self.structs = members.structs + self.typealiasses = members.typealiasses } public func prettyPrint() -> String { @@ -315,6 +348,19 @@ public struct Struct { let implements = ps.isEmpty ? "" : ": \(ps)" pp.append(line: "struct \(name.value)\(implements) {") + pp.indented { pp in + for talias in typealiasses { + if !talias.comments.isEmpty { + pp.append(line: "") + } + talias.render(&pp) + } + } + + if !typealiasses.isEmpty && !inits.isEmpty { + pp.append(line: "") + } + pp.indented { pp in for inib in inits { if !inib.comments.isEmpty { diff --git a/Sources/RswiftGenerators/SwiftSyntax/StructMembersBuilder.swift b/Sources/RswiftGenerators/SwiftSyntax/StructMembersBuilder.swift index 1554bb6e..9cccd703 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/StructMembersBuilder.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/StructMembersBuilder.swift @@ -13,6 +13,7 @@ public struct StructMembers { var inits: [Init] = [] var funcs: [Function] = [] var structs: [Struct] = [] + var typealiasses: [TypeAlias] = [] func sorted() -> StructMembers { var new = self @@ -20,6 +21,7 @@ public struct StructMembers { new.vars.sort { $0.name < $1.name } new.funcs.sort { $0.name < $1.name } new.structs.sort { $0.name < $1.name } + new.typealiasses.sort { $0.name < $1.name } return new } } @@ -70,6 +72,14 @@ public struct StructMembersBuilder { StructMembers(structs: expressions) } + public static func buildExpression(_ expression: TypeAlias) -> StructMembers { + StructMembers(typealiasses: [expression]) + } + + public static func buildExpression(_ expressions: [TypeAlias]) -> StructMembers { + StructMembers(typealiasses: expressions) + } + public static func buildExpression(_ members: StructMembers) -> StructMembers { members } @@ -80,7 +90,8 @@ public struct StructMembersBuilder { vars: members.flatMap(\.vars), inits: members.flatMap(\.inits), funcs: members.flatMap(\.funcs), - structs: members.flatMap(\.structs) + structs: members.flatMap(\.structs), + typealiasses: members.flatMap(\.typealiasses) ) } @@ -90,7 +101,8 @@ public struct StructMembersBuilder { vars: members.flatMap(\.vars), inits: members.flatMap(\.inits), funcs: members.flatMap(\.funcs), - structs: members.flatMap(\.structs) + structs: members.flatMap(\.structs), + typealiasses: members.flatMap(\.typealiasses) ) } diff --git a/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift b/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift new file mode 100644 index 00000000..455b35fa --- /dev/null +++ b/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift @@ -0,0 +1,51 @@ +// +// StoryboardReference+Integrations.swift +// +// +// Created by Tom Lokhorst on 2022-07-30. +// + +import Foundation + + +#if canImport(UIKit) +import UIKit + + +public extension StoryboardReference where Self: InitialControllerContainer { + /** + Instantiates and returns the initial view controller in the view controller graph. + + - returns: The initial view controller in the storyboard. + */ + func instantiateInitialViewController() -> InitialController? { + UIStoryboard(name: name, bundle: bundle).instantiateInitialViewController() as? InitialController + } +} + + +public extension UIStoryboard { + /** + Creates and returns a storyboard object for the specified storyboard resource (R.storyboard.*) file. + + - parameter resource: The storyboard resource (R.storyboard.*) for the specific storyboard to load + + - returns: A storyboard object for the specified file. If no storyboard resource file matching name exists, an exception is thrown with description: `Could not find a storyboard named 'XXXXXX' in bundle....` + */ + convenience init(resource: Reference) { + self.init(name: resource.name, bundle: resource.bundle) + } + + + /** + Instantiates and returns the view controller with the specified resource (R.storyboard.*.*). + + - parameter resource: An resource (R.storyboard.*.*) that uniquely identifies the view controller in the storyboard file. If the specified resource does not exist in the storyboard file, this method raises an exception. + + - returns: The view controller corresponding to the specified resource (R.storyboard.*.*). If no view controller is associated, this method throws an exception. + */ + func instantiateViewController(withIdentifier identifier: ViewControllerIdentifier) -> ViewController? { + self.instantiateViewController(withIdentifier: identifier.identifier) as? ViewController + } +} +#endif diff --git a/Sources/RswiftResources/Shared/ModuleReference.swift b/Sources/RswiftResources/Shared/ModuleReference.swift index f12dfa0c..fc533dd9 100644 --- a/Sources/RswiftResources/Shared/ModuleReference.swift +++ b/Sources/RswiftResources/Shared/ModuleReference.swift @@ -32,5 +32,5 @@ public enum ModuleReference: Hashable { extension ModuleReference { public static var foundation: ModuleReference { .custom(name: "Foundation") } public static var uiKit: ModuleReference { .custom(name: "UIKit") } - public static var rswift: ModuleReference { .custom(name: "Rswift") } + public static var rswiftResources: ModuleReference { .custom(name: "RswiftResources") } } diff --git a/Sources/RswiftResources/StoryboardIdentifier.swift b/Sources/RswiftResources/Shared/StoryboardReference.swift similarity index 75% rename from Sources/RswiftResources/StoryboardIdentifier.swift rename to Sources/RswiftResources/Shared/StoryboardReference.swift index 94ca8ca8..dfaf3ad0 100644 --- a/Sources/RswiftResources/StoryboardIdentifier.swift +++ b/Sources/RswiftResources/Shared/StoryboardReference.swift @@ -9,12 +9,22 @@ import Foundation -/// Storyboard identifier -public protocol StoryboardIdentifier { - /// Storyboard identifier of this view controller - static var identifier: String { get } +/// Storyboard reference +public protocol StoryboardReference { + /// Name of the storyboard file on disk + var name: String { get } + + /// Bundle this storyboard is in + var bundle: Bundle { get } } + +public protocol InitialControllerContainer { + /// Type of the inital controller + associatedtype InitialController +} + + /// Nib reference public struct NibReference { @@ -22,7 +32,7 @@ public struct NibReference { public let name: String /** - Create a new NibRefence based on the string name + Create a new NibRefence based on the name string - parameter name: The string name for this nib - returns: A new NibReference */ @@ -30,6 +40,38 @@ public struct NibReference { self.name = name } } +// +///// View controller reference +//public struct ViewControllerReference { +// +// /// String name of this view controller +// public let name: String +// +// /** +// Create a new ViewControllerReference based on the name string +// - parameter name: The string name for this view controller +// - returns: A new ViewControllerReference +// */ +// public init(name: String) { +// self.name = name +// } +//} + +/// View controller identifier +public struct ViewControllerIdentifier { + + /// Identifier of this view controller + public let identifier: String + + /** + Create a new ViewControllerIdentifier based on the identifier string + - parameter identifier: The string identifier for this view controller + - returns: A new ViewControllerIdentifier + */ + public init(identifier: String) { + self.identifier = identifier + } +} /// Reuse identifier public struct ReuseIdentifier { @@ -63,22 +105,6 @@ public struct SegueIdentifier { } } -/// View controller identifier -public struct ViewControllerIdentifier { - - /// Identifier string of this view controller - public let identifier: String - - /** - Create a new ViewControllerIdentifier based on the identifier string - - parameter identifier: The string identifier for this view controller - - returns: A new ViewControllerIdentifier - */ - public init(identifier: String) { - self.identifier = identifier - } -} - /// Typed segue information public struct TypedSegue { From 12cb6a9628831c1a5bf8276895326e778132a6fe Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 31 Jul 2022 15:39:15 +0200 Subject: [PATCH 057/161] Add Image/Data/Color integrations --- Sources/RswiftCore/RswiftCore.swift | 21 ++-- .../AssetCatalog+Generator.swift | 10 +- .../Resources/AssetCatalog+Parser.swift | 4 +- Sources/RswiftResources/ColorResource.swift | 4 +- Sources/RswiftResources/DataResource.swift | 4 +- .../ColorResource+Integrations.swift | 61 ++++++++++ .../DataResource+Integrations.swift | 27 +++++ .../ImageResource+Integrations.swift | 104 +++++++++++++++--- .../SegueIdentifier+Integrations.swift | 2 +- .../StoryboardReference+Integrations.swift | 2 +- 10 files changed, 209 insertions(+), 30 deletions(-) create mode 100644 Sources/RswiftResources/Integrations/ColorResource+Integrations.swift create mode 100644 Sources/RswiftResources/Integrations/DataResource+Integrations.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 16550e84..96e3f881 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -69,10 +69,10 @@ public struct RswiftCore { toplevel: project.images, prefix: qualifiedName ) -// let colorStruct = ColorResource.generateStruct( -// catalogs: assetCatalogs, -// prefix: qualifiedName -// ) + let colorStruct = ColorResource.generateStruct( + catalogs: project.assetCatalogs, + prefix: qualifiedName + ) // let dataStruct = DataResource.generateStruct( // catalogs: assetCatalogs, // prefix: qualifiedName @@ -142,11 +142,17 @@ public struct RswiftCore { Init.bundle projectStruct -// imageStruct.generateBundleVarGetter(name: "segue") -// imageStruct.generateBundleFunction(name: "segue") + imageStruct.generateBundleVarGetter(name: "image") + imageStruct.generateBundleFunction(name: "image") + imageStruct + segueStruct.generateLetBinding() segueStruct + colorStruct.generateBundleVarGetter(name: "color") + colorStruct.generateBundleFunction(name: "color") + colorStruct + storyboardStruct.generateBundleVarGetter(name: "storyboard") storyboardStruct.generateBundleFunction(name: "storyboard") storyboardStruct @@ -160,7 +166,8 @@ public struct RswiftCore { print("") print("extension R {") // print(" static let string = S.string") -// print(" static let image = S.image") + print(" static let image = S.image") + print(" static let color = S.color") print(" static let segue = S.segue") print(" static let storyboard = S.storyboard") print("}") diff --git a/Sources/RswiftGenerators/AssetCatalog+Generator.swift b/Sources/RswiftGenerators/AssetCatalog+Generator.swift index f285e8d4..eb4a5952 100644 --- a/Sources/RswiftGenerators/AssetCatalog+Generator.swift +++ b/Sources/RswiftGenerators/AssetCatalog+Generator.swift @@ -81,6 +81,11 @@ extension AssetCatalog.Namespace { Init.bundle letbindings structs + + for s in structs { + s.generateBundleVarGetter(name: s.name.value) + s.generateBundleFunction(name: s.name.value) + } } } } @@ -88,7 +93,7 @@ extension AssetCatalog.Namespace { extension ColorResource: AssetCatalogContent { public func generateLetBinding() -> LetBinding { let fullname = (path + [name]).joined(separator: "/") - let code = "ColorResource(name: \"\(fullname)\")" + let code = "ColorResource(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: nil)" return LetBinding( comments: ["Color `\(fullname)`."], name: SwiftIdentifier(name: name), @@ -100,7 +105,8 @@ extension ColorResource: AssetCatalogContent { extension DataResource: AssetCatalogContent { public func generateLetBinding() -> LetBinding { let fullname = (path + [name]).joined(separator: "/") - let code = "DataResource(name: \"\(fullname)\")" + let odrt = onDemandResourceTags?.debugDescription ?? "nil" + let code = "DataResource(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: nil, onDemandResourceTags: \(odrt))" return LetBinding( comments: ["Data asset `\(fullname)`."], name: SwiftIdentifier(name: name), diff --git a/Sources/RswiftParsers/Resources/AssetCatalog+Parser.swift b/Sources/RswiftParsers/Resources/AssetCatalog+Parser.swift index 2a34a807..6999ef4c 100644 --- a/Sources/RswiftParsers/Resources/AssetCatalog+Parser.swift +++ b/Sources/RswiftParsers/Resources/AssetCatalog+Parser.swift @@ -114,7 +114,7 @@ extension AssetCatalog: SupportedExtensions { var colors: [ColorResource] = [] for fileURL in directory.colors { let name = fileURL.filenameWithoutExtension! - colors.append(.init(name: name, path: path)) + colors.append(.init(name: name, path: path, bundle: nil)) } var images: [ImageResource] = [] @@ -128,7 +128,7 @@ extension AssetCatalog: SupportedExtensions { for fileURL in directory.dataAssets { let name = fileURL.filenameWithoutExtension! let tags = parseOnDemandResourceTags(directory: fileURL) - dataAssets.append(.init(name: name, path: path, onDemandResourceTags: tags)) + dataAssets.append(.init(name: name, path: path, bundle: nil, onDemandResourceTags: tags)) } return AssetCatalog.Namespace( diff --git a/Sources/RswiftResources/ColorResource.swift b/Sources/RswiftResources/ColorResource.swift index d5cb90c3..c2988aef 100644 --- a/Sources/RswiftResources/ColorResource.swift +++ b/Sources/RswiftResources/ColorResource.swift @@ -10,9 +10,11 @@ import Foundation public struct ColorResource { public let name: String public let path: [String] + public let bundle: Bundle? - public init(name: String, path: [String]) { + public init(name: String, path: [String], bundle: Bundle?) { self.name = name self.path = path + self.bundle = bundle } } diff --git a/Sources/RswiftResources/DataResource.swift b/Sources/RswiftResources/DataResource.swift index b19e2926..d9653b28 100644 --- a/Sources/RswiftResources/DataResource.swift +++ b/Sources/RswiftResources/DataResource.swift @@ -10,11 +10,13 @@ import Foundation public struct DataResource { public let name: String public let path: [String] + public let bundle: Bundle? public let onDemandResourceTags: [String]? - public init(name: String, path: [String], onDemandResourceTags: [String]?) { + public init(name: String, path: [String], bundle: Bundle?, onDemandResourceTags: [String]?) { self.name = name self.path = path + self.bundle = bundle self.onDemandResourceTags = onDemandResourceTags } } diff --git a/Sources/RswiftResources/Integrations/ColorResource+Integrations.swift b/Sources/RswiftResources/Integrations/ColorResource+Integrations.swift new file mode 100644 index 00000000..d1dfea9a --- /dev/null +++ b/Sources/RswiftResources/Integrations/ColorResource+Integrations.swift @@ -0,0 +1,61 @@ +// +// UIColor+ColorResource.swift +// R.swift.Library +// +// Created by Tom Lokhorst on 2017-06-06. +// From: https://github.com/mac-cain13/R.swift.Library +// License: MIT License +// + +import Foundation +import SwiftUI + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +extension Color { + + /** + Creates a color from this resource (R.color.*). + + - parameter resource: The resource you want the color of (R.color.*) + */ + public init(_ resource: ColorResource) { + self.init(resource.name, bundle: resource.bundle) + } +} + + +#if os(iOS) || os(tvOS) +import UIKit + +extension ColorResource { + + /** + Returns the color from this resource (R.color.*) that is compatible with the trait collection. + + - parameter resource: The resource you want the color of (R.color.*) + - parameter traitCollection: Traits that describe the desired color to retrieve, pass nil to use traits that describe the main screen. + + - returns: A color that exactly or best matches the desired traits with the given resource (R.color.*), or nil if no suitable color was found. + */ + @available(*, deprecated, message: "Use UIColor(resource:) initializer instead") + public func callAsFunction(compatibleWith traitCollection: UITraitCollection? = nil) -> UIColor? { + UIColor(named: name, in: bundle, compatibleWith: traitCollection) + } +} + +extension UIColor { + + /** + Returns the color from this resource (R.color.*) that is compatible with the trait collection. + + - parameter resource: The resource you want the color of (R.color.*) + - parameter traitCollection: Traits that describe the desired color to retrieve, pass nil to use traits that describe the main screen. + + - returns: A color that exactly or best matches the desired traits with the given resource (R.color.*), or nil if no suitable color was found. + */ + public convenience init?(resource: ColorResource, compatibleWith traitCollection: UITraitCollection? = nil) { + self.init(named: resource.name, in: resource.bundle, compatibleWith: traitCollection) + } + +} +#endif diff --git a/Sources/RswiftResources/Integrations/DataResource+Integrations.swift b/Sources/RswiftResources/Integrations/DataResource+Integrations.swift new file mode 100644 index 00000000..783bbf57 --- /dev/null +++ b/Sources/RswiftResources/Integrations/DataResource+Integrations.swift @@ -0,0 +1,27 @@ +// +// DataResource+Integrations.swift +// +// +// Created by Tom Lokhorst on 2022-07-31. +// + +import Foundation + +#if canImport(UIKit) +import UIKit +#elseif canImport(AppKit) +import AppKit +#endif + +extension NSDataAsset { + + /** + Returns the data asset from this resource (R.data.*) + + - parameter resource: The resource you want the data asset of (R.data.*) + */ + public convenience init?(resource: DataResource) { + self.init(name: resource.name, bundle: resource.bundle ?? .main) + } + +} diff --git a/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift b/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift index 6da9260f..368c0d49 100644 --- a/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift +++ b/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift @@ -1,49 +1,81 @@ // -// ImageResource+Integrations.swift -// +// UIImage+ImageResource.swift +// R.swift.Library // -// Created by Tom Lokhorst on 2022-07-30. +// Created by Mathijs Kadijk on 11-01-16. +// From: https://github.com/mac-cain13/R.swift.Library +// License: MIT License // import Foundation import SwiftUI -#if canImport(UIKit) -extension ImageResource { - - @available(*, deprecated, message: "Use UIImage(resource:) initializer instead") - public func callAsFunction(compatibleWith traitCollection: UITraitCollection? = nil) -> UIImage? { - UIImage(named: name, in: bundle, compatibleWith: traitCollection) - } -} -#endif - -@available(iOS 13, tvOS 13, watchOS 6, *) +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension Image { + /** + Creates a labelled image from this resource (R.image.*). + + - parameter resource: The resource you want the image of (R.image.*) + */ public init(_ resource: ImageResource) { self.init(resource.name, bundle: resource.bundle) } + + /** + Creates a labelled image from this resource (R.image.*), with the variable value. + + - parameter resource: The resource you want the image of (R.image.*) + - parameter variableValue: Optional value between 1 and 0 + */ @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) public init(_ resource: ImageResource, variableValue: Double?) { self.init(resource.name, variableValue: variableValue, bundle: resource.bundle) } + + /** + Creates a labelled image from this resource (R.image.*), with the specified label + + - parameter resource: The resource you want the image of (R.image.*) + - parameter label: The label associated with the image, for accessibility + */ public init(_ resource: ImageResource, label: Text) { self.init(resource.name, bundle: resource.bundle, label: label) } + + /** + Creates a labelled image from this resource (R.image.*), with the specified label and variable value. + + - parameter resource: The resource you want the image of (R.image.*) + - parameter variableValue: Optional value between 1 and 0 + - parameter label: The label associated with the image, for accessibility + */ @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) public init(_ resource: ImageResource, variableValue: Double?, label: Text) { self.init(resource.name, variableValue: variableValue, bundle: resource.bundle, label: label) } + + /** + Creates an unlabelled, decorative image from this resource (R.image.*). + + - parameter resource: The resource you want the image of (R.image.*) + */ public init(decorative resource: ImageResource) { self.init(decorative: resource.name, bundle: resource.bundle) } + + /** + Creates an unlabelled, decorative image from this resource (R.image.*), with variable value. + + - parameter resource: The resource you want the image of (R.image.*) + - parameter variableValue: Optional value between 1 and 0 + */ @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) public init(decorative resource: ImageResource, variableValue: Double?) { self.init(decorative: resource.name, variableValue: variableValue, bundle: resource.bundle) @@ -51,20 +83,62 @@ extension Image { } -#if canImport(UIKit) +#if os(iOS) || os(tvOS) import UIKit +extension ImageResource { + + /** + Returns the image from this resource (R.image.*) that is compatible with the trait collection. + + - parameter resource: The resource you want the image of (R.image.*) + - parameter traitCollection: Traits that describe the desired image to retrieve, pass nil to use traits that describe the main screen. + + - returns: An image that exactly or best matches the desired traits with the given resource (R.image.*), or nil if no suitable image was found. + */ + @available(*, deprecated, message: "Use UIImage(resource:) initializer instead") + public func callAsFunction(compatibleWith traitCollection: UITraitCollection? = nil) -> UIImage? { + UIImage(named: name, in: bundle, compatibleWith: traitCollection) + } +} + extension UIImage { + /** + Returns the image from this resource (R.image.*) that is compatible with the trait collection. + + - parameter resource: The resource you want the image of (R.image.*) + - parameter traitCollection: Traits that describe the desired image to retrieve, pass nil to use traits that describe the main screen. + + - returns: An image that exactly or best matches the desired traits with the given resource (R.image.*), or nil if no suitable image was found. + */ public convenience init?(resource: ImageResource, compatibleWith traitCollection: UITraitCollection? = nil) { self.init(named: resource.name, in: resource.bundle, compatibleWith: traitCollection) } + /** + Returns the image from this resource (R.image.*) using the configuration specified. + + - parameter resource: The resource you want the image of (R.image.*) + - parameter configuration: The image configuration the system appllies to the image + + - returns: An image that exactly or best matches the configuration of the given resource (R.image.*), or nil if no suitable image was found. + */ @available(iOS 13, tvOS 13, watchOS 6, *) public convenience init?(resource: ImageResource, with configuration: UIImage.Configuration?) { self.init(named: resource.name, in: resource.bundle, with: configuration) } + + /** + Returns the image from this resource (R.image.*) using the configuration, and variable value specified. + + - parameter resource: The resource you want the image of (R.image.*) + - parameter variableValue: The value the system uses to customize the image content, between 0 and 1 + - parameter configuration: The image configuration the system appllies to the image + + - returns: An image that exactly or best matches the configuration of the given resource (R.image.*), or nil if no suitable image was found. + */ @available(iOS 16, tvOS 16, watchOS 9, *) public convenience init?(resource: ImageResource, variableValue: Double, with configuration: UIImage.Configuration? = nil) { self.init(named: resource.name, in: resource.bundle, variableValue: variableValue, configuration: configuration) diff --git a/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift b/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift index fe361363..cb3c584c 100644 --- a/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift +++ b/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift @@ -9,7 +9,7 @@ import Foundation -#if canImport(UIKit) +#if os(iOS) || os(tvOS) import UIKit public protocol SeguePerformer { diff --git a/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift b/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift index 455b35fa..e14dcee5 100644 --- a/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift +++ b/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift @@ -8,7 +8,7 @@ import Foundation -#if canImport(UIKit) +#if os(iOS) || os(tvOS) import UIKit From 4a28c402fdb97f1ae04c80b74763e6083c22df75 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 31 Jul 2022 16:48:13 +0200 Subject: [PATCH 058/161] Work on storyboard view controllers --- .../Shared/SwiftIdentifier.swift | 14 +++++- .../Storyboard+Generator.swift | 30 ++++++------ .../ColorResource+Integrations.swift | 16 +++---- .../DataResource+Integrations.swift | 4 +- .../ImageResource+Integrations.swift | 48 +++++++++---------- .../SegueIdentifier+Integrations.swift | 4 +- .../StoryboardReference+Integrations.swift | 26 +++++++--- .../Shared/StoryboardReference.swift | 21 +++++--- 8 files changed, 98 insertions(+), 65 deletions(-) diff --git a/Sources/RswiftGenerators/Shared/SwiftIdentifier.swift b/Sources/RswiftGenerators/Shared/SwiftIdentifier.swift index 133b05e5..58c99338 100644 --- a/Sources/RswiftGenerators/Shared/SwiftIdentifier.swift +++ b/Sources/RswiftGenerators/Shared/SwiftIdentifier.swift @@ -84,7 +84,8 @@ struct SwiftNameGroups { let resultPlural = "\(result)s" for (sanitizedName, dups) in duplicates { - warning("Skipping \(dups.count) \(sourcePlural) because symbol '\(sanitizedName.value)' would be generated for all of these \(resultPlural): \(dups.joined(separator: ", "))") + let source = dups.count == 1 ? sourceSingular : sourcePlural + warning("Skipping \(dups.count) \(source) because symbol '\(sanitizedName.value)' would be generated for all of these \(resultPlural): \(dups.joined(separator: ", "))") } if let empty = empties.first , empties.count == 1 { @@ -94,6 +95,17 @@ struct SwiftNameGroups { warning("Skipping \(empties.count) \(sourcePlural) because no swift identifier can be generated for all of these \(resultPlural): \(empties.joined(separator: ", "))") } } + + func reportWarningsForReservedNames(source: String, container: String? = nil, result: String, warning: (String) -> Void) { + let sourceSingular = [source, container].compactMap { $0 }.joined(separator: " ") + let sourcePlural = ["\(source)s", container].compactMap { $0 }.joined(separator: " ") + + for (sanitizedName, dups) in duplicates { + let count = dups.count - 1 + let source = count == 1 ? sourceSingular : sourcePlural + warning("Skipping \(count) \(source) because symbol '\(sanitizedName.value)' would conflict with reserved name") + } + } } extension Sequence { diff --git a/Sources/RswiftGenerators/Storyboard+Generator.swift b/Sources/RswiftGenerators/Storyboard+Generator.swift index e326aea5..f6aa04d7 100644 --- a/Sources/RswiftGenerators/Storyboard+Generator.swift +++ b/Sources/RswiftGenerators/Storyboard+Generator.swift @@ -41,6 +41,8 @@ extension StoryboardResource { func generateStruct(prefix: SwiftIdentifier, warning: (String) -> Void) -> Struct { let nameIdentifier = SwiftIdentifier(rawValue: "name") + let bundleIdentifier = SwiftIdentifier(name: "bundle") + let reservedIdentifiers: Set = [nameIdentifier, bundleIdentifier] // View controllers with identifiers let grouped = viewControllers @@ -52,23 +54,21 @@ extension StoryboardResource { grouped.reportWarningsForDuplicatesAndEmpties(source: "view controller", result: "view controller identifier", warning: warning) - let skip = grouped.uniques - .map(\.identifier) - .first { SwiftIdentifier(rawValue: $0) == nameIdentifier } - if let skip = skip { - warning("Skipping 1 view controller because symbol '\(skip)' conflicts with reserved name '\(nameIdentifier.value)'") - } + // Warning about conflicts with reserved identifiers + (grouped.uniques.map(\.identifier) + reservedIdentifiers.map(\.value)) + .grouped(bySwiftIdentifier: { $0 }) + .reportWarningsForReservedNames(source: "view controller", container: "in storyboard '\(name)'", result: "view controller", warning: warning) - let letbindings = grouped.uniques - .filter { (id, _) in SwiftIdentifier(rawValue: id) != nameIdentifier } - .map { (id, vc) in vc.generateLetBinding(identifier: id) } + let vargetters = grouped.uniques + .filter { !reservedIdentifiers.contains(SwiftIdentifier(rawValue: $0.identifier)) } + .map { (id, vc) in vc.generateVarGetter(identifier: id) } .sorted { $0.name < $1.name } let letName = LetBinding( name: nameIdentifier, valueCodeString: "\"\(name)\"") let varBundle = VarGetter( - name: SwiftIdentifier(name: "bundle"), + name: bundleIdentifier, typeReference: TypeReference.bundle, valueCodeString: "_bundle") @@ -88,7 +88,7 @@ extension StoryboardResource { varBundle letName - letbindings + vargetters } } } @@ -98,16 +98,16 @@ extension StoryboardResource.ViewController { var genericTypeReference: TypeReference { TypeReference( module: .rswiftResources, - name: "ViewControllerIdentifier", + name: "StoryboardViewControllerIdentifier", genericArgs: [self.type] ) } - func generateLetBinding(identifier: String) -> LetBinding { - LetBinding( + func generateVarGetter(identifier: String) -> VarGetter { + VarGetter( name: SwiftIdentifier(name: identifier), typeReference: genericTypeReference, - valueCodeString: #"ViewControllerIdentifier(identifier: "\#(identifier)")"# + valueCodeString: #"StoryboardViewControllerIdentifier(identifier: "\#(identifier.escapedStringLiteral)", storyboard: name, bundle: bundle)"# ) } } diff --git a/Sources/RswiftResources/Integrations/ColorResource+Integrations.swift b/Sources/RswiftResources/Integrations/ColorResource+Integrations.swift index d1dfea9a..a5443b3b 100644 --- a/Sources/RswiftResources/Integrations/ColorResource+Integrations.swift +++ b/Sources/RswiftResources/Integrations/ColorResource+Integrations.swift @@ -14,9 +14,9 @@ import SwiftUI extension Color { /** - Creates a color from this resource (R.color.*). + Creates a color from this resource (R.color.\*). - - parameter resource: The resource you want the color of (R.color.*) + - parameter resource: The resource you want the color of (R.color.\*) */ public init(_ resource: ColorResource) { self.init(resource.name, bundle: resource.bundle) @@ -30,12 +30,12 @@ import UIKit extension ColorResource { /** - Returns the color from this resource (R.color.*) that is compatible with the trait collection. + Returns the color from this resource (R.color.\*) that is compatible with the trait collection. - - parameter resource: The resource you want the color of (R.color.*) + - parameter resource: The resource you want the color of (R.color.\*) - parameter traitCollection: Traits that describe the desired color to retrieve, pass nil to use traits that describe the main screen. - - returns: A color that exactly or best matches the desired traits with the given resource (R.color.*), or nil if no suitable color was found. + - returns: A color that exactly or best matches the desired traits with the given resource (R.color.\*), or nil if no suitable color was found. */ @available(*, deprecated, message: "Use UIColor(resource:) initializer instead") public func callAsFunction(compatibleWith traitCollection: UITraitCollection? = nil) -> UIColor? { @@ -46,12 +46,12 @@ extension ColorResource { extension UIColor { /** - Returns the color from this resource (R.color.*) that is compatible with the trait collection. + Returns the color from this resource (R.color.\*) that is compatible with the trait collection. - - parameter resource: The resource you want the color of (R.color.*) + - parameter resource: The resource you want the color of (R.color.\*) - parameter traitCollection: Traits that describe the desired color to retrieve, pass nil to use traits that describe the main screen. - - returns: A color that exactly or best matches the desired traits with the given resource (R.color.*), or nil if no suitable color was found. + - returns: A color that exactly or best matches the desired traits with the given resource (R.color.\*), or nil if no suitable color was found. */ public convenience init?(resource: ColorResource, compatibleWith traitCollection: UITraitCollection? = nil) { self.init(named: resource.name, in: resource.bundle, compatibleWith: traitCollection) diff --git a/Sources/RswiftResources/Integrations/DataResource+Integrations.swift b/Sources/RswiftResources/Integrations/DataResource+Integrations.swift index 783bbf57..377fe9fe 100644 --- a/Sources/RswiftResources/Integrations/DataResource+Integrations.swift +++ b/Sources/RswiftResources/Integrations/DataResource+Integrations.swift @@ -16,9 +16,9 @@ import AppKit extension NSDataAsset { /** - Returns the data asset from this resource (R.data.*) + Returns the data asset from this resource (R.data.\*) - - parameter resource: The resource you want the data asset of (R.data.*) + - parameter resource: The resource you want the data asset of (R.data.\*) */ public convenience init?(resource: DataResource) { self.init(name: resource.name, bundle: resource.bundle ?? .main) diff --git a/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift b/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift index 368c0d49..0bc00441 100644 --- a/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift +++ b/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift @@ -15,9 +15,9 @@ import SwiftUI extension Image { /** - Creates a labelled image from this resource (R.image.*). + Creates a labelled image from this resource (R.image.\*). - - parameter resource: The resource you want the image of (R.image.*) + - parameter resource: The resource you want the image of (R.image.\*) */ public init(_ resource: ImageResource) { self.init(resource.name, bundle: resource.bundle) @@ -25,9 +25,9 @@ extension Image { /** - Creates a labelled image from this resource (R.image.*), with the variable value. + Creates a labelled image from this resource (R.image.\*), with the variable value. - - parameter resource: The resource you want the image of (R.image.*) + - parameter resource: The resource you want the image of (R.image.\*) - parameter variableValue: Optional value between 1 and 0 */ @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) @@ -37,9 +37,9 @@ extension Image { /** - Creates a labelled image from this resource (R.image.*), with the specified label + Creates a labelled image from this resource (R.image.\*), with the specified label - - parameter resource: The resource you want the image of (R.image.*) + - parameter resource: The resource you want the image of (R.image.\*) - parameter label: The label associated with the image, for accessibility */ public init(_ resource: ImageResource, label: Text) { @@ -48,9 +48,9 @@ extension Image { /** - Creates a labelled image from this resource (R.image.*), with the specified label and variable value. + Creates a labelled image from this resource (R.image.\*), with the specified label and variable value. - - parameter resource: The resource you want the image of (R.image.*) + - parameter resource: The resource you want the image of (R.image.\*) - parameter variableValue: Optional value between 1 and 0 - parameter label: The label associated with the image, for accessibility */ @@ -61,9 +61,9 @@ extension Image { /** - Creates an unlabelled, decorative image from this resource (R.image.*). + Creates an unlabelled, decorative image from this resource (R.image.\*). - - parameter resource: The resource you want the image of (R.image.*) + - parameter resource: The resource you want the image of (R.image.\*) */ public init(decorative resource: ImageResource) { self.init(decorative: resource.name, bundle: resource.bundle) @@ -71,9 +71,9 @@ extension Image { /** - Creates an unlabelled, decorative image from this resource (R.image.*), with variable value. + Creates an unlabelled, decorative image from this resource (R.image.\*), with variable value. - - parameter resource: The resource you want the image of (R.image.*) + - parameter resource: The resource you want the image of (R.image.\*) - parameter variableValue: Optional value between 1 and 0 */ @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) @@ -89,12 +89,12 @@ import UIKit extension ImageResource { /** - Returns the image from this resource (R.image.*) that is compatible with the trait collection. + Returns the image from this resource (R.image.\*) that is compatible with the trait collection. - - parameter resource: The resource you want the image of (R.image.*) + - parameter resource: The resource you want the image of (R.image.\*) - parameter traitCollection: Traits that describe the desired image to retrieve, pass nil to use traits that describe the main screen. - - returns: An image that exactly or best matches the desired traits with the given resource (R.image.*), or nil if no suitable image was found. + - returns: An image that exactly or best matches the desired traits with the given resource (R.image.\*), or nil if no suitable image was found. */ @available(*, deprecated, message: "Use UIImage(resource:) initializer instead") public func callAsFunction(compatibleWith traitCollection: UITraitCollection? = nil) -> UIImage? { @@ -105,24 +105,24 @@ extension ImageResource { extension UIImage { /** - Returns the image from this resource (R.image.*) that is compatible with the trait collection. + Returns the image from this resource (R.image.\*) that is compatible with the trait collection. - - parameter resource: The resource you want the image of (R.image.*) + - parameter resource: The resource you want the image of (R.image.\*) - parameter traitCollection: Traits that describe the desired image to retrieve, pass nil to use traits that describe the main screen. - - returns: An image that exactly or best matches the desired traits with the given resource (R.image.*), or nil if no suitable image was found. + - returns: An image that exactly or best matches the desired traits with the given resource (R.image.\*), or nil if no suitable image was found. */ public convenience init?(resource: ImageResource, compatibleWith traitCollection: UITraitCollection? = nil) { self.init(named: resource.name, in: resource.bundle, compatibleWith: traitCollection) } /** - Returns the image from this resource (R.image.*) using the configuration specified. + Returns the image from this resource (R.image.\*) using the configuration specified. - - parameter resource: The resource you want the image of (R.image.*) + - parameter resource: The resource you want the image of (R.image.\*) - parameter configuration: The image configuration the system appllies to the image - - returns: An image that exactly or best matches the configuration of the given resource (R.image.*), or nil if no suitable image was found. + - returns: An image that exactly or best matches the configuration of the given resource (R.image.\*), or nil if no suitable image was found. */ @available(iOS 13, tvOS 13, watchOS 6, *) public convenience init?(resource: ImageResource, with configuration: UIImage.Configuration?) { @@ -131,13 +131,13 @@ extension UIImage { /** - Returns the image from this resource (R.image.*) using the configuration, and variable value specified. + Returns the image from this resource (R.image.\*) using the configuration, and variable value specified. - - parameter resource: The resource you want the image of (R.image.*) + - parameter resource: The resource you want the image of (R.image.\*) - parameter variableValue: The value the system uses to customize the image content, between 0 and 1 - parameter configuration: The image configuration the system appllies to the image - - returns: An image that exactly or best matches the configuration of the given resource (R.image.*), or nil if no suitable image was found. + - returns: An image that exactly or best matches the configuration of the given resource (R.image.\*), or nil if no suitable image was found. */ @available(iOS 16, tvOS 16, watchOS 9, *) public convenience init?(resource: ImageResource, variableValue: Double, with configuration: UIImage.Configuration? = nil) { diff --git a/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift b/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift index cb3c584c..1a49a00e 100644 --- a/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift +++ b/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift @@ -20,8 +20,8 @@ extension UIViewController: SeguePerformer {} extension SeguePerformer { /** - Initiates the segue with the specified identifier (R.segue.*) from the current view controller's storyboard file. - - parameter identifier: The R.segue.* that identifies the triggered segue. + Initiates the segue with the specified identifier (R.segue.\*) from the current view controller's storyboard file. + - parameter identifier: The R.segue.\* that identifies the triggered segue. - parameter sender: The object that you want to use to initiate the segue. This object is made available for informational purposes during the actual segue. - SeeAlso: Library for typed block based segues: [tomlokhorst/SegueManager](https://github.com/tomlokhorst/SegueManager) */ diff --git a/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift b/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift index e14dcee5..be8852c5 100644 --- a/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift +++ b/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift @@ -1,6 +1,6 @@ // // StoryboardReference+Integrations.swift -// +// // // Created by Tom Lokhorst on 2022-07-30. // @@ -24,11 +24,23 @@ public extension StoryboardReference where Self: InitialControllerContainer { } +public extension StoryboardViewControllerIdentifier { + /** + Instantiates and returns the view controller with the specified resource (R.storyboard.\*.\*). + + - returns: The view controller corresponding to the specified resource (R.storyboard.\*.\*). If no view controller is associated, this method throws an exception. + */ + func callAsFunction() -> ViewController? { + UIStoryboard(name: storyboard, bundle: bundle).instantiateViewController(withIdentifier: identifier) as? ViewController + } +} + + public extension UIStoryboard { /** - Creates and returns a storyboard object for the specified storyboard resource (R.storyboard.*) file. + Creates and returns a storyboard object for the specified storyboard resource (R.storyboard.\*) file. - - parameter resource: The storyboard resource (R.storyboard.*) for the specific storyboard to load + - parameter resource: The storyboard resource (R.storyboard.\*) for the specific storyboard to load - returns: A storyboard object for the specified file. If no storyboard resource file matching name exists, an exception is thrown with description: `Could not find a storyboard named 'XXXXXX' in bundle....` */ @@ -38,13 +50,13 @@ public extension UIStoryboard { /** - Instantiates and returns the view controller with the specified resource (R.storyboard.*.*). + Instantiates and returns the view controller with the specified resource (R.storyboard.\*.\*). - - parameter resource: An resource (R.storyboard.*.*) that uniquely identifies the view controller in the storyboard file. If the specified resource does not exist in the storyboard file, this method raises an exception. + - parameter resource: An resource (R.storyboard.\*.\*) that uniquely identifies the view controller in the storyboard file. If the specified resource does not exist in the storyboard file, this method raises an exception. - - returns: The view controller corresponding to the specified resource (R.storyboard.*.*). If no view controller is associated, this method throws an exception. + - returns: The view controller corresponding to the specified resource (R.storyboard.\*.\*). If no view controller is associated, this method throws an exception. */ - func instantiateViewController(withIdentifier identifier: ViewControllerIdentifier) -> ViewController? { + func instantiateViewController(withIdentifier identifier: StoryboardViewControllerIdentifier) -> ViewController? { self.instantiateViewController(withIdentifier: identifier.identifier) as? ViewController } } diff --git a/Sources/RswiftResources/Shared/StoryboardReference.swift b/Sources/RswiftResources/Shared/StoryboardReference.swift index dfaf3ad0..4e58d074 100644 --- a/Sources/RswiftResources/Shared/StoryboardReference.swift +++ b/Sources/RswiftResources/Shared/StoryboardReference.swift @@ -57,19 +57,28 @@ public struct NibReference { // } //} -/// View controller identifier -public struct ViewControllerIdentifier { +/// Storyboard view controller identifier +public struct StoryboardViewControllerIdentifier { - /// Identifier of this view controller + /// Storyboard identifier of this view controller public let identifier: String + /// Name of the storyboard file on disk + public let storyboard: String + + /// Bundle this storyboard is in + public let bundle: Bundle + /** - Create a new ViewControllerIdentifier based on the identifier string + Create a new StoryboardViewControllerIdentifier based on the identifier string - parameter identifier: The string identifier for this view controller - - returns: A new ViewControllerIdentifier + - parameter storyboard: The name of the storyboard file + - parameter bundle: The bundle the storyboard is in */ - public init(identifier: String) { + public init(identifier: String, storyboard: String, bundle: Bundle) { self.identifier = identifier + self.storyboard = storyboard + self.bundle = bundle } } From c4ae178f868729d30b8c54f5b6e589451fe13eba Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 31 Jul 2022 17:14:32 +0200 Subject: [PATCH 059/161] Print data struct --- Sources/RswiftCore/RswiftCore.swift | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 96e3f881..464f1a09 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -73,10 +73,10 @@ public struct RswiftCore { catalogs: project.assetCatalogs, prefix: qualifiedName ) -// let dataStruct = DataResource.generateStruct( -// catalogs: assetCatalogs, -// prefix: qualifiedName -// ) + let dataStruct = DataResource.generateStruct( + catalogs: project.assetCatalogs, + prefix: qualifiedName + ) // let fileStruct = FileResource.generateStruct( // resources: files, @@ -142,6 +142,14 @@ public struct RswiftCore { Init.bundle projectStruct + dataStruct.generateBundleVarGetter(name: "data") + dataStruct.generateBundleFunction(name: "data") + dataStruct + + colorStruct.generateBundleVarGetter(name: "color") + colorStruct.generateBundleFunction(name: "color") + colorStruct + imageStruct.generateBundleVarGetter(name: "image") imageStruct.generateBundleFunction(name: "image") imageStruct @@ -149,10 +157,6 @@ public struct RswiftCore { segueStruct.generateLetBinding() segueStruct - colorStruct.generateBundleVarGetter(name: "color") - colorStruct.generateBundleFunction(name: "color") - colorStruct - storyboardStruct.generateBundleVarGetter(name: "storyboard") storyboardStruct.generateBundleFunction(name: "storyboard") storyboardStruct @@ -166,8 +170,9 @@ public struct RswiftCore { print("") print("extension R {") // print(" static let string = S.string") - print(" static let image = S.image") + print(" static let data = S.data") print(" static let color = S.color") + print(" static let image = S.image") print(" static let segue = S.segue") print(" static let storyboard = S.storyboard") print("}") From f3fa2b07f206c7f7d4d941df1079c5404fbbc633 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 31 Jul 2022 17:55:59 +0200 Subject: [PATCH 060/161] Update comments --- Sources/RswiftCore/RswiftCore.swift | 12 ++- .../FontResource+Generator.swift | 1 - .../ColorResource+Integrations.swift | 16 ++-- .../DataResource+Integrations.swift | 4 +- .../FontResource+Integrations.swift | 73 +++++++++++++++++++ .../ImageResource+Integrations.swift | 48 ++++++------ .../SegueIdentifier+Integrations.swift | 2 +- .../StoryboardReference+Integrations.swift | 14 ++-- 8 files changed, 123 insertions(+), 47 deletions(-) create mode 100644 Sources/RswiftResources/Integrations/FontResource+Integrations.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 464f1a09..69d80c3c 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -89,10 +89,10 @@ public struct RswiftCore { // prefix: qualifiedName // ) -// let fontStruct = FontResource.generateStruct( -// resources: fonts, -// prefix: qualifiedName -// ) + let fontStruct = FontResource.generateStruct( + resources: project.fonts, + prefix: qualifiedName + ) let storyboardStruct = StoryboardResource.generateStruct( storyboards: project.storyboards, @@ -154,6 +154,9 @@ public struct RswiftCore { imageStruct.generateBundleFunction(name: "image") imageStruct + fontStruct.generateLetBinding() + fontStruct + segueStruct.generateLetBinding() segueStruct @@ -173,6 +176,7 @@ public struct RswiftCore { print(" static let data = S.data") print(" static let color = S.color") print(" static let image = S.image") + print(" static let font = S.font") print(" static let segue = S.segue") print(" static let storyboard = S.storyboard") print("}") diff --git a/Sources/RswiftGenerators/FontResource+Generator.swift b/Sources/RswiftGenerators/FontResource+Generator.swift index 03d64ffb..87a55055 100644 --- a/Sources/RswiftGenerators/FontResource+Generator.swift +++ b/Sources/RswiftGenerators/FontResource+Generator.swift @@ -32,7 +32,6 @@ extension FontResource { let code = "FontResource(name: \"\(name)\", filename: \"\(filename)\")" return LetBinding( comments: ["Font `\(name)`."], - isStatic: true, name: SwiftIdentifier(name: name), valueCodeString: code) } diff --git a/Sources/RswiftResources/Integrations/ColorResource+Integrations.swift b/Sources/RswiftResources/Integrations/ColorResource+Integrations.swift index a5443b3b..2feff6b4 100644 --- a/Sources/RswiftResources/Integrations/ColorResource+Integrations.swift +++ b/Sources/RswiftResources/Integrations/ColorResource+Integrations.swift @@ -14,9 +14,9 @@ import SwiftUI extension Color { /** - Creates a color from this resource (R.color.\*). + Creates a color from this resource (`R.color.*`). - - parameter resource: The resource you want the color of (R.color.\*) + - parameter resource: The resource you want the color of (`R.color.*`) */ public init(_ resource: ColorResource) { self.init(resource.name, bundle: resource.bundle) @@ -30,12 +30,12 @@ import UIKit extension ColorResource { /** - Returns the color from this resource (R.color.\*) that is compatible with the trait collection. + Returns the color from this resource (`R.color.*`) that is compatible with the trait collection. - - parameter resource: The resource you want the color of (R.color.\*) + - parameter resource: The resource you want the color of (`R.color.*`) - parameter traitCollection: Traits that describe the desired color to retrieve, pass nil to use traits that describe the main screen. - - returns: A color that exactly or best matches the desired traits with the given resource (R.color.\*), or nil if no suitable color was found. + - returns: A color that exactly or best matches the desired traits with the given resource (`R.color.*`), or nil if no suitable color was found. */ @available(*, deprecated, message: "Use UIColor(resource:) initializer instead") public func callAsFunction(compatibleWith traitCollection: UITraitCollection? = nil) -> UIColor? { @@ -46,12 +46,12 @@ extension ColorResource { extension UIColor { /** - Returns the color from this resource (R.color.\*) that is compatible with the trait collection. + Returns the color from this resource (`R.color.*`) that is compatible with the trait collection. - - parameter resource: The resource you want the color of (R.color.\*) + - parameter resource: The resource you want the color of (`R.color.*`) - parameter traitCollection: Traits that describe the desired color to retrieve, pass nil to use traits that describe the main screen. - - returns: A color that exactly or best matches the desired traits with the given resource (R.color.\*), or nil if no suitable color was found. + - returns: A color that exactly or best matches the desired traits with the given resource (`R.color.*`), or nil if no suitable color was found. */ public convenience init?(resource: ColorResource, compatibleWith traitCollection: UITraitCollection? = nil) { self.init(named: resource.name, in: resource.bundle, compatibleWith: traitCollection) diff --git a/Sources/RswiftResources/Integrations/DataResource+Integrations.swift b/Sources/RswiftResources/Integrations/DataResource+Integrations.swift index 377fe9fe..162d7b40 100644 --- a/Sources/RswiftResources/Integrations/DataResource+Integrations.swift +++ b/Sources/RswiftResources/Integrations/DataResource+Integrations.swift @@ -16,9 +16,9 @@ import AppKit extension NSDataAsset { /** - Returns the data asset from this resource (R.data.\*) + Returns the data asset from this resource (`R.data.*`) - - parameter resource: The resource you want the data asset of (R.data.\*) + - parameter resource: The resource you want the data asset of (`R.data.*`) */ public convenience init?(resource: DataResource) { self.init(name: resource.name, bundle: resource.bundle ?? .main) diff --git a/Sources/RswiftResources/Integrations/FontResource+Integrations.swift b/Sources/RswiftResources/Integrations/FontResource+Integrations.swift new file mode 100644 index 00000000..90a689c0 --- /dev/null +++ b/Sources/RswiftResources/Integrations/FontResource+Integrations.swift @@ -0,0 +1,73 @@ +// +// UIFont+FontResource.swift +// R.swift.Library +// +// Created by Mathijs Kadijk on 06-01-16. +// From: https://github.com/mac-cain13/R.swift.Library +// License: MIT License +// + +import Foundation +import SwiftUI + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +extension Font { + + /** + Create a custom font from this resource (`R.font.*`) and and size that scales with the body text style. + */ + static func custom(_ resource: FontResource, size: CGFloat) -> Font { + .custom(resource.name, size: size) + } + + /** + Create a custom font from this resource (`R.font.*`) and a fixed size that does not scale with Dynamic Type. + */ + @available(macOS 11, iOS 14, tvOS 14, watchOS 7, *) + static func custom(_ resource: FontResource, fixedSize: CGFloat) -> Font { + .custom(resource.name, fixedSize: fixedSize) + } + + /** + Create a custom font from this resource (`R.font.*`) and and size that is relative to the given `textStyle`. + */ + @available(macOS 11, iOS 14, tvOS 14, watchOS 7, *) + static func custom(_ resource: FontResource, size: CGFloat, relativeTo textStyle: Font.TextStyle) -> Font { + .custom(resource.name, size: size, relativeTo: textStyle) + } +} + + +#if os(iOS) || os(tvOS) +import UIKit + +extension FontResource { + + /** + Returns the font from this resource (`R.font.*`) at the specified zie. + + - parameter resource: The font resource (`R.font.*`) for the specific font to load + - parameter size: The size (in points) to which the font is scaled. This value must be greater than 0.0. + + - returns: A color that exactly or best matches the desired traits with the given resource (R.color.\*), or nil if no suitable color was found. + */ + @available(*, deprecated, message: "Use UIFont(resource:size:) initializer instead") + public func callAsFunction(size: CGFloat) -> UIFont? { + UIFont(name: name, size: size) + } +} + +public extension UIFont { + /** + Creates and returns a font object for the specified font resource (`R.font.*`) and size. + + - parameter resource: The font resource (`R.font.*`) for the specific font to load + - parameter size: The size (in points) to which the font is scaled. This value must be greater than 0.0. + + - returns: A font object of the specified font resource and size. + */ + convenience init?(resource: FontResource, size: CGFloat) { + self.init(name: resource.name, size: size) + } +} +#endif diff --git a/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift b/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift index 0bc00441..28b2ca97 100644 --- a/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift +++ b/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift @@ -15,9 +15,9 @@ import SwiftUI extension Image { /** - Creates a labelled image from this resource (R.image.\*). + Creates a labelled image from this resource (`R.image.*`). - - parameter resource: The resource you want the image of (R.image.\*) + - parameter resource: The resource you want the image of (`R.image.*`) */ public init(_ resource: ImageResource) { self.init(resource.name, bundle: resource.bundle) @@ -25,9 +25,9 @@ extension Image { /** - Creates a labelled image from this resource (R.image.\*), with the variable value. + Creates a labelled image from this resource (`R.image.*`), with the variable value. - - parameter resource: The resource you want the image of (R.image.\*) + - parameter resource: The resource you want the image of (`R.image.*`) - parameter variableValue: Optional value between 1 and 0 */ @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) @@ -37,9 +37,9 @@ extension Image { /** - Creates a labelled image from this resource (R.image.\*), with the specified label + Creates a labelled image from this resource (`R.image.*`), with the specified label - - parameter resource: The resource you want the image of (R.image.\*) + - parameter resource: The resource you want the image of (`R.image.*`) - parameter label: The label associated with the image, for accessibility */ public init(_ resource: ImageResource, label: Text) { @@ -48,9 +48,9 @@ extension Image { /** - Creates a labelled image from this resource (R.image.\*), with the specified label and variable value. + Creates a labelled image from this resource (`R.image.*`), with the specified label and variable value. - - parameter resource: The resource you want the image of (R.image.\*) + - parameter resource: The resource you want the image of (`R.image.*`) - parameter variableValue: Optional value between 1 and 0 - parameter label: The label associated with the image, for accessibility */ @@ -61,9 +61,9 @@ extension Image { /** - Creates an unlabelled, decorative image from this resource (R.image.\*). + Creates an unlabelled, decorative image from this resource (`R.image.*`). - - parameter resource: The resource you want the image of (R.image.\*) + - parameter resource: The resource you want the image of (`R.image.*`) */ public init(decorative resource: ImageResource) { self.init(decorative: resource.name, bundle: resource.bundle) @@ -71,9 +71,9 @@ extension Image { /** - Creates an unlabelled, decorative image from this resource (R.image.\*), with variable value. + Creates an unlabelled, decorative image from this resource (`R.image.*`), with variable value. - - parameter resource: The resource you want the image of (R.image.\*) + - parameter resource: The resource you want the image of (`R.image.*`) - parameter variableValue: Optional value between 1 and 0 */ @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) @@ -89,12 +89,12 @@ import UIKit extension ImageResource { /** - Returns the image from this resource (R.image.\*) that is compatible with the trait collection. + Returns the image from this resource (`R.image.*`) that is compatible with the trait collection. - - parameter resource: The resource you want the image of (R.image.\*) + - parameter resource: The resource you want the image of (`R.image.*`) - parameter traitCollection: Traits that describe the desired image to retrieve, pass nil to use traits that describe the main screen. - - returns: An image that exactly or best matches the desired traits with the given resource (R.image.\*), or nil if no suitable image was found. + - returns: An image that exactly or best matches the desired traits with the given resource (`R.image.*`), or nil if no suitable image was found. */ @available(*, deprecated, message: "Use UIImage(resource:) initializer instead") public func callAsFunction(compatibleWith traitCollection: UITraitCollection? = nil) -> UIImage? { @@ -105,24 +105,24 @@ extension ImageResource { extension UIImage { /** - Returns the image from this resource (R.image.\*) that is compatible with the trait collection. + Returns the image from this resource (`R.image.*`) that is compatible with the trait collection. - - parameter resource: The resource you want the image of (R.image.\*) + - parameter resource: The resource you want the image of (`R.image.*`) - parameter traitCollection: Traits that describe the desired image to retrieve, pass nil to use traits that describe the main screen. - - returns: An image that exactly or best matches the desired traits with the given resource (R.image.\*), or nil if no suitable image was found. + - returns: An image that exactly or best matches the desired traits with the given resource (`R.image.*`), or nil if no suitable image was found. */ public convenience init?(resource: ImageResource, compatibleWith traitCollection: UITraitCollection? = nil) { self.init(named: resource.name, in: resource.bundle, compatibleWith: traitCollection) } /** - Returns the image from this resource (R.image.\*) using the configuration specified. + Returns the image from this resource (`R.image.*`) using the configuration specified. - - parameter resource: The resource you want the image of (R.image.\*) + - parameter resource: The resource you want the image of (`R.image.*`) - parameter configuration: The image configuration the system appllies to the image - - returns: An image that exactly or best matches the configuration of the given resource (R.image.\*), or nil if no suitable image was found. + - returns: An image that exactly or best matches the configuration of the given resource (`R.image.*`), or nil if no suitable image was found. */ @available(iOS 13, tvOS 13, watchOS 6, *) public convenience init?(resource: ImageResource, with configuration: UIImage.Configuration?) { @@ -131,13 +131,13 @@ extension UIImage { /** - Returns the image from this resource (R.image.\*) using the configuration, and variable value specified. + Returns the image from this resource (`R.image.*`) using the configuration, and variable value specified. - - parameter resource: The resource you want the image of (R.image.\*) + - parameter resource: The resource you want the image of (`R.image.*`) - parameter variableValue: The value the system uses to customize the image content, between 0 and 1 - parameter configuration: The image configuration the system appllies to the image - - returns: An image that exactly or best matches the configuration of the given resource (R.image.\*), or nil if no suitable image was found. + - returns: An image that exactly or best matches the configuration of the given resource (`R.image.*`), or nil if no suitable image was found. */ @available(iOS 16, tvOS 16, watchOS 9, *) public convenience init?(resource: ImageResource, variableValue: Double, with configuration: UIImage.Configuration? = nil) { diff --git a/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift b/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift index 1a49a00e..7f0ebf0b 100644 --- a/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift +++ b/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift @@ -20,7 +20,7 @@ extension UIViewController: SeguePerformer {} extension SeguePerformer { /** - Initiates the segue with the specified identifier (R.segue.\*) from the current view controller's storyboard file. + Initiates the segue with the specified identifier (`R.segue.*`) from the current view controller's storyboard file. - parameter identifier: The R.segue.\* that identifies the triggered segue. - parameter sender: The object that you want to use to initiate the segue. This object is made available for informational purposes during the actual segue. - SeeAlso: Library for typed block based segues: [tomlokhorst/SegueManager](https://github.com/tomlokhorst/SegueManager) diff --git a/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift b/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift index be8852c5..7ac7c8a6 100644 --- a/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift +++ b/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift @@ -26,9 +26,9 @@ public extension StoryboardReference where Self: InitialControllerContainer { public extension StoryboardViewControllerIdentifier { /** - Instantiates and returns the view controller with the specified resource (R.storyboard.\*.\*). + Instantiates and returns the view controller with the specified resource (`R.storyboard.*.*`). - - returns: The view controller corresponding to the specified resource (R.storyboard.\*.\*). If no view controller is associated, this method throws an exception. + - returns: The view controller corresponding to the specified resource (`R.storyboard.*.*`). If no view controller is associated, this method throws an exception. */ func callAsFunction() -> ViewController? { UIStoryboard(name: storyboard, bundle: bundle).instantiateViewController(withIdentifier: identifier) as? ViewController @@ -38,9 +38,9 @@ public extension StoryboardViewControllerIdentifier { public extension UIStoryboard { /** - Creates and returns a storyboard object for the specified storyboard resource (R.storyboard.\*) file. + Creates and returns a storyboard object for the specified storyboard resource (`R.storyboard.*`) file. - - parameter resource: The storyboard resource (R.storyboard.\*) for the specific storyboard to load + - parameter resource: The storyboard resource (`R.storyboard.*`) for the specific storyboard to load - returns: A storyboard object for the specified file. If no storyboard resource file matching name exists, an exception is thrown with description: `Could not find a storyboard named 'XXXXXX' in bundle....` */ @@ -50,11 +50,11 @@ public extension UIStoryboard { /** - Instantiates and returns the view controller with the specified resource (R.storyboard.\*.\*). + Instantiates and returns the view controller with the specified resource (`R.storyboard.*.*`). - - parameter resource: An resource (R.storyboard.\*.\*) that uniquely identifies the view controller in the storyboard file. If the specified resource does not exist in the storyboard file, this method raises an exception. + - parameter resource: An resource (`R.storyboard.*.*`) that uniquely identifies the view controller in the storyboard file. If the specified resource does not exist in the storyboard file, this method raises an exception. - - returns: The view controller corresponding to the specified resource (R.storyboard.\*.\*). If no view controller is associated, this method throws an exception. + - returns: The view controller corresponding to the specified resource (`R.storyboard.*.*`). If no view controller is associated, this method throws an exception. */ func instantiateViewController(withIdentifier identifier: StoryboardViewControllerIdentifier) -> ViewController? { self.instantiateViewController(withIdentifier: identifier.identifier) as? ViewController From f8cc090e93f9aa5c6e4dabb23128ce13884cbc75 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 31 Jul 2022 20:36:54 +0200 Subject: [PATCH 061/161] Generate info and entitlements --- Sources/RswiftCore/RswiftCore.swift | 31 +++++--- .../PropertyListResource+Generator.swift | 73 ++++++++++++++----- .../Shared/TypeReference+Generator.swift | 1 + .../Integrations/Bundle+Extensions.swift | 12 +++ Sources/rswift/main.swift | 13 +++- 5 files changed, 97 insertions(+), 33 deletions(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 69d80c3c..96bf2224 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -99,19 +99,17 @@ public struct RswiftCore { prefix: qualifiedName ) -// let infoStruct = PropertyListResource.generateStruct( -// resourceName: "info", -// plists: [plist], -// toplevelKeysWhitelist: infoPlistWhitelist, -// prefix: qualifiedName -// ) + let infoStruct = PropertyListResource.generateInfoStruct( + resourceName: "info", + plists: project.infoPlists, + prefix: qualifiedName + ) -// let entitlementsStruct = PropertyListResource.generateStruct( -// resourceName: "entitlements", -// plists: [entitlements], -// toplevelKeysWhitelist: nil, -// prefix: qualifiedName -// ) + let entitlementsStruct = PropertyListResource.generateStruct( + resourceName: "entitlements", + plists: project.codeSignEntitlements, + prefix: qualifiedName + ) // let reuseIdentifierStruct = Reusable.generateStruct( // nibs: nibs, @@ -154,6 +152,13 @@ public struct RswiftCore { imageStruct.generateBundleFunction(name: "image") imageStruct + infoStruct.generateBundleVarGetter(name: "info") + infoStruct.generateBundleFunction(name: "info") + infoStruct + + entitlementsStruct.generateLetBinding() + entitlementsStruct + fontStruct.generateLetBinding() fontStruct @@ -179,6 +184,8 @@ public struct RswiftCore { print(" static let font = S.font") print(" static let segue = S.segue") print(" static let storyboard = S.storyboard") + print(" static let entitlements = S.entitlements") + print(" static let info = S.info") print("}") print("TOTAL", Date().timeIntervalSince(start)) diff --git a/Sources/RswiftGenerators/PropertyListResource+Generator.swift b/Sources/RswiftGenerators/PropertyListResource+Generator.swift index b2738266..444c0dce 100644 --- a/Sources/RswiftGenerators/PropertyListResource+Generator.swift +++ b/Sources/RswiftGenerators/PropertyListResource+Generator.swift @@ -9,7 +9,17 @@ import Foundation import RswiftResources extension PropertyListResource { - public static func generateStruct(resourceName: String, plists: [PropertyListResource], toplevelKeysWhitelist: [String]?, prefix: SwiftIdentifier) -> Struct { + public static func generateInfoStruct(resourceName: String, plists: [PropertyListResource], prefix: SwiftIdentifier) -> Struct { + generateStruct( + resourceName: resourceName, + plists: plists, + toplevelKeysWhitelist: ["UIApplicationShortcutItems", "UIApplicationSceneManifest", "NSUserActivityTypes", "NSExtension"], + isInfoPlist: true, + prefix: prefix + ) + } + + public static func generateStruct(resourceName: String, plists: [PropertyListResource], toplevelKeysWhitelist: [String]? = nil, isInfoPlist: Bool = false, prefix: SwiftIdentifier) -> Struct { let structName = SwiftIdentifier(name: resourceName) let qualifiedName = prefix + structName let warning: (String) -> Void = { print("warning:", $0) } @@ -29,18 +39,21 @@ extension PropertyListResource { contents = plist.contents } - let members = contents.generateMembers(resourceName: resourceName, path: [], warning: warning) + let members = contents.generateMembers(resourceName: resourceName, path: [], isInfoPlist: isInfoPlist, warning: warning) .sorted() let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(members.structs.count) properties."] return Struct(comments: comments, name: structName) { + if isInfoPlist { + Init.bundle + } members } } } extension PropertyListResource.Contents { - @StructMembersBuilder func generateMembers(resourceName: String, path: [String], warning: (String) -> Void) -> StructMembers { + @StructMembersBuilder func generateMembers(resourceName: String, path: [String], isInfoPlist: Bool, warning: (String) -> Void) -> StructMembers { let groupedContents = self.grouped(bySwiftIdentifier: { $0.key }) groupedContents.reportWarningsForDuplicatesAndEmpties(source: resourceName, result: resourceName, warning: warning) @@ -50,46 +63,53 @@ extension PropertyListResource.Contents { switch value { case let value as Bool: LetBinding( - isStatic: true, name: SwiftIdentifier(name: key), + typeReference: .bool, valueCodeString: "\(value)" ) case let value as String: - let keyPath = (path + [key.escapedStringLiteral]).joined(separator: ".") - LetBinding( - isStatic: true, - name: SwiftIdentifier(name: key), - valueCodeString: "\"\(value.escapedStringLiteral)\"" // "NSDictionary().value(forKeyPath: \"\(keyPath)\") ?? \"\(value.escapedStringLiteral)\"" - ) + if isInfoPlist { + VarGetter( + name: SwiftIdentifier(name: key), + typeReference: .string, + valueCodeString: "_bundle.infoDictionaryString(path: \(path), key: \"\(key.escapedStringLiteral)\") ?? \"\(value.escapedStringLiteral)\"" + ) + } else { + LetBinding( + name: SwiftIdentifier(name: key), + typeReference: .string, + valueCodeString: "\"\(value.escapedStringLiteral)\"" + ) + } case let duplicateArray as [String]: let groupedArray = duplicateArray.grouped(bySwiftIdentifier: { $0 }) groupedArray.reportWarningsForDuplicatesAndEmpties(source: resourceName, result: resourceName, warning: warning) let dicts = Dictionary(groupedArray.uniques.map { ($0, $0) }, uniquingKeysWith: { l, r in l }) - Struct(name: SwiftIdentifier(name: key)) { + bundleStruct(name: key, usesBundle: isInfoPlist) { dicts - .generateMembers(resourceName: resourceName, path: newPath, warning: warning) + .generateMembers(resourceName: resourceName, path: newPath, isInfoPlist: isInfoPlist, warning: warning) .sorted() } + case var dict as [String: Any]: dict["_key"] = key - Struct(name: SwiftIdentifier(name: key)) { - dict.generateMembers(resourceName: resourceName, path: newPath, warning: warning) + bundleStruct(name: key, usesBundle: isInfoPlist) { + dict.generateMembers(resourceName: resourceName, path: newPath, isInfoPlist: isInfoPlist, warning: warning) .sorted() } case let dicts as [[String: Any]] where arrayOfDictionariesPrimaryKeys.keys.contains(key): - - Struct(name: SwiftIdentifier(name: key)) { + bundleStruct(name: key, usesBundle: isInfoPlist) { for dict in dicts { if let primaryKey = arrayOfDictionariesPrimaryKeys[key], let primary = dict[primaryKey] as? String { - Struct(name: SwiftIdentifier(name: primary)) { - dict.generateMembers(resourceName: resourceName, path: path, warning: warning) + bundleStruct(name: primary, usesBundle: isInfoPlist) { + dict.generateMembers(resourceName: resourceName, path: newPath, isInfoPlist: isInfoPlist, warning: warning) .sorted() } } @@ -104,6 +124,23 @@ extension PropertyListResource.Contents { } +@StructMembersBuilder func bundleStruct(name: String, usesBundle: Bool, @StructMembersBuilder builder: () -> StructMembers) -> StructMembers { + let str = Struct(name: SwiftIdentifier(name: name)) { + if usesBundle { + Init.bundle + } + builder() + } + + if usesBundle { + str.generateBundleVarGetter(name: name) + str.generateBundleFunction(name: name) + } else { + str.generateLetBinding() + } + str +} + // For arrays of dictionaries we need a primary key. // This key will be used as a name for the struct in the generated code. private let arrayOfDictionariesPrimaryKeys: [String: String] = [ diff --git a/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift b/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift index 5485529b..bc82af68 100644 --- a/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift +++ b/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift @@ -28,6 +28,7 @@ extension TypeReference { static var bundle: TypeReference = .init(module: .foundation, rawName: "Bundle") static var locale: TypeReference = .init(module: .foundation, rawName: "Locale") + static var bool: TypeReference = .init(module: .stdLib, rawName: "Bool") static var string: TypeReference = .init(module: .stdLib, rawName: "String") static var uiView: TypeReference = .init(module: .uiKit, rawName: "UIView") static var uiViewController: TypeReference = .init(module: .uiKit, rawName: "UIViewController") diff --git a/Sources/RswiftResources/Integrations/Bundle+Extensions.swift b/Sources/RswiftResources/Integrations/Bundle+Extensions.swift index 117a6347..cef80ee4 100644 --- a/Sources/RswiftResources/Integrations/Bundle+Extensions.swift +++ b/Sources/RswiftResources/Integrations/Bundle+Extensions.swift @@ -8,10 +8,22 @@ import Foundation extension Bundle { + + // Locale of first preferred localization, fallback to current Locale public var firstPreferredLocale: Foundation.Locale { self.preferredLocalizations.first.flatMap { Foundation.Locale(identifier: $0) } ?? Foundation.Locale.current } + /// Returns the string associated with the specified path + key in the receiver's information property list. + public func infoDictionaryString(path: [String], key: String) -> String? { + var dict = infoDictionary + for step in path { + guard let obj = dict?[step] as? [String: Any] else { return nil } + dict = obj + } + return dict?[key] as? String + } + /// Find first bundle and locale for which the table exists public func firstBundleAndLocale(tableName: String, preferredLanguages: [String]) -> (bundle: Foundation.Bundle, locale: Foundation.Locale)? { let hostingBundle = self diff --git a/Sources/rswift/main.swift b/Sources/rswift/main.swift index 99d2bf56..7b839089 100644 --- a/Sources/rswift/main.swift +++ b/Sources/rswift/main.swift @@ -33,7 +33,14 @@ struct EnvironmentKeys { let processInfo = ProcessInfo() let targetName = processInfo.environment[EnvironmentKeys.target] ?? "ResourceApp" -let xcodeprojURL = URL(fileURLWithPath: processInfo.environment[EnvironmentKeys.xcodeproj] ?? "/Users/tom/Projects/R.swift/Examples/ResourceApp/ResourceApp.xcodeproj") +let xcodeprojURL = processInfo.environment[EnvironmentKeys.xcodeproj].map { URL(fileURLWithPath: $0) } +// ?? URL(fileURLWithPath: "/Users/tom/Projects/R.swift/Examples/ResourceApp/ResourceApp.xcodeproj") + ?? URL(fileURLWithPath: "/Users/tom/Projects/Ratemp/Examples/ResourceApp/ResourceApp.xcodeproj") +let infoPlistFile = processInfo.environment[EnvironmentKeys.infoPlistFile].map { URL(fileURLWithPath: $0) } +// ?? URL(fileURLWithPath: "/Users/tom/Projects/R.swift/Examples/ResourceApp/ResourceApp/Info.plist") + ?? URL(fileURLWithPath: "/Users/tom/Projects/Ratemp/Examples/ResourceApp/ResourceApp/Info.plist") +let codeSignEntitlements = processInfo.environment[EnvironmentKeys.codeSignEntitlements].map { URL(fileURLWithPath: $0) } + ?? URL(fileURLWithPath: "/Users/tom/Projects/Ratemp/Examples/ResourceApp/ResourceApp/ResourceApp.entitlements") let sourceRootURL = xcodeprojURL.deletingLastPathComponent() let rswiftIgnoreURL = sourceRootURL.appendingPathComponent(".rswiftignore") let fakeURL = URL(fileURLWithPath: "/FAKE") @@ -50,8 +57,8 @@ let core = RswiftCore( xcodeprojURL: xcodeprojURL, targetName: targetName, productModuleName: processInfo.environment[EnvironmentKeys.productModuleName], - infoPlistFile: processInfo.environment[EnvironmentKeys.infoPlistFile].map { URL(fileURLWithPath: $0) }, - codeSignEntitlements: processInfo.environment[EnvironmentKeys.codeSignEntitlements].map { URL(fileURLWithPath: $0) }, + infoPlistFile: infoPlistFile, + codeSignEntitlements: codeSignEntitlements, rswiftIgnoreURL: rswiftIgnoreURL, sourceTreeURLs: sourceTreeURLs ) From f9518c3f857c0ecdfc0b2a0aa4da6287c2c79ade Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 31 Jul 2022 21:15:28 +0200 Subject: [PATCH 062/161] R.file generation --- Sources/RswiftCore/RswiftCore.swift | 13 ++++-- .../FileResource+Generator.swift | 24 +++++----- .../Resources/FileResource+Parser.swift | 8 ++-- Sources/RswiftResources/FileResource.swift | 15 ++++--- .../FileResource+Integrations.swift | 45 +++++++++++++++++++ 5 files changed, 78 insertions(+), 27 deletions(-) create mode 100644 Sources/RswiftResources/Integrations/FileResource+Integrations.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 96bf2224..2d566c8b 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -78,10 +78,10 @@ public struct RswiftCore { prefix: qualifiedName ) -// let fileStruct = FileResource.generateStruct( -// resources: files, -// prefix: qualifiedName -// ) + let fileStruct = FileResource.generateStruct( + resources: project.files, + prefix: qualifiedName + ) // let idStruct = AccessibilityIdentifier.generateStruct( // nibs: nibs, @@ -162,6 +162,10 @@ public struct RswiftCore { fontStruct.generateLetBinding() fontStruct + fileStruct.generateBundleVarGetter(name: "file") + fileStruct.generateBundleFunction(name: "file") + fileStruct + segueStruct.generateLetBinding() segueStruct @@ -183,6 +187,7 @@ public struct RswiftCore { print(" static let image = S.image") print(" static let font = S.font") print(" static let segue = S.segue") + print(" static let file = S.file") print(" static let storyboard = S.storyboard") print(" static let entitlements = S.entitlements") print(" static let info = S.info") diff --git a/Sources/RswiftGenerators/FileResource+Generator.swift b/Sources/RswiftGenerators/FileResource+Generator.swift index ed0d318a..95e99db2 100644 --- a/Sources/RswiftGenerators/FileResource+Generator.swift +++ b/Sources/RswiftGenerators/FileResource+Generator.swift @@ -15,31 +15,31 @@ extension FileResource { let warning: (String) -> Void = { print("warning:", $0) } // For resource files, the contents of the different locales don't matter, so we just use the first one - let firstLocales = Dictionary(grouping: resources, by: \.fullname) + let firstLocales = Dictionary(grouping: resources, by: \.filename) .values.map(\.first!) - let groupedFiles = firstLocales.grouped(bySwiftIdentifier: \.fullname) + let groupedFiles = firstLocales.grouped(bySwiftIdentifier: \.filename) groupedFiles.reportWarningsForDuplicatesAndEmpties(source: "resource file", result: "file", warning: warning) - let letbindings = groupedFiles.uniques.map { $0.generateLetBinding() } + let vargetters = groupedFiles.uniques.map { $0.generateVarGetter() } // .sorted { $0.name < $1.name } - let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) resource files."] + let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(vargetters.count) resource files."] return Struct(comments: comments, name: structName) { - letbindings + Init.bundle + vargetters } } } extension FileResource { - func generateLetBinding() -> LetBinding { - let code = "FileResource(name: \"\(name)\", filename: \"\(fullname)\")" - return LetBinding( - comments: ["Resource file `\(fullname)`."], - isStatic: true, - name: SwiftIdentifier(name: fullname), - valueCodeString: code + func generateVarGetter() -> VarGetter { + VarGetter( + comments: ["Resource file `\(filename)`."], + name: SwiftIdentifier(name: filename), + typeReference: TypeReference(module: .rswiftResources, rawName: "FileResource"), + valueCodeString: "FileResource(filename: \"\(filename.escapedStringLiteral)\", bundle: _bundle, locale: \(locale?.codeString() ?? "nil"))" ) } } diff --git a/Sources/RswiftParsers/Resources/FileResource+Parser.swift b/Sources/RswiftParsers/Resources/FileResource+Parser.swift index 617dc3b8..74b312b2 100644 --- a/Sources/RswiftParsers/Resources/FileResource+Parser.swift +++ b/Sources/RswiftParsers/Resources/FileResource+Parser.swift @@ -21,12 +21,12 @@ extension FileResource { .reduce([]) { $0.union($1) } static public func parse(url: URL) throws -> FileResource { - guard let basename = url.filenameWithoutExtension else { - throw ResourceParsingError("Couldn't extract filename from URL: \(url)") - } +// guard let basename = url.filenameWithoutExtension else { +// throw ResourceParsingError("Couldn't extract filename from URL: \(url)") +// } let locale = LocaleReference(url: url) - return FileResource(fullname: url.lastPathComponent, locale: locale, name: basename, pathExtension: url.pathExtension) + return FileResource(filename: url.lastPathComponent, bundle: nil, locale: locale) } } diff --git a/Sources/RswiftResources/FileResource.swift b/Sources/RswiftResources/FileResource.swift index b2506cae..be5b614b 100644 --- a/Sources/RswiftResources/FileResource.swift +++ b/Sources/RswiftResources/FileResource.swift @@ -10,15 +10,16 @@ import Foundation public struct FileResource { - public let fullname: String + + public let filename: String +// public let name: String +// public let pathExtension: String + public let bundle: Bundle? public let locale: LocaleReference? - public let name: String - public let pathExtension: String - public init(fullname: String, locale: LocaleReference?, name: String, pathExtension: String) { - self.fullname = fullname + public init(filename: String, bundle: Bundle?, locale: LocaleReference?) { + self.filename = filename + self.bundle = bundle self.locale = locale - self.name = name - self.pathExtension = pathExtension } } diff --git a/Sources/RswiftResources/Integrations/FileResource+Integrations.swift b/Sources/RswiftResources/Integrations/FileResource+Integrations.swift new file mode 100644 index 00000000..3dea4a0e --- /dev/null +++ b/Sources/RswiftResources/Integrations/FileResource+Integrations.swift @@ -0,0 +1,45 @@ +// +// Bundle+FileResource.swift +// R.swift.Library +// +// Created by Mathijs Kadijk on 10-01-16. +// From: https://github.com/mac-cain13/R.swift.Library +// License: MIT License +// + +import Foundation + +public extension FileResource { + /** + Returns the file URL for the given resource (`R.file.*`). + + - returns: The file URL for the resource file (`R.file.*`) or nil if the file could not be located. + */ + func callAsFunction() -> URL? { + (bundle ?? .main).url(forResource: filename, withExtension: nil) + } +} + +public extension Bundle { + /** + Returns the file URL for the given resource (`R.file.*`). + + - parameter resource: The resource to get the file URL for (`R.file.*`). + + - returns: The file URL for the resource file (`R.file.*`) or nil if the file could not be located. + */ + func url(forResource resource: FileResource) -> URL? { + url(forResource: resource.filename, withExtension: nil) + } + + /** + Returns the full pathname for the resource (`R.file.*`). + + - parameter resource: The resource file to get the path for (`R.file.*`). + + - returns: The full pathname for the resource file (`R.file.*`) or nil if the file could not be located. + */ + func path(forResource resource: FileResource) -> String? { + path(forResource: resource.filename, ofType: nil) + } +} From 387b96f7b26c7df65014c41e8ea843d78f171b2e Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 31 Jul 2022 21:31:05 +0200 Subject: [PATCH 063/161] Accessibility id generation --- Sources/RswiftCore/RswiftCore.swift | 14 +++++++++----- .../AccessibilityIdentifier+Generator.swift | 6 ++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 2d566c8b..b1ebf404 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -83,11 +83,11 @@ public struct RswiftCore { prefix: qualifiedName ) -// let idStruct = AccessibilityIdentifier.generateStruct( -// nibs: nibs, -// storyboards: storyboards, -// prefix: qualifiedName -// ) + let idStruct = AccessibilityIdentifier.generateStruct( + nibs: project.nibs, + storyboards: project.storyboards, + prefix: qualifiedName + ) let fontStruct = FontResource.generateStruct( resources: project.fonts, @@ -169,6 +169,9 @@ public struct RswiftCore { segueStruct.generateLetBinding() segueStruct + idStruct.generateLetBinding() + idStruct + storyboardStruct.generateBundleVarGetter(name: "storyboard") storyboardStruct.generateBundleFunction(name: "storyboard") storyboardStruct @@ -191,6 +194,7 @@ public struct RswiftCore { print(" static let storyboard = S.storyboard") print(" static let entitlements = S.entitlements") print(" static let info = S.info") + print(" static let id = S.id") print("}") print("TOTAL", Date().timeIntervalSince(start)) diff --git a/Sources/RswiftGenerators/AccessibilityIdentifier+Generator.swift b/Sources/RswiftGenerators/AccessibilityIdentifier+Generator.swift index 8362b650..218c8b2e 100644 --- a/Sources/RswiftGenerators/AccessibilityIdentifier+Generator.swift +++ b/Sources/RswiftGenerators/AccessibilityIdentifier+Generator.swift @@ -41,7 +41,10 @@ public struct AccessibilityIdentifier { let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(structs.count) accessibility identifiers."] return Struct(comments: comments, name: structName) { - structs + for s in structs { + s.generateLetBinding() + s + } } } @@ -53,7 +56,6 @@ public struct AccessibilityIdentifier { .map { id in LetBinding( comments: ["Accessibility identifier `\(id)`."], - isStatic: true, name: SwiftIdentifier(name: id), valueCodeString: "\"\(id)\"" ) From 8828c1d06577baef65b61e49e382c995ca75567a Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sun, 31 Jul 2022 22:13:29 +0200 Subject: [PATCH 064/161] Nib generation --- Sources/RswiftCore/RswiftCore.swift | 15 ++-- Sources/RswiftGenerators/Nib+Generator.swift | 17 ++-- .../NibReference+Integrations.swift | 85 +++++++++++++++++++ .../Shared/StoryboardReference.swift | 24 ++---- 4 files changed, 110 insertions(+), 31 deletions(-) create mode 100644 Sources/RswiftResources/Integrations/NibReference+Integrations.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index b1ebf404..cac0d028 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -111,17 +111,17 @@ public struct RswiftCore { prefix: qualifiedName ) + let nibStruct = NibResource.generateStruct( + nibs: project.nibs, + prefix: qualifiedName + ) + // let reuseIdentifierStruct = Reusable.generateStruct( // nibs: nibs, // storyboards: storyboards, // prefix: qualifiedName // ) -// let nibStruct = NibResource.generateStruct( -// nibs: nibs, -// prefix: qualifiedName -// ) - // let stringStruct = LocalizableStrings.generateStruct( // resources: project.localizableStrings, // developmentLanguage: project.xcodeproj.developmentRegion, @@ -172,6 +172,10 @@ public struct RswiftCore { idStruct.generateLetBinding() idStruct + nibStruct.generateBundleVarGetter(name: "nib") + nibStruct.generateBundleFunction(name: "nib") + nibStruct + storyboardStruct.generateBundleVarGetter(name: "storyboard") storyboardStruct.generateBundleFunction(name: "storyboard") storyboardStruct @@ -194,6 +198,7 @@ public struct RswiftCore { print(" static let storyboard = S.storyboard") print(" static let entitlements = S.entitlements") print(" static let info = S.info") + print(" static let nib = S.nib") print(" static let id = S.id") print("}") diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift index 61114cc9..da6c5269 100644 --- a/Sources/RswiftGenerators/Nib+Generator.swift +++ b/Sources/RswiftGenerators/Nib+Generator.swift @@ -21,14 +21,16 @@ extension NibResource { let groupedNibs = nibs.grouped(bySwiftIdentifier: \.name) groupedNibs.reportWarningsForDuplicatesAndEmpties(source: "nib", result: "nib", warning: warning) - let letbindings = groupedNibs.uniques - .map { $0.generateLetBinding() } + let vargetters = groupedNibs.uniques + .map { $0.generateVarGetter() } .sorted { $0.name < $1.name } - let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) nibs."] + let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(vargetters.count) nibs."] return Struct(comments: comments, name: structName) { - letbindings + Init.bundle + + vargetters } } @@ -43,13 +45,12 @@ extension NibResource { ) } - func generateLetBinding() -> LetBinding { - LetBinding( + func generateVarGetter() -> VarGetter { + VarGetter( comments: ["Nib `\(name)`."], - isStatic: true, name: SwiftIdentifier(name: name), typeReference: genericTypeReference, - valueCodeString: "NibReference(name: \"\(name)\")" + valueCodeString: "NibReference(name: \"\(name)\", bundle: _bundle)" ) } } diff --git a/Sources/RswiftResources/Integrations/NibReference+Integrations.swift b/Sources/RswiftResources/Integrations/NibReference+Integrations.swift new file mode 100644 index 00000000..331f9885 --- /dev/null +++ b/Sources/RswiftResources/Integrations/NibReference+Integrations.swift @@ -0,0 +1,85 @@ +// +// UINib+NibResource.swift +// R.swift.Library +// +// Created by Mathijs Kadijk on 08-01-16. +// From: https://github.com/mac-cain13/R.swift.Library +// License: MIT License +// + + +#if os(iOS) || os(tvOS) +import UIKit + + +public extension NibReference { + + /** + Instantiate the nib to get first object from this nib + + - parameter ownerOrNil: The owner, if the owner parameter is nil, connections to File's Owner are not permitted. + - parameter options: Options are identical to the options specified with` -[NSBundle loadNibNamed:owner:options:]` + */ + func callAsFunction(withOwner ownerOrNil: Any?, options optionsOrNil: [UINib.OptionsKey : Any]? = nil) -> FirstView? { + UINib(nibName: name, bundle: bundle).instantiate(withOwner: ownerOrNil, options: optionsOrNil).first as? FirstView + } + + @available(*, deprecated, message: "renamed to (withOwner:options:)") + func callAsFunction(owner ownerOrNil: Any?, options optionsOrNil: [UINib.OptionsKey : Any]? = nil) -> FirstView? { + UINib(nibName: name, bundle: bundle).instantiate(withOwner: ownerOrNil, options: optionsOrNil).first as? FirstView + } + + /** + Instantiate the nib to get first object from this nib + + - parameter ownerOrNil: The owner, if the owner parameter is nil, connections to File's Owner are not permitted. + - parameter options: Options are identical to the options specified with` -[NSBundle loadNibNamed:owner:options:]` + */ + func firstView(withOwner ownerOrNil: Any?, options optionsOrNil: [UINib.OptionsKey : Any]? = nil) -> FirstView? { + UINib(nibName: name, bundle: bundle).instantiate(withOwner: ownerOrNil, options: optionsOrNil).first as? FirstView + } + + @available(*, deprecated, renamed: "firstView(withOwner:options:)") + func firstView(owner ownerOrNil: Any?, options optionsOrNil: [UINib.OptionsKey : Any]? = nil) -> FirstView? { + UINib(nibName: name, bundle: bundle).instantiate(withOwner: ownerOrNil, options: optionsOrNil).first as? FirstView + } + + /** + Instantiate the nib to get the top-level objects from this nib + + - parameter ownerOrNil: The owner, if the owner parameter is nil, connections to File's Owner are not permitted. + - parameter options: Options are identical to the options specified with` -[NSBundle loadNibNamed:owner:options:]` + + - returns: An array containing the top-level objects from the NIB + */ + func instantiate(withOwner ownerOrNil: Any?, options optionsOrNil: [UINib.OptionsKey : Any]? = [:]) -> [Any] { + UINib(nibName: name, bundle: bundle).instantiate(withOwner: ownerOrNil, options: optionsOrNil) + } +} + +public extension UINib { + /** + Returns a UINib object initialized to the nib file of the specified resource (`R.nib.*`). + + - parameter resource: The resource (`R.nib.*`) to load + + - returns: The initialized UINib object. An exception is thrown if there were errors during initialization or the nib file could not be located. + */ + convenience init(resource: NibReference) { + self.init(nibName: resource.name, bundle: resource.bundle) + } +} + +public extension UIViewController { + /** + Returns a newly initialized view controller with the nib resource (`R.nib.*`). + + - parameter nib: The nib resource (`R.nib.*`) to associate with the view controller. + + - returns: A newly initialized UIViewController object. + */ + convenience init(nib: NibReference) { + self.init(nibName: nib.name, bundle: nib.bundle) + } +} +#endif diff --git a/Sources/RswiftResources/Shared/StoryboardReference.swift b/Sources/RswiftResources/Shared/StoryboardReference.swift index 4e58d074..004cbfae 100644 --- a/Sources/RswiftResources/Shared/StoryboardReference.swift +++ b/Sources/RswiftResources/Shared/StoryboardReference.swift @@ -31,31 +31,19 @@ public struct NibReference { /// String name of this nib public let name: String + /// Bundle this nib is in + public let bundle: Bundle + /** Create a new NibRefence based on the name string - parameter name: The string name for this nib - - returns: A new NibReference + - parameter bundle: The bundle the nib is in */ - public init(name: String) { + public init(name: String, bundle: Bundle) { self.name = name + self.bundle = bundle } } -// -///// View controller reference -//public struct ViewControllerReference { -// -// /// String name of this view controller -// public let name: String -// -// /** -// Create a new ViewControllerReference based on the name string -// - parameter name: The string name for this view controller -// - returns: A new ViewControllerReference -// */ -// public init(name: String) { -// self.name = name -// } -//} /// Storyboard view controller identifier public struct StoryboardViewControllerIdentifier { From cecdab8247ef4083d601cb1e8ffef852e20bc76d Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Mon, 1 Aug 2022 00:02:14 +0200 Subject: [PATCH 065/161] Add reuseIdentifier integrations --- Sources/RswiftCore/RswiftCore.swift | 14 +- Sources/RswiftGenerators/Nib+Generator.swift | 31 +++- .../ReuseIdentifier+Generator.swift | 9 +- .../NibReference+Integrations.swift | 6 +- .../ReuseIdentifier+Integrations.swift | 146 ++++++++++++++++++ .../Shared/StoryboardReference.swift | 78 +++++++--- 6 files changed, 244 insertions(+), 40 deletions(-) create mode 100644 Sources/RswiftResources/Integrations/ReuseIdentifier+Integrations.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index cac0d028..7d13ee7d 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -116,11 +116,11 @@ public struct RswiftCore { prefix: qualifiedName ) -// let reuseIdentifierStruct = Reusable.generateStruct( -// nibs: nibs, -// storyboards: storyboards, -// prefix: qualifiedName -// ) + let reuseIdentifierStruct = Reusable.generateStruct( + nibs: project.nibs, + storyboards: project.storyboards, + prefix: qualifiedName + ) // let stringStruct = LocalizableStrings.generateStruct( // resources: project.localizableStrings, @@ -176,6 +176,9 @@ public struct RswiftCore { nibStruct.generateBundleFunction(name: "nib") nibStruct + reuseIdentifierStruct.generateLetBinding() + reuseIdentifierStruct + storyboardStruct.generateBundleVarGetter(name: "storyboard") storyboardStruct.generateBundleFunction(name: "storyboard") storyboardStruct @@ -199,6 +202,7 @@ public struct RswiftCore { print(" static let entitlements = S.entitlements") print(" static let info = S.info") print(" static let nib = S.nib") + print(" static let reuseIdentifier = S.reuseIdentifier") print(" static let id = S.id") print("}") diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift index da6c5269..5255ae6a 100644 --- a/Sources/RswiftGenerators/Nib+Generator.swift +++ b/Sources/RswiftGenerators/Nib+Generator.swift @@ -46,11 +46,30 @@ extension NibResource { } func generateVarGetter() -> VarGetter { - VarGetter( - comments: ["Nib `\(name)`."], - name: SwiftIdentifier(name: name), - typeReference: genericTypeReference, - valueCodeString: "NibReference(name: \"\(name)\", bundle: _bundle)" - ) + if let reusable = reusables.first { + let typeReference = TypeReference( + module: .rswiftResources, + name: "NibReferenceReuseIdentifier", + genericArgs: [rootViews.first ?? TypeReference.uiView, reusable.type] + ) + return VarGetter( + comments: ["Nib `\(name)`."], + name: SwiftIdentifier(name: name), + typeReference: typeReference, + valueCodeString: "NibReferenceReuseIdentifier(name: \"\(name.escapedStringLiteral)\", bundle: _bundle, identifier: \"\(reusable.identifier.escapedStringLiteral)\")" + ) + } else { + let typeReference = TypeReference( + module: .rswiftResources, + name: "NibReference", + genericArgs: [rootViews.first ?? TypeReference.uiView] + ) + return VarGetter( + comments: ["Nib `\(name)`."], + name: SwiftIdentifier(name: name), + typeReference: typeReference, + valueCodeString: "NibReference(name: \"\(name.escapedStringLiteral)\", bundle: _bundle)" + ) + } } } diff --git a/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift b/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift index 928e6180..11e9448d 100644 --- a/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift +++ b/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift @@ -10,7 +10,7 @@ import RswiftResources extension Reusable { public static func generateStruct(nibs: [NibResource], storyboards: [StoryboardResource], prefix: SwiftIdentifier) -> Struct { - let structName = SwiftIdentifier(name: "reuseableIdentifier") + let structName = SwiftIdentifier(name: "reuseIdentifier") let qualifiedName = prefix + structName let warning: (String) -> Void = { print("warning:", $0) } @@ -39,18 +39,17 @@ extension Reusable { var genericTypeReference: TypeReference { TypeReference( module: .rswiftResources, - name: "ReusableIdentifier", + name: "ReuseIdentifier", genericArgs: [type] ) } func generateLetBinding() -> LetBinding { LetBinding( - comments: ["Reuseable identifier `\(identifier)`."], - isStatic: true, + comments: ["Reuse identifier `\(identifier)`."], name: SwiftIdentifier(name: identifier), typeReference: genericTypeReference, - valueCodeString: "ReuseableIdentifier(identifier: \"\(identifier)\")" + valueCodeString: "ReuseIdentifier(identifier: \"\(identifier)\")" ) } } diff --git a/Sources/RswiftResources/Integrations/NibReference+Integrations.swift b/Sources/RswiftResources/Integrations/NibReference+Integrations.swift index 331f9885..b8f421ea 100644 --- a/Sources/RswiftResources/Integrations/NibReference+Integrations.swift +++ b/Sources/RswiftResources/Integrations/NibReference+Integrations.swift @@ -12,7 +12,7 @@ import UIKit -public extension NibReference { +public extension NibReferenceContainer { /** Instantiate the nib to get first object from this nib @@ -65,7 +65,7 @@ public extension UINib { - returns: The initialized UINib object. An exception is thrown if there were errors during initialization or the nib file could not be located. */ - convenience init(resource: NibReference) { + convenience init(resource: Nib) { self.init(nibName: resource.name, bundle: resource.bundle) } } @@ -78,7 +78,7 @@ public extension UIViewController { - returns: A newly initialized UIViewController object. */ - convenience init(nib: NibReference) { + convenience init(nib: Nib) { self.init(nibName: nib.name, bundle: nib.bundle) } } diff --git a/Sources/RswiftResources/Integrations/ReuseIdentifier+Integrations.swift b/Sources/RswiftResources/Integrations/ReuseIdentifier+Integrations.swift new file mode 100644 index 00000000..1644ac51 --- /dev/null +++ b/Sources/RswiftResources/Integrations/ReuseIdentifier+Integrations.swift @@ -0,0 +1,146 @@ +// +// UITableView+ReuseIdentifierProtocol.swift +// R.swift Library +// +// Created by Mathijs Kadijk on 06-12-15. +// From: https://github.com/mac-cain13/R.swift.Library +// License: MIT License +// + + +#if os(iOS) || os(tvOS) +import UIKit + + +public extension UITableView { + + /** + Register a `R.nib.*` containing a cell with the table view under it's contained identifier. + + - parameter resource: A nib resource (`R.nib.*`) containing a table view cell that has a reuse identifier + */ + func register(_ resource: Resource) where Resource.Reusable: UITableViewCell { + register(UINib(resource: resource), forCellReuseIdentifier: resource.identifier) + } + + /** + Register a `R.reuseIdentifier.*` containing a cell with the table view under it's contained identifier. + + - parameter resource: A reuse identifier + */ + func register(_ resource: Resource) where Resource.Reusable: UITableViewCell { + register(Resource.Reusable.self, forCellReuseIdentifier: resource.identifier) + } + + /** + Register a `R.nib.*` containing a header or footer with the table view under it's contained identifier. + + - parameter resource: A nib resource (`R.nib.*`) containing a view that has a reuse identifier + */ + func registerHeaderFooterView(_ resource: Resource) where Resource.Reusable: UIView { + register(UINib(resource: resource), forHeaderFooterViewReuseIdentifier: resource.identifier) + } + + /** + Register a `R.reuseIdentifier.*` containing a header or footer with the table view under it's contained identifier. + + - parameter resource: A reuse identifier + */ + func registerHeaderFooterView(_ resource: Resource) where Resource.Reusable: UITableViewHeaderFooterView { + register(Resource.Reusable.self, forHeaderFooterViewReuseIdentifier: resource.identifier) + } + + /** + Returns a typed reusable table-view cell object for the specified reuse identifier and adds it to the table. + + - parameter identifier: A `R.reuseIdentifier.*` value identifying the cell object to be reused. + - parameter indexPath: The index path specifying the location of the cell. The data source receives this information when it is asked for the cell and should just pass it along. This method uses the index path to perform additional configuration based on the cell’s position in the table view. + + - returns: The UITableViewCell subclass with the associated reuse identifier or nil if it couldn't be casted correctly. + + - precondition: You must register a class or nib file using the registerNib: or registerClass:forCellReuseIdentifier: method before calling this method. + */ + func dequeueReusableCell(withIdentifier identifier: Identifier, for indexPath: IndexPath) -> Identifier.Reusable? where Identifier.Reusable: UITableViewCell { + dequeueReusableCell(withIdentifier: identifier.identifier, for: indexPath) as? Identifier.Reusable + } + + + /** + Returns a typed reusable header or footer view located by its identifier. + + - parameter identifier: A `R.reuseIdentifier.*` value identifying the header or footer view to be reused. + + - returns: A UITableViewHeaderFooterView object with the associated identifier or nil if no such object exists in the reusable view queue or if it couldn't be cast correctly. + */ + func dequeueReusableHeaderFooterView(withIdentifier identifier: Identifier) -> Identifier.Reusable? where Identifier.Reusable: UITableViewHeaderFooterView { + dequeueReusableHeaderFooterView(withIdentifier: identifier.identifier) as? Identifier.Reusable + } +} + + +public extension UICollectionView { + + /** + Register a `R.nib.*` for use in creating new collection view cells. + + - parameter resource: A nib resource (`R.nib.*`) containing a object of type UICollectionViewCell that has a reuse identifier + */ + func register(_ resource: Resource) where Resource.Reusable: UICollectionViewCell { + register(UINib(resource: resource), forCellWithReuseIdentifier: resource.identifier) + } + + /** + Register a `R.reuseIdentifier.*` for use in creating new collection view cells. + + - parameter resource: A reuse identifier + */ + func register(_ resource: Resource) where Resource.Reusable: UICollectionViewCell { + register(Resource.Reusable.self, forCellWithReuseIdentifier: resource.identifier) + } + + /** + Register a `R.nib.*` for use in creating supplementary views for the collection view. + + - parameter resource: A nib resource (`R.nib.*`) containing a object of type UICollectionReusableView. that has a reuse identifier + */ + func register(_ resource: Resource, forSupplementaryViewOfKind kind: String) where Resource.Reusable: UICollectionReusableView { + register(UINib(resource: resource), forSupplementaryViewOfKind: kind, withReuseIdentifier: resource.identifier) + } + + /** + Register a `R.reuseIdentfier.*` for use in creating supplementary views for the collection view. + + - parameter resource: A reuseIdentifier + */ + func register(_ resource: Resource, forSupplementaryViewOfKind kind: String) where Resource.Reusable: UICollectionReusableView { + register(Resource.Reusable.self, forSupplementaryViewOfKind: kind, withReuseIdentifier: resource.identifier) + } + + /** + Returns a typed reusable cell object located by its identifier + + - parameter identifier: The `R.reuseIdentifier.*` value for the specified cell. + - parameter indexPath: The index path specifying the location of the cell. The data source receives this information when it is asked for the cell and should just pass it along. This method uses the index path to perform additional configuration based on the cell’s position in the collection view. + + - returns: A subclass of UICollectionReusableView or nil if the cast fails. + */ + func dequeueReusableCell(withReuseIdentifier identifier: Identifier, for indexPath: IndexPath) -> Identifier.Reusable? where Identifier.Reusable: UICollectionReusableView { + dequeueReusableCell(withReuseIdentifier: identifier.identifier, for: indexPath) as? Identifier.Reusable + } + + /** + Returns a typed reusable supplementary view located by its identifier and kind. + + - parameter elementKind: The kind of supplementary view to retrieve. This value is defined by the layout object. + - parameter identifier: The `R.reuseIdentifier.*` value for the specified view. + - parameter indexPath: The index path specifying the location of the cell. The data source receives this information when it is asked for the cell and should just pass it along. This method uses the index path to perform additional configuration based on the cell’s position in the collection view. + + - returns: A subclass of UICollectionReusableView or nil if the cast fails. + */ + func dequeueReusableSupplementaryView(ofKind elementKind: String, withReuseIdentifier identifier: Identifier, for indexPath: IndexPath) -> Identifier.Reusable? where Identifier.Reusable: UICollectionReusableView { + dequeueReusableSupplementaryView(ofKind: elementKind, withReuseIdentifier: identifier.identifier, for: indexPath) as? Identifier.Reusable + } + +} + +#endif diff --git a/Sources/RswiftResources/Shared/StoryboardReference.swift b/Sources/RswiftResources/Shared/StoryboardReference.swift index 004cbfae..0b6a071a 100644 --- a/Sources/RswiftResources/Shared/StoryboardReference.swift +++ b/Sources/RswiftResources/Shared/StoryboardReference.swift @@ -25,26 +25,6 @@ public protocol InitialControllerContainer { } -/// Nib reference -public struct NibReference { - - /// String name of this nib - public let name: String - - /// Bundle this nib is in - public let bundle: Bundle - - /** - Create a new NibRefence based on the name string - - parameter name: The string name for this nib - - parameter bundle: The bundle the nib is in - */ - public init(name: String, bundle: Bundle) { - self.name = name - self.bundle = bundle - } -} - /// Storyboard view controller identifier public struct StoryboardViewControllerIdentifier { @@ -70,8 +50,39 @@ public struct StoryboardViewControllerIdentifier { } } +public protocol NibReferenceContainer { + associatedtype FirstView + var name: String { get } + var bundle: Bundle { get } +} + +public protocol ReuseIdentifierContainer { + associatedtype Reusable + var identifier: String { get } +} + +/// Nib reference +public struct NibReference: NibReferenceContainer { + + /// String name of this nib + public let name: String + + /// Bundle this nib is in + public let bundle: Bundle + + /** + Create a new NibRefence based on the name string + - parameter name: The string name for this nib + - parameter bundle: The bundle the nib is in + */ + public init(name: String, bundle: Bundle) { + self.name = name + self.bundle = bundle + } +} + /// Reuse identifier -public struct ReuseIdentifier { +public struct ReuseIdentifier: ReuseIdentifierContainer { /// String identifier of this reusable public let identifier: String @@ -86,6 +97,31 @@ public struct ReuseIdentifier { } } +/// Nib reference, reuse identifier +public struct NibReferenceReuseIdentifier: NibReferenceContainer, ReuseIdentifierContainer { + + /// String name of this nib + public let name: String + + /// Bundle this nib is in + public let bundle: Bundle + + /// String identifier of this reusable + public let identifier: String + + /** + Create a new NibRefence based on the name string + - parameter name: The string name for this nib + - parameter bundle: The bundle the nib is in + - parameter identifier: The string identifier for this reusable + */ + public init(name: String, bundle: Bundle, identifier: String) { + self.name = name + self.bundle = bundle + self.identifier = identifier + } +} + /// Segue identifier public struct SegueIdentifier { From a46ff0f4ead42e585935b1f51477f5a871cd7ed0 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Mon, 1 Aug 2022 00:22:03 +0200 Subject: [PATCH 066/161] Add deprecated preferredLanguages string functions --- Sources/RswiftCore/RswiftCore.swift | 16 +++-- .../LocalizableStrings+Generator.swift | 2 +- .../StringResource+Integrations.swift | 69 +++++++++++++++++++ 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 7d13ee7d..d6836bc3 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -122,11 +122,11 @@ public struct RswiftCore { prefix: qualifiedName ) -// let stringStruct = LocalizableStrings.generateStruct( -// resources: project.localizableStrings, -// developmentLanguage: project.xcodeproj.developmentRegion, -// prefix: qualifiedName -// ) + let stringStruct = LocalizableStrings.generateStruct( + resources: project.localizableStrings, + developmentLanguage: project.xcodeproj.developmentRegion, + prefix: qualifiedName + ) let projectStruct = Struct(name: SwiftIdentifier(name: "project")) { LetBinding(name: SwiftIdentifier(name: "developmentRegion"), valueCodeString: #""\#(project.xcodeproj.developmentRegion)""#) @@ -140,6 +140,10 @@ public struct RswiftCore { Init.bundle projectStruct + stringStruct.generateBundleVarGetter(name: "string") + stringStruct.generateBundleFunction(name: "string") + stringStruct + dataStruct.generateBundleVarGetter(name: "data") dataStruct.generateBundleFunction(name: "data") dataStruct @@ -191,7 +195,7 @@ public struct RswiftCore { print("let S = _S(bundle: Bundle.main)") print("") print("extension R {") -// print(" static let string = S.string") + print(" static let string = S.string") print(" static let data = S.data") print(" static let color = S.color") print(" static let image = S.image") diff --git a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift index 07664788..e8283b0f 100644 --- a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift +++ b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift @@ -285,7 +285,7 @@ private struct StringWithParams { } private var typeReference: TypeReference { - TypeReference(module: .host, name: "StringResource\(params.isEmpty ? "" : "\(params.count)")", genericArgs: params.map(\.spec.typeReference)) + TypeReference(module: .rswiftResources, name: "StringResource\(params.isEmpty ? "" : "\(params.count)")", genericArgs: params.map(\.spec.typeReference)) } private var typeName: String { diff --git a/Sources/RswiftResources/Integrations/StringResource+Integrations.swift b/Sources/RswiftResources/Integrations/StringResource+Integrations.swift index 65216703..8d56d18d 100644 --- a/Sources/RswiftResources/Integrations/StringResource+Integrations.swift +++ b/Sources/RswiftResources/Integrations/StringResource+Integrations.swift @@ -64,6 +64,12 @@ extension StringResource { NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") } + @available(*, deprecated, message: "Use R.string(preferredLanguages:).*.* instead") + public func callAsFunction(preferredLanguages: [String]) -> String { + let (bundle, _) = bundle.firstBundleAndLocale(tableName: tableName, preferredLanguages: preferredLanguages) ?? (bundle, locale) + return NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + } + @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) public var localizedStringResource: LocalizedStringResource { LocalizedStringResource(key, defaultValue: String.LocalizationValue(stringLiteral: defaultValue), bundle: bundle == .main ? .main : .atURL(bundle.bundleURL), comment: comment) @@ -75,6 +81,13 @@ extension StringResource1 { let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") return String(format: format, locale: locale, arguments: [arg1]) } + + @available(*, deprecated, message: "Use R.string(preferredLanguages:).*.* instead") + public func callAsFunction(_ arg1: Arg1, preferredLanguages: [String]) -> String { + let (bundle, locale) = bundle.firstBundleAndLocale(tableName: tableName, preferredLanguages: preferredLanguages) ?? (bundle, locale) + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1]) + } } extension StringResource2 { @@ -82,6 +95,13 @@ extension StringResource2 { let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") return String(format: format, locale: locale, arguments: [arg1, arg2]) } + + @available(*, deprecated, message: "Use R.string(preferredLanguages:).*.* instead") + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, preferredLanguages: [String]) -> String { + let (bundle, locale) = bundle.firstBundleAndLocale(tableName: tableName, preferredLanguages: preferredLanguages) ?? (bundle, locale) + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2]) + } } extension StringResource3 { @@ -89,6 +109,13 @@ extension StringResource3 { let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") return String(format: format, locale: locale, arguments: [arg1, arg2, arg3]) } + + @available(*, deprecated, message: "Use R.string(preferredLanguages:).*.* instead") + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, preferredLanguages: [String]) -> String { + let (bundle, locale) = bundle.firstBundleAndLocale(tableName: tableName, preferredLanguages: preferredLanguages) ?? (bundle, locale) + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3]) + } } extension StringResource4 { @@ -96,6 +123,13 @@ extension StringResource4 { let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4]) } + + @available(*, deprecated, message: "Use R.string(preferredLanguages:).*.* instead") + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, preferredLanguages: [String]) -> String { + let (bundle, locale) = bundle.firstBundleAndLocale(tableName: tableName, preferredLanguages: preferredLanguages) ?? (bundle, locale) + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4]) + } } extension StringResource5 { @@ -103,6 +137,13 @@ extension StringResource5 { let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5]) } + + @available(*, deprecated, message: "Use R.string(preferredLanguages:).*.* instead") + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, preferredLanguages: [String]) -> String { + let (bundle, locale) = bundle.firstBundleAndLocale(tableName: tableName, preferredLanguages: preferredLanguages) ?? (bundle, locale) + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5]) + } } extension StringResource6 { @@ -110,6 +151,13 @@ extension StringResource6 { let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6]) } + + @available(*, deprecated, message: "Use R.string(preferredLanguages:).*.* instead") + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, preferredLanguages: [String]) -> String { + let (bundle, locale) = bundle.firstBundleAndLocale(tableName: tableName, preferredLanguages: preferredLanguages) ?? (bundle, locale) + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6]) + } } extension StringResource7 { @@ -117,6 +165,13 @@ extension StringResource7 { let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7]) } + + @available(*, deprecated, message: "Use R.string(preferredLanguages:).*.* instead") + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, preferredLanguages: [String]) -> String { + let (bundle, locale) = bundle.firstBundleAndLocale(tableName: tableName, preferredLanguages: preferredLanguages) ?? (bundle, locale) + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7]) + } } extension StringResource8 { @@ -124,6 +179,13 @@ extension StringResource8 { let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8]) } + + @available(*, deprecated, message: "Use R.string(preferredLanguages:).*.* instead") + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8, preferredLanguages: [String]) -> String { + let (bundle, locale) = bundle.firstBundleAndLocale(tableName: tableName, preferredLanguages: preferredLanguages) ?? (bundle, locale) + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8]) + } } extension StringResource9 { @@ -131,4 +193,11 @@ extension StringResource9 { let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9]) } + + @available(*, deprecated, message: "Use R.string(preferredLanguages:).*.* instead") + public func callAsFunction(_ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7, _ arg8: Arg8, _ arg9: Arg9, preferredLanguages: [String]) -> String { + let (bundle, locale) = bundle.firstBundleAndLocale(tableName: tableName, preferredLanguages: preferredLanguages) ?? (bundle, locale) + let format = NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") + return String(format: format, locale: locale, arguments: [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9]) + } } From c1a4b09ad134e2af86695a5b6fafeaee5a11ea96 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Tue, 27 Sep 2022 11:25:48 +0200 Subject: [PATCH 067/161] Work on CLI --- Package.resolved | 9 + Package.swift | 14 +- Sources/RswiftCore/RswiftCore.swift | 31 ++++ .../ImageResource+Integrations.swift | 24 +-- .../StringResource+Integrations.swift | 8 +- Sources/rswift/App.swift | 162 ++++++++++++++++++ Sources/rswift/Config.swift | 14 ++ Sources/rswift/main.swift | 67 -------- 8 files changed, 243 insertions(+), 86 deletions(-) create mode 100644 Sources/rswift/App.swift create mode 100644 Sources/rswift/Config.swift delete mode 100644 Sources/rswift/main.swift diff --git a/Package.resolved b/Package.resolved index 045d6e5a..d3e36446 100644 --- a/Package.resolved +++ b/Package.resolved @@ -19,6 +19,15 @@ "version": "0.10.1" } }, + { + "package": "swift-argument-parser", + "repositoryURL": "https://github.com/apple/swift-argument-parser", + "state": { + "branch": null, + "revision": "9f39744e025c7d377987f30b03770805dcb0bcd1", + "version": "1.1.4" + } + }, { "package": "XcodeEdit", "repositoryURL": "https://github.com/tomlokhorst/XcodeEdit", diff --git a/Package.swift b/Package.swift index 5b6c8752..ecdf99a7 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.6 import PackageDescription let package = Package( @@ -17,6 +17,7 @@ let package = Package( dependencies: [ .package(url: "https://github.com/kylef/Commander.git", from: "0.8.0"), .package(url: "https://github.com/tomlokhorst/XcodeEdit", from: "2.8.0"), + .package(url: "https://github.com/apple/swift-argument-parser", from: "1.1.0"), ], targets: [ .target(name: "RswiftResources"), @@ -24,10 +25,17 @@ let package = Package( .target(name: "RswiftParsers", dependencies: ["RswiftResources", "XcodeEdit"]), // Core of R.swift, brings all previous parts together - .target(name: "RswiftCore", dependencies: ["RswiftParsers", "RswiftGenerators"]), + .target(name: "RswiftCore", dependencies: [ + .target(name: "RswiftParsers"), + .target(name: "RswiftGenerators"), + .product(name: "ArgumentParser", package: "swift-argument-parser"), + ]), // Executable that calls Core - .target(name: "rswift", dependencies: ["RswiftCore"]), + .executableTarget(name: "rswift", dependencies: [ + .target(name: "RswiftCore"), + .product(name: "ArgumentParser", package: "swift-argument-parser"), + ]), // Legacy code .target(name: "rswift-legacy", dependencies: ["RswiftCoreLegacy"]), diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index d6836bc3..760c501f 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -6,12 +6,38 @@ // import Foundation +import ArgumentParser import XcodeEdit import RswiftParsers import RswiftResources import RswiftGenerators +public enum Generator: String, CaseIterable, ExpressibleByArgument { + case image + case string + case color + case file + case font + case nib + case segue + case storyboard + case reuseIdentifier + case entitlements + case info + case id +} + +public enum AccessLevel: String, ExpressibleByArgument { + case publicLevel = "public" + case internalLevel = "internal" + case filePrivate = "fileprivate" + case privateLevel = "private" +} + + public struct RswiftCore { + let outputURL: URL + let generators: [Generator] let xcodeprojURL: URL let targetName: String let productModuleName: String? @@ -23,6 +49,8 @@ public struct RswiftCore { let rswiftIgnoreURL: URL public init( + outputURL: URL, + generators: [Generator], xcodeprojURL: URL, targetName: String, productModuleName: String?, @@ -31,6 +59,8 @@ public struct RswiftCore { rswiftIgnoreURL: URL, sourceTreeURLs: SourceTreeURLs ) { + self.outputURL = outputURL + self.generators = generators self.xcodeprojURL = xcodeprojURL self.targetName = targetName self.productModuleName = productModuleName @@ -188,6 +218,7 @@ public struct RswiftCore { storyboardStruct } + try s.prettyPrint().write(to: outputURL, atomically: true, encoding: .utf8) print(s.prettyPrint()) print() diff --git a/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift b/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift index 28b2ca97..07d813d8 100644 --- a/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift +++ b/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift @@ -30,10 +30,10 @@ extension Image { - parameter resource: The resource you want the image of (`R.image.*`) - parameter variableValue: Optional value between 1 and 0 */ - @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) - public init(_ resource: ImageResource, variableValue: Double?) { - self.init(resource.name, variableValue: variableValue, bundle: resource.bundle) - } +// @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) +// public init(_ resource: ImageResource, variableValue: Double?) { +// self.init(resource.name, variableValue: variableValue, bundle: resource.bundle) +// } /** @@ -54,10 +54,10 @@ extension Image { - parameter variableValue: Optional value between 1 and 0 - parameter label: The label associated with the image, for accessibility */ - @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) - public init(_ resource: ImageResource, variableValue: Double?, label: Text) { - self.init(resource.name, variableValue: variableValue, bundle: resource.bundle, label: label) - } +// @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) +// public init(_ resource: ImageResource, variableValue: Double?, label: Text) { +// self.init(resource.name, variableValue: variableValue, bundle: resource.bundle, label: label) +// } /** @@ -76,10 +76,10 @@ extension Image { - parameter resource: The resource you want the image of (`R.image.*`) - parameter variableValue: Optional value between 1 and 0 */ - @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) - public init(decorative resource: ImageResource, variableValue: Double?) { - self.init(decorative: resource.name, variableValue: variableValue, bundle: resource.bundle) - } +// @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) +// public init(decorative resource: ImageResource, variableValue: Double?) { +// self.init(decorative: resource.name, variableValue: variableValue, bundle: resource.bundle) +// } } diff --git a/Sources/RswiftResources/Integrations/StringResource+Integrations.swift b/Sources/RswiftResources/Integrations/StringResource+Integrations.swift index 8d56d18d..b509f314 100644 --- a/Sources/RswiftResources/Integrations/StringResource+Integrations.swift +++ b/Sources/RswiftResources/Integrations/StringResource+Integrations.swift @@ -70,10 +70,10 @@ extension StringResource { return NSLocalizedString(key.description, tableName: tableName, bundle: bundle, value: defaultValue, comment: "") } - @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) - public var localizedStringResource: LocalizedStringResource { - LocalizedStringResource(key, defaultValue: String.LocalizationValue(stringLiteral: defaultValue), bundle: bundle == .main ? .main : .atURL(bundle.bundleURL), comment: comment) - } +// @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) +// public var localizedStringResource: LocalizedStringResource { +// LocalizedStringResource(key, defaultValue: String.LocalizationValue(stringLiteral: defaultValue), bundle: bundle == .main ? .main : .atURL(bundle.bundleURL), comment: comment) +// } } extension StringResource1 { diff --git a/Sources/rswift/App.swift b/Sources/rswift/App.swift new file mode 100644 index 00000000..dc90c080 --- /dev/null +++ b/Sources/rswift/App.swift @@ -0,0 +1,162 @@ +// +// App.swift +// rswift +// +// Created by Tom Lokhorst on 2021-04-18. +// + +import ArgumentParser +import Foundation +import RswiftCore +import RswiftParsers +import XcodeEdit + + +@main +struct App: ParsableCommand { + static var configuration = CommandConfiguration( + commandName: "rswift", + abstract: "Generate static references for autocompleted resources like images, fonts and localized strings in Swift projects", + version: Config.version, + subcommands: [Generate.self, PrintCommand.self] + ) +} + +extension App { + struct Generate: ParsableCommand { + static var configuration = CommandConfiguration(abstract: "Generates R.generated.swift file") + + @Option(help: "Only run specified generators, options: \(Generator.allCases.map(\.rawValue).joined(separator: ", "))") + var generators: [Generator] = [] + +// @Option(help: "Output path for an extra generated file that contains resources commonly used in UI tests such as accessibility identifiers") +// var generateUITestFile: String? + + @Option(help: "Add extra modules as import in the generated file") + var imports: [String] = [] + + @Option(help: "The access level [public|internal] to use for the generated R-file") + var accessLevel: AccessLevel = .internalLevel + + @Option(help: "Path to pattern file that describes files that should be ignored") + var rswiftignore = ".rswiftignore" + + @Option(help: "Override bundle from which resources are loaded") + var hostingBundle: String? + + + // MARK: Project specific - Environment variable overrides + + @Option(help: "Override environment variable \(EnvironmentKeys.xcodeproj)") + var xcodeproj: String? + + @Option(help: "Override environment variable \(EnvironmentKeys.target)") + var target: String? + +// @Option(help: "Override environment variable \(EnvironmentKeys.productModuleName)") +// var productModuleName: String? + + @Option(help: "Override environment variable \(EnvironmentKeys.infoPlistFile)") + var infoPlistFile: String? + + @Option(help: "Override environment variable \(EnvironmentKeys.codeSignEntitlements)") + var codeSignEntitlements: String? + + + // MARK: Xcode build - Environment variable overrides + + @Option(help: "Override environment variable \(EnvironmentKeys.builtProductsDir)") + var builtProductsDir: String? + + @Option(help: "Override environment variable \(EnvironmentKeys.developerDir)") + var developerDir: String? + + @Option(help: "Override environment variable \(EnvironmentKeys.platformDir)") + var platformDir: String? + + @Option(help: "Override environment variable \(EnvironmentKeys.sdkRoot)") + var sdkRoot: String? + + @Option(help: "Override environment variable \(EnvironmentKeys.sourceRoot)") + var sourceRoot: String? + + + // MARK: Output path argument + + @Argument(help: "Output path for the generated file") + var outputPath: String + + mutating func run() throws { + let processInfo = ProcessInfo() + + let xcodeprojPath = try self.xcodeproj ?? processInfo.environmentVariable(name: EnvironmentKeys.xcodeproj) + let targetName = try self.target ?? processInfo.environmentVariable(name: EnvironmentKeys.target) +// let productModuleName = try self.productModuleName ?? processInfo.environmentVariable(name: EnvironmentKeys.productModuleName) + let infoPlistFile = self.infoPlistFile ?? processInfo.environment[EnvironmentKeys.infoPlistFile] + let codeSignEntitlements = self.codeSignEntitlements ?? processInfo.environment[EnvironmentKeys.codeSignEntitlements] + + let sourceTreeURLs = try SourceTreeURLs( + builtProductsDirURL: URL(fileURLWithPath: builtProductsDir ?? processInfo.environmentVariable(name: EnvironmentKeys.builtProductsDir)), + developerDirURL: URL(fileURLWithPath: developerDir ?? processInfo.environmentVariable(name: EnvironmentKeys.developerDir)), + sourceRootURL: URL(fileURLWithPath: sourceRoot ?? processInfo.environmentVariable(name: EnvironmentKeys.sourceRoot)), + sdkRootURL: URL(fileURLWithPath: sdkRoot ?? processInfo.environmentVariable(name: EnvironmentKeys.sdkRoot)), + platformURL: URL(fileURLWithPath: platformDir ?? processInfo.environmentVariable(name: EnvironmentKeys.platformDir)) + ) + + let outputURL = URL(fileURLWithPath: outputPath) +// let uiTestOutputURL = generateUITestFile.map(URL.init(fileURLWithPath:)) + let rswiftIgnoreURL = sourceTreeURLs.sourceRootURL.appendingPathComponent(rswiftignore, isDirectory: false) + + let core = RswiftCore( + outputURL: outputURL, + generators: generators.isEmpty ? Generator.allCases : generators, + xcodeprojURL: URL(fileURLWithPath: xcodeprojPath), + targetName: targetName, + productModuleName: nil, + infoPlistFile: infoPlistFile.map(URL.init(fileURLWithPath:)), + codeSignEntitlements: codeSignEntitlements.map(URL.init(fileURLWithPath:)), + rswiftIgnoreURL: rswiftIgnoreURL, + sourceTreeURLs: sourceTreeURLs + ) + +// print("RSWIFT", outputURL) + try core.developRun() + } + } +} + +extension App { + struct PrintCommand: ParsableCommand { + static var configuration = CommandConfiguration(abstract: "Prints the command rswift for use in CLI") + + + mutating func run() { + print("PRINT COMMAND") + } + } +} + +struct EnvironmentKeys { + static let action = "ACTION" + + static let bundleIdentifier = "PRODUCT_BUNDLE_IDENTIFIER" + static let productModuleName = "PRODUCT_MODULE_NAME" + static let target = "TARGET_NAME" + static let xcodeproj = "PROJECT_FILE_PATH" + static let infoPlistFile = "INFOPLIST_FILE" + static let codeSignEntitlements = "CODE_SIGN_ENTITLEMENTS" + + static let builtProductsDir = SourceTreeFolder.buildProductsDir.rawValue + static let developerDir = SourceTreeFolder.developerDir.rawValue + static let platformDir = SourceTreeFolder.platformDir.rawValue + static let sdkRoot = SourceTreeFolder.sdkRoot.rawValue + static let sourceRoot = SourceTreeFolder.sourceRoot.rawValue +} + +extension ProcessInfo { + func environmentVariable(name: String) throws -> String { + guard let value = self.environment[name] else { throw ValidationError("Missing argument \(name)") } + return value + } +} + diff --git a/Sources/rswift/Config.swift b/Sources/rswift/Config.swift new file mode 100644 index 00000000..0289f14c --- /dev/null +++ b/Sources/rswift/Config.swift @@ -0,0 +1,14 @@ +// +// Config.swift +// R.swift +// +// Created by Tom Lokhorst on 2017-04-22. +// From: https://github.com/mac-cain13/R.swift +// License: MIT License +// + +import Foundation + +struct Config { + static let version = "Unknown" +} diff --git a/Sources/rswift/main.swift b/Sources/rswift/main.swift deleted file mode 100644 index 7b839089..00000000 --- a/Sources/rswift/main.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// main.swift -// rswift -// -// Created by Tom Lokhorst on 2021-04-18. -// - -import Foundation -import RswiftCore -import RswiftParsers -import XcodeEdit - - -// Temporary development code - -struct EnvironmentKeys { - static let action = "ACTION" - - static let bundleIdentifier = "PRODUCT_BUNDLE_IDENTIFIER" - static let productModuleName = "PRODUCT_MODULE_NAME" - static let target = "TARGET_NAME" - static let xcodeproj = "PROJECT_FILE_PATH" - static let infoPlistFile = "INFOPLIST_FILE" - static let codeSignEntitlements = "CODE_SIGN_ENTITLEMENTS" - - static let builtProductsDir = SourceTreeFolder.buildProductsDir.rawValue - static let developerDir = SourceTreeFolder.developerDir.rawValue - static let platformDir = SourceTreeFolder.platformDir.rawValue - static let sdkRoot = SourceTreeFolder.sdkRoot.rawValue - static let sourceRoot = SourceTreeFolder.sourceRoot.rawValue -} - -let processInfo = ProcessInfo() -let targetName = processInfo.environment[EnvironmentKeys.target] ?? "ResourceApp" - -let xcodeprojURL = processInfo.environment[EnvironmentKeys.xcodeproj].map { URL(fileURLWithPath: $0) } -// ?? URL(fileURLWithPath: "/Users/tom/Projects/R.swift/Examples/ResourceApp/ResourceApp.xcodeproj") - ?? URL(fileURLWithPath: "/Users/tom/Projects/Ratemp/Examples/ResourceApp/ResourceApp.xcodeproj") -let infoPlistFile = processInfo.environment[EnvironmentKeys.infoPlistFile].map { URL(fileURLWithPath: $0) } -// ?? URL(fileURLWithPath: "/Users/tom/Projects/R.swift/Examples/ResourceApp/ResourceApp/Info.plist") - ?? URL(fileURLWithPath: "/Users/tom/Projects/Ratemp/Examples/ResourceApp/ResourceApp/Info.plist") -let codeSignEntitlements = processInfo.environment[EnvironmentKeys.codeSignEntitlements].map { URL(fileURLWithPath: $0) } - ?? URL(fileURLWithPath: "/Users/tom/Projects/Ratemp/Examples/ResourceApp/ResourceApp/ResourceApp.entitlements") -let sourceRootURL = xcodeprojURL.deletingLastPathComponent() -let rswiftIgnoreURL = sourceRootURL.appendingPathComponent(".rswiftignore") -let fakeURL = URL(fileURLWithPath: "/FAKE") - -let sourceTreeURLs = SourceTreeURLs( - builtProductsDirURL: fakeURL, - developerDirURL: fakeURL, - sourceRootURL: sourceRootURL, - sdkRootURL: fakeURL, - platformURL: fakeURL -) - -let core = RswiftCore( - xcodeprojURL: xcodeprojURL, - targetName: targetName, - productModuleName: processInfo.environment[EnvironmentKeys.productModuleName], - infoPlistFile: infoPlistFile, - codeSignEntitlements: codeSignEntitlements, - rswiftIgnoreURL: rswiftIgnoreURL, - sourceTreeURLs: sourceTreeURLs -) - -print("Start") -try core.developRun() From f251b0c7adaebb9d6aef12dd61fcb7621392403f Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Tue, 27 Sep 2022 14:25:53 +0200 Subject: [PATCH 068/161] basename + extension in FileResource --- Sources/RswiftCore/RswiftCore.swift | 110 ++++++++++++------ .../FileResource+Generator.swift | 2 +- .../Resources/FileResource+Parser.swift | 13 ++- Sources/RswiftResources/FileResource.swift | 23 +++- .../FileResource+Integrations.swift | 16 ++- 5 files changed, 115 insertions(+), 49 deletions(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 760c501f..725ff2b6 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -16,6 +16,7 @@ public enum Generator: String, CaseIterable, ExpressibleByArgument { case image case string case color + case data case file case font case nib @@ -86,7 +87,7 @@ public struct RswiftCore { warning: { print("[warning]", $0) } ) - let structName = SwiftIdentifier(rawValue: "_S") + let structName = SwiftIdentifier(rawValue: "_R") let qualifiedName = structName let segueStruct = Segue.generateStruct( @@ -170,55 +171,91 @@ public struct RswiftCore { Init.bundle projectStruct - stringStruct.generateBundleVarGetter(name: "string") - stringStruct.generateBundleFunction(name: "string") - stringStruct + if generators.contains(.string) { + stringStruct.generateBundleVarGetter(name: "string") + stringStruct.generateBundleFunction(name: "string") + stringStruct + } - dataStruct.generateBundleVarGetter(name: "data") - dataStruct.generateBundleFunction(name: "data") - dataStruct + if generators.contains(.data) { + dataStruct.generateBundleVarGetter(name: "data") + dataStruct.generateBundleFunction(name: "data") + dataStruct + } - colorStruct.generateBundleVarGetter(name: "color") - colorStruct.generateBundleFunction(name: "color") - colorStruct + if generators.contains(.color) { + colorStruct.generateBundleVarGetter(name: "color") + colorStruct.generateBundleFunction(name: "color") + colorStruct + } - imageStruct.generateBundleVarGetter(name: "image") - imageStruct.generateBundleFunction(name: "image") - imageStruct + if generators.contains(.image) { + imageStruct.generateBundleVarGetter(name: "image") + imageStruct.generateBundleFunction(name: "image") + imageStruct + } - infoStruct.generateBundleVarGetter(name: "info") - infoStruct.generateBundleFunction(name: "info") - infoStruct + if generators.contains(.info) { + infoStruct.generateBundleVarGetter(name: "info") + infoStruct.generateBundleFunction(name: "info") + infoStruct + } - entitlementsStruct.generateLetBinding() - entitlementsStruct + if generators.contains(.entitlements) { + entitlementsStruct.generateLetBinding() + entitlementsStruct + } - fontStruct.generateLetBinding() - fontStruct + if generators.contains(.font) { + fontStruct.generateLetBinding() + fontStruct + } - fileStruct.generateBundleVarGetter(name: "file") - fileStruct.generateBundleFunction(name: "file") - fileStruct + if generators.contains(.file) { + fileStruct.generateBundleVarGetter(name: "file") + fileStruct.generateBundleFunction(name: "file") + fileStruct + } - segueStruct.generateLetBinding() - segueStruct + if generators.contains(.segue) { + segueStruct.generateLetBinding() + segueStruct + } - idStruct.generateLetBinding() - idStruct + if generators.contains(.id) { + idStruct.generateLetBinding() + idStruct + } - nibStruct.generateBundleVarGetter(name: "nib") - nibStruct.generateBundleFunction(name: "nib") - nibStruct + if generators.contains(.nib) { + nibStruct.generateBundleVarGetter(name: "nib") + nibStruct.generateBundleFunction(name: "nib") + nibStruct + } - reuseIdentifierStruct.generateLetBinding() - reuseIdentifierStruct + if generators.contains(.reuseIdentifier) { + reuseIdentifierStruct.generateLetBinding() + reuseIdentifierStruct + } - storyboardStruct.generateBundleVarGetter(name: "storyboard") - storyboardStruct.generateBundleFunction(name: "storyboard") - storyboardStruct + if generators.contains(.storyboard) { + storyboardStruct.generateBundleVarGetter(name: "storyboard") + storyboardStruct.generateBundleFunction(name: "storyboard") + storyboardStruct + } } - try s.prettyPrint().write(to: outputURL, atomically: true, encoding: .utf8) + let str = s.prettyPrint() + let code = """ + import Foundation + import RswiftResources + + \(str) + + let R = _R(bundle: Bundle.main) + """ + try code.write(to: outputURL, atomically: true, encoding: .utf8) + /* print(s.prettyPrint()) print() @@ -240,6 +277,7 @@ public struct RswiftCore { print(" static let reuseIdentifier = S.reuseIdentifier") print(" static let id = S.id") print("}") + */ print("TOTAL", Date().timeIntervalSince(start)) print() diff --git a/Sources/RswiftGenerators/FileResource+Generator.swift b/Sources/RswiftGenerators/FileResource+Generator.swift index 95e99db2..bf93a124 100644 --- a/Sources/RswiftGenerators/FileResource+Generator.swift +++ b/Sources/RswiftGenerators/FileResource+Generator.swift @@ -39,7 +39,7 @@ extension FileResource { comments: ["Resource file `\(filename)`."], name: SwiftIdentifier(name: filename), typeReference: TypeReference(module: .rswiftResources, rawName: "FileResource"), - valueCodeString: "FileResource(filename: \"\(filename.escapedStringLiteral)\", bundle: _bundle, locale: \(locale?.codeString() ?? "nil"))" + valueCodeString: "FileResource(name: \"\(name.escapedStringLiteral)\", pathExtension: \"\(pathExtension.escapedStringLiteral)\", bundle: _bundle, locale: \(locale?.codeString() ?? "nil"))" ) } } diff --git a/Sources/RswiftParsers/Resources/FileResource+Parser.swift b/Sources/RswiftParsers/Resources/FileResource+Parser.swift index 74b312b2..c11a9987 100644 --- a/Sources/RswiftParsers/Resources/FileResource+Parser.swift +++ b/Sources/RswiftParsers/Resources/FileResource+Parser.swift @@ -21,12 +21,17 @@ extension FileResource { .reduce([]) { $0.union($1) } static public func parse(url: URL) throws -> FileResource { -// guard let basename = url.filenameWithoutExtension else { -// throw ResourceParsingError("Couldn't extract filename from URL: \(url)") -// } + guard let basename = url.filenameWithoutExtension else { + throw ResourceParsingError("Couldn't extract filename from URL: \(url)") + } let locale = LocaleReference(url: url) - return FileResource(filename: url.lastPathComponent, bundle: nil, locale: locale) + return FileResource( + name: basename, + pathExtension: url.pathExtension, + bundle: nil, + locale: locale + ) } } diff --git a/Sources/RswiftResources/FileResource.swift b/Sources/RswiftResources/FileResource.swift index be5b614b..32af0585 100644 --- a/Sources/RswiftResources/FileResource.swift +++ b/Sources/RswiftResources/FileResource.swift @@ -11,14 +11,27 @@ import Foundation public struct FileResource { - public let filename: String -// public let name: String -// public let pathExtension: String +// public let filename: String + public let name: String + public let pathExtension: String public let bundle: Bundle? public let locale: LocaleReference? +// +// public init(filename: String, bundle: Bundle?, locale: LocaleReference?) { +// self.filename = filename +// self.bundle = bundle +// self.locale = locale +// } + + public var filename: String { + name.isEmpty || pathExtension.isEmpty + ? "\(name)\(pathExtension)" + : "\(name).\(pathExtension)" + } - public init(filename: String, bundle: Bundle?, locale: LocaleReference?) { - self.filename = filename + public init(name: String, pathExtension: String, bundle: Bundle?, locale: LocaleReference?) { + self.name = name + self.pathExtension = pathExtension self.bundle = bundle self.locale = locale } diff --git a/Sources/RswiftResources/Integrations/FileResource+Integrations.swift b/Sources/RswiftResources/Integrations/FileResource+Integrations.swift index 3dea4a0e..f7723315 100644 --- a/Sources/RswiftResources/Integrations/FileResource+Integrations.swift +++ b/Sources/RswiftResources/Integrations/FileResource+Integrations.swift @@ -15,8 +15,18 @@ public extension FileResource { - returns: The file URL for the resource file (`R.file.*`) or nil if the file could not be located. */ + func url() -> URL? { + (bundle ?? .main).url(forResource: name, withExtension: pathExtension) + } + + /** + Returns the file URL for the given resource (`R.file.*`). + + - returns: The file URL for the resource file (`R.file.*`) or nil if the file could not be located. + */ + @available(*, renamed: "url()") func callAsFunction() -> URL? { - (bundle ?? .main).url(forResource: filename, withExtension: nil) + url() } } @@ -29,7 +39,7 @@ public extension Bundle { - returns: The file URL for the resource file (`R.file.*`) or nil if the file could not be located. */ func url(forResource resource: FileResource) -> URL? { - url(forResource: resource.filename, withExtension: nil) + url(forResource: resource.name, withExtension: resource.pathExtension) } /** @@ -40,6 +50,6 @@ public extension Bundle { - returns: The full pathname for the resource file (`R.file.*`) or nil if the file could not be located. */ func path(forResource resource: FileResource) -> String? { - path(forResource: resource.filename, ofType: nil) + path(forResource: resource.name, ofType: resource.pathExtension) } } From fc8bffb9694cfb0f0c59d01687a107c888ea07e3 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Tue, 27 Sep 2022 19:53:50 +0200 Subject: [PATCH 069/161] Implement access-level --- Sources/RswiftCore/RswiftCore.swift | 20 ++++++++++-- .../AssetCatalog+Generator.swift | 29 +++++++++-------- .../LocalizableStrings+Generator.swift | 1 - .../RswiftGenerators/SwiftSyntax/Struct.swift | 32 +++++++++++++++---- Sources/rswift/App.swift | 1 + 5 files changed, 61 insertions(+), 22 deletions(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 725ff2b6..8af3b6a1 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -39,6 +39,7 @@ public enum AccessLevel: String, ExpressibleByArgument { public struct RswiftCore { let outputURL: URL let generators: [Generator] + let accessLevel: AccessLevel let xcodeprojURL: URL let targetName: String let productModuleName: String? @@ -52,6 +53,7 @@ public struct RswiftCore { public init( outputURL: URL, generators: [Generator], + accessLevel: AccessLevel, xcodeprojURL: URL, targetName: String, productModuleName: String?, @@ -62,6 +64,7 @@ public struct RswiftCore { ) { self.outputURL = outputURL self.generators = generators + self.accessLevel = accessLevel self.xcodeprojURL = xcodeprojURL self.targetName = targetName self.productModuleName = productModuleName @@ -167,7 +170,7 @@ public struct RswiftCore { } } - let s = Struct(name: structName) { + var s = Struct(name: structName) { Init.bundle projectStruct @@ -245,14 +248,27 @@ public struct RswiftCore { } } + if accessLevel == .publicLevel { + s.setAccessControl(.public) + } + + let mainLet = "\(accessLevel == .publicLevel ? "public " : "")let R = _R(bundle: Bundle(for: BundleClass.self))" + let str = s.prettyPrint() let code = """ + import UIKit import Foundation import RswiftResources \(str) - let R = _R(bundle: Bundle.main) + extension _R { + func validate() throws { + } + } + + private class BundleClass {} + \(mainLet) """ try code.write(to: outputURL, atomically: true, encoding: .utf8) /* diff --git a/Sources/RswiftGenerators/AssetCatalog+Generator.swift b/Sources/RswiftGenerators/AssetCatalog+Generator.swift index eb4a5952..01fb3e65 100644 --- a/Sources/RswiftGenerators/AssetCatalog+Generator.swift +++ b/Sources/RswiftGenerators/AssetCatalog+Generator.swift @@ -10,7 +10,7 @@ import RswiftResources public protocol AssetCatalogContent { var name: String { get } - func generateLetBinding() -> LetBinding + func generateVarGetter() -> VarGetter } extension ColorResource { @@ -53,7 +53,7 @@ extension AssetCatalog.Namespace { let groupedResources = allResources.grouped(bySwiftIdentifier: { $0.name }) groupedResources.reportWarningsForDuplicatesAndEmpties(source: resourceName, result: resourceName, warning: warning) - let letbindings = groupedResources.uniques.map { $0.generateLetBinding() } + let vargetters = groupedResources.uniques.map { $0.generateVarGetter() } let otherIdentifiers = groupedResources.uniques.map { SwiftIdentifier(name: $0.name) } let mergedNamespaces = AssetCatalogMergedNamespaces(all: subnamespaces, otherIdentifiers: otherIdentifiers) @@ -71,7 +71,7 @@ extension AssetCatalog.Namespace { .filter { !$0.isEmpty } let comment = [ - "This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) \(resourceName)s", + "This `\(qualifiedName.value)` struct is generated, and contains static references to \(vargetters.count) \(resourceName)s", structs.isEmpty ? "" : ", and \(structs.count) namespaces", "." ].joined() @@ -79,7 +79,7 @@ extension AssetCatalog.Namespace { let comments = [comment] return Struct(comments: comments, name: structName) { Init.bundle - letbindings + vargetters structs for s in structs { @@ -91,39 +91,42 @@ extension AssetCatalog.Namespace { } extension ColorResource: AssetCatalogContent { - public func generateLetBinding() -> LetBinding { + public func generateVarGetter() -> VarGetter { let fullname = (path + [name]).joined(separator: "/") - let code = "ColorResource(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: nil)" - return LetBinding( + let code = ".init(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: _bundle)" + return VarGetter( comments: ["Color `\(fullname)`."], name: SwiftIdentifier(name: name), + typeReference: TypeReference(module: .host, rawName: "ColorResource"), valueCodeString: code ) } } extension DataResource: AssetCatalogContent { - public func generateLetBinding() -> LetBinding { + public func generateVarGetter() -> VarGetter { let fullname = (path + [name]).joined(separator: "/") let odrt = onDemandResourceTags?.debugDescription ?? "nil" - let code = "DataResource(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: nil, onDemandResourceTags: \(odrt))" - return LetBinding( + let code = ".init(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: nil, onDemandResourceTags: \(odrt))" + return VarGetter( comments: ["Data asset `\(fullname)`."], name: SwiftIdentifier(name: name), + typeReference: TypeReference(module: .host, rawName: "DataResource"), valueCodeString: code ) } } extension ImageResource: AssetCatalogContent { - public func generateLetBinding() -> LetBinding { + public func generateVarGetter() -> VarGetter { let locs = locale.map { $0.codeString() } ?? "nil" let odrt = onDemandResourceTags?.debugDescription ?? "nil" let fullname = (path + [name]).joined(separator: "/") - let code = "ImageResource(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: nil, locale: \(locs), onDemandResourceTags: \(odrt))" - return LetBinding( + let code = ".init(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: nil, locale: \(locs), onDemandResourceTags: \(odrt))" + return VarGetter( comments: ["Image `\(fullname)`."], name: SwiftIdentifier(name: name), + typeReference: TypeReference(module: .host, rawName: "ImageResource"), valueCodeString: code ) } diff --git a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift index e8283b0f..49e74a7e 100644 --- a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift +++ b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift @@ -250,7 +250,6 @@ private struct StringWithParams { func generateVarGetter() -> VarGetter { VarGetter( comments: self.comments, - isLazy: true, name: SwiftIdentifier(name: key), typeReference: typeReference, valueCodeString: varValueCodeString diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index 3bd71fbe..a7bb9e8c 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -75,7 +75,7 @@ public struct VarGetter { public let typeReference: TypeReference public let valueCodeString: String - public init(comments: [String] = [], accessControl: AccessControl = AccessControl.none, isLazy: Bool = false, name: SwiftIdentifier, typeReference: TypeReference, valueCodeString: String) { + public init(comments: [String] = [], accessControl: AccessControl = AccessControl.none, name: SwiftIdentifier, typeReference: TypeReference, valueCodeString: String) { self.comments = comments self.accessControl = accessControl self.name = name @@ -90,16 +90,14 @@ public struct VarGetter { "\(name.value):", typeReference.codeString(), "{", + valueCodeString, + "}" ] for c in comments { pp.append(words: ["///", c == "" ? nil : c]) } pp.append(words: words) - pp.indented { pp in - pp.append(line: valueCodeString) - } - pp.append(line: "}") } } @@ -333,6 +331,28 @@ public struct Struct { self.typealiasses = members.typealiasses } + public mutating func setAccessControl(_ accessControl: AccessControl) { + self.accessControl = accessControl + for i in lets.indices { + lets[i].accessControl = accessControl + } + for i in vars.indices { + vars[i].accessControl = accessControl + } + for i in inits.indices { + inits[i].accessControl = accessControl + } + for i in funcs.indices { + funcs[i].accessControl = accessControl + } + for i in structs.indices { + structs[i].setAccessControl(accessControl) + } + for i in typealiasses.indices { + typealiasses[i].accessControl = accessControl + } + } + public func prettyPrint() -> String { var pp = PrettyPrinter() render(&pp) @@ -346,7 +366,7 @@ public struct Struct { let ps = protocols.map { $0.codeString() }.joined(separator: ", ") let implements = ps.isEmpty ? "" : ": \(ps)" - pp.append(line: "struct \(name.value)\(implements) {") + pp.append(words: [accessControl.code(), "struct", "\(name.value)\(implements)", "{"]) pp.indented { pp in for talias in typealiasses { diff --git a/Sources/rswift/App.swift b/Sources/rswift/App.swift index dc90c080..d516a1c6 100644 --- a/Sources/rswift/App.swift +++ b/Sources/rswift/App.swift @@ -110,6 +110,7 @@ extension App { let core = RswiftCore( outputURL: outputURL, generators: generators.isEmpty ? Generator.allCases : generators, + accessLevel: accessLevel, xcodeprojURL: URL(fileURLWithPath: xcodeprojPath), targetName: targetName, productModuleName: nil, From f502204f6a162e7cbe9fb7253e9c36c55e9d627b Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Tue, 27 Sep 2022 21:34:01 +0200 Subject: [PATCH 070/161] Work on code generation --- Sources/RswiftCore/RswiftCore.swift | 26 +++++++++---------- .../FileResource+Generator.swift | 2 +- Sources/RswiftGenerators/Nib+Generator.swift | 4 +-- .../ReuseIdentifier+Generator.swift | 2 +- .../RswiftGenerators/Segue+Generator.swift | 2 +- .../RswiftGenerators/SwiftSyntax/Struct.swift | 4 ++- 6 files changed, 21 insertions(+), 19 deletions(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 8af3b6a1..5f09bc1d 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -174,74 +174,74 @@ public struct RswiftCore { Init.bundle projectStruct - if generators.contains(.string) { + if generators.contains(.string) && !stringStruct.isEmpty { stringStruct.generateBundleVarGetter(name: "string") stringStruct.generateBundleFunction(name: "string") stringStruct } - if generators.contains(.data) { + if generators.contains(.data) && !dataStruct.isEmpty { dataStruct.generateBundleVarGetter(name: "data") dataStruct.generateBundleFunction(name: "data") dataStruct } - if generators.contains(.color) { + if generators.contains(.color) && !colorStruct.isEmpty { colorStruct.generateBundleVarGetter(name: "color") colorStruct.generateBundleFunction(name: "color") colorStruct } - if generators.contains(.image) { + if generators.contains(.image) && !imageStruct.isEmpty { imageStruct.generateBundleVarGetter(name: "image") imageStruct.generateBundleFunction(name: "image") imageStruct } - if generators.contains(.info) { + if generators.contains(.info) && !infoStruct.isEmpty { infoStruct.generateBundleVarGetter(name: "info") infoStruct.generateBundleFunction(name: "info") infoStruct } - if generators.contains(.entitlements) { + if generators.contains(.entitlements) && !entitlementsStruct.isEmpty { entitlementsStruct.generateLetBinding() entitlementsStruct } - if generators.contains(.font) { + if generators.contains(.font) && !fontStruct.isEmpty { fontStruct.generateLetBinding() fontStruct } - if generators.contains(.file) { + if generators.contains(.file) && !fileStruct.isEmpty { fileStruct.generateBundleVarGetter(name: "file") fileStruct.generateBundleFunction(name: "file") fileStruct } - if generators.contains(.segue) { + if generators.contains(.segue) && !segueStruct.isEmpty { segueStruct.generateLetBinding() segueStruct } - if generators.contains(.id) { + if generators.contains(.id) && !idStruct.isEmpty { idStruct.generateLetBinding() idStruct } - if generators.contains(.nib) { + if generators.contains(.nib) && !nibStruct.isEmpty { nibStruct.generateBundleVarGetter(name: "nib") nibStruct.generateBundleFunction(name: "nib") nibStruct } - if generators.contains(.reuseIdentifier) { + if generators.contains(.reuseIdentifier) && !reuseIdentifierStruct.isEmpty { reuseIdentifierStruct.generateLetBinding() reuseIdentifierStruct } - if generators.contains(.storyboard) { + if generators.contains(.storyboard) && !storyboardStruct.isEmpty { storyboardStruct.generateBundleVarGetter(name: "storyboard") storyboardStruct.generateBundleFunction(name: "storyboard") storyboardStruct diff --git a/Sources/RswiftGenerators/FileResource+Generator.swift b/Sources/RswiftGenerators/FileResource+Generator.swift index bf93a124..eda84816 100644 --- a/Sources/RswiftGenerators/FileResource+Generator.swift +++ b/Sources/RswiftGenerators/FileResource+Generator.swift @@ -39,7 +39,7 @@ extension FileResource { comments: ["Resource file `\(filename)`."], name: SwiftIdentifier(name: filename), typeReference: TypeReference(module: .rswiftResources, rawName: "FileResource"), - valueCodeString: "FileResource(name: \"\(name.escapedStringLiteral)\", pathExtension: \"\(pathExtension.escapedStringLiteral)\", bundle: _bundle, locale: \(locale?.codeString() ?? "nil"))" + valueCodeString: ".init(name: \"\(name.escapedStringLiteral)\", pathExtension: \"\(pathExtension.escapedStringLiteral)\", bundle: _bundle, locale: \(locale?.codeString() ?? "nil"))" ) } } diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift index 5255ae6a..ba531868 100644 --- a/Sources/RswiftGenerators/Nib+Generator.swift +++ b/Sources/RswiftGenerators/Nib+Generator.swift @@ -56,7 +56,7 @@ extension NibResource { comments: ["Nib `\(name)`."], name: SwiftIdentifier(name: name), typeReference: typeReference, - valueCodeString: "NibReferenceReuseIdentifier(name: \"\(name.escapedStringLiteral)\", bundle: _bundle, identifier: \"\(reusable.identifier.escapedStringLiteral)\")" + valueCodeString: ".init(name: \"\(name.escapedStringLiteral)\", bundle: _bundle, identifier: \"\(reusable.identifier.escapedStringLiteral)\")" ) } else { let typeReference = TypeReference( @@ -68,7 +68,7 @@ extension NibResource { comments: ["Nib `\(name)`."], name: SwiftIdentifier(name: name), typeReference: typeReference, - valueCodeString: "NibReference(name: \"\(name.escapedStringLiteral)\", bundle: _bundle)" + valueCodeString: ".init(name: \"\(name.escapedStringLiteral)\", bundle: _bundle)" ) } } diff --git a/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift b/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift index 11e9448d..2b94c7ab 100644 --- a/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift +++ b/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift @@ -49,7 +49,7 @@ extension Reusable { comments: ["Reuse identifier `\(identifier)`."], name: SwiftIdentifier(name: identifier), typeReference: genericTypeReference, - valueCodeString: "ReuseIdentifier(identifier: \"\(identifier)\")" + valueCodeString: ".init(identifier: \"\(identifier)\")" ) } } diff --git a/Sources/RswiftGenerators/Segue+Generator.swift b/Sources/RswiftGenerators/Segue+Generator.swift index d50bbb20..5b55369f 100644 --- a/Sources/RswiftGenerators/Segue+Generator.swift +++ b/Sources/RswiftGenerators/Segue+Generator.swift @@ -172,7 +172,7 @@ struct SegueWithInfo { comments: ["Segue identifier `\(segue.identifier)`."], name: SwiftIdentifier(name: segue.identifier), typeReference: genericTypeReference, - valueCodeString: "SegueIdentifier(identifier: \"\(segue.identifier)\")" + valueCodeString: ".init(identifier: \"\(segue.identifier)\")" ) } } diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index a7bb9e8c..cbfb890e 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -306,7 +306,9 @@ public struct Struct { public var structs: [Struct] = [] public var typealiasses: [TypeAlias] = [] - public var isEmpty: Bool { lets.isEmpty && funcs.isEmpty && structs.isEmpty } + public var isEmpty: Bool { + lets.isEmpty && vars.isEmpty && funcs.isEmpty && structs.isEmpty + } public static var empty: Struct = Struct(name: SwiftIdentifier(name: "empty"), membersBuilder: {}) From b1b2feb284e935f7a6e3dc29e118017169bfdda7 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 30 Sep 2022 11:13:13 +0200 Subject: [PATCH 071/161] Generate warnings for empty accessibility identifiers --- .../AccessibilityIdentifier+Generator.swift | 14 ++++++++++---- .../RswiftGenerators/FileResource+Generator.swift | 2 +- .../Shared/StoryboardReference.swift | 12 +++++++++++- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Sources/RswiftGenerators/AccessibilityIdentifier+Generator.swift b/Sources/RswiftGenerators/AccessibilityIdentifier+Generator.swift index 218c8b2e..f8298998 100644 --- a/Sources/RswiftGenerators/AccessibilityIdentifier+Generator.swift +++ b/Sources/RswiftGenerators/AccessibilityIdentifier+Generator.swift @@ -21,7 +21,7 @@ public struct AccessibilityIdentifier { let structName = SwiftIdentifier(name: "id") let qualifiedName = prefix + structName -// let warning: (String) -> Void = { print("warning:", $0) } + let warning: (String) -> Void = { print("warning:", $0) } let containers: [AccessibilityIdentifierContainer] = nibs + storyboards let mergedContainers = Dictionary(grouping: containers, by: \.name) @@ -33,7 +33,8 @@ public struct AccessibilityIdentifier { generateStruct( viewControllerName: name, usedAccessibilityIdentifiers: ids, - prefix: qualifiedName + prefix: qualifiedName, + warning: warning ) } .sorted { $0.name < $1.name } @@ -48,11 +49,16 @@ public struct AccessibilityIdentifier { } } - static func generateStruct(viewControllerName: String, usedAccessibilityIdentifiers: [String], prefix: SwiftIdentifier) -> Struct { + static func generateStruct(viewControllerName: String, usedAccessibilityIdentifiers: [String], prefix: SwiftIdentifier, warning: (String) -> Void) -> Struct { let structName = SwiftIdentifier(name: viewControllerName) let qualifiedName = prefix + structName - let letbindings = usedAccessibilityIdentifiers + // Deduplicate identifiers, report warnings for empties + let groupedIdentifiers = Array(Set(usedAccessibilityIdentifiers)) + .grouped(bySwiftIdentifier: { $0 }) + groupedIdentifiers.reportWarningsForDuplicatesAndEmpties(source: "accessibility identifier", container: "in \(viewControllerName)", result: "accessibility identifier", warning: warning) + + let letbindings = groupedIdentifiers.uniques .map { id in LetBinding( comments: ["Accessibility identifier `\(id)`."], diff --git a/Sources/RswiftGenerators/FileResource+Generator.swift b/Sources/RswiftGenerators/FileResource+Generator.swift index eda84816..cc76f9d0 100644 --- a/Sources/RswiftGenerators/FileResource+Generator.swift +++ b/Sources/RswiftGenerators/FileResource+Generator.swift @@ -17,8 +17,8 @@ extension FileResource { // For resource files, the contents of the different locales don't matter, so we just use the first one let firstLocales = Dictionary(grouping: resources, by: \.filename) .values.map(\.first!) - let groupedFiles = firstLocales.grouped(bySwiftIdentifier: \.filename) + let groupedFiles = firstLocales.grouped(bySwiftIdentifier: \.filename) groupedFiles.reportWarningsForDuplicatesAndEmpties(source: "resource file", result: "file", warning: warning) let vargetters = groupedFiles.uniques.map { $0.generateVarGetter() } diff --git a/Sources/RswiftResources/Shared/StoryboardReference.swift b/Sources/RswiftResources/Shared/StoryboardReference.swift index 0b6a071a..902558de 100644 --- a/Sources/RswiftResources/Shared/StoryboardReference.swift +++ b/Sources/RswiftResources/Shared/StoryboardReference.swift @@ -56,7 +56,7 @@ public protocol NibReferenceContainer { var bundle: Bundle { get } } -public protocol ReuseIdentifierContainer { +public protocol ReuseIdentifierContainer { associatedtype Reusable var identifier: String { get } } @@ -168,3 +168,13 @@ public struct TypedSegue { self.identifier = identifier } } + + +@available(*, renamed: "ReuseIdentifierContainer") +public protocol ReuseIdentifierType {} + +@available(*, renamed: "SegueIdentifier") +public struct StoryboardSegueIdentifier {} + +@available(*, renamed: "TypedSegue") +public struct TypedStoryboardSegueInfo {} From 280af73f49530ccdf14a076920de694140d468a0 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 30 Sep 2022 21:58:33 +0200 Subject: [PATCH 072/161] Make bundle fields for resources non-optionals --- .../AssetCatalog+Generator.swift | 4 ++-- .../Resources/AssetCatalog+Parser.swift | 6 +++--- .../Resources/FileResource+Parser.swift | 2 +- .../Resources/ImageResource+Parser.swift | 2 +- .../Shared/Bundle+Extensions.swift | 12 +++++++++++ Sources/RswiftResources/ColorResource.swift | 4 ++-- Sources/RswiftResources/DataResource.swift | 4 ++-- Sources/RswiftResources/FileResource.swift | 4 ++-- Sources/RswiftResources/ImageResource.swift | 4 ++-- .../FileResource+Integrations.swift | 2 +- .../RswiftResources/LocalizableStrings.swift | 20 +++++++++---------- 11 files changed, 38 insertions(+), 26 deletions(-) create mode 100644 Sources/RswiftParsers/Shared/Bundle+Extensions.swift diff --git a/Sources/RswiftGenerators/AssetCatalog+Generator.swift b/Sources/RswiftGenerators/AssetCatalog+Generator.swift index 01fb3e65..bfe344dd 100644 --- a/Sources/RswiftGenerators/AssetCatalog+Generator.swift +++ b/Sources/RswiftGenerators/AssetCatalog+Generator.swift @@ -107,7 +107,7 @@ extension DataResource: AssetCatalogContent { public func generateVarGetter() -> VarGetter { let fullname = (path + [name]).joined(separator: "/") let odrt = onDemandResourceTags?.debugDescription ?? "nil" - let code = ".init(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: nil, onDemandResourceTags: \(odrt))" + let code = ".init(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: _bundle, onDemandResourceTags: \(odrt))" return VarGetter( comments: ["Data asset `\(fullname)`."], name: SwiftIdentifier(name: name), @@ -122,7 +122,7 @@ extension ImageResource: AssetCatalogContent { let locs = locale.map { $0.codeString() } ?? "nil" let odrt = onDemandResourceTags?.debugDescription ?? "nil" let fullname = (path + [name]).joined(separator: "/") - let code = ".init(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: nil, locale: \(locs), onDemandResourceTags: \(odrt))" + let code = ".init(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: _bundle, locale: \(locs), onDemandResourceTags: \(odrt))" return VarGetter( comments: ["Image `\(fullname)`."], name: SwiftIdentifier(name: name), diff --git a/Sources/RswiftParsers/Resources/AssetCatalog+Parser.swift b/Sources/RswiftParsers/Resources/AssetCatalog+Parser.swift index 6999ef4c..cba3788c 100644 --- a/Sources/RswiftParsers/Resources/AssetCatalog+Parser.swift +++ b/Sources/RswiftParsers/Resources/AssetCatalog+Parser.swift @@ -114,21 +114,21 @@ extension AssetCatalog: SupportedExtensions { var colors: [ColorResource] = [] for fileURL in directory.colors { let name = fileURL.filenameWithoutExtension! - colors.append(.init(name: name, path: path, bundle: nil)) + colors.append(.init(name: name, path: path, bundle: .temp)) } var images: [ImageResource] = [] for fileURL in directory.images { let name = fileURL.filenameWithoutExtension! let tags = parseOnDemandResourceTags(directory: fileURL) - images.append(.init(name: name, path: path, bundle: nil, locale: nil, onDemandResourceTags: tags)) + images.append(.init(name: name, path: path, bundle: .temp, locale: nil, onDemandResourceTags: tags)) } var dataAssets: [DataResource] = [] for fileURL in directory.dataAssets { let name = fileURL.filenameWithoutExtension! let tags = parseOnDemandResourceTags(directory: fileURL) - dataAssets.append(.init(name: name, path: path, bundle: nil, onDemandResourceTags: tags)) + dataAssets.append(.init(name: name, path: path, bundle: .temp, onDemandResourceTags: tags)) } return AssetCatalog.Namespace( diff --git a/Sources/RswiftParsers/Resources/FileResource+Parser.swift b/Sources/RswiftParsers/Resources/FileResource+Parser.swift index c11a9987..8a95813d 100644 --- a/Sources/RswiftParsers/Resources/FileResource+Parser.swift +++ b/Sources/RswiftParsers/Resources/FileResource+Parser.swift @@ -30,7 +30,7 @@ extension FileResource { return FileResource( name: basename, pathExtension: url.pathExtension, - bundle: nil, + bundle: .temp, locale: locale ) } diff --git a/Sources/RswiftParsers/Resources/ImageResource+Parser.swift b/Sources/RswiftParsers/Resources/ImageResource+Parser.swift index 6c5649d8..f305c868 100644 --- a/Sources/RswiftParsers/Resources/ImageResource+Parser.swift +++ b/Sources/RswiftParsers/Resources/ImageResource+Parser.swift @@ -31,6 +31,6 @@ extension ImageResource: SupportedExtensions { let pathExtensionToUse = (pathExtension == "png") ? "" : ".\(pathExtension)" let name = regex.stringByReplacingMatches(in: filename, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: fullFileNameRange, withTemplate: pathExtensionToUse) - return ImageResource(name: name, path: [], bundle: nil, locale: locale, onDemandResourceTags: assetTags) + return ImageResource(name: name, path: [], bundle: .temp, locale: locale, onDemandResourceTags: assetTags) } } diff --git a/Sources/RswiftParsers/Shared/Bundle+Extensions.swift b/Sources/RswiftParsers/Shared/Bundle+Extensions.swift new file mode 100644 index 00000000..304b26f1 --- /dev/null +++ b/Sources/RswiftParsers/Shared/Bundle+Extensions.swift @@ -0,0 +1,12 @@ +// +// Bundle+Extensions.swift +// +// +// Created by Tom Lokhorst on 2022-09-30. +// + +import Foundation + +extension Bundle { + public static var temp: Bundle = .main +} diff --git a/Sources/RswiftResources/ColorResource.swift b/Sources/RswiftResources/ColorResource.swift index c2988aef..6b08df1f 100644 --- a/Sources/RswiftResources/ColorResource.swift +++ b/Sources/RswiftResources/ColorResource.swift @@ -10,9 +10,9 @@ import Foundation public struct ColorResource { public let name: String public let path: [String] - public let bundle: Bundle? + public let bundle: Bundle - public init(name: String, path: [String], bundle: Bundle?) { + public init(name: String, path: [String], bundle: Bundle) { self.name = name self.path = path self.bundle = bundle diff --git a/Sources/RswiftResources/DataResource.swift b/Sources/RswiftResources/DataResource.swift index d9653b28..5364da94 100644 --- a/Sources/RswiftResources/DataResource.swift +++ b/Sources/RswiftResources/DataResource.swift @@ -10,10 +10,10 @@ import Foundation public struct DataResource { public let name: String public let path: [String] - public let bundle: Bundle? + public let bundle: Bundle public let onDemandResourceTags: [String]? - public init(name: String, path: [String], bundle: Bundle?, onDemandResourceTags: [String]?) { + public init(name: String, path: [String], bundle: Bundle, onDemandResourceTags: [String]?) { self.name = name self.path = path self.bundle = bundle diff --git a/Sources/RswiftResources/FileResource.swift b/Sources/RswiftResources/FileResource.swift index 32af0585..96251a73 100644 --- a/Sources/RswiftResources/FileResource.swift +++ b/Sources/RswiftResources/FileResource.swift @@ -14,7 +14,7 @@ public struct FileResource { // public let filename: String public let name: String public let pathExtension: String - public let bundle: Bundle? + public let bundle: Bundle public let locale: LocaleReference? // // public init(filename: String, bundle: Bundle?, locale: LocaleReference?) { @@ -29,7 +29,7 @@ public struct FileResource { : "\(name).\(pathExtension)" } - public init(name: String, pathExtension: String, bundle: Bundle?, locale: LocaleReference?) { + public init(name: String, pathExtension: String, bundle: Bundle, locale: LocaleReference?) { self.name = name self.pathExtension = pathExtension self.bundle = bundle diff --git a/Sources/RswiftResources/ImageResource.swift b/Sources/RswiftResources/ImageResource.swift index f62b88b7..0bd1ea00 100644 --- a/Sources/RswiftResources/ImageResource.swift +++ b/Sources/RswiftResources/ImageResource.swift @@ -12,11 +12,11 @@ import Foundation public struct ImageResource { public let name: String public let path: [String] - public let bundle: Bundle? + public let bundle: Bundle public let locale: LocaleReference? public let onDemandResourceTags: [String]? - public init(name: String, path: [String], bundle: Bundle?, locale: LocaleReference?, onDemandResourceTags: [String]?) { + public init(name: String, path: [String], bundle: Bundle, locale: LocaleReference?, onDemandResourceTags: [String]?) { self.name = name self.path = path self.bundle = bundle diff --git a/Sources/RswiftResources/Integrations/FileResource+Integrations.swift b/Sources/RswiftResources/Integrations/FileResource+Integrations.swift index f7723315..216fe63b 100644 --- a/Sources/RswiftResources/Integrations/FileResource+Integrations.swift +++ b/Sources/RswiftResources/Integrations/FileResource+Integrations.swift @@ -16,7 +16,7 @@ public extension FileResource { - returns: The file URL for the resource file (`R.file.*`) or nil if the file could not be located. */ func url() -> URL? { - (bundle ?? .main).url(forResource: name, withExtension: pathExtension) + bundle.url(forResource: name, withExtension: pathExtension) } /** diff --git a/Sources/RswiftResources/LocalizableStrings.swift b/Sources/RswiftResources/LocalizableStrings.swift index 4be552f4..f1d9022b 100644 --- a/Sources/RswiftResources/LocalizableStrings.swift +++ b/Sources/RswiftResources/LocalizableStrings.swift @@ -10,6 +10,16 @@ import Foundation public struct LocalizableStrings { + public let filename: String + public let locale: LocaleReference + public let dictionary: [Key: Value] + + public init(filename: String, locale: LocaleReference, dictionary: [Key: Value]) { + self.filename = filename + self.locale = locale + self.dictionary = dictionary + } + public typealias Key = String public struct Value { public let params: [StringParam] @@ -20,14 +30,4 @@ public struct LocalizableStrings { self.originalValue = originalValue } } - - public let filename: String - public let locale: LocaleReference - public let dictionary: [Key: Value] - - public init(filename: String, locale: LocaleReference, dictionary: [Key: Value]) { - self.filename = filename - self.locale = locale - self.dictionary = dictionary - } } From cbe0e1991b54c4fc4928c006895d1917e022c6e8 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Mon, 3 Oct 2022 14:12:39 +0200 Subject: [PATCH 073/161] ImageResource+Integrations --- Package.swift | 2 +- .../DataResource+Integrations.swift | 2 +- .../ImageResource+Integrations.swift | 50 ++++++++++--------- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/Package.swift b/Package.swift index ecdf99a7..96159bd0 100644 --- a/Package.swift +++ b/Package.swift @@ -12,7 +12,7 @@ let package = Package( products: [ .executable(name: "rswift", targets: ["rswift"]), .executable(name: "rswift-legacy", targets: ["rswift-legacy"]), - .library(name: "RswiftCombined", targets: ["RswiftResources"]) + .library(name: "RswiftLibrary", targets: ["RswiftResources"]) ], dependencies: [ .package(url: "https://github.com/kylef/Commander.git", from: "0.8.0"), diff --git a/Sources/RswiftResources/Integrations/DataResource+Integrations.swift b/Sources/RswiftResources/Integrations/DataResource+Integrations.swift index 162d7b40..4a591e0e 100644 --- a/Sources/RswiftResources/Integrations/DataResource+Integrations.swift +++ b/Sources/RswiftResources/Integrations/DataResource+Integrations.swift @@ -21,7 +21,7 @@ extension NSDataAsset { - parameter resource: The resource you want the data asset of (`R.data.*`) */ public convenience init?(resource: DataResource) { - self.init(name: resource.name, bundle: resource.bundle ?? .main) + self.init(name: resource.name, bundle: resource.bundle) } } diff --git a/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift b/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift index 07d813d8..7dd221c4 100644 --- a/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift +++ b/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift @@ -25,48 +25,52 @@ extension Image { /** - Creates a labelled image from this resource (`R.image.*`), with the variable value. + Creates a labelled image from this resource (`R.image.*`), with the specified label - parameter resource: The resource you want the image of (`R.image.*`) - - parameter variableValue: Optional value between 1 and 0 + - parameter label: The label associated with the image, for accessibility */ -// @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) -// public init(_ resource: ImageResource, variableValue: Double?) { -// self.init(resource.name, variableValue: variableValue, bundle: resource.bundle) -// } + public init(_ resource: ImageResource, label: Text) { + self.init(resource.name, bundle: resource.bundle, label: label) + } /** - Creates a labelled image from this resource (`R.image.*`), with the specified label + Creates an unlabelled, decorative image from this resource (`R.image.*`). - parameter resource: The resource you want the image of (`R.image.*`) - - parameter label: The label associated with the image, for accessibility */ - public init(_ resource: ImageResource, label: Text) { - self.init(resource.name, bundle: resource.bundle, label: label) + public init(decorative resource: ImageResource) { + self.init(decorative: resource.name, bundle: resource.bundle) } +} +// For some reason, this requires Xcode 14.1, doesn't work in Xcode 14.0 +#if swift(>=5.8) +@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) +extension Image { + /** - Creates a labelled image from this resource (`R.image.*`), with the specified label and variable value. + Creates a labelled image from this resource (`R.image.*`), with the variable value. - parameter resource: The resource you want the image of (`R.image.*`) - parameter variableValue: Optional value between 1 and 0 - - parameter label: The label associated with the image, for accessibility */ -// @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) -// public init(_ resource: ImageResource, variableValue: Double?, label: Text) { -// self.init(resource.name, variableValue: variableValue, bundle: resource.bundle, label: label) -// } + public init(_ resource: ImageResource, variableValue: Double?) { + self.init(resource.name, variableValue: variableValue, bundle: resource.bundle) + } /** - Creates an unlabelled, decorative image from this resource (`R.image.*`). + Creates a labelled image from this resource (`R.image.*`), with the specified label and variable value. - parameter resource: The resource you want the image of (`R.image.*`) + - parameter variableValue: Optional value between 1 and 0 + - parameter label: The label associated with the image, for accessibility */ - public init(decorative resource: ImageResource) { - self.init(decorative: resource.name, bundle: resource.bundle) + public init(_ resource: ImageResource, variableValue: Double?, label: Text) { + self.init(resource.name, variableValue: variableValue, bundle: resource.bundle, label: label) } @@ -76,11 +80,11 @@ extension Image { - parameter resource: The resource you want the image of (`R.image.*`) - parameter variableValue: Optional value between 1 and 0 */ -// @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) -// public init(decorative resource: ImageResource, variableValue: Double?) { -// self.init(decorative: resource.name, variableValue: variableValue, bundle: resource.bundle) -// } + public init(decorative resource: ImageResource, variableValue: Double?) { + self.init(decorative: resource.name, variableValue: variableValue, bundle: resource.bundle) + } } +#endif #if os(iOS) || os(tvOS) From 897210736c08ed4be06f1ed632e5a67e2881165e Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Thu, 6 Oct 2022 14:09:42 +0200 Subject: [PATCH 074/161] Collect all module references --- .../RswiftGenerators/SwiftSyntax/Struct.swift | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index cbfb890e..3770a29e 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -48,6 +48,14 @@ public struct LetBinding { self.valueCodeString = valueCodeString } + public var allModuleReferences: Set { + if let typeReference { + return Set([typeReference.module]) + } else { + return Set() + } + } + func render(_ pp: inout PrettyPrinter) { var words: [String?] = [ accessControl.code(), @@ -83,6 +91,10 @@ public struct VarGetter { self.valueCodeString = valueCodeString } + public var allModuleReferences: Set { + Set([typeReference.module]) + } + func render(_ pp: inout PrettyPrinter) { let words: [String?] = [ accessControl.code(), @@ -148,6 +160,10 @@ public struct Function { } } + public var allModuleReferences: Set { + Set(params.map(\.typeReference.module)).union([returnType.module]) + } + func render(_ pp: inout PrettyPrinter) { let prs = params.map { $0.codeString() }.joined(separator: ", ") let words: [String?] = [ @@ -216,6 +232,10 @@ public struct Init { } } + public var allModuleReferences: Set { + Set(params.map(\.typeReference.module)) + } + func render(_ pp: inout PrettyPrinter) { for param in params { @@ -276,6 +296,10 @@ public struct TypeAlias { self.value = value } + public var allModuleReferences: Set { + Set([value.module]) + } + func render(_ pp: inout PrettyPrinter) { for c in comments { @@ -306,10 +330,6 @@ public struct Struct { public var structs: [Struct] = [] public var typealiasses: [TypeAlias] = [] - public var isEmpty: Bool { - lets.isEmpty && vars.isEmpty && funcs.isEmpty && structs.isEmpty - } - public static var empty: Struct = Struct(name: SwiftIdentifier(name: "empty"), membersBuilder: {}) public init( @@ -333,6 +353,23 @@ public struct Struct { self.typealiasses = members.typealiasses } + public var isEmpty: Bool { + lets.isEmpty && vars.isEmpty && funcs.isEmpty && structs.isEmpty + } + + public var allModuleReferences: Set { + var result: Set = [] + result.formUnion(protocols.map(\.module)) + result.formUnion(lets.flatMap(\.allModuleReferences)) + result.formUnion(vars.flatMap(\.allModuleReferences)) + result.formUnion(inits.flatMap(\.allModuleReferences)) + result.formUnion(funcs.flatMap(\.allModuleReferences)) + result.formUnion(structs.flatMap(\.allModuleReferences)) + result.formUnion(typealiasses.flatMap(\.allModuleReferences)) + + return result + } + public mutating func setAccessControl(_ accessControl: AccessControl) { self.accessControl = accessControl for i in lets.indices { From 4c893e1f27868cfde7e5a6ae0699d181e8fe0f49 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Mon, 10 Oct 2022 10:37:31 +0200 Subject: [PATCH 075/161] Implement imports --- Sources/RswiftCore/RswiftCore.swift | 13 ++++++++++--- .../LocalizableStrings+Generator.swift | 2 +- Sources/RswiftGenerators/Segue+Generator.swift | 8 ++++---- .../Shared/TypeReference+Generator.swift | 8 +------- .../RswiftGenerators/SwiftSyntax/Struct.swift | 13 +++++++------ .../RswiftResources/Shared/ModuleReference.swift | 16 ++++++++++++---- .../RswiftResources/Shared/TypeReference.swift | 16 ++++++++++++++-- Sources/rswift/App.swift | 1 + 8 files changed, 50 insertions(+), 27 deletions(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 5f09bc1d..5b84faa1 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -40,6 +40,7 @@ public struct RswiftCore { let outputURL: URL let generators: [Generator] let accessLevel: AccessLevel + let importModules: [String] let xcodeprojURL: URL let targetName: String let productModuleName: String? @@ -54,6 +55,7 @@ public struct RswiftCore { outputURL: URL, generators: [Generator], accessLevel: AccessLevel, + importModules: [String], xcodeprojURL: URL, targetName: String, productModuleName: String?, @@ -65,6 +67,7 @@ public struct RswiftCore { self.outputURL = outputURL self.generators = generators self.accessLevel = accessLevel + self.importModules = importModules self.xcodeprojURL = xcodeprojURL self.targetName = targetName self.productModuleName = productModuleName @@ -252,13 +255,17 @@ public struct RswiftCore { s.setAccessControl(.public) } + let imports = Set(s.allModuleReferences.compactMap(\.name)) + .union(importModules) + .sorted() + .map { "import \($0)" } + .joined(separator: "\n") + let mainLet = "\(accessLevel == .publicLevel ? "public " : "")let R = _R(bundle: Bundle(for: BundleClass.self))" let str = s.prettyPrint() let code = """ - import UIKit - import Foundation - import RswiftResources + \(imports) \(str) diff --git a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift index 49e74a7e..16d93336 100644 --- a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift +++ b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift @@ -289,7 +289,7 @@ private struct StringWithParams { private var typeName: String { "StringResource" - + (params.isEmpty ? "" : "\(params.count)<\(params.map(\.spec.typeReference.rawName).joined(separator: ", "))>") + + (params.isEmpty ? "" : "\(params.count)<\(params.map(\.spec.typeReference.name).joined(separator: ", "))>") } private var primaryLanguageValues: [(LocaleReference, String)] { diff --git a/Sources/RswiftGenerators/Segue+Generator.swift b/Sources/RswiftGenerators/Segue+Generator.swift index 5b55369f..271eb1ad 100644 --- a/Sources/RswiftGenerators/Segue+Generator.swift +++ b/Sources/RswiftGenerators/Segue+Generator.swift @@ -33,8 +33,8 @@ public struct Segue { } private static func generateStruct(sourceType: TypeReference, segues: [SegueWithInfo]) -> Struct { - let comments = ["This struct is generated for `\(sourceType.rawName)`, and contains static references to \(segues.count) segues."] - return Struct(comments: comments, name: SwiftIdentifier(name: sourceType.rawName)) { + let comments = ["This struct is generated for `\(sourceType.name)`, and contains static references to \(segues.count) segues."] + return Struct(comments: comments, name: SwiftIdentifier(name: sourceType.name)) { segues.map { $0.generateLetBinding() } } } @@ -45,7 +45,7 @@ public struct Segue { let grouped = Dictionary(grouping: segues, by: \.sourceType) for (sourceType, seguesBySourceType) in grouped { let segues = seguesBySourceType.grouped(bySwiftIdentifier: { $0.segue.identifier }) - segues.reportWarningsForDuplicatesAndEmpties(source: "segue", container: "for '\(sourceType.rawName)'", result: "segue", warning: warning) + segues.reportWarningsForDuplicatesAndEmpties(source: "segue", container: "for '\(sourceType.name)'", result: "segue", warning: warning) result[sourceType] = segues.uniques } @@ -64,7 +64,7 @@ public struct Segue { allStoryboards: storyboards) else { - warning("Destination view controller with id \(segue.destination) for segue \(segue.identifier) in \(viewController.type.rawName) not found in storyboard \(storyboard.name). Is this storyboard corrupt?") + warning("Destination view controller with id \(segue.destination) for segue \(segue.identifier) in \(viewController.type.codeString()) not found in storyboard \(storyboard.name). Is this storyboard corrupt?") return nil } diff --git a/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift b/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift index bc82af68..3ae1c30c 100644 --- a/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift +++ b/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift @@ -9,16 +9,10 @@ import Foundation import RswiftResources extension TypeReference { - init(module: ModuleReference, name: String, genericArgs: [TypeReference]) { + func codeString() -> String { let args = genericArgs.map { $0.codeString() }.joined(separator: ", ") let rawName = args.isEmpty ? name : "\(name)<\(args)>" - self.init(module: module, rawName: rawName) - } -} - -extension TypeReference { - func codeString() -> String { if case .custom(let module) = module { return "\(module).\(rawName)" } else { diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index 3770a29e..21a6349f 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -50,7 +50,7 @@ public struct LetBinding { public var allModuleReferences: Set { if let typeReference { - return Set([typeReference.module]) + return typeReference.allModuleReferences } else { return Set() } @@ -92,7 +92,7 @@ public struct VarGetter { } public var allModuleReferences: Set { - Set([typeReference.module]) + typeReference.allModuleReferences } func render(_ pp: inout PrettyPrinter) { @@ -161,7 +161,8 @@ public struct Function { } public var allModuleReferences: Set { - Set(params.map(\.typeReference.module)).union([returnType.module]) + Set(params.flatMap(\.typeReference.allModuleReferences)) + .union(returnType.allModuleReferences) } func render(_ pp: inout PrettyPrinter) { @@ -233,7 +234,7 @@ public struct Init { } public var allModuleReferences: Set { - Set(params.map(\.typeReference.module)) + Set(params.flatMap(\.typeReference.allModuleReferences)) } func render(_ pp: inout PrettyPrinter) { @@ -297,7 +298,7 @@ public struct TypeAlias { } public var allModuleReferences: Set { - Set([value.module]) + value.allModuleReferences } func render(_ pp: inout PrettyPrinter) { @@ -359,7 +360,7 @@ public struct Struct { public var allModuleReferences: Set { var result: Set = [] - result.formUnion(protocols.map(\.module)) + result.formUnion(protocols.flatMap(\.allModuleReferences)) result.formUnion(lets.flatMap(\.allModuleReferences)) result.formUnion(vars.flatMap(\.allModuleReferences)) result.formUnion(inits.flatMap(\.allModuleReferences)) diff --git a/Sources/RswiftResources/Shared/ModuleReference.swift b/Sources/RswiftResources/Shared/ModuleReference.swift index fc533dd9..a7c0107b 100644 --- a/Sources/RswiftResources/Shared/ModuleReference.swift +++ b/Sources/RswiftResources/Shared/ModuleReference.swift @@ -14,7 +14,12 @@ public enum ModuleReference: Hashable { case stdLib case custom(name: String) - var isCustom: Bool { + public init(name: String?, fallback: ModuleReference = .host) { + let cleaned = name?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + self = cleaned.isEmpty ? fallback : .custom(name: cleaned) + } + + public var isCustom: Bool { switch self { case .custom: return true @@ -23,9 +28,12 @@ public enum ModuleReference: Hashable { } } - public init(name: String?, fallback: ModuleReference = .host) { - let cleaned = name?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - self = cleaned.isEmpty ? fallback : .custom(name: cleaned) + public var name: String? { + if case .custom(let name) = self { + return name + } else { + return nil + } } } diff --git a/Sources/RswiftResources/Shared/TypeReference.swift b/Sources/RswiftResources/Shared/TypeReference.swift index 040f12b4..d9e7b62a 100644 --- a/Sources/RswiftResources/Shared/TypeReference.swift +++ b/Sources/RswiftResources/Shared/TypeReference.swift @@ -11,10 +11,22 @@ import Foundation public struct TypeReference: Hashable { public let module: ModuleReference - public let rawName: String + public let name: String + public let genericArgs: [TypeReference] public init(module: ModuleReference, rawName: String) { self.module = module - self.rawName = rawName + self.name = rawName + self.genericArgs = [] + } + + public init(module: ModuleReference, name: String, genericArgs: [TypeReference]) { + self.module = module + self.name = name + self.genericArgs = genericArgs + } + + public var allModuleReferences: Set { + Set(genericArgs.flatMap(\.allModuleReferences)).union([module]) } } diff --git a/Sources/rswift/App.swift b/Sources/rswift/App.swift index d516a1c6..8a0043c0 100644 --- a/Sources/rswift/App.swift +++ b/Sources/rswift/App.swift @@ -111,6 +111,7 @@ extension App { outputURL: outputURL, generators: generators.isEmpty ? Generator.allCases : generators, accessLevel: accessLevel, + importModules: imports, xcodeprojURL: URL(fileURLWithPath: xcodeprojPath), targetName: targetName, productModuleName: nil, From bc27c8278efd9d1e9c2e766b7c3f4c0c99767d44 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Tue, 11 Oct 2022 11:21:32 +0200 Subject: [PATCH 076/161] Add Font validation --- .../FontResource+Generator.swift | 32 ++++++++++++++++++- .../Shared/TypeReference+Generator.swift | 12 +++++++ .../RswiftGenerators/SwiftSyntax/Struct.swift | 8 +++-- .../Shared/TypeReference.swift | 2 +- .../Shared/ValidationError.swift | 20 ++++++++++++ 5 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 Sources/RswiftResources/Shared/ValidationError.swift diff --git a/Sources/RswiftGenerators/FontResource+Generator.swift b/Sources/RswiftGenerators/FontResource+Generator.swift index 87a55055..2363e15e 100644 --- a/Sources/RswiftGenerators/FontResource+Generator.swift +++ b/Sources/RswiftGenerators/FontResource+Generator.swift @@ -21,10 +21,40 @@ extension FontResource { let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) fonts."] - return Struct(comments: comments, name: structName) { + return Struct(comments: comments, name: structName, protocols: [.sequence]) { + if letbindings.count > 0 { + generateMakeIterator(names: letbindings.map(\.name)) + } letbindings + + generateValidate() } } + + private static func generateMakeIterator(names: [SwiftIdentifier]) -> Function { + .init( + comments: [], + name: .init(name: "makeIterator"), + params: [], + returnType: .someIteratorProtocol(.fontResource), + valueCodeString: "[\(names.map(\.value).joined(separator: ", "))].makeIterator()" + ) + } + + private static func generateValidate() -> Function { + .init( + comments: [], + name: .init(name: "validate"), + params: [], + returnThrows: true, + returnType: .void, + valueCodeString: #""" + for font in self { + if UIFont(resource: font, size: 42) == nil { throw RswiftResources.ValidationError("[R.swift] Font '\(font.name)' could not be loaded, is '\(font.filename)' added to the UIAppFonts array in this targets Info.plist?") } + } + """# + ) + } } extension FontResource { diff --git a/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift b/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift index 3ae1c30c..6f1d74a3 100644 --- a/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift +++ b/Sources/RswiftGenerators/Shared/TypeReference+Generator.swift @@ -20,10 +20,22 @@ extension TypeReference { } } + static func someIteratorProtocol(_ element: TypeReference) -> TypeReference { + var result = TypeReference(module: .stdLib, rawName: "some IteratorProtocol") + result.genericArgs = [element] + return result + } + static var bundle: TypeReference = .init(module: .foundation, rawName: "Bundle") static var locale: TypeReference = .init(module: .foundation, rawName: "Locale") + static var void: TypeReference = .init(module: .stdLib, rawName: "Void") static var bool: TypeReference = .init(module: .stdLib, rawName: "Bool") static var string: TypeReference = .init(module: .stdLib, rawName: "String") + static var sequence: TypeReference = .init(module: .stdLib, rawName: "Sequence") + static var someIteratorProtocol: TypeReference = .init(module: .stdLib, rawName: "some IteratorProtocol") static var uiView: TypeReference = .init(module: .uiKit, rawName: "UIView") static var uiViewController: TypeReference = .init(module: .uiKit, rawName: "UIViewController") + + + static var fontResource: TypeReference = .init(module: .rswiftResources, rawName: "FontResource") } diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index 21a6349f..cd367b8d 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -120,15 +120,17 @@ public struct Function { public let isStatic: Bool public let name: SwiftIdentifier public let params: [Parameter] + public var returnThrows: Bool public let returnType: TypeReference public let valueCodeString: String - public init(comments: [String], accessControl: AccessControl = AccessControl.none, isStatic: Bool = false, name: SwiftIdentifier, params: [Parameter], returnType: TypeReference, valueCodeString: String) { + public init(comments: [String], accessControl: AccessControl = AccessControl.none, isStatic: Bool = false, name: SwiftIdentifier, params: [Parameter], returnThrows: Bool = false, returnType: TypeReference, valueCodeString: String) { self.comments = comments self.accessControl = accessControl self.isStatic = isStatic self.name = name self.params = params + self.returnThrows = returnThrows self.returnType = returnType self.valueCodeString = valueCodeString } @@ -172,8 +174,8 @@ public struct Function { isStatic ? "static" : nil, "func", "\(name.value)(\(prs))", - "->", - returnType.codeString(), + returnThrows ? "throws" : nil, + returnType != .void ? "-> \(returnType.codeString())" : nil, "{" ] diff --git a/Sources/RswiftResources/Shared/TypeReference.swift b/Sources/RswiftResources/Shared/TypeReference.swift index d9e7b62a..c911937e 100644 --- a/Sources/RswiftResources/Shared/TypeReference.swift +++ b/Sources/RswiftResources/Shared/TypeReference.swift @@ -12,7 +12,7 @@ import Foundation public struct TypeReference: Hashable { public let module: ModuleReference public let name: String - public let genericArgs: [TypeReference] + public var genericArgs: [TypeReference] public init(module: ModuleReference, rawName: String) { self.module = module diff --git a/Sources/RswiftResources/Shared/ValidationError.swift b/Sources/RswiftResources/Shared/ValidationError.swift new file mode 100644 index 00000000..1124cd1f --- /dev/null +++ b/Sources/RswiftResources/Shared/ValidationError.swift @@ -0,0 +1,20 @@ +// +// Validatable.swift +// R.swift.Library +// +// Created by Mathijs Kadijk on 17-12-15. +// From: https://github.com/mac-cain13/R.swift.Library +// License: MIT License +// + +import Foundation + +/// Error thrown during validation +public struct ValidationError: Error, CustomStringConvertible { + /// Human readable description + public let description: String + + public init(_ description: String) { + self.description = description + } +} From 172a6cee3b10e2151e64adac70ca8e9484c9349a Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Tue, 11 Oct 2022 20:25:06 +0200 Subject: [PATCH 077/161] Add validate functions --- Sources/RswiftCore/RswiftCore.swift | 64 +++++++++++++------ .../Extensions/Array+Extensions.swift | 14 ++++ .../FontResource+Generator.swift | 10 +-- Sources/RswiftGenerators/Nib+Generator.swift | 32 ++++++++++ .../Storyboard+Generator.swift | 52 +++++++++++++++ 5 files changed, 149 insertions(+), 23 deletions(-) create mode 100644 Sources/RswiftGenerators/Extensions/Array+Extensions.swift diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 5b84faa1..faf7f393 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -173,82 +173,115 @@ public struct RswiftCore { } } + let generateString = generators.contains(.string) && !stringStruct.isEmpty + let generateData = generators.contains(.data) && !dataStruct.isEmpty + let generateColor = generators.contains(.color) && !colorStruct.isEmpty + let generateImage = generators.contains(.image) && !imageStruct.isEmpty + let generateInfo = generators.contains(.info) && !infoStruct.isEmpty + let generateEntitlements = generators.contains(.entitlements) && !entitlementsStruct.isEmpty + let generateFont = generators.contains(.font) && !fontStruct.isEmpty + let generateFile = generators.contains(.file) && !fileStruct.isEmpty + let generateSegue = generators.contains(.segue) && !segueStruct.isEmpty + let generateId = generators.contains(.id) && !idStruct.isEmpty + let generateNib = generators.contains(.nib) && !nibStruct.isEmpty + let generateReuseIdentifier = generators.contains(.reuseIdentifier) && !reuseIdentifierStruct.isEmpty + let generateStoryboard = generators.contains(.storyboard) && !storyboardStruct.isEmpty + + let validateLines = [ + generateFont ? "try self.font.validate()" : "", + generateNib ? "try self.nib.validate()" : "", + generateStoryboard ? "try self.storyboard.validate()" : "", + ] + .filter { $0 != "" } + .joined(separator: "\n") + + let validate = Function( + comments: [], + name: SwiftIdentifier(name: "validate"), + params: [], + returnThrows: true, + returnType: .init(module: .stdLib, rawName: "Void"), + valueCodeString: validateLines + ) + var s = Struct(name: structName) { Init.bundle projectStruct - if generators.contains(.string) && !stringStruct.isEmpty { + if generateString { stringStruct.generateBundleVarGetter(name: "string") stringStruct.generateBundleFunction(name: "string") stringStruct } - if generators.contains(.data) && !dataStruct.isEmpty { + if generateData { dataStruct.generateBundleVarGetter(name: "data") dataStruct.generateBundleFunction(name: "data") dataStruct } - if generators.contains(.color) && !colorStruct.isEmpty { + if generateColor { colorStruct.generateBundleVarGetter(name: "color") colorStruct.generateBundleFunction(name: "color") colorStruct } - if generators.contains(.image) && !imageStruct.isEmpty { + if generateImage { imageStruct.generateBundleVarGetter(name: "image") imageStruct.generateBundleFunction(name: "image") imageStruct } - if generators.contains(.info) && !infoStruct.isEmpty { + if generateInfo { infoStruct.generateBundleVarGetter(name: "info") infoStruct.generateBundleFunction(name: "info") infoStruct } - if generators.contains(.entitlements) && !entitlementsStruct.isEmpty { + if generateEntitlements { entitlementsStruct.generateLetBinding() entitlementsStruct } - if generators.contains(.font) && !fontStruct.isEmpty { + if generateFont { fontStruct.generateLetBinding() fontStruct } - if generators.contains(.file) && !fileStruct.isEmpty { + if generateFile { fileStruct.generateBundleVarGetter(name: "file") fileStruct.generateBundleFunction(name: "file") fileStruct } - if generators.contains(.segue) && !segueStruct.isEmpty { + if generateSegue { segueStruct.generateLetBinding() segueStruct } - if generators.contains(.id) && !idStruct.isEmpty { + if generateId { idStruct.generateLetBinding() idStruct } - if generators.contains(.nib) && !nibStruct.isEmpty { + if generateNib { nibStruct.generateBundleVarGetter(name: "nib") nibStruct.generateBundleFunction(name: "nib") nibStruct } - if generators.contains(.reuseIdentifier) && !reuseIdentifierStruct.isEmpty { + if generateReuseIdentifier { reuseIdentifierStruct.generateLetBinding() reuseIdentifierStruct } - if generators.contains(.storyboard) && !storyboardStruct.isEmpty { + if generateStoryboard { storyboardStruct.generateBundleVarGetter(name: "storyboard") storyboardStruct.generateBundleFunction(name: "storyboard") storyboardStruct } + + validate } if accessLevel == .publicLevel { @@ -269,11 +302,6 @@ public struct RswiftCore { \(str) - extension _R { - func validate() throws { - } - } - private class BundleClass {} \(mainLet) """ diff --git a/Sources/RswiftGenerators/Extensions/Array+Extensions.swift b/Sources/RswiftGenerators/Extensions/Array+Extensions.swift new file mode 100644 index 00000000..7d31f918 --- /dev/null +++ b/Sources/RswiftGenerators/Extensions/Array+Extensions.swift @@ -0,0 +1,14 @@ +// +// Array+Extensions.swift +// RswiftGenerators +// +// Created by Tom Lokhorst on 2022-10-11. +// + +import Foundation + +extension Array where Element: Comparable, Element: Hashable { + func uniqueAndSorted() -> [Element] { + Set(self).sorted() + } +} diff --git a/Sources/RswiftGenerators/FontResource+Generator.swift b/Sources/RswiftGenerators/FontResource+Generator.swift index 2363e15e..e58725cd 100644 --- a/Sources/RswiftGenerators/FontResource+Generator.swift +++ b/Sources/RswiftGenerators/FontResource+Generator.swift @@ -32,7 +32,7 @@ extension FontResource { } private static func generateMakeIterator(names: [SwiftIdentifier]) -> Function { - .init( + Function( comments: [], name: .init(name: "makeIterator"), params: [], @@ -42,7 +42,7 @@ extension FontResource { } private static func generateValidate() -> Function { - .init( + Function( comments: [], name: .init(name: "validate"), params: [], @@ -59,10 +59,10 @@ extension FontResource { extension FontResource { func generateLetBinding() -> LetBinding { - let code = "FontResource(name: \"\(name)\", filename: \"\(filename)\")" - return LetBinding( + LetBinding( comments: ["Font `\(name)`."], name: SwiftIdentifier(name: name), - valueCodeString: code) + valueCodeString: "FontResource(name: \"\(name)\", filename: \"\(filename)\")" + ) } } diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift index ba531868..b2e08144 100644 --- a/Sources/RswiftGenerators/Nib+Generator.swift +++ b/Sources/RswiftGenerators/Nib+Generator.swift @@ -31,9 +31,21 @@ extension NibResource { Init.bundle vargetters + + generateValidate(nibs: groupedNibs.uniques) } } + private static func generateValidate(nibs: some Collection) -> Function { + Function( + comments: [], + name: .init(name: "validate"), + params: [], + returnThrows: true, + returnType: .void, + valueCodeString: nibs.flatMap { $0.generateValidateLines() }.joined(separator: "\n") + ) + } } extension NibResource { @@ -72,4 +84,24 @@ extension NibResource { ) } } + + func generateValidateLines() -> [String] { + let validateImagesLines = self.usedImageIdentifiers.uniqueAndSorted() + .map { nameCatalog -> String in + if nameCatalog.isSystemCatalog { + return "if #available(iOS 13.0, *) { if UIKit.UIImage(systemName: \"\(nameCatalog.name)\") == nil { throw RswiftResources.ValidationError(\"[R.swift] System image named '\(nameCatalog.name)' is used in nib '\(self.name)', but couldn't be loaded.\") } }" + } else { + return "if UIKit.UIImage(named: \"\(nameCatalog.name)\", in: _bundle, compatibleWith: nil) == nil { throw RswiftResources.ValidationError(\"[R.swift] Image named '\(nameCatalog.name)' is used in nib '\(self.name)', but couldn't be loaded.\") }" + } + } + + let validateColorLines = self.usedColorResources.uniqueAndSorted() + .filter { !$0.isSystemCatalog } + .map { nameCatalog in + "if UIKit.UIColor(named: \"\(nameCatalog.name)\", in: _bundle, compatibleWith: nil) == nil { throw RswiftResources.ValidationError(\"[R.swift] Color named '\(nameCatalog.name)' is used in nib '\(self.name)', but couldn't be loaded.\") }" + } + + + return (validateImagesLines + validateColorLines) + } } diff --git a/Sources/RswiftGenerators/Storyboard+Generator.swift b/Sources/RswiftGenerators/Storyboard+Generator.swift index f6aa04d7..db339b66 100644 --- a/Sources/RswiftGenerators/Storyboard+Generator.swift +++ b/Sources/RswiftGenerators/Storyboard+Generator.swift @@ -33,8 +33,25 @@ extension StoryboardResource { } structs + + generateValidate(names: structs.map(\.name)) } } + + private static func generateValidate(names: [SwiftIdentifier]) -> Function { + let lines = names + .map { name -> String in + "try self.\(name.value).validate()" + } + return Function( + comments: [], + name: .init(name: "validate"), + params: [], + returnThrows: true, + returnType: .void, + valueCodeString: lines.joined(separator: "\n") + ) + } } extension StoryboardResource { @@ -89,8 +106,43 @@ extension StoryboardResource { letName vargetters + + generateValidate(viewControllers: grouped.uniques.map(\.vc)) } } + + func generateValidate(viewControllers: some Collection) -> Function { + let validateImagesLines = self.usedImageIdentifiers.uniqueAndSorted() + .map { nameCatalog -> String in + if nameCatalog.isSystemCatalog { + return "if #available(iOS 13.0, *) { if UIKit.UIImage(systemName: \"\(nameCatalog.name)\") == nil { throw RswiftResources.ValidationError(\"[R.swift] System image named '\(nameCatalog.name)' is used in storyboard '\(self.name)', but couldn't be loaded.\") } }" + } else { + return "if UIKit.UIImage(named: \"\(nameCatalog.name)\", in: _bundle, compatibleWith: nil) == nil { throw RswiftResources.ValidationError(\"[R.swift] Image named '\(nameCatalog.name)' is used in storyboard '\(self.name)', but couldn't be loaded.\") }" + } + } + let validateColorLines = self.usedColorResources.uniqueAndSorted() + .filter { !$0.isSystemCatalog } + .map { nameCatalog in + "if UIKit.UIColor(named: \"\(nameCatalog.name)\", in: _bundle, compatibleWith: nil) == nil { throw RswiftResources.ValidationError(\"[R.swift] Color named '\(nameCatalog.name)' is used in nib '\(self.name)', but couldn't be loaded.\") }" + } + let validateViewControllersLines = viewControllers + .compactMap { vc -> String? in + guard let storyboardName = vc.storyboardIdentifier else { return nil } + let storyboardIdentifier = SwiftIdentifier(name: storyboardName) + return "if \(storyboardIdentifier.value)() == nil { throw RswiftResources.ValidationError(\"[R.swift] ViewController with identifier '\(storyboardIdentifier.value)' could not be loaded from storyboard '\(self.name)' as '\(vc.type.codeString())'.\") }" + } + + let validateLines = validateImagesLines + validateColorLines + validateViewControllersLines + + return Function( + comments: [], + name: .init(name: "validate"), + params: [], + returnThrows: true, + returnType: .void, + valueCodeString: validateLines.joined(separator: "\n") + ) + } } extension StoryboardResource.ViewController { From cd63b64e4990909c38d9fe8dd150c3924590731a Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Wed, 12 Oct 2022 13:42:31 +0200 Subject: [PATCH 078/161] Update example tests --- .../ResourceAppTests/FontsTests.swift | 22 +++++++++----- .../ResourceAppTests/NibTests.swift | 2 +- .../ResourceAppTests/StringsTests.swift | 30 ++++++++++++------- .../ResourceAppTests/ValidationTests.swift | 6 ++-- 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/Examples/ResourceApp/ResourceAppTests/FontsTests.swift b/Examples/ResourceApp/ResourceAppTests/FontsTests.swift index efad4f0a..f8091a1c 100644 --- a/Examples/ResourceApp/ResourceAppTests/FontsTests.swift +++ b/Examples/ResourceApp/ResourceAppTests/FontsTests.swift @@ -8,16 +8,24 @@ import UIKit import XCTest +import RswiftResources @testable import ResourceApp class FontsTests: XCTestCase { - func testNoNilFonts() { - XCTAssertNotNil(R.font.averiaLibreBold(size: 10)) - XCTAssertNotNil(R.font.averiaLibreBoldItalic(size: 20)) - XCTAssertNotNil(R.font.averiaLibreLight(size: 30)) - XCTAssertNotNil(R.font.averiaLibreRegular(size: 40)) - XCTAssertNotNil(R.font.goudyBookletter1911(size: 50)) - } + func testNoNilFonts() { + XCTAssertNotNil(R.font.averiaLibreBold(size: 10)) + XCTAssertNotNil(R.font.averiaLibreBoldItalic(size: 20)) + XCTAssertNotNil(R.font.averiaLibreLight(size: 30)) + XCTAssertNotNil(R.font.averiaLibreRegular(size: 40)) + XCTAssertNotNil(R.font.goudyBookletter1911(size: 50)) + } + func testNoValidationError() { + XCTAssertNoThrow(try R.font.validate()) + } + + func testAllFonts() { + XCTAssertEqual(Array(R.font.map(\.name)), ["AveriaLibre-Bold", "AveriaLibre-BoldItalic", "AveriaLibre-Light", "AveriaLibre-Regular", "GoudyBookletter1911"]) + } } diff --git a/Examples/ResourceApp/ResourceAppTests/NibTests.swift b/Examples/ResourceApp/ResourceAppTests/NibTests.swift index 05d22374..603663b3 100644 --- a/Examples/ResourceApp/ResourceAppTests/NibTests.swift +++ b/Examples/ResourceApp/ResourceAppTests/NibTests.swift @@ -22,7 +22,7 @@ class NibTests: XCTestCase { } func testNibIsOfCorrectType() { - XCTAssertTrue(type(of: R.nib.supplementaryElement).ReusableType.classForCoder() == UICollectionReusableView.classForCoder()) + XCTAssertTrue(type(of: R.nib.supplementaryElement).Reusable.classForCoder() == UICollectionReusableView.classForCoder()) } } diff --git a/Examples/ResourceApp/ResourceAppTests/StringsTests.swift b/Examples/ResourceApp/ResourceAppTests/StringsTests.swift index ea969041..0d6ff02e 100644 --- a/Examples/ResourceApp/ResourceAppTests/StringsTests.swift +++ b/Examples/ResourceApp/ResourceAppTests/StringsTests.swift @@ -30,17 +30,25 @@ class StringsTests: XCTestCase { func testCorrectValues() { - // Question: Why is this different between iOS 12 and 13? - // "precision1" = "one - %012.2f"; - if #available(iOS 13, *) { - XCTAssertEqual(R.string.generic.precision1(12345.678), "one - 12,345.68") - } else { - XCTAssertEqual(R.string.generic.precision1(12345.678), "one - 0,000,012,345.68") - } - - XCTAssertEqual(R.string.generic.precision2(12345.678), "two - 12,345.68") - XCTAssertEqual(R.string.generic.precision3(12345.678), "three - 12,345.6780") - XCTAssertEqual(R.string.generic.precision4(12345.678), "four - 12,345.68") + // Force locales, for decimal point and decimal comma versions + let genericEN = R.string.generic(bundle: .main, locale: Locale(identifier: "en_US")) + let genericNL = R.string.generic(bundle: .main, locale: Locale(identifier: "nl_NL")) + let genericFR = R.string.generic(bundle: .main, locale: Locale(identifier: "fr_FR")) + + XCTAssertEqual(genericEN.precision1(12345.678), "one - 12,345.68") + XCTAssertEqual(genericEN.precision2(12345.678), "two - 12,345.68") + XCTAssertEqual(genericEN.precision3(12345.678), "three - 12,345.6780") + XCTAssertEqual(genericEN.precision4(12345.678), "four - 12,345.68") + + XCTAssertEqual(genericNL.precision1(12345.678), "one - 12.345,68") + XCTAssertEqual(genericNL.precision2(12345.678), "two - 12.345,68") + XCTAssertEqual(genericNL.precision3(12345.678), "three - 12.345,6780") + XCTAssertEqual(genericNL.precision4(12345.678), "four - 12.345,68") + + XCTAssertEqual(genericFR.precision1(12345.678), "one - 12\u{202F}345,68") + XCTAssertEqual(genericFR.precision2(12345.678), "two - 12\u{202F}345,68") + XCTAssertEqual(genericFR.precision3(12345.678), "three - 12\u{202F}345,6780") + XCTAssertEqual(genericFR.precision4(12345.678), "four - 12\u{202F}345,68") XCTAssertEqual( R.string.settings.multilineKeyWeird(), diff --git a/Examples/ResourceApp/ResourceAppTests/ValidationTests.swift b/Examples/ResourceApp/ResourceAppTests/ValidationTests.swift index c6565673..1b198ec4 100644 --- a/Examples/ResourceApp/ResourceAppTests/ValidationTests.swift +++ b/Examples/ResourceApp/ResourceAppTests/ValidationTests.swift @@ -8,7 +8,7 @@ import UIKit import XCTest -import Rswift +import RswiftResources @testable import ResourceApp class ValidationTests: XCTestCase { @@ -26,13 +26,13 @@ class ValidationTests: XCTestCase { func testRunSpecificValidateMethods() { do { - try _R.storyboard.main.validate() + try R.storyboard.main.validate() } catch { XCTFail("Wrong error thrown") } do { - try _R.storyboard.secondary.validate() + try R.storyboard.secondary.validate() XCTFail("No error thrown") } catch let error as ValidationError { XCTAssertEqual(error.description, "[R.swift] Image named 'First' is used in storyboard 'Secondary', but couldn't be loaded.") From 86c96869fab43a4e3ced45d8f72544b5c06032df Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Wed, 12 Oct 2022 19:46:03 +0200 Subject: [PATCH 079/161] Generate warnings --- .../RswiftGenerators/AccessibilityIdentifier+Generator.swift | 2 +- Sources/RswiftGenerators/AssetCatalog+Generator.swift | 2 +- Sources/RswiftGenerators/FileResource+Generator.swift | 2 +- Sources/RswiftGenerators/FontResource+Generator.swift | 5 ++--- Sources/RswiftGenerators/LocalizableStrings+Generator.swift | 2 +- Sources/RswiftGenerators/Nib+Generator.swift | 2 +- .../RswiftGenerators/PropertyListResource+Generator.swift | 2 +- Sources/RswiftGenerators/ReuseIdentifier+Generator.swift | 2 +- Sources/RswiftGenerators/Segue+Generator.swift | 2 +- Sources/RswiftGenerators/Storyboard+Generator.swift | 2 +- 10 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Sources/RswiftGenerators/AccessibilityIdentifier+Generator.swift b/Sources/RswiftGenerators/AccessibilityIdentifier+Generator.swift index f8298998..2f5a8e48 100644 --- a/Sources/RswiftGenerators/AccessibilityIdentifier+Generator.swift +++ b/Sources/RswiftGenerators/AccessibilityIdentifier+Generator.swift @@ -21,7 +21,7 @@ public struct AccessibilityIdentifier { let structName = SwiftIdentifier(name: "id") let qualifiedName = prefix + structName - let warning: (String) -> Void = { print("warning:", $0) } + let warning: (String) -> Void = { print("warning: [R.swift]", $0) } let containers: [AccessibilityIdentifierContainer] = nibs + storyboards let mergedContainers = Dictionary(grouping: containers, by: \.name) diff --git a/Sources/RswiftGenerators/AssetCatalog+Generator.swift b/Sources/RswiftGenerators/AssetCatalog+Generator.swift index bfe344dd..15b99c3c 100644 --- a/Sources/RswiftGenerators/AssetCatalog+Generator.swift +++ b/Sources/RswiftGenerators/AssetCatalog+Generator.swift @@ -47,7 +47,7 @@ extension AssetCatalog.Namespace { public func generateStruct(resourceName: String, resourcesSelector: (Self) -> [AssetCatalogContent], prefix: SwiftIdentifier) -> Struct { let structName = SwiftIdentifier(name: resourceName) let qualifiedName = prefix + structName - let warning: (String) -> Void = { print("warning:", $0) } + let warning: (String) -> Void = { print("warning: [R.swift]", $0) } let allResources = resourcesSelector(self) let groupedResources = allResources.grouped(bySwiftIdentifier: { $0.name }) diff --git a/Sources/RswiftGenerators/FileResource+Generator.swift b/Sources/RswiftGenerators/FileResource+Generator.swift index cc76f9d0..3f44c27b 100644 --- a/Sources/RswiftGenerators/FileResource+Generator.swift +++ b/Sources/RswiftGenerators/FileResource+Generator.swift @@ -12,7 +12,7 @@ extension FileResource { public static func generateStruct(resources: [FileResource], prefix: SwiftIdentifier) -> Struct { let structName = SwiftIdentifier(name: "file") let qualifiedName = prefix + structName - let warning: (String) -> Void = { print("warning:", $0) } + let warning: (String) -> Void = { print("warning: [R.swift]", $0) } // For resource files, the contents of the different locales don't matter, so we just use the first one let firstLocales = Dictionary(grouping: resources, by: \.filename) diff --git a/Sources/RswiftGenerators/FontResource+Generator.swift b/Sources/RswiftGenerators/FontResource+Generator.swift index e58725cd..96f721f0 100644 --- a/Sources/RswiftGenerators/FontResource+Generator.swift +++ b/Sources/RswiftGenerators/FontResource+Generator.swift @@ -12,7 +12,7 @@ extension FontResource { public static func generateStruct(resources: [FontResource], prefix: SwiftIdentifier) -> Struct { let structName = SwiftIdentifier(name: "font") let qualifiedName = prefix + structName - let warning: (String) -> Void = { print("warning:", $0) } + let warning: (String) -> Void = { print("warning: [R.swift]", $0) } let groupedResources = resources.grouped(bySwiftIdentifier: { $0.name }) groupedResources.reportWarningsForDuplicatesAndEmpties(source: "font resource", result: "font", warning: warning) @@ -24,10 +24,9 @@ extension FontResource { return Struct(comments: comments, name: structName, protocols: [.sequence]) { if letbindings.count > 0 { generateMakeIterator(names: letbindings.map(\.name)) + generateValidate() } letbindings - - generateValidate() } } diff --git a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift index 16d93336..2eb5e3df 100644 --- a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift +++ b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift @@ -12,7 +12,7 @@ extension LocalizableStrings { public static func generateStruct(resources: [LocalizableStrings], developmentLanguage: String, prefix: SwiftIdentifier) -> Struct { let structName = SwiftIdentifier(name: "string", lowercaseStartingCharacters: false) let qualifiedName = prefix + structName - let warning: (String) -> Void = { print("warning:", $0) } + let warning: (String) -> Void = { print("warning: [R.swift]", $0) } let localized = Dictionary(grouping: resources, by: \.filename) let groupedLocalized = localized.grouped(bySwiftIdentifier: \.key) diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift index b2e08144..588df61d 100644 --- a/Sources/RswiftGenerators/Nib+Generator.swift +++ b/Sources/RswiftGenerators/Nib+Generator.swift @@ -13,7 +13,7 @@ extension NibResource { let structName = SwiftIdentifier(name: "nib") let qualifiedName = prefix + structName - let warning: (String) -> Void = { print("warning:", $0) } + let warning: (String) -> Void = { print("warning: [R.swift]", $0) } // TODO: Generate warnings for mismatched identifier/root view in different locales // let firstLocales = Dictionary(grouping: nibs, by: \.name) diff --git a/Sources/RswiftGenerators/PropertyListResource+Generator.swift b/Sources/RswiftGenerators/PropertyListResource+Generator.swift index 444c0dce..8f7d84c7 100644 --- a/Sources/RswiftGenerators/PropertyListResource+Generator.swift +++ b/Sources/RswiftGenerators/PropertyListResource+Generator.swift @@ -22,7 +22,7 @@ extension PropertyListResource { public static func generateStruct(resourceName: String, plists: [PropertyListResource], toplevelKeysWhitelist: [String]? = nil, isInfoPlist: Bool = false, prefix: SwiftIdentifier) -> Struct { let structName = SwiftIdentifier(name: resourceName) let qualifiedName = prefix + structName - let warning: (String) -> Void = { print("warning:", $0) } + let warning: (String) -> Void = { print("warning: [R.swift]", $0) } guard let plist = plists.first else { return .empty } diff --git a/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift b/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift index 2b94c7ab..b94fb5ca 100644 --- a/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift +++ b/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift @@ -13,7 +13,7 @@ extension Reusable { let structName = SwiftIdentifier(name: "reuseIdentifier") let qualifiedName = prefix + structName - let warning: (String) -> Void = { print("warning:", $0) } + let warning: (String) -> Void = { print("warning: [R.swift]", $0) } let reusables = nibs.flatMap(\.reusables) + storyboards.flatMap(\.reusables) let deduplicatedReusables = Dictionary(grouping: reusables, by: \.hashValue) diff --git a/Sources/RswiftGenerators/Segue+Generator.swift b/Sources/RswiftGenerators/Segue+Generator.swift index 271eb1ad..278c444a 100644 --- a/Sources/RswiftGenerators/Segue+Generator.swift +++ b/Sources/RswiftGenerators/Segue+Generator.swift @@ -13,7 +13,7 @@ public struct Segue { let structName = SwiftIdentifier(name: "segue") let qualifiedName = prefix + structName - let warning: (String) -> Void = { print("warning:", $0) } + let warning: (String) -> Void = { print("warning: [R.swift]", $0) } let allSegues = allSegueInfos(storyboards: storyboards, warning: warning) let viewControllers = viewControllers(segues: allSegues, warning: warning) diff --git a/Sources/RswiftGenerators/Storyboard+Generator.swift b/Sources/RswiftGenerators/Storyboard+Generator.swift index db339b66..675d50df 100644 --- a/Sources/RswiftGenerators/Storyboard+Generator.swift +++ b/Sources/RswiftGenerators/Storyboard+Generator.swift @@ -13,7 +13,7 @@ extension StoryboardResource { let structName = SwiftIdentifier(name: "storyboard") let qualifiedName = prefix + structName - let warning: (String) -> Void = { print("warning:", $0) } + let warning: (String) -> Void = { print("warning: [R.swift]", $0) } let groupedStoryboards = storyboards.grouped(bySwiftIdentifier: { $0.name }) groupedStoryboards.reportWarningsForDuplicatesAndEmpties(source: "storyboard", result: "storyboard", warning: warning) From 2b1fa4a89bfdbcb8ab781cebdd764fbc8c3f3070 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Wed, 12 Oct 2022 20:45:04 +0200 Subject: [PATCH 080/161] Update Example projects --- Examples/Podfile | 2 +- Examples/Podfile.lock | 2 +- .../ResourceApp.xcodeproj/project.pbxproj | 33 ++++++++++-- .../xcshareddata/swiftpm/Package.resolved | 51 ++++++++++++++----- .../RswiftUI.xcodeproj/project.pbxproj | 31 ----------- .../RtvApp/RtvApp.xcodeproj/project.pbxproj | 40 ++++++++------- .../InterfaceController.swift | 4 +- .../RwatchApp.xcodeproj/project.pbxproj | 24 ++++++++- 8 files changed, 116 insertions(+), 71 deletions(-) diff --git a/Examples/Podfile b/Examples/Podfile index 77c965e1..fdd92054 100644 --- a/Examples/Podfile +++ b/Examples/Podfile @@ -2,7 +2,7 @@ use_frameworks! workspace 'RswiftExamples' def rswiftlib - pod 'R.swift.Library', :git => 'https://github.com/mac-cain13/R.swift.Library.git' # for CI builds +# pod 'R.swift.Library', :git => 'https://github.com/mac-cain13/R.swift.Library.git' # for CI builds # pod 'R.swift.Library', :path => '../../R.swift.Library' # for development end diff --git a/Examples/Podfile.lock b/Examples/Podfile.lock index 8551664d..747d31d2 100644 --- a/Examples/Podfile.lock +++ b/Examples/Podfile.lock @@ -25,4 +25,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: d9a891a898cc2f561fdd4b8424cb30a1e14741d9 -COCOAPODS: 1.11.2 +COCOAPODS: 1.11.3 diff --git a/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj b/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj index 022c63f9..b4e72cd7 100644 --- a/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj +++ b/Examples/ResourceApp/ResourceApp.xcodeproj/project.pbxproj @@ -82,6 +82,7 @@ E296935A1CAD64D100401D53 /* associatedtype in Resources */ = {isa = PBXBuildFile; fileRef = E29693591CAD64D100401D53 /* associatedtype */; }; E296935C1CAD666200401D53 /* #column in Resources */ = {isa = PBXBuildFile; fileRef = E296935B1CAD666200401D53 /* #column */; }; E2A10EF81CD13779006BFC63 /* RelativeToProject.xib in Resources */ = {isa = PBXBuildFile; fileRef = E2A10EF71CD13779006BFC63 /* RelativeToProject.xib */; }; + E2C415D028EED7890028D537 /* RswiftLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = E2C415CF28EED7890028D537 /* RswiftLibrary */; }; E2CD68671D7CADEA00BEBE59 /* hello.txt in Resources */ = {isa = PBXBuildFile; fileRef = E2CD68641D7CACC100BEBE59 /* hello.txt */; }; E2DB0EB02334DCC100815AAF /* InfoPlistTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2DB0EAF2334DCC100815AAF /* InfoPlistTests.swift */; }; E2F768FC244D92A200761E14 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F768FB244D92A200761E14 /* SceneDelegate.swift */; }; @@ -221,6 +222,7 @@ E29693591CAD64D100401D53 /* associatedtype */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = associatedtype; sourceTree = ""; }; E296935B1CAD666200401D53 /* #column */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "#column"; sourceTree = ""; }; E2A10EF71CD13779006BFC63 /* RelativeToProject.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RelativeToProject.xib; sourceTree = ""; }; + E2C415CE28EED7580028D537 /* R.swift */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = R.swift; path = ../..; sourceTree = ""; }; E2CD68631D7CACC100BEBE59 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text; name = Base; path = Base.lproj/hello.txt; sourceTree = ""; }; E2CD68651D7CACCA00BEBE59 /* es */ = {isa = PBXFileReference; lastKnownFileType = text; name = es; path = es.lproj/hello.txt; sourceTree = ""; }; E2CD68661D7CACCB00BEBE59 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text; name = nl; path = nl.lproj/hello.txt; sourceTree = ""; }; @@ -234,6 +236,7 @@ buildActionMask = 2147483647; files = ( C30DF7218982DFFDAFAD2A11 /* Pods_ResourceApp.framework in Frameworks */, + E2C415D028EED7890028D537 /* RswiftLibrary in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -311,6 +314,7 @@ D55C6CAF1B5D757300301B0D = { isa = PBXGroup; children = ( + E2C415CD28EED7580028D537 /* Packages */, E243EFA32510DFA600DC653F /* RswiftUI.xcodeproj */, D5DE480D1B5E1CC7000F6A85 /* R.generated.swift */, D5EE1B5722DEEFBF00A901EC /* R.UITest.generated.swift */, @@ -457,6 +461,14 @@ path = "ResourceApp/Relative To Project"; sourceTree = SOURCE_ROOT; }; + E2C415CD28EED7580028D537 /* Packages */ = { + isa = PBXGroup; + children = ( + E2C415CE28EED7580028D537 /* R.swift */, + ); + name = Packages; + sourceTree = ""; + }; E2CD68611D7CAC9200BEBE59 /* Localized */ = { isa = PBXGroup; children = ( @@ -485,6 +497,9 @@ dependencies = ( ); name = ResourceApp; + packageProductDependencies = ( + E2C415CF28EED7890028D537 /* RswiftLibrary */, + ); productName = ResourceApp; productReference = D55C6CB81B5D757300301B0D /* ResourceApp.app */; productType = "com.apple.product-type.application"; @@ -727,12 +742,13 @@ ); name = R.swift; outputPaths = ( - "$(SRCROOT)/R.generated.swift", + "$(SRCROOT)/R.generated.swif", "$(SRCROOT)/R.UITest.generated.swift", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# \"$SRCROOT/../../build/rswift\" generate --generateUITestFile \"$SRCROOT/R.UITest.generated.swift\" --import SWRevealViewController \"$SRCROOT/R.generated.swift\" > \"$SRCROOT/rswift.log\"\n# /Users/tom/Library/Developer/Xcode/DerivedData/R-gzdnepbnjmlzctbxokifiegoyhmk/Build/Products/Debug/rswift generate --generateUITestFile \"$SRCROOT/R.UITest.generated.swift\" --import SWRevealViewController \"$SRCROOT/R.generated.swift\" > \"$SRCROOT/rswift.log\"\n"; + shellScript = "# \"$SRCROOT/../../build/rswift\" generate --generateUITestFile \"$SRCROOT/R.UITest.generated.swift\" --import SWRevealViewController \"$SRCROOT/R.generated.swift\" > \"$SRCROOT/rswift.log\"\n/Users/tom/Projects/R.swift/.build/debug/rswift generate --imports SWRevealViewController \"$SRCROOT/R.generated.swift\" > \"$SRCROOT/rswift.log\"\n/Users/tom/Projects/R.swift/.build/debug/rswift generate \"$SRCROOT/R.UITest.generated.swift\" --generators id\n"; + showEnvVarsInLog = 0; }; ED8FCF67313DC003391627B7 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -981,7 +997,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = ResourceApp/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -999,7 +1015,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = ResourceApp/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1023,6 +1039,7 @@ "$(inherited)", ); INFOPLIST_FILE = ResourceAppTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1042,6 +1059,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = ResourceAppTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -1087,6 +1105,13 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + E2C415CF28EED7890028D537 /* RswiftLibrary */ = { + isa = XCSwiftPackageProductDependency; + productName = RswiftLibrary; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = D55C6CB01B5D757300301B0D /* Project object */; } diff --git a/Examples/RswiftExamples.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/RswiftExamples.xcworkspace/xcshareddata/swiftpm/Package.resolved index 627ca543..4d30055e 100644 --- a/Examples/RswiftExamples.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/RswiftExamples.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,16 +1,41 @@ { - "object": { - "pins": [ - { - "package": "R.swift.Library", - "repositoryURL": "https://github.com/mac-cain13/R.swift.Library.git", - "state": { - "branch": null, - "revision": "5025163d8f60d3c56124c309db0246ba421aa8c6", - "version": "5.3.0" - } + "pins" : [ + { + "identity" : "commander", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kylef/Commander.git", + "state" : { + "revision" : "4a1f2fb82fb6cef613c4a25d2e38f702e4d812c2", + "version" : "0.9.2" } - ] - }, - "version": 1 + }, + { + "identity" : "spectre", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kylef/Spectre.git", + "state" : { + "revision" : "26cc5e9ae0947092c7139ef7ba612e34646086c7", + "version" : "0.10.1" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "9f39744e025c7d377987f30b03770805dcb0bcd1", + "version" : "1.1.4" + } + }, + { + "identity" : "xcodeedit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tomlokhorst/XcodeEdit", + "state" : { + "revision" : "99547c5af5850155b52c43b716ba1b094b02a3b2", + "version" : "2.8.0" + } + } + ], + "version" : 2 } diff --git a/Examples/RswiftUI/RswiftUI.xcodeproj/project.pbxproj b/Examples/RswiftUI/RswiftUI.xcodeproj/project.pbxproj index 81b5a3fa..f0008126 100644 --- a/Examples/RswiftUI/RswiftUI.xcodeproj/project.pbxproj +++ b/Examples/RswiftUI/RswiftUI.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - E223B7AF2510E43300060B16 /* Rswift in Frameworks */ = {isa = PBXBuildFile; productRef = E223B7AE2510E43300060B16 /* Rswift */; }; E243EF952510DF9100DC653F /* RswiftUIApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = E243EF942510DF9100DC653F /* RswiftUIApp.swift */; }; E243EF972510DF9100DC653F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E243EF962510DF9100DC653F /* ContentView.swift */; }; E243EF992510DF9200DC653F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E243EF982510DF9200DC653F /* Assets.xcassets */; }; @@ -19,7 +18,6 @@ E243EFBC2510E08E00DC653F /* RswiftUIAppClip.app in Embed App Clips */ = {isa = PBXBuildFile; fileRef = E243EFAD2510E08D00DC653F /* RswiftUIAppClip.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; E2CD1B072510EFE0000ACEFB /* R.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CD1B062510EFE0000ACEFB /* R.generated.swift */; }; E2CD1B092510EFF8000ACEFB /* R.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CD1B082510EFF8000ACEFB /* R.generated.swift */; }; - E2CD1B0B2510F8A4000ACEFB /* Rswift in Frameworks */ = {isa = PBXBuildFile; productRef = E2CD1B0A2510F8A4000ACEFB /* Rswift */; }; E2CD1B0C2510F900000ACEFB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E243EF982510DF9200DC653F /* Assets.xcassets */; }; /* End PBXBuildFile section */ @@ -71,7 +69,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E223B7AF2510E43300060B16 /* Rswift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -79,7 +76,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E2CD1B0B2510F8A4000ACEFB /* Rswift in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -176,7 +172,6 @@ ); name = RswiftUI; packageProductDependencies = ( - E223B7AE2510E43300060B16 /* Rswift */, ); productName = RswiftUI; productReference = E243EF912510DF9100DC653F /* RswiftUI.app */; @@ -197,7 +192,6 @@ ); name = RswiftUIAppClip; packageProductDependencies = ( - E2CD1B0A2510F8A4000ACEFB /* Rswift */, ); productName = RswiftUIAppClip; productReference = E243EFAD2510E08D00DC653F /* RswiftUIAppClip.app */; @@ -230,7 +224,6 @@ ); mainGroup = E243EF882510DF9100DC653F; packageReferences = ( - E223B7AD2510E43300060B16 /* XCRemoteSwiftPackageReference "R.swift.Library" */, ); productRefGroup = E243EF922510DF9100DC653F /* Products */; projectDirPath = ""; @@ -574,30 +567,6 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ - -/* Begin XCRemoteSwiftPackageReference section */ - E223B7AD2510E43300060B16 /* XCRemoteSwiftPackageReference "R.swift.Library" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/mac-cain13/R.swift.Library.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 5.2.0; - }; - }; -/* End XCRemoteSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - E223B7AE2510E43300060B16 /* Rswift */ = { - isa = XCSwiftPackageProductDependency; - package = E223B7AD2510E43300060B16 /* XCRemoteSwiftPackageReference "R.swift.Library" */; - productName = Rswift; - }; - E2CD1B0A2510F8A4000ACEFB /* Rswift */ = { - isa = XCSwiftPackageProductDependency; - package = E223B7AD2510E43300060B16 /* XCRemoteSwiftPackageReference "R.swift.Library" */; - productName = Rswift; - }; -/* End XCSwiftPackageProductDependency section */ }; rootObject = E243EF892510DF9100DC653F /* Project object */; } diff --git a/Examples/RtvApp/RtvApp.xcodeproj/project.pbxproj b/Examples/RtvApp/RtvApp.xcodeproj/project.pbxproj index 702a779b..f047d845 100644 --- a/Examples/RtvApp/RtvApp.xcodeproj/project.pbxproj +++ b/Examples/RtvApp/RtvApp.xcodeproj/project.pbxproj @@ -13,8 +13,8 @@ DEF5599F1CA4873D009B8C51 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DEF5599D1CA4873D009B8C51 /* Main.storyboard */; }; DEF559A11CA4873D009B8C51 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DEF559A01CA4873D009B8C51 /* Assets.xcassets */; }; DEF559B71CA48DC2009B8C51 /* R.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF559B61CA48DC2009B8C51 /* R.generated.swift */; }; - E223B7B22510E46700060B16 /* Rswift in Frameworks */ = {isa = PBXBuildFile; productRef = E223B7B12510E46700060B16 /* Rswift */; }; E26B761B2451E7D100E1170F /* ImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26B761A2451E7D100E1170F /* ImageTests.swift */; }; + E2C415D428F6E5E00028D537 /* RswiftLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = E2C415D328F6E5E00028D537 /* RswiftLibrary */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -40,6 +40,7 @@ DEF559AF1CA48892009B8C51 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DEF559B61CA48DC2009B8C51 /* R.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = R.generated.swift; sourceTree = ""; }; E26B761A2451E7D100E1170F /* ImageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTests.swift; sourceTree = ""; }; + E2C415D228F6E5C60028D537 /* R.swift */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = R.swift; path = ../..; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -47,7 +48,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E223B7B22510E46700060B16 /* Rswift in Frameworks */, + E2C415D428F6E5E00028D537 /* RswiftLibrary in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -72,6 +73,7 @@ D55C6CAF1B5D757300301B0D = { isa = PBXGroup; children = ( + E2C415D128F6E5C60028D537 /* Packages */, DEF559981CA4873D009B8C51 /* ResourceApp-tvOS */, DEF559AC1CA48892009B8C51 /* ResourceAppTests-tvOS */, D55C6CB91B5D757300301B0D /* Products */, @@ -122,6 +124,14 @@ path = "ResourceAppTests-tvOS"; sourceTree = ""; }; + E2C415D128F6E5C60028D537 /* Packages */ = { + isa = PBXGroup; + children = ( + E2C415D228F6E5C60028D537 /* R.swift */, + ); + name = Packages; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -140,7 +150,7 @@ ); name = "ResourceApp-tvOS"; packageProductDependencies = ( - E223B7B12510E46700060B16 /* Rswift */, + E2C415D328F6E5E00028D537 /* RswiftLibrary */, ); productName = "ResourceApp-tvOS"; productReference = DEF559971CA4873D009B8C51 /* ResourceApp-tvOS.app */; @@ -200,7 +210,6 @@ ); mainGroup = D55C6CAF1B5D757300301B0D; packageReferences = ( - E223B7B02510E46700060B16 /* XCRemoteSwiftPackageReference "R.swift.Library" */, ); productRefGroup = D55C6CB91B5D757300301B0D /* Products */; projectDirPath = ""; @@ -247,7 +256,8 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"$SRCROOT/../../build/Debug/rswift\" generate \"$SRCROOT/ResourceApp-tvOS/R.generated.swift\" > \"$SRCROOT/rswift-tv.log\"\n"; + shellScript = "# \"$SRCROOT/../../build/Debug/rswift\" generate \"$SRCROOT/ResourceApp-tvOS/R.generated.swift\" > \"$SRCROOT/rswift-tv.log\"\n/Users/tom/Projects/R.swift/.build/debug/rswift generate \"$SRCROOT/ResourceApp-tvOS/R.generated.swift\" > \"$SRCROOT/rswift-tv.log\"\n"; + showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -418,6 +428,7 @@ SDKROOT = appletvos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 11.0; }; name = Debug; }; @@ -438,6 +449,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 11.0; }; name = Release; }; @@ -458,6 +470,7 @@ SDKROOT = appletvos; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ResourceApp-tvOS.app/ResourceApp-tvOS"; + TVOS_DEPLOYMENT_TARGET = 11.0; }; name = Debug; }; @@ -479,6 +492,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ResourceApp-tvOS.app/ResourceApp-tvOS"; + TVOS_DEPLOYMENT_TARGET = 11.0; }; name = Release; }; @@ -514,22 +528,10 @@ }; /* End XCConfigurationList section */ -/* Begin XCRemoteSwiftPackageReference section */ - E223B7B02510E46700060B16 /* XCRemoteSwiftPackageReference "R.swift.Library" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/mac-cain13/R.swift.Library.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 5.2.0; - }; - }; -/* End XCRemoteSwiftPackageReference section */ - /* Begin XCSwiftPackageProductDependency section */ - E223B7B12510E46700060B16 /* Rswift */ = { + E2C415D328F6E5E00028D537 /* RswiftLibrary */ = { isa = XCSwiftPackageProductDependency; - package = E223B7B02510E46700060B16 /* XCRemoteSwiftPackageReference "R.swift.Library" */; - productName = Rswift; + productName = RswiftLibrary; }; /* End XCSwiftPackageProductDependency section */ }; diff --git a/Examples/RwatchApp/ResourceApp-watchOS-Extension/InterfaceController.swift b/Examples/RwatchApp/ResourceApp-watchOS-Extension/InterfaceController.swift index dcdfcc50..3cfc178b 100644 --- a/Examples/RwatchApp/ResourceApp-watchOS-Extension/InterfaceController.swift +++ b/Examples/RwatchApp/ResourceApp-watchOS-Extension/InterfaceController.swift @@ -11,6 +11,8 @@ import Foundation import Rswift + + class InterfaceController: WKInterfaceController { @IBOutlet weak var label: WKInterfaceLabel! @@ -19,7 +21,7 @@ class InterfaceController: WKInterfaceController { super.awake(withContext: context) label.setText(R.string.localizable.quote(11)) - label.setTextColor(R.color.myColor()) + label.setTextColor(UIColor(named: R.color.myColor.name)) } override func willActivate() { diff --git a/Examples/RwatchApp/RwatchApp.xcodeproj/project.pbxproj b/Examples/RwatchApp/RwatchApp.xcodeproj/project.pbxproj index 3604869d..e5ab1115 100644 --- a/Examples/RwatchApp/RwatchApp.xcodeproj/project.pbxproj +++ b/Examples/RwatchApp/RwatchApp.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ 2F5FBC5D21355F8A00A83A69 /* R.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F5FBC5C21355F8A00A83A69 /* R.generated.swift */; }; 2F5FBC622135665000A83A69 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2F5FBC642135665000A83A69 /* Localizable.strings */; }; 42A84FB2DF3030B3B1476BA0 /* Pods_ResourceApp_watchOS_Extension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB9FAA28BAEEE5C813ABC9FD /* Pods_ResourceApp_watchOS_Extension.framework */; }; + E2C415D828F6F9A90028D537 /* RswiftLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = E2C415D728F6F9A90028D537 /* RswiftLibrary */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -62,6 +63,7 @@ 503417A0BA42879EF1F80A75 /* Pods-ResourceApp-watchOS-Extension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ResourceApp-watchOS-Extension.release.xcconfig"; path = "Target Support Files/Pods-ResourceApp-watchOS-Extension/Pods-ResourceApp-watchOS-Extension.release.xcconfig"; sourceTree = ""; }; C5E806140ACB19BACD937511 /* Pods-ResourceApp-watchOS-Extension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ResourceApp-watchOS-Extension.debug.xcconfig"; path = "Target Support Files/Pods-ResourceApp-watchOS-Extension/Pods-ResourceApp-watchOS-Extension.debug.xcconfig"; sourceTree = ""; }; D5B799881C1B8F0C009EA901 /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = System/Library/Frameworks/AVKit.framework; sourceTree = SDKROOT; }; + E2C415D628F6F99A0028D537 /* R.swift */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = R.swift; path = ../..; sourceTree = ""; }; FB9FAA28BAEEE5C813ABC9FD /* Pods_ResourceApp_watchOS_Extension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ResourceApp_watchOS_Extension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -78,6 +80,7 @@ buildActionMask = 2147483647; files = ( 42A84FB2DF3030B3B1476BA0 /* Pods_ResourceApp_watchOS_Extension.framework in Frameworks */, + E2C415D828F6F9A90028D537 /* RswiftLibrary in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -139,6 +142,7 @@ D55C6CAF1B5D757300301B0D = { isa = PBXGroup; children = ( + E2C415D528F6F99A0028D537 /* Packages */, 2F5FBC162135561200A83A69 /* ResourceApp-watchOS */, 2F5FBC252135561300A83A69 /* ResourceApp-watchOS-Extension */, D55C6CB91B5D757300301B0D /* Products */, @@ -159,6 +163,14 @@ name = Products; sourceTree = ""; }; + E2C415D528F6F99A0028D537 /* Packages */ = { + isa = PBXGroup; + children = ( + E2C415D628F6F99A0028D537 /* R.swift */, + ); + name = Packages; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -196,6 +208,9 @@ dependencies = ( ); name = "ResourceApp-watchOS-Extension"; + packageProductDependencies = ( + E2C415D728F6F9A90028D537 /* RswiftLibrary */, + ); productName = "ResourceApp-watchOS Extension"; productReference = 2F5FBC212135561300A83A69 /* ResourceApp-watchOS-Extension.appex */; productType = "com.apple.product-type.watchkit2-extension"; @@ -306,7 +321,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"$SRCROOT/../../build/debug/rswift\" generate \"$SRCROOT/ResourceApp-watchOS-Extension/R.generated.swift\" > \"$SRCROOT/rswift-watchos.log\"\n"; + shellScript = "# \"$SRCROOT/../../build/debug/rswift\" generate \"$SRCROOT/ResourceApp-watchOS-Extension/R.generated.swift\" > \"$SRCROOT/rswift-watchos.log\"\n/Users/tom/Projects/R.swift/.build/debug/rswift generate \"$SRCROOT/ResourceApp-watchOS-Extension/R.generated.swift\" > \"$SRCROOT/rswift-watchos.log\" \n"; }; F052DE37060C47522CB0DCDD /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; @@ -637,6 +652,13 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + E2C415D728F6F9A90028D537 /* RswiftLibrary */ = { + isa = XCSwiftPackageProductDependency; + productName = RswiftLibrary; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = D55C6CB01B5D757300301B0D /* Project object */; } From a643c836e6e53b8f53aedc472a7da9264900348a Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Wed, 12 Oct 2022 22:10:04 +0200 Subject: [PATCH 081/161] Update generated warnings --- .../ResourceAppTests/ResourceAppTests.swift | 8 ++++---- Sources/RswiftCore/RswiftCore.swift | 5 +++-- .../AssetCatalog+Generator.swift | 17 ++++++++++++----- Sources/RswiftGenerators/Nib+Generator.swift | 2 +- .../Shared/SwiftIdentifier.swift | 1 - .../RswiftGenerators/Storyboard+Generator.swift | 4 ++-- 6 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Examples/ResourceApp/ResourceAppTests/ResourceAppTests.swift b/Examples/ResourceApp/ResourceAppTests/ResourceAppTests.swift index ea4a589c..94c895c8 100644 --- a/Examples/ResourceApp/ResourceAppTests/ResourceAppTests.swift +++ b/Examples/ResourceApp/ResourceAppTests/ResourceAppTests.swift @@ -21,16 +21,16 @@ class ResourceAppTests: XCTestCase { warning: [R.swift] Missing reference 'first' in 'incorrect in dutch' 'Settings.stringsdict' (nl) warning: [R.swift] Skipping 2 images because symbol 'second' would be generated for all of these images: Second, second warning: [R.swift] Skipping 2 images because symbol 'theAppIcon' would be generated for all of these images: The App Icon, TheAppIcon - warning: [R.swift] Skipping asset subfolder because symbol 'conflicting' would conflict with image: conflicting - warning: [R.swift] Skipping 2 images because symbol 'second' would be generated for all of these images: Second, Second + warning: [R.swift] Skipping asset namespace 'conflicting' because symbol 'conflicting' would conflict with image: conflicting + warning: [R.swift] Skipping 2 images namespace because symbol 'second' would be generated for all of these images: Second, Second warning: [R.swift] Skipping 2 colors because symbol 'myRed' would be generated for all of these colors: My Red, My Red warning: [R.swift] Destination view controller with id Zbd-89-K73 for segue toUnknown in FirstViewController not found in storyboard References. Is this storyboard corrupt? warning: [R.swift] Skipping 2 segues for 'SecondViewController' because symbol 'toFirst' would be generated for all of these segues: ToFirst, toFirst warning: [R.swift] Skipping 2 storyboards because symbol 'duplicate' would be generated for all of these files: Duplicate, duplicate warning: [R.swift] Skipping 2 view controllers because symbol 'validationClash' would be generated for all of these view controller identifiers: Validation clash, ValidationClash warning: [R.swift] Skipping 2 xibs because symbol 'duplicate' would be generated for all of these files: Duplicate, duplicate - warning: [R.swift] Skipping 2 reuseIdentifiers because symbol 'duplicateCellView' would be generated for all of these reuseIdentifiers: DuplicateCellView, duplicateCellView - warning: [R.swift] Skipping 1 reuseIdentifier because no swift identifier can be generated for reuseIdentifier: ' ' + warning: [R.swift] Skipping 2 reuseIdentifiers because symbol 'duplicateCellView' would be generated for all of these reuse identifiers: DuplicateCellView, duplicateCellView + warning: [R.swift] Skipping 1 reuseIdentifier because no swift identifier can be generated for reuse identifier: ' ' warning: [R.swift] Skipping 2 resource files because symbol 'duplicateJson' would be generated for all of these files: Duplicate.json, duplicateJson warning: [R.swift] Skipping 2 strings files because symbol 'duplicate' would be generated for all of these files: Duplicate, Duplicate# warning: [R.swift] Skipping 1 strings file because no swift identifier can be generated for file: '@@' diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index faf7f393..7043e08f 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -330,7 +330,8 @@ public struct RswiftCore { print("}") */ - print("TOTAL", Date().timeIntervalSince(start)) - print() + let _ = Date().timeIntervalSince(start) +// print("TOTAL", Date().timeIntervalSince(start)) +// print() } } diff --git a/Sources/RswiftGenerators/AssetCatalog+Generator.swift b/Sources/RswiftGenerators/AssetCatalog+Generator.swift index 15b99c3c..d70db2f6 100644 --- a/Sources/RswiftGenerators/AssetCatalog+Generator.swift +++ b/Sources/RswiftGenerators/AssetCatalog+Generator.swift @@ -17,7 +17,7 @@ extension ColorResource { public static func generateStruct(catalogs: [AssetCatalog], prefix: SwiftIdentifier) -> Struct { let merged: AssetCatalog.Namespace = catalogs.map(\.root).reduce(.init(), { $0.merging($1) }) - return merged.generateStruct(resourceName: "color", resourcesSelector: { $0.colors }, prefix: prefix) + return merged.generateStruct(name: "color", resourcesSelector: { $0.colors }, prefix: prefix) } } @@ -25,7 +25,7 @@ extension DataResource { public static func generateStruct(catalogs: [AssetCatalog], prefix: SwiftIdentifier) -> Struct { let merged: AssetCatalog.Namespace = catalogs.map(\.root).reduce(.init(), { $0.merging($1) }) - return merged.generateStruct(resourceName: "data", resourcesSelector: { $0.dataAssets }, prefix: prefix) + return merged.generateStruct(name: "data", resourcesSelector: { $0.dataAssets }, prefix: prefix) } } @@ -39,19 +39,24 @@ extension ImageResource { var merged: AssetCatalog.Namespace = catalogs.map(\.root).reduce(.init(), { $0.merging($1) }) merged.images += namedResources - return merged.generateStruct(resourceName: "image", resourcesSelector: { $0.images }, prefix: prefix) + return merged.generateStruct(name: "image", resourcesSelector: { $0.images }, prefix: prefix) } } extension AssetCatalog.Namespace { - public func generateStruct(resourceName: String, resourcesSelector: (Self) -> [AssetCatalogContent], prefix: SwiftIdentifier) -> Struct { + public func generateStruct(name: String, resourcesSelector: (Self) -> [AssetCatalogContent], prefix: SwiftIdentifier) -> Struct { + generateStruct(resourceName: name, source: name, path: [], resourcesSelector: resourcesSelector, prefix: prefix) + } + + private func generateStruct(resourceName: String, source: String, path: [String], resourcesSelector: (Self) -> [AssetCatalogContent], prefix: SwiftIdentifier) -> Struct { let structName = SwiftIdentifier(name: resourceName) let qualifiedName = prefix + structName let warning: (String) -> Void = { print("warning: [R.swift]", $0) } + let container = path.isEmpty ? nil : path.joined(separator: "/") let allResources = resourcesSelector(self) let groupedResources = allResources.grouped(bySwiftIdentifier: { $0.name }) - groupedResources.reportWarningsForDuplicatesAndEmpties(source: resourceName, result: resourceName, warning: warning) + groupedResources.reportWarningsForDuplicatesAndEmpties(source: source, container: container, result: source, warning: warning) let vargetters = groupedResources.uniques.map { $0.generateVarGetter() } let otherIdentifiers = groupedResources.uniques.map { SwiftIdentifier(name: $0.name) } @@ -64,6 +69,8 @@ extension AssetCatalog.Namespace { .map { (name, namespace) in namespace.generateStruct( resourceName: name.value, + source: source, + path: path + [name.value], resourcesSelector: resourcesSelector, prefix: qualifiedName ) diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift index 588df61d..1357cd96 100644 --- a/Sources/RswiftGenerators/Nib+Generator.swift +++ b/Sources/RswiftGenerators/Nib+Generator.swift @@ -19,7 +19,7 @@ extension NibResource { // let firstLocales = Dictionary(grouping: nibs, by: \.name) // .values.map(\.first!) let groupedNibs = nibs.grouped(bySwiftIdentifier: \.name) - groupedNibs.reportWarningsForDuplicatesAndEmpties(source: "nib", result: "nib", warning: warning) + groupedNibs.reportWarningsForDuplicatesAndEmpties(source: "xib", result: "file", warning: warning) let vargetters = groupedNibs.uniques .map { $0.generateVarGetter() } diff --git a/Sources/RswiftGenerators/Shared/SwiftIdentifier.swift b/Sources/RswiftGenerators/Shared/SwiftIdentifier.swift index 58c99338..efdb2702 100644 --- a/Sources/RswiftGenerators/Shared/SwiftIdentifier.swift +++ b/Sources/RswiftGenerators/Shared/SwiftIdentifier.swift @@ -76,7 +76,6 @@ struct SwiftNameGroups { // source: "segue", container: "for MyViewController", result: "segue" // "Skipping 2 segues for MyViewController, because ... for all these segues" func reportWarningsForDuplicatesAndEmpties(source: String, container: String? = nil, result: String, warning: (String) -> Void) { - let sourceSingular = [source, container].compactMap { $0 }.joined(separator: " ") let sourcePlural = ["\(source)s", container].compactMap { $0 }.joined(separator: " ") diff --git a/Sources/RswiftGenerators/Storyboard+Generator.swift b/Sources/RswiftGenerators/Storyboard+Generator.swift index 675d50df..d30671d2 100644 --- a/Sources/RswiftGenerators/Storyboard+Generator.swift +++ b/Sources/RswiftGenerators/Storyboard+Generator.swift @@ -16,7 +16,7 @@ extension StoryboardResource { let warning: (String) -> Void = { print("warning: [R.swift]", $0) } let groupedStoryboards = storyboards.grouped(bySwiftIdentifier: { $0.name }) - groupedStoryboards.reportWarningsForDuplicatesAndEmpties(source: "storyboard", result: "storyboard", warning: warning) + groupedStoryboards.reportWarningsForDuplicatesAndEmpties(source: "storyboard", result: "file", warning: warning) let structs = groupedStoryboards.uniques .map { $0.generateStruct(prefix: qualifiedName, warning: warning) } @@ -123,7 +123,7 @@ extension StoryboardResource { let validateColorLines = self.usedColorResources.uniqueAndSorted() .filter { !$0.isSystemCatalog } .map { nameCatalog in - "if UIKit.UIColor(named: \"\(nameCatalog.name)\", in: _bundle, compatibleWith: nil) == nil { throw RswiftResources.ValidationError(\"[R.swift] Color named '\(nameCatalog.name)' is used in nib '\(self.name)', but couldn't be loaded.\") }" + "if UIKit.UIColor(named: \"\(nameCatalog.name)\", in: _bundle, compatibleWith: nil) == nil { throw RswiftResources.ValidationError(\"[R.swift] Color named '\(nameCatalog.name)' is used in storyboard '\(self.name)', but couldn't be loaded.\") }" } let validateViewControllersLines = viewControllers .compactMap { vc -> String? in From 11486b4e770207d591c79d0fe368a5eff5faebbd Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Wed, 12 Oct 2022 22:30:41 +0200 Subject: [PATCH 082/161] Log unify warning --- .../Resources/LocalizableStrings+Parser.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Sources/RswiftParsers/Resources/LocalizableStrings+Parser.swift b/Sources/RswiftParsers/Resources/LocalizableStrings+Parser.swift index 2a0640b0..9382c891 100644 --- a/Sources/RswiftParsers/Resources/LocalizableStrings+Parser.swift +++ b/Sources/RswiftParsers/Resources/LocalizableStrings+Parser.swift @@ -14,6 +14,8 @@ extension LocalizableStrings: SupportedExtensions { static public let supportedExtensions: Set = ["strings", "stringsdict"] static public func parse(url: URL) throws -> LocalizableStrings { + let warning: (String) -> Void = { print("warning: [R.swift]", $0) } + guard let basename = url.filenameWithoutExtension else { throw ResourceParsingError("Couldn't extract filename from URL: \(url)") } @@ -32,7 +34,7 @@ extension LocalizableStrings: SupportedExtensions { case "strings": dictionary = try parseStrings(nsDictionary, source: locale.debugDescription(filename: "\(basename).strings")) case "stringsdict": - dictionary = try parseStringsdict(nsDictionary, source: locale.debugDescription(filename: "\(basename).stringsdict")) + dictionary = try parseStringsdict(nsDictionary, source: locale.debugDescription(filename: "\(basename).stringsdict"), warning: warning) default: throw ResourceParsingError("File could not be parsed as a strings file: \(url.absoluteString)") } @@ -72,7 +74,7 @@ private func parseStrings(_ nsDictionary: NSDictionary, source: String) throws - return dictionary } -private func parseStringsdict(_ nsDictionary: NSDictionary, source: String) throws -> [LocalizableStrings.Key: LocalizableStrings.Value] { +private func parseStringsdict(_ nsDictionary: NSDictionary, source: String, warning: (String) -> Void) throws -> [LocalizableStrings.Key: LocalizableStrings.Value] { var dictionary: [LocalizableStrings.Key: LocalizableStrings.Value] = [:] for (key, obj) in nsDictionary { @@ -88,8 +90,7 @@ private func parseStringsdict(_ nsDictionary: NSDictionary, source: String) thro let params = try parseStringsdictParams(localizedFormat, dict: dict) dictionary[key] = .init(params: params, originalValue: localizedFormat) } catch let error as ResourceParsingError { - // TODO: Log warning -// warn("\(error) in '\(key)' \(source)") + warning("\(error.description) in '\(key)' \(source)") } } else { From 975ff7fd2ae4d916ab05631d9a7e99d2c081c308 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 14 Oct 2022 14:21:48 +0200 Subject: [PATCH 083/161] Only generate inits for public --- .../AssetCatalog+Generator.swift | 6 +- .../FileResource+Generator.swift | 2 +- .../LocalizableStrings+Generator.swift | 16 ++--- Sources/RswiftGenerators/Nib+Generator.swift | 12 ++-- .../PropertyListResource+Generator.swift | 2 +- .../Storyboard+Generator.swift | 14 ++-- .../RswiftGenerators/SwiftSyntax/Struct.swift | 65 +++++++++---------- 7 files changed, 57 insertions(+), 60 deletions(-) diff --git a/Sources/RswiftGenerators/AssetCatalog+Generator.swift b/Sources/RswiftGenerators/AssetCatalog+Generator.swift index d70db2f6..a4363165 100644 --- a/Sources/RswiftGenerators/AssetCatalog+Generator.swift +++ b/Sources/RswiftGenerators/AssetCatalog+Generator.swift @@ -100,7 +100,7 @@ extension AssetCatalog.Namespace { extension ColorResource: AssetCatalogContent { public func generateVarGetter() -> VarGetter { let fullname = (path + [name]).joined(separator: "/") - let code = ".init(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: _bundle)" + let code = ".init(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: bundle)" return VarGetter( comments: ["Color `\(fullname)`."], name: SwiftIdentifier(name: name), @@ -114,7 +114,7 @@ extension DataResource: AssetCatalogContent { public func generateVarGetter() -> VarGetter { let fullname = (path + [name]).joined(separator: "/") let odrt = onDemandResourceTags?.debugDescription ?? "nil" - let code = ".init(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: _bundle, onDemandResourceTags: \(odrt))" + let code = ".init(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: bundle, onDemandResourceTags: \(odrt))" return VarGetter( comments: ["Data asset `\(fullname)`."], name: SwiftIdentifier(name: name), @@ -129,7 +129,7 @@ extension ImageResource: AssetCatalogContent { let locs = locale.map { $0.codeString() } ?? "nil" let odrt = onDemandResourceTags?.debugDescription ?? "nil" let fullname = (path + [name]).joined(separator: "/") - let code = ".init(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: _bundle, locale: \(locs), onDemandResourceTags: \(odrt))" + let code = ".init(name: \"\(fullname.escapedStringLiteral)\", path: \(path), bundle: bundle, locale: \(locs), onDemandResourceTags: \(odrt))" return VarGetter( comments: ["Image `\(fullname)`."], name: SwiftIdentifier(name: name), diff --git a/Sources/RswiftGenerators/FileResource+Generator.swift b/Sources/RswiftGenerators/FileResource+Generator.swift index 3f44c27b..af6f7d90 100644 --- a/Sources/RswiftGenerators/FileResource+Generator.swift +++ b/Sources/RswiftGenerators/FileResource+Generator.swift @@ -39,7 +39,7 @@ extension FileResource { comments: ["Resource file `\(filename)`."], name: SwiftIdentifier(name: filename), typeReference: TypeReference(module: .rswiftResources, rawName: "FileResource"), - valueCodeString: ".init(name: \"\(name.escapedStringLiteral)\", pathExtension: \"\(pathExtension.escapedStringLiteral)\", bundle: _bundle, locale: \(locale?.codeString() ?? "nil"))" + valueCodeString: ".init(name: \"\(name.escapedStringLiteral)\", pathExtension: \"\(pathExtension.escapedStringLiteral)\", bundle: bundle, locale: \(locale?.codeString() ?? "nil"))" ) } } diff --git a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift index 2eb5e3df..8dd07c40 100644 --- a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift +++ b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift @@ -34,6 +34,7 @@ extension LocalizableStrings { return Struct(comments: comments, name: structName) { Init.bundle + for name in groupedLocalized.uniques.map(\.0) { generateBundleLocaleVarGetter(name: SwiftIdentifier(name: name)) generateBundleLocaleFunction(name: SwiftIdentifier(name: name)) @@ -65,7 +66,7 @@ extension LocalizableStrings { VarGetter( name: name, typeReference: TypeReference(module: .host, rawName: name.value), - valueCodeString: ".init(bundle: _bundle, locale: _bundle.firstPreferredLocale)" + valueCodeString: ".init(bundle: bundle, locale: bundle.firstPreferredLocale)" ) } @@ -91,11 +92,8 @@ extension LocalizableStrings { ], returnType: TypeReference(module: .host, rawName: name.value), valueCodeString: """ - if let (bundle, locale) = _bundle.firstBundleAndLocale(tableName: "\(tableName.escapedStringLiteral)", preferredLanguages: preferredLanguages) { - return .init(bundle: bundle, locale: locale) - } else { - return .init(bundle: _bundle, locale: _bundle.firstPreferredLocale) - } + let (bundle, locale) = bundle.firstBundleAndLocale(tableName: "\(tableName.escapedStringLiteral)", preferredLanguages: preferredLanguages) ?? (bundle, bundle.firstPreferredLocale) + return .init(bundle: bundle, locale: locale) """ ) } @@ -270,15 +268,15 @@ private struct StringWithParams { private var varValueCodeString: String { - #".init(key: "\#(key.escapedStringLiteral)", tableName: "\#(tableName)", bundle: _bundle, locale: _locale, defaultValue: "\#(fallbackValue.escapedStringLiteral)", comment: nil)"# + #".init(key: "\#(key.escapedStringLiteral)", tableName: "\#(tableName)", bundle: bundle, locale: locale, defaultValue: "\#(fallbackValue.escapedStringLiteral)", comment: nil)"# } private var funcBodyCodeString: String { let ps = params.indices.map { "value\($0 + 1)" } - let args = ["format: format", "locale: _locale"] + ps + let args = ["format: format", "locale: locale"] + ps return """ - let format = NSLocalizedString("\(key.escapedStringLiteral)", tableName: "\(tableName)", bundle: _bundle, value: "\(fallbackValue.escapedStringLiteral)", comment: "") + let format = NSLocalizedString("\(key.escapedStringLiteral)", tableName: "\(tableName)", bundle: bundle, value: "\(fallbackValue.escapedStringLiteral)", comment: "") return String(\(args.joined(separator: ", "))) """ } diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift index 1357cd96..5cf4626a 100644 --- a/Sources/RswiftGenerators/Nib+Generator.swift +++ b/Sources/RswiftGenerators/Nib+Generator.swift @@ -32,7 +32,9 @@ extension NibResource { vargetters - generateValidate(nibs: groupedNibs.uniques) + if groupedNibs.uniques.count > 0 { + generateValidate(nibs: groupedNibs.uniques) + } } } @@ -68,7 +70,7 @@ extension NibResource { comments: ["Nib `\(name)`."], name: SwiftIdentifier(name: name), typeReference: typeReference, - valueCodeString: ".init(name: \"\(name.escapedStringLiteral)\", bundle: _bundle, identifier: \"\(reusable.identifier.escapedStringLiteral)\")" + valueCodeString: ".init(name: \"\(name.escapedStringLiteral)\", bundle: bundle, identifier: \"\(reusable.identifier.escapedStringLiteral)\")" ) } else { let typeReference = TypeReference( @@ -80,7 +82,7 @@ extension NibResource { comments: ["Nib `\(name)`."], name: SwiftIdentifier(name: name), typeReference: typeReference, - valueCodeString: ".init(name: \"\(name.escapedStringLiteral)\", bundle: _bundle)" + valueCodeString: ".init(name: \"\(name.escapedStringLiteral)\", bundle: bundle)" ) } } @@ -91,14 +93,14 @@ extension NibResource { if nameCatalog.isSystemCatalog { return "if #available(iOS 13.0, *) { if UIKit.UIImage(systemName: \"\(nameCatalog.name)\") == nil { throw RswiftResources.ValidationError(\"[R.swift] System image named '\(nameCatalog.name)' is used in nib '\(self.name)', but couldn't be loaded.\") } }" } else { - return "if UIKit.UIImage(named: \"\(nameCatalog.name)\", in: _bundle, compatibleWith: nil) == nil { throw RswiftResources.ValidationError(\"[R.swift] Image named '\(nameCatalog.name)' is used in nib '\(self.name)', but couldn't be loaded.\") }" + return "if UIKit.UIImage(named: \"\(nameCatalog.name)\", in: bundle, compatibleWith: nil) == nil { throw RswiftResources.ValidationError(\"[R.swift] Image named '\(nameCatalog.name)' is used in nib '\(self.name)', but couldn't be loaded.\") }" } } let validateColorLines = self.usedColorResources.uniqueAndSorted() .filter { !$0.isSystemCatalog } .map { nameCatalog in - "if UIKit.UIColor(named: \"\(nameCatalog.name)\", in: _bundle, compatibleWith: nil) == nil { throw RswiftResources.ValidationError(\"[R.swift] Color named '\(nameCatalog.name)' is used in nib '\(self.name)', but couldn't be loaded.\") }" + "if UIKit.UIColor(named: \"\(nameCatalog.name)\", in: bundle, compatibleWith: nil) == nil { throw RswiftResources.ValidationError(\"[R.swift] Color named '\(nameCatalog.name)' is used in nib '\(self.name)', but couldn't be loaded.\") }" } diff --git a/Sources/RswiftGenerators/PropertyListResource+Generator.swift b/Sources/RswiftGenerators/PropertyListResource+Generator.swift index 8f7d84c7..74be8b93 100644 --- a/Sources/RswiftGenerators/PropertyListResource+Generator.swift +++ b/Sources/RswiftGenerators/PropertyListResource+Generator.swift @@ -73,7 +73,7 @@ extension PropertyListResource.Contents { VarGetter( name: SwiftIdentifier(name: key), typeReference: .string, - valueCodeString: "_bundle.infoDictionaryString(path: \(path), key: \"\(key.escapedStringLiteral)\") ?? \"\(value.escapedStringLiteral)\"" + valueCodeString: "bundle.infoDictionaryString(path: \(path), key: \"\(key.escapedStringLiteral)\") ?? \"\(value.escapedStringLiteral)\"" ) } else { LetBinding( diff --git a/Sources/RswiftGenerators/Storyboard+Generator.swift b/Sources/RswiftGenerators/Storyboard+Generator.swift index d30671d2..8d3be875 100644 --- a/Sources/RswiftGenerators/Storyboard+Generator.swift +++ b/Sources/RswiftGenerators/Storyboard+Generator.swift @@ -34,7 +34,9 @@ extension StoryboardResource { structs - generateValidate(names: structs.map(\.name)) + if structs.count > 0 { + generateValidate(names: structs.map(\.name)) + } } } @@ -84,10 +86,6 @@ extension StoryboardResource { let letName = LetBinding( name: nameIdentifier, valueCodeString: "\"\(name)\"") - let varBundle = VarGetter( - name: bundleIdentifier, - typeReference: TypeReference.bundle, - valueCodeString: "_bundle") let identifier = SwiftIdentifier(name: name) let storyboardReference = TypeReference(module: .rswiftResources, rawName: "StoryboardReference") @@ -102,7 +100,7 @@ extension StoryboardResource { TypeAlias(name: "InitialController", value: initialViewController.type) } Init.bundle - varBundle + letName vargetters @@ -117,13 +115,13 @@ extension StoryboardResource { if nameCatalog.isSystemCatalog { return "if #available(iOS 13.0, *) { if UIKit.UIImage(systemName: \"\(nameCatalog.name)\") == nil { throw RswiftResources.ValidationError(\"[R.swift] System image named '\(nameCatalog.name)' is used in storyboard '\(self.name)', but couldn't be loaded.\") } }" } else { - return "if UIKit.UIImage(named: \"\(nameCatalog.name)\", in: _bundle, compatibleWith: nil) == nil { throw RswiftResources.ValidationError(\"[R.swift] Image named '\(nameCatalog.name)' is used in storyboard '\(self.name)', but couldn't be loaded.\") }" + return "if UIKit.UIImage(named: \"\(nameCatalog.name)\", in: bundle, compatibleWith: nil) == nil { throw RswiftResources.ValidationError(\"[R.swift] Image named '\(nameCatalog.name)' is used in storyboard '\(self.name)', but couldn't be loaded.\") }" } } let validateColorLines = self.usedColorResources.uniqueAndSorted() .filter { !$0.isSystemCatalog } .map { nameCatalog in - "if UIKit.UIColor(named: \"\(nameCatalog.name)\", in: _bundle, compatibleWith: nil) == nil { throw RswiftResources.ValidationError(\"[R.swift] Color named '\(nameCatalog.name)' is used in storyboard '\(self.name)', but couldn't be loaded.\") }" + "if UIKit.UIColor(named: \"\(nameCatalog.name)\", in: bundle, compatibleWith: nil) == nil { throw RswiftResources.ValidationError(\"[R.swift] Color named '\(nameCatalog.name)' is used in storyboard '\(self.name)', but couldn't be loaded.\") }" } let validateViewControllersLines = viewControllers .compactMap { vc -> String? in diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index cd367b8d..c862a54d 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -222,17 +222,6 @@ public struct Init { return result } - - var privateLetCodeString: String { - let words: [String] = [ - "private", - "let", - "\(localName ?? name):", - typeReference.codeString() - ] - - return words.joined(separator: " ") - } } public var allModuleReferences: Set { @@ -242,32 +231,40 @@ public struct Init { func render(_ pp: inout PrettyPrinter) { for param in params { - pp.append(line: param.privateLetCodeString) + let words: [String?] = [ + accessControl.code(), + "let", + "\(param.localName ?? param.name):", + param.typeReference.codeString() + ] + pp.append(words: words) } - for c in comments { - pp.append(words: ["///", c == "" ? nil : c]) - } + if accessControl != .none { + for c in comments { + pp.append(words: ["///", c == "" ? nil : c]) + } - let prs = params.map { $0.codeString() }.joined(separator: ", ") - let words: [String?] = [ - accessControl.code(), - "init(\(prs))", - "{" - ] + let prs = params.map { $0.codeString() }.joined(separator: ", ") + let words: [String?] = [ + accessControl.code(), + "init(\(prs))", + "{" + ] - pp.append(words: words) - pp.indented { pp in - pp.append(line: valueCodeString) + pp.append(words: words) + pp.indented { pp in + pp.append(line: valueCodeString) + } + pp.append(line: "}") } - pp.append(line: "}") } public static var bundle: Init { Init( comments: [], - params: [Parameter(name: "bundle", localName: "_bundle", typeReference: .bundle, defaultValue: nil),], - valueCodeString: "self._bundle = _bundle" + params: [Parameter(name: "bundle", localName: nil, typeReference: .bundle, defaultValue: nil),], + valueCodeString: "self.bundle = bundle" ) } @@ -275,12 +272,12 @@ public struct Init { Init( comments: [], params: [ - Parameter(name: "bundle", localName: "_bundle", typeReference: .bundle, defaultValue: nil), - Parameter(name: "locale", localName: "_locale", typeReference: .locale, defaultValue: nil), + Parameter(name: "bundle", localName: nil, typeReference: .bundle, defaultValue: nil), + Parameter(name: "locale", localName: nil, typeReference: .locale, defaultValue: nil), ], valueCodeString: """ - self._bundle = _bundle - self._locale = _locale + self.bundle = bundle + self.locale = locale """ ) } @@ -516,7 +513,9 @@ struct PrettyPrinter { func render() -> String { let ls = lines.map { (indent, line) in - String(repeating: " ", count: indent) + line + line.isEmpty + ? line + : String(repeating: " ", count: indent) + line } return ls.joined(separator: "\n") } @@ -535,7 +534,7 @@ extension Struct { VarGetter( name: SwiftIdentifier(name: name), typeReference: TypeReference(module: .host, rawName: self.name.value), - valueCodeString: ".init(bundle: _bundle)" + valueCodeString: ".init(bundle: bundle)" ) } From d426b375c7ecc58767a887ac4f672948ef1f5c56 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 14 Oct 2022 23:53:48 +0200 Subject: [PATCH 084/161] Unify localizations of nibs --- Sources/RswiftGenerators/Nib+Generator.swift | 67 ++++++++++++++++++-- Sources/RswiftResources/NibResource.swift | 6 +- 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift index 5cf4626a..4cbc7d75 100644 --- a/Sources/RswiftGenerators/Nib+Generator.swift +++ b/Sources/RswiftGenerators/Nib+Generator.swift @@ -15,10 +15,10 @@ extension NibResource { let warning: (String) -> Void = { print("warning: [R.swift]", $0) } - // TODO: Generate warnings for mismatched identifier/root view in different locales -// let firstLocales = Dictionary(grouping: nibs, by: \.name) -// .values.map(\.first!) - let groupedNibs = nibs.grouped(bySwiftIdentifier: \.name) + // Unify different localizations of nibs + let unifiedNibs = unify(nibs: nibs, warning: warning) + + let groupedNibs = unifiedNibs.grouped(bySwiftIdentifier: \.name) groupedNibs.reportWarningsForDuplicatesAndEmpties(source: "xib", result: "file", warning: warning) let vargetters = groupedNibs.uniques @@ -48,9 +48,68 @@ extension NibResource { valueCodeString: nibs.flatMap { $0.generateValidateLines() }.joined(separator: "\n") ) } + + private static func unify(nibs: [NibResource], warning: (String) -> Void) -> [NibResource] { + var result: [NibResource] = [] + + for siblings in Dictionary(grouping: nibs, by: \.name).values { + guard let merged = unify(siblings: siblings, warning: warning) else { continue } + result.append(merged) + } + + return result + } + + private static func unify(siblings: [NibResource], warning: (String) -> Void) -> NibResource? { + guard var result = siblings.first else { return nil } + var warnings: [String] = [] + + for nib in siblings { + let (merged, fields, warns) = result.unify(nib) + warnings.append(contentsOf: warns) + + guard let merged else { + let locales = "\(result.locale.localeDescription ?? "-") and \(nib.locale.localeDescription ?? "-")" + if let fields { + warning("Skipping generation of nib '\(nib.name)', because \(fields) don't match in localizations \(locales)") + } else { + warning("Skipping generation of nib '\(nib.name)', because localizations \(locales) are different from each other") + } + return nil + } + result = merged + } + + // Only report warnings, if all siblings merged successfully + for w in warnings { + warning(w) + } + + return result + } } extension NibResource { + func unify(_ other: NibResource) -> (NibResource?, String?, [String]) { + if rootViews.first != other.rootViews.first { return (nil, "root views", []) } + if reusables.first != other.reusables.first { return (nil, "reuseIdentifiers", []) } + if name != other.name { return (nil, "names", []) } + + let diffImages = Set(self.usedImageIdentifiers).symmetricDifference(other.usedImageIdentifiers) + let diffColors = Set(self.usedColorResources).symmetricDifference(other.usedColorResources) + + let warnings: [String?] = [ + diffImages.count > 0 ? "Skipping validation of \(diffImages.count) images in `validate` function, because these don't exist in all localizations: \(diffImages.map(\.name).joined(separator: ", "))" : nil, + diffColors.count > 0 ? "Skipping validation of \(diffColors.count) colors in `validate` function, because these don't exist in all localizations: \(diffColors.map(\.name).joined(separator: ", "))" : nil + ] + + var result = self + result.usedImageIdentifiers = Array(Set(self.usedImageIdentifiers).union(other.usedImageIdentifiers)) + result.usedColorResources = Array(Set(self.usedColorResources).union(other.usedColorResources)) + + return (result, nil, warnings.compactMap { $0 }) + } + var genericTypeReference: TypeReference { TypeReference( module: .rswiftResources, diff --git a/Sources/RswiftResources/NibResource.swift b/Sources/RswiftResources/NibResource.swift index 7b615f9b..6b08a4a2 100644 --- a/Sources/RswiftResources/NibResource.swift +++ b/Sources/RswiftResources/NibResource.swift @@ -16,9 +16,9 @@ public struct NibResource { public let rootViews: [TypeReference] public let reusables: [Reusable] public let generatedIds: [String] - public let usedImageIdentifiers: [NameCatalog] - public let usedColorResources: [NameCatalog] - public let usedAccessibilityIdentifiers: [String] + public var usedImageIdentifiers: [NameCatalog] + public var usedColorResources: [NameCatalog] + public var usedAccessibilityIdentifiers: [String] public init( name: String, From decde95e7d7461bc56afa26e16fab7e1a4c4407b Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sat, 15 Oct 2022 13:37:51 +0200 Subject: [PATCH 085/161] No (erroneous) warnings about validate function --- Sources/RswiftGenerators/Nib+Generator.swift | 47 ++++++++------------ Sources/RswiftResources/NibResource.swift | 2 +- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift index 4cbc7d75..a9131b61 100644 --- a/Sources/RswiftGenerators/Nib+Generator.swift +++ b/Sources/RswiftGenerators/Nib+Generator.swift @@ -62,27 +62,17 @@ extension NibResource { private static func unify(siblings: [NibResource], warning: (String) -> Void) -> NibResource? { guard var result = siblings.first else { return nil } - var warnings: [String] = [] for nib in siblings { - let (merged, fields, warns) = result.unify(nib) - warnings.append(contentsOf: warns) - - guard let merged else { + switch result.unify(nib) { + case let .failed(fields): let locales = "\(result.locale.localeDescription ?? "-") and \(nib.locale.localeDescription ?? "-")" - if let fields { - warning("Skipping generation of nib '\(nib.name)', because \(fields) don't match in localizations \(locales)") - } else { - warning("Skipping generation of nib '\(nib.name)', because localizations \(locales) are different from each other") - } + warning("Skipping generation of nib '\(nib.name)', because \(fields) don't match in localizations \(locales)") return nil - } - result = merged - } - // Only report warnings, if all siblings merged successfully - for w in warnings { - warning(w) + case let .success(merged): + result = merged + } } return result @@ -90,24 +80,25 @@ extension NibResource { } extension NibResource { - func unify(_ other: NibResource) -> (NibResource?, String?, [String]) { - if rootViews.first != other.rootViews.first { return (nil, "root views", []) } - if reusables.first != other.reusables.first { return (nil, "reuseIdentifiers", []) } - if name != other.name { return (nil, "names", []) } - - let diffImages = Set(self.usedImageIdentifiers).symmetricDifference(other.usedImageIdentifiers) - let diffColors = Set(self.usedColorResources).symmetricDifference(other.usedColorResources) + enum UnifyResult { + case success(NibResource) + case failed(String) + } - let warnings: [String?] = [ - diffImages.count > 0 ? "Skipping validation of \(diffImages.count) images in `validate` function, because these don't exist in all localizations: \(diffImages.map(\.name).joined(separator: ", "))" : nil, - diffColors.count > 0 ? "Skipping validation of \(diffColors.count) colors in `validate` function, because these don't exist in all localizations: \(diffColors.map(\.name).joined(separator: ", "))" : nil - ] + func unify(_ other: NibResource) -> UnifyResult { + if rootViews.first != other.rootViews.first { return .failed("root views") } + if reusables.first != other.reusables.first { return .failed("reuseIdentifiers") } + if name != other.name { return .failed("names") } + // Merged used images/colors from both localizations, they all need to be validated var result = self result.usedImageIdentifiers = Array(Set(self.usedImageIdentifiers).union(other.usedImageIdentifiers)) result.usedColorResources = Array(Set(self.usedColorResources).union(other.usedColorResources)) - return (result, nil, warnings.compactMap { $0 }) + // Remove locale, this is a merger of both + result.locale = .none + + return .success(result) } var genericTypeReference: TypeReference { diff --git a/Sources/RswiftResources/NibResource.swift b/Sources/RswiftResources/NibResource.swift index 6b08a4a2..eb54b755 100644 --- a/Sources/RswiftResources/NibResource.swift +++ b/Sources/RswiftResources/NibResource.swift @@ -11,7 +11,7 @@ import Foundation public struct NibResource { public let name: String - public let locale: LocaleReference + public var locale: LocaleReference public let deploymentTarget: DeploymentTarget? public let rootViews: [TypeReference] public let reusables: [Reusable] From fb9f4bd244a87a7841d37fe4f1eee5d8965836e7 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sat, 15 Oct 2022 17:17:06 +0200 Subject: [PATCH 086/161] Unify localizations of storyboards --- Sources/RswiftGenerators/Nib+Generator.swift | 32 ++++---- .../Storyboard+Generator.swift | 76 ++++++++++++++++++- .../RswiftResources/StoryboardResource.swift | 10 +-- 3 files changed, 98 insertions(+), 20 deletions(-) diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift index a9131b61..70b3cc8d 100644 --- a/Sources/RswiftGenerators/Nib+Generator.swift +++ b/Sources/RswiftGenerators/Nib+Generator.swift @@ -53,36 +53,40 @@ extension NibResource { var result: [NibResource] = [] for siblings in Dictionary(grouping: nibs, by: \.name).values { - guard let merged = unify(siblings: siblings, warning: warning) else { continue } - result.append(merged) + guard let first = siblings.first else { continue } + switch first.unify(siblings: siblings) { + case .failed(let message): + warning(message) + case .success(let merged): + result.append(merged) + } } return result } +} - private static func unify(siblings: [NibResource], warning: (String) -> Void) -> NibResource? { - guard var result = siblings.first else { return nil } +extension NibResource { + enum UnifyResult { + case success(NibResource) + case failed(String) + } + + private func unify(siblings: [NibResource]) -> UnifyResult { + var result = self for nib in siblings { switch result.unify(nib) { case let .failed(fields): let locales = "\(result.locale.localeDescription ?? "-") and \(nib.locale.localeDescription ?? "-")" - warning("Skipping generation of nib '\(nib.name)', because \(fields) don't match in localizations \(locales)") - return nil + return .failed("Skipping generation of nib '\(nib.name)', because \(fields) don't match in localizations \(locales)") case let .success(merged): result = merged } } - return result - } -} - -extension NibResource { - enum UnifyResult { - case success(NibResource) - case failed(String) + return .success(result) } func unify(_ other: NibResource) -> UnifyResult { diff --git a/Sources/RswiftGenerators/Storyboard+Generator.swift b/Sources/RswiftGenerators/Storyboard+Generator.swift index 8d3be875..4a649868 100644 --- a/Sources/RswiftGenerators/Storyboard+Generator.swift +++ b/Sources/RswiftGenerators/Storyboard+Generator.swift @@ -15,7 +15,10 @@ extension StoryboardResource { let warning: (String) -> Void = { print("warning: [R.swift]", $0) } - let groupedStoryboards = storyboards.grouped(bySwiftIdentifier: { $0.name }) + // Unify different localizations of storyboards + let unifiedStoryboards = unify(storyboards: storyboards, warning: warning) + + let groupedStoryboards = unifiedStoryboards.grouped(bySwiftIdentifier: { $0.name }) groupedStoryboards.reportWarningsForDuplicatesAndEmpties(source: "storyboard", result: "file", warning: warning) let structs = groupedStoryboards.uniques @@ -54,9 +57,74 @@ extension StoryboardResource { valueCodeString: lines.joined(separator: "\n") ) } + + private static func unify(storyboards: [StoryboardResource], warning: (String) -> Void) -> [StoryboardResource] { + var result: [StoryboardResource] = [] + + for siblings in Dictionary(grouping: storyboards, by: \.name).values { + guard let storyboard = siblings.first else { continue } + let (merged, vcs) = storyboard.unify(siblings: siblings) + result.append(merged) + + if vcs.count > 0 { + let ns = vcs.map { "'\($0)'" }.joined(separator: ", ") + warning("Skipping generation of \(vcs.count) view controllers in storyboard '\(storyboard.name)', because view controllers \(ns) don't exist in all localizations, or have different classes") + } + } + + return result + } } extension StoryboardResource { + private func unify(siblings: [StoryboardResource]) -> (StoryboardResource, [String]) { + var result = self + var vcs: [String] = [] + + for storyboard in siblings { + let (merged, names) = result.unify(storyboard) + vcs.append(contentsOf: names) + result = merged + } + + return (result, vcs) + } + + func unify(_ other: StoryboardResource) -> (StoryboardResource, [String]) { + let lhsPairs = self.viewControllers.compactMap { vc -> (String, ViewController)? in + guard let identifier = vc.storyboardIdentifier else { return nil } + return (identifier, vc) + } + let rhsPairs = other.viewControllers.compactMap { vc -> (String, ViewController)? in + guard let identifier = vc.storyboardIdentifier else { return nil } + return (identifier, vc) + } + let lhsVcs = Dictionary(uniqueKeysWithValues: lhsPairs) + let rhsVcs = Dictionary(uniqueKeysWithValues: rhsPairs) + + let vcs = lhsVcs.compactMap { (id, lhs) -> ViewController? in + guard let rhs = rhsVcs[id] else { return nil } + return lhs.canUnify(with: rhs) ? lhs : nil + } + + var result = self + result.viewControllers = vcs + + // Merged used images/colors from both localizations, they all need to be validated + result.usedImageIdentifiers = Array(Set(self.usedImageIdentifiers).union(other.usedImageIdentifiers)) + result.usedColorResources = Array(Set(self.usedColorResources).union(other.usedColorResources)) + + // Remove locale, this is a merger of both + result.locale = .none + + let allVcs = self.viewControllers + other.viewControllers + let usedIds = Set(vcs.map(\.id)) + let skipped = allVcs.compactMap { vc -> String? in + usedIds.contains(vc.id) ? nil : vc.storyboardIdentifier + } + + return (result, skipped.uniqueAndSorted()) + } func generateStruct(prefix: SwiftIdentifier, warning: (String) -> Void) -> Struct { let nameIdentifier = SwiftIdentifier(rawValue: "name") @@ -145,6 +213,12 @@ extension StoryboardResource { extension StoryboardResource.ViewController { + func canUnify(with other: Self) -> Bool { + self.id == other.id + && self.storyboardIdentifier == other.storyboardIdentifier + && self.type == other.type + } + var genericTypeReference: TypeReference { TypeReference( module: .rswiftResources, diff --git a/Sources/RswiftResources/StoryboardResource.swift b/Sources/RswiftResources/StoryboardResource.swift index d808aac4..645fc0f1 100644 --- a/Sources/RswiftResources/StoryboardResource.swift +++ b/Sources/RswiftResources/StoryboardResource.swift @@ -11,15 +11,15 @@ import Foundation public struct StoryboardResource { public let name: String - public let locale: LocaleReference + public var locale: LocaleReference public let deploymentTarget: DeploymentTarget? public let initialViewControllerIdentifier: String? - public let viewControllers: [ViewController] + public var viewControllers: [ViewController] public let viewControllerPlaceholders: [ViewControllerPlaceholder] public let generatedIds: [String] - public let usedAccessibilityIdentifiers: [String] - public let usedImageIdentifiers: [NameCatalog] - public let usedColorResources: [NameCatalog] + public var usedAccessibilityIdentifiers: [String] + public var usedImageIdentifiers: [NameCatalog] + public var usedColorResources: [NameCatalog] public let reusables: [Reusable] public var initialViewController: ViewController? { From 703c532981e5afa75cdf0560f312bc13cd188bc0 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Mon, 17 Oct 2022 14:59:57 +0200 Subject: [PATCH 087/161] Unify Sotryboards and Segues --- Sources/RswiftGenerators/Nib+Generator.swift | 28 ++-- .../RswiftGenerators/Segue+Generator.swift | 125 +++++++++++++++--- .../Storyboard+Generator.swift | 91 ++++++++----- .../RswiftResources/StoryboardResource.swift | 27 ++-- 4 files changed, 202 insertions(+), 69 deletions(-) diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift index 70b3cc8d..20013b66 100644 --- a/Sources/RswiftGenerators/Nib+Generator.swift +++ b/Sources/RswiftGenerators/Nib+Generator.swift @@ -16,7 +16,7 @@ extension NibResource { let warning: (String) -> Void = { print("warning: [R.swift]", $0) } // Unify different localizations of nibs - let unifiedNibs = unify(nibs: nibs, warning: warning) + let unifiedNibs = unifyLocalizations(nibs: nibs, warning: warning) let groupedNibs = unifiedNibs.grouped(bySwiftIdentifier: \.name) groupedNibs.reportWarningsForDuplicatesAndEmpties(source: "xib", result: "file", warning: warning) @@ -49,13 +49,13 @@ extension NibResource { ) } - private static func unify(nibs: [NibResource], warning: (String) -> Void) -> [NibResource] { + private static func unifyLocalizations(nibs: [NibResource], warning: (String) -> Void) -> [NibResource] { var result: [NibResource] = [] - for siblings in Dictionary(grouping: nibs, by: \.name).values { - guard let first = siblings.first else { continue } - switch first.unify(siblings: siblings) { - case .failed(let message): + for localizations in Dictionary(grouping: nibs, by: \.name).values { + guard let first = localizations.first else { continue } + switch first.unify(localizations: localizations) { + case .differentFields(let message): warning(message) case .success(let merged): result.append(merged) @@ -69,17 +69,17 @@ extension NibResource { extension NibResource { enum UnifyResult { case success(NibResource) - case failed(String) + case differentFields(String) } - private func unify(siblings: [NibResource]) -> UnifyResult { + private func unify(localizations: [NibResource]) -> UnifyResult { var result = self - for nib in siblings { + for nib in localizations { switch result.unify(nib) { - case let .failed(fields): + case let .differentFields(fields): let locales = "\(result.locale.localeDescription ?? "-") and \(nib.locale.localeDescription ?? "-")" - return .failed("Skipping generation of nib '\(nib.name)', because \(fields) don't match in localizations \(locales)") + return .differentFields("Skipping generation of nib '\(nib.name)', because \(fields) don't match in localizations \(locales)") case let .success(merged): result = merged @@ -90,9 +90,9 @@ extension NibResource { } func unify(_ other: NibResource) -> UnifyResult { - if rootViews.first != other.rootViews.first { return .failed("root views") } - if reusables.first != other.reusables.first { return .failed("reuseIdentifiers") } - if name != other.name { return .failed("names") } + if rootViews.first != other.rootViews.first { return .differentFields("root views") } + if reusables.first != other.reusables.first { return .differentFields("reuseIdentifiers") } + if name != other.name { return .differentFields("names") } // Merged used images/colors from both localizations, they all need to be validated var result = self diff --git a/Sources/RswiftGenerators/Segue+Generator.swift b/Sources/RswiftGenerators/Segue+Generator.swift index 278c444a..b7745ebd 100644 --- a/Sources/RswiftGenerators/Segue+Generator.swift +++ b/Sources/RswiftGenerators/Segue+Generator.swift @@ -15,7 +15,10 @@ public struct Segue { let warning: (String) -> Void = { print("warning: [R.swift]", $0) } - let allSegues = allSegueInfos(storyboards: storyboards, warning: warning) + // Unify different localizations of storyboards + let unifiedStoryboards = unify(storyboards: storyboards, warning: warning) + + let allSegues = allSegueInfos(storyboards: unifiedStoryboards, warning: warning) let viewControllers = viewControllers(segues: allSegues, warning: warning) let structs = viewControllers .map { generateStruct(sourceType: $0.key, segues: $0.value) } @@ -39,26 +42,12 @@ public struct Segue { } } - private static func viewControllers(segues: [SegueWithInfo], warning: (String) -> Void) -> [TypeReference: [SegueWithInfo]] { - var result: [TypeReference: [SegueWithInfo]] = [:] - - let grouped = Dictionary(grouping: segues, by: \.sourceType) - for (sourceType, seguesBySourceType) in grouped { - let segues = seguesBySourceType.grouped(bySwiftIdentifier: { $0.segue.identifier }) - segues.reportWarningsForDuplicatesAndEmpties(source: "segue", container: "for '\(sourceType.name)'", result: "segue", warning: warning) - - result[sourceType] = segues.uniques - } - - return result - } - private static func allSegueInfos(storyboards: [StoryboardResource], warning: (String) -> Void) -> [SegueWithInfo] { let allSegues = storyboards.flatMap { storyboard in storyboard.viewControllers.flatMap { viewController in viewController.segues.compactMap { segue -> SegueWithInfo? in guard let destinationType = resolveDestinationType( - for:segue, + for: segue, inViewController: viewController, inStoryboard: storyboard, allStoryboards: storyboards) @@ -89,6 +78,20 @@ public struct Segue { return deduplicatedSeguesWithInfo } + private static func viewControllers(segues: [SegueWithInfo], warning: (String) -> Void) -> [TypeReference: [SegueWithInfo]] { + var result: [TypeReference: [SegueWithInfo]] = [:] + + let grouped = Dictionary(grouping: segues, by: \.sourceType) + for (sourceType, seguesBySourceType) in grouped { + let segues = seguesBySourceType.grouped(bySwiftIdentifier: { $0.segue.identifier }) + segues.reportWarningsForDuplicatesAndEmpties(source: "segue", container: "for '\(sourceType.name)'", result: "segue", warning: warning) + + result[sourceType] = segues.uniques + } + + return result + } + private static func resolveDestinationType(for segue: StoryboardResource.Segue, inViewController: StoryboardResource.ViewController, inStoryboard storyboard: StoryboardResource, allStoryboards storyboards: [StoryboardResource]) -> TypeReference? { let uiViewController = TypeReference.uiViewController @@ -115,6 +118,96 @@ public struct Segue { return destinationViewControllerType ?? destinationViewControllerPlaceholderType } + + private static func unify(storyboards: [StoryboardResource], warning: (String) -> Void) -> [StoryboardResource] { + var result: [StoryboardResource] = [] + + for siblings in Dictionary(grouping: storyboards, by: \.name).values { + guard let storyboard = siblings.first else { continue } + let r = storyboard.unify(siblings: siblings) + result.append(r.storyboard) + + let segues = r.differentSegueIDs + if segues.count > 0 { + let ns = segues.map { "'\($0)'" }.joined(separator: ", ") + warning("Skipping generation of \(segues.count) segues in storyboard '\(storyboard.name)', because segues \(ns) aren't identical in all localizations") + } + } + + return result + } +} + +fileprivate extension StoryboardResource { + struct UnifyResult { + let storyboard: StoryboardResource + let differentSegueIDs: [String] + } + + func unify(siblings: [StoryboardResource]) -> UnifyResult { + var result = self + var segues: [String] = [] + + for storyboard in siblings { + let r = result.unify(storyboard) + segues.append(contentsOf: r.differentSegueIDs) + result = r.storyboard + } + + return UnifyResult(storyboard: result, differentSegueIDs: segues) + } + + func unify(_ other: StoryboardResource) -> UnifyResult { + let lhsVcs = self.viewControllersByIdentifier + let rhsVcs = other.viewControllersByIdentifier + + let vcs = lhsVcs.compactMap { (id, lhs) -> ViewController.UnifyResult? in + guard let rhs = rhsVcs[id] else { return nil } + return lhs.unify(rhs) + } + + var result = self + result.viewControllers = vcs.map(\.viewcontroller) + + // Remove fields that haven't been merged + result.locale = .none + result.usedImageIdentifiers = [] + result.usedColorResources = [] + + let different = vcs.flatMap(\.differentSegueIDs) + + return UnifyResult(storyboard: result, differentSegueIDs: different.uniqueAndSorted()) + } +} + +private extension StoryboardResource.ViewController { + struct UnifyResult { + let viewcontroller: StoryboardResource.ViewController + let differentSegueIDs: [String] + } + + func unify(_ other: Self) -> UnifyResult { + let rhsSegues = Dictionary(grouping: other.segues, by: \.identifier) + + var result = self + result.segues = result.segues.filter { l in + guard let ss = rhsSegues[l.identifier], let r = ss.first else { return false } + return l.canUnify(r) + } + + let usedIDs = Set(result.segues.map(\.identifier)) + let different = (self.segues + other.segues).filter { s in + !usedIDs.contains(s.identifier) + } + + return .init(viewcontroller: result, differentSegueIDs: different.map(\.identifier)) + } +} + +private extension StoryboardResource.Segue { + func canUnify(_ other: Self) -> Bool { + self == other + } } private extension StoryboardResource.ViewControllerPlaceholder { diff --git a/Sources/RswiftGenerators/Storyboard+Generator.swift b/Sources/RswiftGenerators/Storyboard+Generator.swift index 4a649868..cde5e23b 100644 --- a/Sources/RswiftGenerators/Storyboard+Generator.swift +++ b/Sources/RswiftGenerators/Storyboard+Generator.swift @@ -16,7 +16,7 @@ extension StoryboardResource { let warning: (String) -> Void = { print("warning: [R.swift]", $0) } // Unify different localizations of storyboards - let unifiedStoryboards = unify(storyboards: storyboards, warning: warning) + let unifiedStoryboards = unifyLocalizations(storyboards: storyboards, warning: warning) let groupedStoryboards = unifiedStoryboards.grouped(bySwiftIdentifier: { $0.name }) groupedStoryboards.reportWarningsForDuplicatesAndEmpties(source: "storyboard", result: "file", warning: warning) @@ -58,17 +58,22 @@ extension StoryboardResource { ) } - private static func unify(storyboards: [StoryboardResource], warning: (String) -> Void) -> [StoryboardResource] { + private static func unifyLocalizations(storyboards: [StoryboardResource], warning: (String) -> Void) -> [StoryboardResource] { var result: [StoryboardResource] = [] - for siblings in Dictionary(grouping: storyboards, by: \.name).values { - guard let storyboard = siblings.first else { continue } - let (merged, vcs) = storyboard.unify(siblings: siblings) - result.append(merged) + for localizations in Dictionary(grouping: storyboards, by: \.name).values { + guard let storyboard = localizations.first else { continue } + switch storyboard.unify(localizations: localizations) { + case .success(let r): + result.append(r.storyboard) - if vcs.count > 0 { - let ns = vcs.map { "'\($0)'" }.joined(separator: ", ") - warning("Skipping generation of \(vcs.count) view controllers in storyboard '\(storyboard.name)', because view controllers \(ns) don't exist in all localizations, or have different classes") + let vcs = r.notUnifiedStoryboardIDs + if vcs.count > 0 { + let ns = vcs.map { "'\($0)'" }.joined(separator: ", ") + warning("Skipping generation of \(vcs.count) view controllers in storyboard '\(storyboard.name)', because view controllers \(ns) don't exist in all localizations, or have different classes") + } + case .differentInitialViewController: + warning("Skipping generation of storyboard '\(storyboard.name)', it has different initial view controllers in different localizations") } } @@ -76,31 +81,50 @@ extension StoryboardResource { } } -extension StoryboardResource { - private func unify(siblings: [StoryboardResource]) -> (StoryboardResource, [String]) { - var result = self - var vcs: [String] = [] +private extension StoryboardResource { + enum UnifyResult { + case success(Result) + case differentInitialViewController +// case differentDeploymentTargets // TODO - for storyboard in siblings { - let (merged, names) = result.unify(storyboard) - vcs.append(contentsOf: names) - result = merged + struct Result { + let storyboard: StoryboardResource + let notUnifiedStoryboardIDs: [String] } - return (result, vcs) + func flatMap(_ transform: (StoryboardResource) -> UnifyResult) -> UnifyResult { + switch self { + case .success(let lhs): + switch transform(lhs.storyboard) { + case .success(let merged): + let r = Result( + storyboard: merged.storyboard, + notUnifiedStoryboardIDs: (lhs.notUnifiedStoryboardIDs + merged.notUnifiedStoryboardIDs).uniqueAndSorted() + ) + return .success(r) + + case .differentInitialViewController: + return .differentInitialViewController + } + case .differentInitialViewController: + return .differentInitialViewController + } + } } - func unify(_ other: StoryboardResource) -> (StoryboardResource, [String]) { - let lhsPairs = self.viewControllers.compactMap { vc -> (String, ViewController)? in - guard let identifier = vc.storyboardIdentifier else { return nil } - return (identifier, vc) - } - let rhsPairs = other.viewControllers.compactMap { vc -> (String, ViewController)? in - guard let identifier = vc.storyboardIdentifier else { return nil } - return (identifier, vc) + func unify(localizations: [StoryboardResource]) -> UnifyResult { + var result = UnifyResult.success(.init(storyboard: self, notUnifiedStoryboardIDs: [])) + + for storyboard in localizations { + result = result.flatMap { $0.unify(storyboard) } } - let lhsVcs = Dictionary(uniqueKeysWithValues: lhsPairs) - let rhsVcs = Dictionary(uniqueKeysWithValues: rhsPairs) + + return result + } + + func unify(_ other: StoryboardResource) -> UnifyResult { + let lhsVcs = self.viewControllersByIdentifier + let rhsVcs = other.viewControllersByIdentifier let vcs = lhsVcs.compactMap { (id, lhs) -> ViewController? in guard let rhs = rhsVcs[id] else { return nil } @@ -123,7 +147,14 @@ extension StoryboardResource { usedIds.contains(vc.id) ? nil : vc.storyboardIdentifier } - return (result, skipped.uniqueAndSorted()) + if self.initialViewControllerIdentifier != other.initialViewControllerIdentifier { + return .differentInitialViewController + } + + return .success(.init( + storyboard: result, + notUnifiedStoryboardIDs: skipped.uniqueAndSorted() + )) } func generateStruct(prefix: SwiftIdentifier, warning: (String) -> Void) -> Struct { @@ -211,7 +242,7 @@ extension StoryboardResource { } } -extension StoryboardResource.ViewController { +private extension StoryboardResource.ViewController { func canUnify(with other: Self) -> Bool { self.id == other.id diff --git a/Sources/RswiftResources/StoryboardResource.swift b/Sources/RswiftResources/StoryboardResource.swift index 645fc0f1..14122f0b 100644 --- a/Sources/RswiftResources/StoryboardResource.swift +++ b/Sources/RswiftResources/StoryboardResource.swift @@ -22,12 +22,6 @@ public struct StoryboardResource { public var usedColorResources: [NameCatalog] public let reusables: [Reusable] - public var initialViewController: ViewController? { - viewControllers - .filter { $0.id == self.initialViewControllerIdentifier } - .first - } - public init( name: String, locale: LocaleReference, @@ -54,7 +48,7 @@ public struct StoryboardResource { self.reusables = reusables } - public struct ViewController { + public struct ViewController: Equatable { public let id: String public let storyboardIdentifier: String? public let type: TypeReference @@ -68,7 +62,7 @@ public struct StoryboardResource { } } - public struct ViewControllerPlaceholder { + public struct ViewControllerPlaceholder: Equatable { public let id: String public let storyboardName: String? public let referencedIdentifier: String? @@ -82,7 +76,7 @@ public struct StoryboardResource { } } - public struct Segue { + public struct Segue: Equatable { public let identifier: String public let type: TypeReference public let destination: String @@ -95,4 +89,19 @@ public struct StoryboardResource { self.kind = kind } } + + public var initialViewController: ViewController? { + viewControllers + .filter { $0.id == self.initialViewControllerIdentifier } + .first + } + + public var viewControllersByIdentifier: [String: ViewController] { + let pairs = self.viewControllers.compactMap { vc -> (String, ViewController)? in + guard let identifier = vc.storyboardIdentifier else { return nil } + return (identifier, vc) + } + + return Dictionary(uniqueKeysWithValues: pairs) + } } From 00476eab5a21aa10596f5ca1966fbb1746f7655d Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Mon, 17 Oct 2022 16:53:40 +0200 Subject: [PATCH 088/161] Implemented deployment targets for nibs and storyboards --- Sources/RswiftGenerators/Nib+Generator.swift | 5 +- .../RswiftGenerators/Segue+Generator.swift | 39 ++--- .../Storyboard+Generator.swift | 29 +++- .../RswiftGenerators/SwiftSyntax/Struct.swift | 145 +++++++++++++----- .../Shared/DeploymentTarget.swift | 8 +- .../RswiftResources/StoryboardResource.swift | 2 +- 6 files changed, 160 insertions(+), 68 deletions(-) diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift index 20013b66..441541f2 100644 --- a/Sources/RswiftGenerators/Nib+Generator.swift +++ b/Sources/RswiftGenerators/Nib+Generator.swift @@ -91,7 +91,8 @@ extension NibResource { func unify(_ other: NibResource) -> UnifyResult { if rootViews.first != other.rootViews.first { return .differentFields("root views") } - if reusables.first != other.reusables.first { return .differentFields("reuseIdentifiers") } +// if reusables.first != other.reusables.first { return .differentFields("reuseIdentifiers") } + if deploymentTarget != other.deploymentTarget { return .differentFields("deployment targets") } if name != other.name { return .differentFields("names") } // Merged used images/colors from both localizations, they all need to be validated @@ -122,6 +123,7 @@ extension NibResource { ) return VarGetter( comments: ["Nib `\(name)`."], + deploymentTarget: deploymentTarget, name: SwiftIdentifier(name: name), typeReference: typeReference, valueCodeString: ".init(name: \"\(name.escapedStringLiteral)\", bundle: bundle, identifier: \"\(reusable.identifier.escapedStringLiteral)\")" @@ -134,6 +136,7 @@ extension NibResource { ) return VarGetter( comments: ["Nib `\(name)`."], + deploymentTarget: deploymentTarget, name: SwiftIdentifier(name: name), typeReference: typeReference, valueCodeString: ".init(name: \"\(name.escapedStringLiteral)\", bundle: bundle)" diff --git a/Sources/RswiftGenerators/Segue+Generator.swift b/Sources/RswiftGenerators/Segue+Generator.swift index b7745ebd..4618847c 100644 --- a/Sources/RswiftGenerators/Segue+Generator.swift +++ b/Sources/RswiftGenerators/Segue+Generator.swift @@ -35,10 +35,24 @@ public struct Segue { } } + private static func viewControllers(segues: [SegueWithInfo], warning: (String) -> Void) -> [TypeReference: [SegueWithInfo]] { + var result: [TypeReference: [SegueWithInfo]] = [:] + + let grouped = Dictionary(grouping: segues, by: \.sourceType) + for (sourceType, seguesBySourceType) in grouped { + let segues = seguesBySourceType.grouped(bySwiftIdentifier: { $0.segue.identifier }) + segues.reportWarningsForDuplicatesAndEmpties(source: "segue", container: "for '\(sourceType.name)'", result: "segue", warning: warning) + + result[sourceType] = segues.uniques + } + + return result + } + private static func generateStruct(sourceType: TypeReference, segues: [SegueWithInfo]) -> Struct { let comments = ["This struct is generated for `\(sourceType.name)`, and contains static references to \(segues.count) segues."] return Struct(comments: comments, name: SwiftIdentifier(name: sourceType.name)) { - segues.map { $0.generateLetBinding() } + segues.map { $0.generateVarGetter() } } } @@ -62,6 +76,7 @@ public struct Segue { } return SegueWithInfo( + deploymentTarget: storyboard.deploymentTarget, segue: segue, sourceType: viewController.type, destinationType: destinationType @@ -78,20 +93,6 @@ public struct Segue { return deduplicatedSeguesWithInfo } - private static func viewControllers(segues: [SegueWithInfo], warning: (String) -> Void) -> [TypeReference: [SegueWithInfo]] { - var result: [TypeReference: [SegueWithInfo]] = [:] - - let grouped = Dictionary(grouping: segues, by: \.sourceType) - for (sourceType, seguesBySourceType) in grouped { - let segues = seguesBySourceType.grouped(bySwiftIdentifier: { $0.segue.identifier }) - segues.reportWarningsForDuplicatesAndEmpties(source: "segue", container: "for '\(sourceType.name)'", result: "segue", warning: warning) - - result[sourceType] = segues.uniques - } - - return result - } - private static func resolveDestinationType(for segue: StoryboardResource.Segue, inViewController: StoryboardResource.ViewController, inStoryboard storyboard: StoryboardResource, allStoryboards storyboards: [StoryboardResource]) -> TypeReference? { let uiViewController = TypeReference.uiViewController @@ -244,12 +245,13 @@ private extension StoryboardResource.ViewControllerPlaceholder { } struct SegueWithInfo { + let deploymentTarget: DeploymentTarget? let segue: StoryboardResource.Segue let sourceType: TypeReference let destinationType: TypeReference var groupKey: String { - "\(segue.identifier)|\(segue.type)|\(sourceType)|\(destinationType)" + "\(String(describing: deploymentTarget))|\(segue.identifier)|\(segue.type)|\(sourceType)|\(destinationType)" } var genericTypeReference: TypeReference { @@ -260,9 +262,10 @@ struct SegueWithInfo { ) } - func generateLetBinding() -> LetBinding { - LetBinding( + func generateVarGetter() -> VarGetter { + VarGetter( comments: ["Segue identifier `\(segue.identifier)`."], + deploymentTarget: deploymentTarget, name: SwiftIdentifier(name: segue.identifier), typeReference: genericTypeReference, valueCodeString: ".init(identifier: \"\(segue.identifier)\")" diff --git a/Sources/RswiftGenerators/Storyboard+Generator.swift b/Sources/RswiftGenerators/Storyboard+Generator.swift index cde5e23b..0e862385 100644 --- a/Sources/RswiftGenerators/Storyboard+Generator.swift +++ b/Sources/RswiftGenerators/Storyboard+Generator.swift @@ -38,15 +38,16 @@ extension StoryboardResource { structs if structs.count > 0 { - generateValidate(names: structs.map(\.name)) + generateValidate(structs: structs) } } } - private static func generateValidate(names: [SwiftIdentifier]) -> Function { - let lines = names - .map { name -> String in - "try self.\(name.value).validate()" + private static func generateValidate(structs: [Struct]) -> Function { + let lines = structs + .map { s -> String in + let code = "try self.\(s.name.value).validate()" + return s.deploymentTarget?.codeIf(around: code) ?? code } return Function( comments: [], @@ -72,8 +73,12 @@ extension StoryboardResource { let ns = vcs.map { "'\($0)'" }.joined(separator: ", ") warning("Skipping generation of \(vcs.count) view controllers in storyboard '\(storyboard.name)', because view controllers \(ns) don't exist in all localizations, or have different classes") } + case .differentInitialViewController: warning("Skipping generation of storyboard '\(storyboard.name)', it has different initial view controllers in different localizations") + + case .differentDeploymentTargets: + warning("Skipping generation of storyboard '\(storyboard.name)', it has different deployment targets in different localizations") } } @@ -85,7 +90,7 @@ private extension StoryboardResource { enum UnifyResult { case success(Result) case differentInitialViewController -// case differentDeploymentTargets // TODO + case differentDeploymentTargets struct Result { let storyboard: StoryboardResource @@ -105,9 +110,16 @@ private extension StoryboardResource { case .differentInitialViewController: return .differentInitialViewController + + case .differentDeploymentTargets: + return .differentDeploymentTargets } + case .differentInitialViewController: return .differentInitialViewController + + case .differentDeploymentTargets: + return .differentDeploymentTargets } } } @@ -151,6 +163,10 @@ private extension StoryboardResource { return .differentInitialViewController } + if self.deploymentTarget != other.deploymentTarget { + return .differentDeploymentTargets + } + return .success(.init( storyboard: result, notUnifiedStoryboardIDs: skipped.uniqueAndSorted() @@ -192,6 +208,7 @@ private extension StoryboardResource { return Struct( comments: ["Storyboard `\(name)`."], + deploymentTarget: deploymentTarget, name: identifier, protocols: [storyboardReference, initialContainer].compactMap { $0 } ) { diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index c862a54d..a75f5685 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -30,7 +30,14 @@ public struct LetBinding { public let typeReference: TypeReference? public let valueCodeString: String? - public init(comments: [String] = [], accessControl: AccessControl = AccessControl.none, isStatic: Bool = false, name: SwiftIdentifier, typeReference: TypeReference, valueCodeString: String?) { + public init( + comments: [String] = [], + accessControl: AccessControl = AccessControl.none, + isStatic: Bool = false, + name: SwiftIdentifier, + typeReference: TypeReference, + valueCodeString: String? + ) { self.comments = comments self.accessControl = accessControl self.isStatic = isStatic @@ -39,7 +46,13 @@ public struct LetBinding { self.valueCodeString = valueCodeString } - public init(comments: [String] = [], accessControl: AccessControl = AccessControl.none, isStatic: Bool = false, name: SwiftIdentifier, valueCodeString: String) { + public init( + comments: [String] = [], + accessControl: AccessControl = AccessControl.none, + isStatic: Bool = false, + name: SwiftIdentifier, + valueCodeString: String + ) { self.comments = comments self.accessControl = accessControl self.isStatic = isStatic @@ -57,6 +70,10 @@ public struct LetBinding { } func render(_ pp: inout PrettyPrinter) { + for c in comments { + pp.append(words: ["///", c == "" ? nil : c]) + } + var words: [String?] = [ accessControl.code(), isStatic ? "static" : nil, @@ -69,22 +86,28 @@ public struct LetBinding { words.append(valueCodeString) } - for c in comments { - pp.append(words: ["///", c == "" ? nil : c]) - } pp.append(words: words) } } public struct VarGetter { public let comments: [String] + public let deploymentTarget: DeploymentTarget? public var accessControl = AccessControl.none public let name: SwiftIdentifier public let typeReference: TypeReference public let valueCodeString: String - public init(comments: [String] = [], accessControl: AccessControl = AccessControl.none, name: SwiftIdentifier, typeReference: TypeReference, valueCodeString: String) { + public init( + comments: [String] = [], + deploymentTarget: DeploymentTarget? = nil, + accessControl: AccessControl = AccessControl.none, + name: SwiftIdentifier, + typeReference: TypeReference, + valueCodeString: String + ) { self.comments = comments + self.deploymentTarget = deploymentTarget self.accessControl = accessControl self.name = name self.typeReference = typeReference @@ -96,6 +119,12 @@ public struct VarGetter { } func render(_ pp: inout PrettyPrinter) { + for c in comments { + pp.append(words: ["///", c == "" ? nil : c]) + } + + deploymentTarget?.render(&pp) + let words: [String?] = [ accessControl.code(), "var", @@ -106,15 +135,13 @@ public struct VarGetter { "}" ] - for c in comments { - pp.append(words: ["///", c == "" ? nil : c]) - } pp.append(words: words) } } public struct Function { + public let deploymentTarget: DeploymentTarget? public let comments: [String] public var accessControl = AccessControl.none public let isStatic: Bool @@ -124,8 +151,19 @@ public struct Function { public let returnType: TypeReference public let valueCodeString: String - public init(comments: [String], accessControl: AccessControl = AccessControl.none, isStatic: Bool = false, name: SwiftIdentifier, params: [Parameter], returnThrows: Bool = false, returnType: TypeReference, valueCodeString: String) { + public init( + comments: [String], + deploymentTarget: DeploymentTarget? = nil, + accessControl: AccessControl = AccessControl.none, + isStatic: Bool = false, + name: SwiftIdentifier, + params: [Parameter], + returnThrows: Bool = false, + returnType: TypeReference, + valueCodeString: String + ) { self.comments = comments + self.deploymentTarget = deploymentTarget self.accessControl = accessControl self.isStatic = isStatic self.name = name @@ -168,6 +206,12 @@ public struct Function { } func render(_ pp: inout PrettyPrinter) { + for c in comments { + pp.append(words: ["///", c == "" ? nil : c]) + } + + deploymentTarget?.render(&pp) + let prs = params.map { $0.codeString() }.joined(separator: ", ") let words: [String?] = [ accessControl.code(), @@ -179,9 +223,6 @@ public struct Function { "{" ] - for c in comments { - pp.append(words: ["///", c == "" ? nil : c]) - } pp.append(words: words) pp.indented { pp in pp.append(line: valueCodeString) @@ -320,6 +361,7 @@ public struct TypeAlias { public struct Struct { public let comments: [String] + public let deploymentTarget: DeploymentTarget? public var accessControl = AccessControl.none public let name: SwiftIdentifier public var protocols: [TypeReference] = [] @@ -334,12 +376,14 @@ public struct Struct { public init( comments: [String] = [], + deploymentTarget: DeploymentTarget? = nil, accessControl: AccessControl = AccessControl.none, name: SwiftIdentifier, protocols: [TypeReference] = [], @StructMembersBuilder membersBuilder: () -> StructMembers ) { self.comments = comments + self.deploymentTarget = deploymentTarget self.accessControl = accessControl self.name = name self.protocols = protocols @@ -403,6 +447,8 @@ public struct Struct { pp.append(words: ["///", c == "" ? nil : c]) } + deploymentTarget?.render(&pp) + let ps = protocols.map { $0.codeString() }.joined(separator: ", ") let implements = ps.isEmpty ? "" : ": \(ps)" pp.append(words: [accessControl.code(), "struct", "\(name.value)\(implements)", "{"]) @@ -485,6 +531,51 @@ public struct Struct { } } +extension Struct { + public func generateLetBinding() -> LetBinding { + LetBinding( + name: name, + valueCodeString: "\(name.value)()" + ) + } + + public func generateBundleVarGetter(name: String) -> VarGetter { + VarGetter( + deploymentTarget: deploymentTarget, + name: SwiftIdentifier(name: name), + typeReference: TypeReference(module: .host, rawName: self.name.value), + valueCodeString: ".init(bundle: bundle)" + ) + } + + public func generateBundleFunction(name: String) -> Function { + Function( + comments: [], + deploymentTarget: deploymentTarget, + name: SwiftIdentifier(name: name), + params: [.init(name: "bundle", localName: nil, typeReference: .bundle, defaultValue: nil)], + returnType: TypeReference(module: .host, rawName: self.name.value), + valueCodeString: ".init(bundle: bundle)" + ) + } +} + +extension DeploymentTarget { + func render(_ pp: inout PrettyPrinter) { + if let version { + pp.append(line: "@available(\(platform) \(version.major).\(version.minor), *)") + } + } + + func codeIf(around code: String) -> String { + if let version { + return "if #available(\(platform) \(version.major).\(version.minor), *) { \(code) }" + } else { + return code + } + } +} + struct PrettyPrinter { private var indent = 0 private var lines: [(Int, String)] = [] @@ -520,31 +611,3 @@ struct PrettyPrinter { return ls.joined(separator: "\n") } } - - -extension Struct { - public func generateLetBinding() -> LetBinding { - LetBinding( - name: name, - valueCodeString: "\(name.value)()" - ) - } - - public func generateBundleVarGetter(name: String) -> VarGetter { - VarGetter( - name: SwiftIdentifier(name: name), - typeReference: TypeReference(module: .host, rawName: self.name.value), - valueCodeString: ".init(bundle: bundle)" - ) - } - - public func generateBundleFunction(name: String) -> Function { - Function( - comments: [], - name: SwiftIdentifier(name: name), - params: [.init(name: "bundle", localName: nil, typeReference: .bundle, defaultValue: nil)], - returnType: TypeReference(module: .host, rawName: self.name.value), - valueCodeString: ".init(bundle: bundle)" - ) - } -} diff --git a/Sources/RswiftResources/Shared/DeploymentTarget.swift b/Sources/RswiftResources/Shared/DeploymentTarget.swift index 232e2867..31133054 100644 --- a/Sources/RswiftResources/Shared/DeploymentTarget.swift +++ b/Sources/RswiftResources/Shared/DeploymentTarget.swift @@ -7,7 +7,7 @@ import Foundation -public struct DeploymentTarget { +public struct DeploymentTarget: Equatable { public typealias Version = (major: Int, minor: Int) public let version: Version? @@ -17,4 +17,10 @@ public struct DeploymentTarget { self.version = version self.platform = platform } + + public static func ==(lhs: DeploymentTarget, rhs: DeploymentTarget) -> Bool { + lhs.platform == rhs.platform + && lhs.version?.major == rhs.version?.major + && lhs.version?.minor == rhs.version?.minor + } } diff --git a/Sources/RswiftResources/StoryboardResource.swift b/Sources/RswiftResources/StoryboardResource.swift index 14122f0b..f058a274 100644 --- a/Sources/RswiftResources/StoryboardResource.swift +++ b/Sources/RswiftResources/StoryboardResource.swift @@ -20,7 +20,7 @@ public struct StoryboardResource { public var usedAccessibilityIdentifiers: [String] public var usedImageIdentifiers: [NameCatalog] public var usedColorResources: [NameCatalog] - public let reusables: [Reusable] + public var reusables: [Reusable] public init( name: String, From dd60f071f87e0745988981f958d38c6ad415e266 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Tue, 18 Oct 2022 12:46:15 +0200 Subject: [PATCH 089/161] Move unification functions to RswiftResources --- Sources/RswiftGenerators/Nib+Generator.swift | 59 ++------ .../ReuseIdentifier+Generator.swift | 40 +++++- .../RswiftGenerators/Segue+Generator.swift | 92 ++---------- .../Storyboard+Generator.swift | 120 +++------------- .../SegueIdentifier+Integrations.swift | 8 ++ Sources/RswiftResources/NibResource.swift | 68 ++++++++- .../RswiftResources/StoryboardResource.swift | 133 ++++++++++++++++++ 7 files changed, 290 insertions(+), 230 deletions(-) diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift index 441541f2..16cce8de 100644 --- a/Sources/RswiftGenerators/Nib+Generator.swift +++ b/Sources/RswiftGenerators/Nib+Generator.swift @@ -53,13 +53,21 @@ extension NibResource { var result: [NibResource] = [] for localizations in Dictionary(grouping: nibs, by: \.name).values { - guard let first = localizations.first else { continue } - switch first.unify(localizations: localizations) { - case .differentFields(let message): - warning(message) - case .success(let merged): - result.append(merged) + guard let nib = localizations.first else { continue } + let ur = nib.unify(localizations: localizations) + + let diffs: [String] = [ + ur.differentNames ? "names" : nil, + ur.differentRootViews ? "root views" : nil, + ur.differentInitialReusables ? "initial reusables" : nil, + ur.differentDeploymentTargets ? "deployment targets" : nil, + ].compactMap { $0 } + + if diffs.count > 0 { + warning("Skipping generation of nib '\(nib.name)', because \(diffs.joined(separator: ", ")) don't match in all localizations") + continue } + result.append(ur.resource) } return result @@ -67,45 +75,6 @@ extension NibResource { } extension NibResource { - enum UnifyResult { - case success(NibResource) - case differentFields(String) - } - - private func unify(localizations: [NibResource]) -> UnifyResult { - var result = self - - for nib in localizations { - switch result.unify(nib) { - case let .differentFields(fields): - let locales = "\(result.locale.localeDescription ?? "-") and \(nib.locale.localeDescription ?? "-")" - return .differentFields("Skipping generation of nib '\(nib.name)', because \(fields) don't match in localizations \(locales)") - - case let .success(merged): - result = merged - } - } - - return .success(result) - } - - func unify(_ other: NibResource) -> UnifyResult { - if rootViews.first != other.rootViews.first { return .differentFields("root views") } -// if reusables.first != other.reusables.first { return .differentFields("reuseIdentifiers") } - if deploymentTarget != other.deploymentTarget { return .differentFields("deployment targets") } - if name != other.name { return .differentFields("names") } - - // Merged used images/colors from both localizations, they all need to be validated - var result = self - result.usedImageIdentifiers = Array(Set(self.usedImageIdentifiers).union(other.usedImageIdentifiers)) - result.usedColorResources = Array(Set(self.usedColorResources).union(other.usedColorResources)) - - // Remove locale, this is a merger of both - result.locale = .none - - return .success(result) - } - var genericTypeReference: TypeReference { TypeReference( module: .rswiftResources, diff --git a/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift b/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift index b94fb5ca..3fe7ab9a 100644 --- a/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift +++ b/Sources/RswiftGenerators/ReuseIdentifier+Generator.swift @@ -15,7 +15,10 @@ extension Reusable { let warning: (String) -> Void = { print("warning: [R.swift]", $0) } - let reusables = nibs.flatMap(\.reusables) + storyboards.flatMap(\.reusables) + let unifiedNibs = unifyLocalizations(nibs: nibs, warning: warning) + let unifiedStoryboards = unifyLocalizations(storyboards: storyboards, warning: warning) + + let reusables = unifiedNibs.flatMap(\.reusables) + unifiedStoryboards.flatMap(\.reusables) let deduplicatedReusables = Dictionary(grouping: reusables, by: \.hashValue) .values.compactMap(\.first) @@ -33,6 +36,41 @@ extension Reusable { } } + private static func unifyLocalizations(nibs: [NibResource], warning: (String) -> Void) -> [NibResource] { + var result: [NibResource] = [] + + for localizations in Dictionary(grouping: nibs, by: \.name).values { + guard let nib = localizations.first else { continue } + let ur = nib.unify(localizations: localizations) + + let rs = ur.differentReusables.map { "'\($0.identifier)'" }.uniqueAndSorted() + if rs.count > 0 { + warning("Skipping generation of \(rs.count) reuseIdentifiers in nib '\(nib.name)', because \(rs.joined(separator: ", ")) don't match in all localizations") + continue + } + result.append(ur.resource) + } + + return result + } + + private static func unifyLocalizations(storyboards: [StoryboardResource], warning: (String) -> Void) -> [StoryboardResource] { + var result: [StoryboardResource] = [] + + for localizations in Dictionary(grouping: storyboards, by: \.name).values { + guard let storyboard = localizations.first else { continue } + let ur = storyboard.unify(localizations: localizations) + + let rs = ur.differentReusables.map { "'\($0.identifier)'" }.uniqueAndSorted() + if rs.count > 0 { + warning("Skipping generation of \(rs.count) reuseIdentifiers in storyboard '\(storyboard.name)', because \(rs.joined(separator: ", ")) don't match in all localizations") + continue + } + result.append(ur.storyboard) + } + + return result + } } extension Reusable { diff --git a/Sources/RswiftGenerators/Segue+Generator.swift b/Sources/RswiftGenerators/Segue+Generator.swift index 4618847c..b4796c12 100644 --- a/Sources/RswiftGenerators/Segue+Generator.swift +++ b/Sources/RswiftGenerators/Segue+Generator.swift @@ -16,7 +16,7 @@ public struct Segue { let warning: (String) -> Void = { print("warning: [R.swift]", $0) } // Unify different localizations of storyboards - let unifiedStoryboards = unify(storyboards: storyboards, warning: warning) + let unifiedStoryboards = unifyLocalizations(storyboards: storyboards, warning: warning) let allSegues = allSegueInfos(storyboards: unifiedStoryboards, warning: warning) let viewControllers = viewControllers(segues: allSegues, warning: warning) @@ -120,96 +120,30 @@ public struct Segue { return destinationViewControllerType ?? destinationViewControllerPlaceholderType } - private static func unify(storyboards: [StoryboardResource], warning: (String) -> Void) -> [StoryboardResource] { + private static func unifyLocalizations(storyboards: [StoryboardResource], warning: (String) -> Void) -> [StoryboardResource] { var result: [StoryboardResource] = [] - for siblings in Dictionary(grouping: storyboards, by: \.name).values { - guard let storyboard = siblings.first else { continue } - let r = storyboard.unify(siblings: siblings) - result.append(r.storyboard) - let segues = r.differentSegueIDs - if segues.count > 0 { - let ns = segues.map { "'\($0)'" }.joined(separator: ", ") - warning("Skipping generation of \(segues.count) segues in storyboard '\(storyboard.name)', because segues \(ns) aren't identical in all localizations") - } - } - - return result - } -} - -fileprivate extension StoryboardResource { - struct UnifyResult { - let storyboard: StoryboardResource - let differentSegueIDs: [String] - } - - func unify(siblings: [StoryboardResource]) -> UnifyResult { - var result = self - var segues: [String] = [] - - for storyboard in siblings { - let r = result.unify(storyboard) - segues.append(contentsOf: r.differentSegueIDs) - result = r.storyboard - } - - return UnifyResult(storyboard: result, differentSegueIDs: segues) - } - - func unify(_ other: StoryboardResource) -> UnifyResult { - let lhsVcs = self.viewControllersByIdentifier - let rhsVcs = other.viewControllersByIdentifier + for localizations in Dictionary(grouping: storyboards, by: \.name).values { + guard let storyboard = localizations.first else { continue } + let ur = storyboard.unify(localizations: localizations) - let vcs = lhsVcs.compactMap { (id, lhs) -> ViewController.UnifyResult? in - guard let rhs = rhsVcs[id] else { return nil } - return lhs.unify(rhs) - } - - var result = self - result.viewControllers = vcs.map(\.viewcontroller) - - // Remove fields that haven't been merged - result.locale = .none - result.usedImageIdentifiers = [] - result.usedColorResources = [] - - let different = vcs.flatMap(\.differentSegueIDs) - return UnifyResult(storyboard: result, differentSegueIDs: different.uniqueAndSorted()) - } -} - -private extension StoryboardResource.ViewController { - struct UnifyResult { - let viewcontroller: StoryboardResource.ViewController - let differentSegueIDs: [String] - } + for vur in ur.viewControllerResults.values { + if vur.differentSegueIDs.isEmpty { continue } - func unify(_ other: Self) -> UnifyResult { - let rhsSegues = Dictionary(grouping: other.segues, by: \.identifier) - - var result = self - result.segues = result.segues.filter { l in - guard let ss = rhsSegues[l.identifier], let r = ss.first else { return false } - return l.canUnify(r) - } + let segues = vur.differentSegueIDs.sorted() + let ns = segues.map { "'\($0)'" }.joined(separator: ", ") + warning("Skipping generation of \(segues.count) segues in view controller '\(vur.viewcontroller.storyboardIdentifier ?? vur.viewcontroller.id)' in storyboard '\(storyboard.name)', because segues \(ns) aren't identical in all localizations") + } - let usedIDs = Set(result.segues.map(\.identifier)) - let different = (self.segues + other.segues).filter { s in - !usedIDs.contains(s.identifier) + result.append(ur.storyboard) } - return .init(viewcontroller: result, differentSegueIDs: different.map(\.identifier)) + return result } } -private extension StoryboardResource.Segue { - func canUnify(_ other: Self) -> Bool { - self == other - } -} private extension StoryboardResource.ViewControllerPlaceholder { enum ResolvedResult { diff --git a/Sources/RswiftGenerators/Storyboard+Generator.swift b/Sources/RswiftGenerators/Storyboard+Generator.swift index 0e862385..2e6544b9 100644 --- a/Sources/RswiftGenerators/Storyboard+Generator.swift +++ b/Sources/RswiftGenerators/Storyboard+Generator.swift @@ -64,115 +64,34 @@ extension StoryboardResource { for localizations in Dictionary(grouping: storyboards, by: \.name).values { guard let storyboard = localizations.first else { continue } - switch storyboard.unify(localizations: localizations) { - case .success(let r): - result.append(r.storyboard) - - let vcs = r.notUnifiedStoryboardIDs - if vcs.count > 0 { - let ns = vcs.map { "'\($0)'" }.joined(separator: ", ") - warning("Skipping generation of \(vcs.count) view controllers in storyboard '\(storyboard.name)', because view controllers \(ns) don't exist in all localizations, or have different classes") - } + let ur = storyboard.unify(localizations: localizations) - case .differentInitialViewController: - warning("Skipping generation of storyboard '\(storyboard.name)', it has different initial view controllers in different localizations") - case .differentDeploymentTargets: - warning("Skipping generation of storyboard '\(storyboard.name)', it has different deployment targets in different localizations") - } - } + let diffs: [String] = [ + ur.differentInitialViewController ? "initial view controllers" : nil, + ur.differentDeploymentTargets ? "deployment targets" : nil, + ].compactMap { $0 } - return result - } -} - -private extension StoryboardResource { - enum UnifyResult { - case success(Result) - case differentInitialViewController - case differentDeploymentTargets - - struct Result { - let storyboard: StoryboardResource - let notUnifiedStoryboardIDs: [String] - } - - func flatMap(_ transform: (StoryboardResource) -> UnifyResult) -> UnifyResult { - switch self { - case .success(let lhs): - switch transform(lhs.storyboard) { - case .success(let merged): - let r = Result( - storyboard: merged.storyboard, - notUnifiedStoryboardIDs: (lhs.notUnifiedStoryboardIDs + merged.notUnifiedStoryboardIDs).uniqueAndSorted() - ) - return .success(r) - - case .differentInitialViewController: - return .differentInitialViewController - - case .differentDeploymentTargets: - return .differentDeploymentTargets - } - - case .differentInitialViewController: - return .differentInitialViewController + if diffs.count > 0 { + warning("Skipping generation of storyboard '\(storyboard.name)', because \(diffs.joined(separator: ", ")) don't match in all localizations") + continue + } - case .differentDeploymentTargets: - return .differentDeploymentTargets + let vcs = ur.differentViewControllerIDs.sorted() + if vcs.count > 0 { + let ns = vcs.map { "'\($0)'" }.joined(separator: ", ") + warning("Skipping generation of \(vcs.count) view controllers in storyboard '\(storyboard.name)', because view controllers \(ns) don't exist (with same class) in all localizations") } - } - } - func unify(localizations: [StoryboardResource]) -> UnifyResult { - var result = UnifyResult.success(.init(storyboard: self, notUnifiedStoryboardIDs: [])) - for storyboard in localizations { - result = result.flatMap { $0.unify(storyboard) } + result.append(ur.storyboard) } return result } +} - func unify(_ other: StoryboardResource) -> UnifyResult { - let lhsVcs = self.viewControllersByIdentifier - let rhsVcs = other.viewControllersByIdentifier - - let vcs = lhsVcs.compactMap { (id, lhs) -> ViewController? in - guard let rhs = rhsVcs[id] else { return nil } - return lhs.canUnify(with: rhs) ? lhs : nil - } - - var result = self - result.viewControllers = vcs - - // Merged used images/colors from both localizations, they all need to be validated - result.usedImageIdentifiers = Array(Set(self.usedImageIdentifiers).union(other.usedImageIdentifiers)) - result.usedColorResources = Array(Set(self.usedColorResources).union(other.usedColorResources)) - - // Remove locale, this is a merger of both - result.locale = .none - - let allVcs = self.viewControllers + other.viewControllers - let usedIds = Set(vcs.map(\.id)) - let skipped = allVcs.compactMap { vc -> String? in - usedIds.contains(vc.id) ? nil : vc.storyboardIdentifier - } - - if self.initialViewControllerIdentifier != other.initialViewControllerIdentifier { - return .differentInitialViewController - } - - if self.deploymentTarget != other.deploymentTarget { - return .differentDeploymentTargets - } - - return .success(.init( - storyboard: result, - notUnifiedStoryboardIDs: skipped.uniqueAndSorted() - )) - } - +private extension StoryboardResource { func generateStruct(prefix: SwiftIdentifier, warning: (String) -> Void) -> Struct { let nameIdentifier = SwiftIdentifier(rawValue: "name") let bundleIdentifier = SwiftIdentifier(name: "bundle") @@ -260,13 +179,6 @@ private extension StoryboardResource { } private extension StoryboardResource.ViewController { - - func canUnify(with other: Self) -> Bool { - self.id == other.id - && self.storyboardIdentifier == other.storyboardIdentifier - && self.type == other.type - } - var genericTypeReference: TypeReference { TypeReference( module: .rswiftResources, diff --git a/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift b/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift index 7f0ebf0b..10a18932 100644 --- a/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift +++ b/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift @@ -39,6 +39,14 @@ extension SegueIdentifier where Segue: UIStoryboardSegue { } } +extension SegueIdentifier where Segue: UIStoryboardSegue, Source: UIViewController, Destination: UIViewController { + /// Trigger a segue by providing a source, destination and handler + public func perform(source: Source, destination: Destination, handler: @escaping () -> Void) { + let segue = Segue(identifier: identifier, source: source, destination: destination, performHandler: handler) + segue.perform() + } +} + extension TypedSegue { /** Returns typed information about the given segue, fails if the segue types don't exactly match types. diff --git a/Sources/RswiftResources/NibResource.swift b/Sources/RswiftResources/NibResource.swift index eb54b755..b4d90cf5 100644 --- a/Sources/RswiftResources/NibResource.swift +++ b/Sources/RswiftResources/NibResource.swift @@ -14,7 +14,7 @@ public struct NibResource { public var locale: LocaleReference public let deploymentTarget: DeploymentTarget? public let rootViews: [TypeReference] - public let reusables: [Reusable] + public var reusables: [Reusable] public let generatedIds: [String] public var usedImageIdentifiers: [NameCatalog] public var usedColorResources: [NameCatalog] @@ -42,3 +42,69 @@ public struct NibResource { self.usedAccessibilityIdentifiers = usedAccessibilityIdentifiers } } + +extension NibResource { + public struct UnifyResult { + public let resource: NibResource + public let differentNames: Bool + public let differentRootViews: Bool + public let differentReusables: Set + public let differentInitialReusables: Bool + public let differentDeploymentTargets: Bool + + public func flatMap(_ transform: (NibResource) -> UnifyResult) -> UnifyResult { + let r = transform(resource) + + return UnifyResult( + resource: r.resource, + differentNames: r.differentNames || self.differentNames, + differentRootViews: r.differentRootViews || self.differentRootViews, + differentReusables: r.differentReusables.union(self.differentReusables), + differentInitialReusables: r.differentInitialReusables || self.differentInitialReusables, + differentDeploymentTargets: r.differentDeploymentTargets || self.differentDeploymentTargets + ) + } + } + + public func unify(localizations: [NibResource]) -> UnifyResult { + var result = UnifyResult( + resource: self, + differentNames: false, + differentRootViews: false, + differentReusables: [], + differentInitialReusables: false, + differentDeploymentTargets: false + ) + + for nib in localizations { + result = result.flatMap { $0.unify(nib) } + } + + return result + } + + public func unify(_ other: NibResource) -> UnifyResult { + + // Merged used images/colors from both localizations, they all need to be validated + var result = self + result.usedImageIdentifiers = Array(Set(self.usedImageIdentifiers).union(other.usedImageIdentifiers)) + result.usedColorResources = Array(Set(self.usedColorResources).union(other.usedColorResources)) + + // Only keep reusables that exist in both localizations + result.reusables = self.reusables.filter { other.reusables.contains($0) } + + // Keep other fields from self only, if they are different, that is recorded in UnifyResult + + // Remove locale, this is a merger of both + result.locale = .none + + return UnifyResult( + resource: result, + differentNames: name != other.name, + differentRootViews: rootViews.first != other.rootViews.first, + differentReusables: Set(reusables).symmetricDifference(other.reusables), + differentInitialReusables: reusables.first != other.reusables.first, + differentDeploymentTargets: deploymentTarget != other.deploymentTarget + ) + } +} diff --git a/Sources/RswiftResources/StoryboardResource.swift b/Sources/RswiftResources/StoryboardResource.swift index f058a274..7d84bdeb 100644 --- a/Sources/RswiftResources/StoryboardResource.swift +++ b/Sources/RswiftResources/StoryboardResource.swift @@ -105,3 +105,136 @@ public struct StoryboardResource { return Dictionary(uniqueKeysWithValues: pairs) } } + +extension StoryboardResource { + public struct UnifyResult { + public let storyboard: StoryboardResource + public let viewControllerResults: [String: StoryboardResource.ViewController.UnifyResult] + public let differentInitialViewController: Bool + public let differentDeploymentTargets: Bool + public let differentViewControllerIDs: Set + public let differentReusables: Set + + public func flatMap(_ transform: (StoryboardResource) -> UnifyResult) -> UnifyResult { + let r = transform(storyboard) + + + return UnifyResult( + storyboard: r.storyboard, + viewControllerResults: viewControllerResults.merging(r.viewControllerResults) { $0.unify($1) }, + differentInitialViewController: differentInitialViewController || r.differentInitialViewController, + differentDeploymentTargets: differentDeploymentTargets || r.differentDeploymentTargets, + differentViewControllerIDs: differentViewControllerIDs.union(r.differentViewControllerIDs), + differentReusables: differentReusables.union(r.differentReusables) + ) + } + } + + public func unify(localizations: [StoryboardResource]) -> UnifyResult { + var result = UnifyResult( + storyboard: self, + viewControllerResults: [:], + differentInitialViewController: false, + differentDeploymentTargets: false, + differentViewControllerIDs: [], + differentReusables: [] + ) + + for storyboard in localizations { + result = result.flatMap { $0.unify(storyboard) } + } + + return result + } + + public func unify(_ other: StoryboardResource) -> UnifyResult { + let lhsVcs = self.viewControllersByIdentifier + let rhsVcs = other.viewControllersByIdentifier + + let unifiedViewControllers = lhsVcs.compactMap { (id, lhs) -> StoryboardResource.ViewController.UnifyResult? in + guard let rhs = rhsVcs[id] else { return nil } + return lhs.unify(rhs) + } + + let vcs = unifiedViewControllers.compactMap { ur -> StoryboardResource.ViewController? in + if ur.differentTypes || ur.differentStoryboardIdentifiers { return nil } + return ur.viewcontroller + } + + var result = self + result.viewControllers = vcs + + // Merged used images/colors from both localizations, they all need to be validated + result.usedImageIdentifiers = Array(Set(self.usedImageIdentifiers).union(other.usedImageIdentifiers)) + result.usedColorResources = Array(Set(self.usedColorResources).union(other.usedColorResources)) + + // Only keep reusables that exist in both localizations + result.reusables = self.reusables.filter { other.reusables.contains($0) } + + // Keep other fields from self only, if they are different, that is recorded in UnifyResult + + // Remove locale, this is a merger of both + result.locale = .none + + let allVcs = self.viewControllers + other.viewControllers + let usedIds = Set(vcs.map(\.id)) + let skipped = allVcs.compactMap { vc -> String? in + usedIds.contains(vc.id) ? nil : vc.storyboardIdentifier + } + + return UnifyResult( + storyboard: result, + viewControllerResults: Dictionary(uniqueKeysWithValues: unifiedViewControllers.map { ($0.viewcontroller.id, $0) }), + differentInitialViewController: initialViewControllerIdentifier != other.initialViewControllerIdentifier, + differentDeploymentTargets: deploymentTarget != other.deploymentTarget, + differentViewControllerIDs: Set(skipped), + differentReusables: Set(reusables).symmetricDifference(other.reusables) + ) + } +} + +extension StoryboardResource.ViewController { + public struct UnifyResult { + public let viewcontroller: StoryboardResource.ViewController + public let differentStoryboardIdentifiers: Bool + public let differentTypes: Bool + public let differentSegueIDs: Set + + func unify(_ other: Self) -> Self { + .init( + viewcontroller: viewcontroller, + differentStoryboardIdentifiers: differentStoryboardIdentifiers || other.differentStoryboardIdentifiers, + differentTypes: differentTypes || other.differentTypes, + differentSegueIDs: differentSegueIDs.union(other.differentSegueIDs) + ) + } + } + + public func unify(_ other: Self) -> UnifyResult { + let rhsSegues = Dictionary(grouping: other.segues, by: \.identifier) + + var result = self + result.segues = result.segues.filter { l in + guard let ss = rhsSegues[l.identifier], let r = ss.first else { return false } + return l.canUnify(r) + } + + let usedIDs = Set(result.segues.map(\.identifier)) + let different = (self.segues + other.segues).filter { s in + !usedIDs.contains(s.identifier) + } + + return UnifyResult( + viewcontroller: result, + differentStoryboardIdentifiers: storyboardIdentifier != other.storyboardIdentifier, + differentTypes: type != other.type, + differentSegueIDs: Set(different.map(\.identifier)) + ) + } +} + +private extension StoryboardResource.Segue { + func canUnify(_ other: Self) -> Bool { + self == other + } +} From b09c97919e45e2975634fe4ee4811c1484f6d185 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Tue, 18 Oct 2022 16:15:16 +0200 Subject: [PATCH 090/161] Unify view controllers in storyboard by id --- Sources/RswiftCore/RswiftCore.swift | 1 + Sources/RswiftGenerators/Segue+Generator.swift | 2 -- Sources/RswiftResources/StoryboardResource.swift | 16 +++++----------- Sources/rswift/App.swift | 8 ++++---- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 7043e08f..80a7711a 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -290,6 +290,7 @@ public struct RswiftCore { let imports = Set(s.allModuleReferences.compactMap(\.name)) .union(importModules) + .subtracting([productModuleName].compactMap { $0 }) .sorted() .map { "import \($0)" } .joined(separator: "\n") diff --git a/Sources/RswiftGenerators/Segue+Generator.swift b/Sources/RswiftGenerators/Segue+Generator.swift index b4796c12..8ee3548f 100644 --- a/Sources/RswiftGenerators/Segue+Generator.swift +++ b/Sources/RswiftGenerators/Segue+Generator.swift @@ -123,12 +123,10 @@ public struct Segue { private static func unifyLocalizations(storyboards: [StoryboardResource], warning: (String) -> Void) -> [StoryboardResource] { var result: [StoryboardResource] = [] - for localizations in Dictionary(grouping: storyboards, by: \.name).values { guard let storyboard = localizations.first else { continue } let ur = storyboard.unify(localizations: localizations) - for vur in ur.viewControllerResults.values { if vur.differentSegueIDs.isEmpty { continue } diff --git a/Sources/RswiftResources/StoryboardResource.swift b/Sources/RswiftResources/StoryboardResource.swift index 7d84bdeb..588037f6 100644 --- a/Sources/RswiftResources/StoryboardResource.swift +++ b/Sources/RswiftResources/StoryboardResource.swift @@ -9,7 +9,7 @@ import Foundation -public struct StoryboardResource { +public struct StoryboardResource: Equatable { public let name: String public var locale: LocaleReference public let deploymentTarget: DeploymentTarget? @@ -96,13 +96,8 @@ public struct StoryboardResource { .first } - public var viewControllersByIdentifier: [String: ViewController] { - let pairs = self.viewControllers.compactMap { vc -> (String, ViewController)? in - guard let identifier = vc.storyboardIdentifier else { return nil } - return (identifier, vc) - } - - return Dictionary(uniqueKeysWithValues: pairs) + public var viewControllersById: [String: ViewController] { + Dictionary(uniqueKeysWithValues: viewControllers.map { ($0.id, $0) }) } } @@ -118,7 +113,6 @@ extension StoryboardResource { public func flatMap(_ transform: (StoryboardResource) -> UnifyResult) -> UnifyResult { let r = transform(storyboard) - return UnifyResult( storyboard: r.storyboard, viewControllerResults: viewControllerResults.merging(r.viewControllerResults) { $0.unify($1) }, @@ -148,8 +142,8 @@ extension StoryboardResource { } public func unify(_ other: StoryboardResource) -> UnifyResult { - let lhsVcs = self.viewControllersByIdentifier - let rhsVcs = other.viewControllersByIdentifier + let lhsVcs = self.viewControllersById + let rhsVcs = other.viewControllersById let unifiedViewControllers = lhsVcs.compactMap { (id, lhs) -> StoryboardResource.ViewController.UnifyResult? in guard let rhs = rhsVcs[id] else { return nil } diff --git a/Sources/rswift/App.swift b/Sources/rswift/App.swift index 8a0043c0..0dad2861 100644 --- a/Sources/rswift/App.swift +++ b/Sources/rswift/App.swift @@ -53,8 +53,8 @@ extension App { @Option(help: "Override environment variable \(EnvironmentKeys.target)") var target: String? -// @Option(help: "Override environment variable \(EnvironmentKeys.productModuleName)") -// var productModuleName: String? + @Option(help: "Override environment variable \(EnvironmentKeys.productModuleName)") + var productModuleName: String? @Option(help: "Override environment variable \(EnvironmentKeys.infoPlistFile)") var infoPlistFile: String? @@ -91,7 +91,7 @@ extension App { let xcodeprojPath = try self.xcodeproj ?? processInfo.environmentVariable(name: EnvironmentKeys.xcodeproj) let targetName = try self.target ?? processInfo.environmentVariable(name: EnvironmentKeys.target) -// let productModuleName = try self.productModuleName ?? processInfo.environmentVariable(name: EnvironmentKeys.productModuleName) + let productModuleName = self.productModuleName ?? processInfo.environment[EnvironmentKeys.productModuleName] let infoPlistFile = self.infoPlistFile ?? processInfo.environment[EnvironmentKeys.infoPlistFile] let codeSignEntitlements = self.codeSignEntitlements ?? processInfo.environment[EnvironmentKeys.codeSignEntitlements] @@ -114,7 +114,7 @@ extension App { importModules: imports, xcodeprojURL: URL(fileURLWithPath: xcodeprojPath), targetName: targetName, - productModuleName: nil, + productModuleName: productModuleName, infoPlistFile: infoPlistFile.map(URL.init(fileURLWithPath:)), codeSignEntitlements: codeSignEntitlements.map(URL.init(fileURLWithPath:)), rswiftIgnoreURL: rswiftIgnoreURL, From 5357ce45715d08277a09aa373dd4d5558bbe5770 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Wed, 19 Oct 2022 15:04:23 +0200 Subject: [PATCH 091/161] Make cmd options optional --- Sources/RswiftCore/RswiftCore.swift | 2 +- Sources/RswiftParsers/Shared/Xcodeproj.swift | 4 ++ Sources/rswift/App.swift | 49 ++++++++++++++++---- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 80a7711a..b02664eb 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -90,7 +90,7 @@ public struct RswiftCore { infoPlistFile: infoPlistFile, codeSignEntitlements: codeSignEntitlements, sourceTreeURLs: sourceTreeURLs, - warning: { print("[warning]", $0) } + warning: { print("warning: [R.swift]", $0) } ) let structName = SwiftIdentifier(rawValue: "_R") diff --git a/Sources/RswiftParsers/Shared/Xcodeproj.swift b/Sources/RswiftParsers/Shared/Xcodeproj.swift index eb9dd5d8..e9815c1a 100644 --- a/Sources/RswiftParsers/Shared/Xcodeproj.swift +++ b/Sources/RswiftParsers/Shared/Xcodeproj.swift @@ -42,6 +42,10 @@ public struct Xcodeproj: SupportedExtensions { self.knownAssetTags = projectFile.project.knownAssetTags } + public var allTargets: [PBXTarget] { + projectFile.project.targets.compactMap { $0.value } + } + private func findTarget(name: String) throws -> PBXTarget { // Look for target in project file let allTargets = projectFile.project.targets.compactMap { $0.value } diff --git a/Sources/rswift/App.swift b/Sources/rswift/App.swift index 0dad2861..9d61e70c 100644 --- a/Sources/rswift/App.swift +++ b/Sources/rswift/App.swift @@ -90,17 +90,20 @@ extension App { let processInfo = ProcessInfo() let xcodeprojPath = try self.xcodeproj ?? processInfo.environmentVariable(name: EnvironmentKeys.xcodeproj) - let targetName = try self.target ?? processInfo.environmentVariable(name: EnvironmentKeys.target) + let xcodeprojURL = URL(fileURLWithPath: xcodeprojPath) + + let targetName = try self.getTargetName(xcodeprojURL: xcodeprojURL) let productModuleName = self.productModuleName ?? processInfo.environment[EnvironmentKeys.productModuleName] let infoPlistFile = self.infoPlistFile ?? processInfo.environment[EnvironmentKeys.infoPlistFile] let codeSignEntitlements = self.codeSignEntitlements ?? processInfo.environment[EnvironmentKeys.codeSignEntitlements] - let sourceTreeURLs = try SourceTreeURLs( - builtProductsDirURL: URL(fileURLWithPath: builtProductsDir ?? processInfo.environmentVariable(name: EnvironmentKeys.builtProductsDir)), - developerDirURL: URL(fileURLWithPath: developerDir ?? processInfo.environmentVariable(name: EnvironmentKeys.developerDir)), - sourceRootURL: URL(fileURLWithPath: sourceRoot ?? processInfo.environmentVariable(name: EnvironmentKeys.sourceRoot)), - sdkRootURL: URL(fileURLWithPath: sdkRoot ?? processInfo.environmentVariable(name: EnvironmentKeys.sdkRoot)), - platformURL: URL(fileURLWithPath: platformDir ?? processInfo.environmentVariable(name: EnvironmentKeys.platformDir)) + // If no environment is provided, we're not running inside Xcode, fallback to names + let sourceTreeURLs = SourceTreeURLs( + builtProductsDirURL: URL(fileURLWithPath: builtProductsDir ?? processInfo.environment[EnvironmentKeys.builtProductsDir] ?? EnvironmentKeys.builtProductsDir), + developerDirURL: URL(fileURLWithPath: developerDir ?? processInfo.environment[EnvironmentKeys.developerDir] ?? EnvironmentKeys.developerDir), + sourceRootURL: URL(fileURLWithPath: sourceRoot ?? processInfo.environment[EnvironmentKeys.sourceRoot] ?? EnvironmentKeys.sourceRoot), + sdkRootURL: URL(fileURLWithPath: sdkRoot ?? processInfo.environment[EnvironmentKeys.sdkRoot] ?? EnvironmentKeys.sdkRoot), + platformURL: URL(fileURLWithPath: platformDir ?? processInfo.environment[EnvironmentKeys.platformDir] ?? EnvironmentKeys.platformDir) ) let outputURL = URL(fileURLWithPath: outputPath) @@ -112,7 +115,7 @@ extension App { generators: generators.isEmpty ? Generator.allCases : generators, accessLevel: accessLevel, importModules: imports, - xcodeprojURL: URL(fileURLWithPath: xcodeprojPath), + xcodeprojURL: xcodeprojURL, targetName: targetName, productModuleName: productModuleName, infoPlistFile: infoPlistFile.map(URL.init(fileURLWithPath:)), @@ -122,7 +125,35 @@ extension App { ) // print("RSWIFT", outputURL) - try core.developRun() + do { + try core.developRun() + } catch let error as ResourceParsingError { + throw ValidationError(error.description) + } + } + + func getTargetName(xcodeprojURL: URL) throws -> String { + let processInfo = ProcessInfo() + if let targetName = self.target ?? processInfo.environment[EnvironmentKeys.target] { + return targetName + } + + let targets = try? Xcodeproj(url: xcodeprojURL, warning: { _ in }).allTargets + + if let targets, let target = targets.first, targets.count == 1 { + return target.name + } + + if let targets, targets.count > 0 { + let lines = [ + "Missing argument --target", + "Available targets:" + ] + targets.map { "- \($0.name)" } + + throw ValidationError(lines.joined(separator: "\n")) + } + + throw ValidationError("Missing argument --target") } } } From 7023dab9092556d757e2052d815e4eaa781b69a0 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Sat, 22 Oct 2022 16:00:56 +0200 Subject: [PATCH 092/161] Start on SPM plugin --- Package.resolved | 60 +++----- Package.swift | 10 +- Plugins/RswiftGenerateResources/Plugin.swift | 64 +++++++++ Sources/RswiftCore/RswiftCore.swift | 100 ++++++------- Sources/RswiftGenerators/Nib+Generator.swift | 2 +- .../Storyboard+Generator.swift | 2 +- .../{Project.swift => ProjectResources.swift} | 66 +++++---- Sources/rswift/App.swift | 132 ++++++++++-------- 8 files changed, 243 insertions(+), 193 deletions(-) create mode 100644 Plugins/RswiftGenerateResources/Plugin.swift rename Sources/RswiftParsers/{Project.swift => ProjectResources.swift} (77%) diff --git a/Package.resolved b/Package.resolved index d3e36446..fde89f75 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,43 +1,23 @@ { - "object": { - "pins": [ - { - "package": "Commander", - "repositoryURL": "https://github.com/kylef/Commander.git", - "state": { - "branch": null, - "revision": "4a1f2fb82fb6cef613c4a25d2e38f702e4d812c2", - "version": "0.9.2" - } - }, - { - "package": "Spectre", - "repositoryURL": "https://github.com/kylef/Spectre.git", - "state": { - "branch": null, - "revision": "26cc5e9ae0947092c7139ef7ba612e34646086c7", - "version": "0.10.1" - } - }, - { - "package": "swift-argument-parser", - "repositoryURL": "https://github.com/apple/swift-argument-parser", - "state": { - "branch": null, - "revision": "9f39744e025c7d377987f30b03770805dcb0bcd1", - "version": "1.1.4" - } - }, - { - "package": "XcodeEdit", - "repositoryURL": "https://github.com/tomlokhorst/XcodeEdit", - "state": { - "branch": null, - "revision": "99547c5af5850155b52c43b716ba1b094b02a3b2", - "version": "2.8.0" - } + "pins" : [ + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "9f39744e025c7d377987f30b03770805dcb0bcd1", + "version" : "1.1.4" } - ] - }, - "version": 1 + }, + { + "identity" : "xcodeedit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tomlokhorst/XcodeEdit", + "state" : { + "revision" : "99547c5af5850155b52c43b716ba1b094b02a3b2", + "version" : "2.8.0" + } + } + ], + "version" : 2 } diff --git a/Package.swift b/Package.swift index 96159bd0..a130f2a1 100644 --- a/Package.swift +++ b/Package.swift @@ -11,11 +11,10 @@ let package = Package( ], products: [ .executable(name: "rswift", targets: ["rswift"]), - .executable(name: "rswift-legacy", targets: ["rswift-legacy"]), - .library(name: "RswiftLibrary", targets: ["RswiftResources"]) + .library(name: "RswiftLibrary", targets: ["RswiftResources"]), + .plugin(name: "RswiftGenerateResources", targets: ["RswiftGenerateResources"]), ], dependencies: [ - .package(url: "https://github.com/kylef/Commander.git", from: "0.8.0"), .package(url: "https://github.com/tomlokhorst/XcodeEdit", from: "2.8.0"), .package(url: "https://github.com/apple/swift-argument-parser", from: "1.1.0"), ], @@ -37,9 +36,6 @@ let package = Package( .product(name: "ArgumentParser", package: "swift-argument-parser"), ]), - // Legacy code - .target(name: "rswift-legacy", dependencies: ["RswiftCoreLegacy"]), - .target(name: "RswiftCoreLegacy", dependencies: ["Commander", "XcodeEdit"]), - .testTarget(name: "RswiftCoreLegacyTests", dependencies: ["RswiftCoreLegacy"]), + .plugin(name: "RswiftGenerateResources", capability: .buildTool(), dependencies: ["rswift", "RswiftCore"]), ] ) diff --git a/Plugins/RswiftGenerateResources/Plugin.swift b/Plugins/RswiftGenerateResources/Plugin.swift new file mode 100644 index 00000000..dad55dca --- /dev/null +++ b/Plugins/RswiftGenerateResources/Plugin.swift @@ -0,0 +1,64 @@ +// +// Plugin.swift +// +// +// Created by Tom Lokhorst on 2022-10-19. +// + +import Foundation +import PackagePlugin + +@main +struct RswiftGenerateResources: BuildToolPlugin { + func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] { + guard let target = target as? SourceModuleTarget else { return [] } + + let resourcesDirectoryPath = context.pluginWorkDirectory + .appending(subpath: target.name) + .appending(subpath: "Resources") + + let rswiftPath = resourcesDirectoryPath.appending(subpath: "R.generated.swift") + + let sourceFiles = target.sourceFiles + .map(\.path.string) + + let inputFilesArguments = sourceFiles + .flatMap { ["--input-files", $0 ] } + +// let rswift = try context.tool(named: "rswift") + return [ + .buildCommand( + displayName: "My display name 1", + executable: Path("/Users/tom/Projects/R.swift/.build/debug/rswift"), + arguments: ["generate", rswiftPath.string, "--target", target.name] + inputFilesArguments + ), + ] + } +} + +#if canImport(XcodeProjectPlugin) +import XcodeProjectPlugin + +extension RswiftGenerateResources: XcodeBuildToolPlugin { + func createBuildCommands(context: XcodePluginContext, target: XcodeTarget) throws -> [Command] { + Diagnostics.error("\(context)") + let resourcesDirectoryPath = context.pluginWorkDirectory + .appending(subpath: target.displayName) + .appending(subpath: "Resources") + + let rswiftPath = resourcesDirectoryPath.appending(subpath: "R.generated.swift") + + Diagnostics.warning("HELLO WORLD " + target.inputFiles.filter { $0.type == .resource }.map(\.path.string).joined(separator: ", ")) + + return [ + .prebuildCommand( + displayName: "My display name 2", + executable: Path("/Users/tom/Projects/R.swift/.build/debug/rswift"), + arguments: ["generate", rswiftPath.string, "--target", target.displayName], + outputFilesDirectory: resourcesDirectoryPath + ) + ] + } +} + +#endif diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index b02664eb..f0176b41 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -41,7 +41,6 @@ public struct RswiftCore { let generators: [Generator] let accessLevel: AccessLevel let importModules: [String] - let xcodeprojURL: URL let targetName: String let productModuleName: String? let infoPlistFile: URL? @@ -56,7 +55,6 @@ public struct RswiftCore { generators: [Generator], accessLevel: AccessLevel, importModules: [String], - xcodeprojURL: URL, targetName: String, productModuleName: String?, infoPlistFile: URL?, @@ -68,7 +66,6 @@ public struct RswiftCore { self.generators = generators self.accessLevel = accessLevel self.importModules = importModules - self.xcodeprojURL = xcodeprojURL self.targetName = targetName self.productModuleName = productModuleName self.infoPlistFile = infoPlistFile @@ -79,96 +76,114 @@ public struct RswiftCore { self.sourceTreeURLs = sourceTreeURLs } - // Temporary function for use during development - public func developRun() throws { - let start = Date() + public func generateFromXcodeproj(url xcodeprojURL: URL) throws { + let warning: (String) -> Void = { print("warning: [R.swift]", $0) } - let project = try Project.parseTarget( - name: targetName, - xcodeprojURL: xcodeprojURL, + let xcodeproj = try Xcodeproj(url: xcodeprojURL, warning: warning) + let resources = try ProjectResources.parseXcodeproj( + xcodeproj: xcodeproj, + targetName: targetName, rswiftIgnoreURL: rswiftIgnoreURL, infoPlistFile: infoPlistFile, codeSignEntitlements: codeSignEntitlements, sourceTreeURLs: sourceTreeURLs, - warning: { print("warning: [R.swift]", $0) } + parseFontsAsFiles: true, + parseImagesAsFiles: true ) + try generateFromProjectResources(resources: resources, developmentRegion: xcodeproj.developmentRegion, knownAssetTags: xcodeproj.knownAssetTags) + } + + public func generateFromFiles(inputFiles: [String]) throws { + let urls = inputFiles.compactMap(URL.init(string:)) + let resources = try ProjectResources.parseURLs( + urls: urls, + infoPlists: [], + codeSignEntitlements: [], + parseFontsAsFiles: true, + parseImagesAsFiles: true + ) + + try generateFromProjectResources(resources: resources, developmentRegion: "en", knownAssetTags: nil) + } + + private func generateFromProjectResources(resources: ProjectResources, developmentRegion: String, knownAssetTags: [String]?) throws { let structName = SwiftIdentifier(rawValue: "_R") let qualifiedName = structName let segueStruct = Segue.generateStruct( - storyboards: project.storyboards, + storyboards: resources.storyboards, prefix: qualifiedName ) let imageStruct = ImageResource.generateStruct( - catalogs: project.assetCatalogs, - toplevel: project.images, + catalogs: resources.assetCatalogs, + toplevel: resources.images, prefix: qualifiedName ) let colorStruct = ColorResource.generateStruct( - catalogs: project.assetCatalogs, + catalogs: resources.assetCatalogs, prefix: qualifiedName ) let dataStruct = DataResource.generateStruct( - catalogs: project.assetCatalogs, + catalogs: resources.assetCatalogs, prefix: qualifiedName ) let fileStruct = FileResource.generateStruct( - resources: project.files, + resources: resources.files, prefix: qualifiedName ) let idStruct = AccessibilityIdentifier.generateStruct( - nibs: project.nibs, - storyboards: project.storyboards, + nibs: resources.nibs, + storyboards: resources.storyboards, prefix: qualifiedName ) let fontStruct = FontResource.generateStruct( - resources: project.fonts, + resources: resources.fonts, prefix: qualifiedName ) let storyboardStruct = StoryboardResource.generateStruct( - storyboards: project.storyboards, + storyboards: resources.storyboards, prefix: qualifiedName ) let infoStruct = PropertyListResource.generateInfoStruct( resourceName: "info", - plists: project.infoPlists, + plists: resources.infoPlists, prefix: qualifiedName ) let entitlementsStruct = PropertyListResource.generateStruct( resourceName: "entitlements", - plists: project.codeSignEntitlements, + plists: resources.codeSignEntitlements, prefix: qualifiedName ) let nibStruct = NibResource.generateStruct( - nibs: project.nibs, + nibs: resources.nibs, prefix: qualifiedName ) let reuseIdentifierStruct = Reusable.generateStruct( - nibs: project.nibs, - storyboards: project.storyboards, + nibs: resources.nibs, + storyboards: resources.storyboards, prefix: qualifiedName ) let stringStruct = LocalizableStrings.generateStruct( - resources: project.localizableStrings, - developmentLanguage: project.xcodeproj.developmentRegion, + resources: resources.localizableStrings, + developmentLanguage: developmentRegion, prefix: qualifiedName ) let projectStruct = Struct(name: SwiftIdentifier(name: "project")) { - LetBinding(name: SwiftIdentifier(name: "developmentRegion"), valueCodeString: #""\#(project.xcodeproj.developmentRegion)""#) + LetBinding(name: SwiftIdentifier(name: "developmentRegion"), valueCodeString: #""\#(developmentRegion)""#) - if let knownAssetTags = project.xcodeproj.knownAssetTags { + if let knownAssetTags { LetBinding(name: SwiftIdentifier(name: "knownAssetTags"), valueCodeString: "\(knownAssetTags)") } } @@ -307,32 +322,5 @@ public struct RswiftCore { \(mainLet) """ try code.write(to: outputURL, atomically: true, encoding: .utf8) - /* - print(s.prettyPrint()) - - print() - - print("let S = _S(bundle: Bundle.main)") - print("") - print("extension R {") - print(" static let string = S.string") - print(" static let data = S.data") - print(" static let color = S.color") - print(" static let image = S.image") - print(" static let font = S.font") - print(" static let segue = S.segue") - print(" static let file = S.file") - print(" static let storyboard = S.storyboard") - print(" static let entitlements = S.entitlements") - print(" static let info = S.info") - print(" static let nib = S.nib") - print(" static let reuseIdentifier = S.reuseIdentifier") - print(" static let id = S.id") - print("}") - */ - - let _ = Date().timeIntervalSince(start) -// print("TOTAL", Date().timeIntervalSince(start)) -// print() } } diff --git a/Sources/RswiftGenerators/Nib+Generator.swift b/Sources/RswiftGenerators/Nib+Generator.swift index 16cce8de..3a00377c 100644 --- a/Sources/RswiftGenerators/Nib+Generator.swift +++ b/Sources/RswiftGenerators/Nib+Generator.swift @@ -38,7 +38,7 @@ extension NibResource { } } - private static func generateValidate(nibs: some Collection) -> Function { + private static func generateValidate(nibs: [NibResource]) -> Function { Function( comments: [], name: .init(name: "validate"), diff --git a/Sources/RswiftGenerators/Storyboard+Generator.swift b/Sources/RswiftGenerators/Storyboard+Generator.swift index 2e6544b9..5218574d 100644 --- a/Sources/RswiftGenerators/Storyboard+Generator.swift +++ b/Sources/RswiftGenerators/Storyboard+Generator.swift @@ -144,7 +144,7 @@ private extension StoryboardResource { } } - func generateValidate(viewControllers: some Collection) -> Function { + func generateValidate(viewControllers: [StoryboardResource.ViewController]) -> Function { let validateImagesLines = self.usedImageIdentifiers.uniqueAndSorted() .map { nameCatalog -> String in if nameCatalog.isSystemCatalog { diff --git a/Sources/RswiftParsers/Project.swift b/Sources/RswiftParsers/ProjectResources.swift similarity index 77% rename from Sources/RswiftParsers/Project.swift rename to Sources/RswiftParsers/ProjectResources.swift index 11b1b33e..6d456d81 100644 --- a/Sources/RswiftParsers/Project.swift +++ b/Sources/RswiftParsers/ProjectResources.swift @@ -1,5 +1,5 @@ // -// File.swift +// ProjectResources.swift // // // Created by Tom Lokhorst on 2022-07-29. @@ -9,7 +9,7 @@ import Foundation import XcodeEdit import RswiftResources -public struct Project { +public struct ProjectResources { public let assetCatalogs: [AssetCatalog] public let files: [FileResource] public let fonts: [FontResource] @@ -19,20 +19,17 @@ public struct Project { public let storyboards: [StoryboardResource] public let infoPlists: [PropertyListResource] public let codeSignEntitlements: [PropertyListResource] - public let xcodeproj: Xcodeproj - public static func parseTarget( - name targetName: String, - xcodeprojURL: URL, + public static func parseXcodeproj( + xcodeproj: Xcodeproj, + targetName: String, rswiftIgnoreURL: URL?, infoPlistFile: URL?, codeSignEntitlements: URL?, sourceTreeURLs: SourceTreeURLs, - parseFontsAsFiles: Bool = true, - parseImagesAsFiles: Bool = true, - warning: (String) -> Void - ) throws -> Project { - let xcodeproj = try Xcodeproj(url: xcodeprojURL, warning: warning) + parseFontsAsFiles: Bool, + parseImagesAsFiles: Bool + ) throws -> ProjectResources { let ignoreFile = rswiftIgnoreURL.flatMap { try? IgnoreFile(ignoreFileURL: $0) } ?? IgnoreFile() let buildConfigurations = try xcodeproj.buildConfigurations(forTarget: targetName) @@ -42,10 +39,36 @@ public struct Project { .map { $0.url(with: sourceTreeURLs.url(for:)) } .filter { !ignoreFile.matches(url: $0) } + let infoPlists = try buildConfigurations.compactMap { config -> PropertyListResource? in + guard let url = infoPlistFile else { return nil } + return try PropertyListResource.parse(url: url, buildConfigurationName: config.name) + } + + let codeSignEntitlements = try buildConfigurations.compactMap { config -> PropertyListResource? in + guard let url = codeSignEntitlements else { return nil } + return try PropertyListResource.parse(url: url, buildConfigurationName: config.name) + } + + return try parseURLs( + urls: urls, + infoPlists: infoPlists, + codeSignEntitlements: codeSignEntitlements, + parseFontsAsFiles: parseFontsAsFiles, + parseImagesAsFiles: parseImagesAsFiles + ) + } + + public static func parseURLs( + urls: [URL], + infoPlists: [PropertyListResource], + codeSignEntitlements: [PropertyListResource], + parseFontsAsFiles: Bool, + parseImagesAsFiles: Bool + ) throws -> ProjectResources { - let assetCatalogs = try urls - .filter { AssetCatalog.supportedExtensions.contains($0.pathExtension) } - .map { try AssetCatalog.parse(url: $0) } + let assetCatalogs = try urls + .filter { AssetCatalog.supportedExtensions.contains($0.pathExtension) } + .map { try AssetCatalog.parse(url: $0) } let dontParseFileForFonts = !parseFontsAsFiles let dontParseFileForImages = !parseImagesAsFiles @@ -75,17 +98,7 @@ public struct Project { .filter { StoryboardResource.supportedExtensions.contains($0.pathExtension) } .map { try StoryboardResource.parse(url: $0) } - let infoPlists = try buildConfigurations.compactMap { config -> PropertyListResource? in - guard let url = infoPlistFile else { return nil } - return try PropertyListResource.parse(url: url, buildConfigurationName: config.name) - } - - let codeSignEntitlements = try buildConfigurations.compactMap { config -> PropertyListResource? in - guard let url = codeSignEntitlements else { return nil } - return try PropertyListResource.parse(url: url, buildConfigurationName: config.name) - } - - return Project( + return ProjectResources( assetCatalogs: assetCatalogs, files: files, fonts: fonts, @@ -94,8 +107,7 @@ public struct Project { nibs: nibs, storyboards: storyboards, infoPlists: infoPlists, - codeSignEntitlements: codeSignEntitlements, - xcodeproj: xcodeproj + codeSignEntitlements: codeSignEntitlements ) } } diff --git a/Sources/rswift/App.swift b/Sources/rswift/App.swift index 9d61e70c..3bb48df0 100644 --- a/Sources/rswift/App.swift +++ b/Sources/rswift/App.swift @@ -22,100 +22,111 @@ struct App: ParsableCommand { ) } -extension App { - struct Generate: ParsableCommand { - static var configuration = CommandConfiguration(abstract: "Generates R.generated.swift file") +struct GlobalOptions: ParsableArguments { - @Option(help: "Only run specified generators, options: \(Generator.allCases.map(\.rawValue).joined(separator: ", "))") - var generators: [Generator] = [] + @Option(help: "Only run specified generators, options: \(Generator.allCases.map(\.rawValue).joined(separator: ", "))", transform: { str in + str.components(separatedBy: ",").map { Generator(rawValue: $0)! } + }) + var generators: [Generator] = [] // @Option(help: "Output path for an extra generated file that contains resources commonly used in UI tests such as accessibility identifiers") // var generateUITestFile: String? - @Option(help: "Add extra modules as import in the generated file") - var imports: [String] = [] + @Option(help: "Add extra modules as import in the generated file") + var imports: [String] = [] - @Option(help: "The access level [public|internal] to use for the generated R-file") - var accessLevel: AccessLevel = .internalLevel + @Option(help: "The access level [public|internal] to use for the generated R-file") + var accessLevel: AccessLevel = .internalLevel - @Option(help: "Path to pattern file that describes files that should be ignored") - var rswiftignore = ".rswiftignore" + @Option(help: "Path to pattern file that describes files that should be ignored") + var rswiftignore = ".rswiftignore" - @Option(help: "Override bundle from which resources are loaded") - var hostingBundle: String? +// @Option(help: "Override bundle from which resources are loaded") +// var hostingBundle: String? - // MARK: Project specific - Environment variable overrides - - @Option(help: "Override environment variable \(EnvironmentKeys.xcodeproj)") - var xcodeproj: String? - - @Option(help: "Override environment variable \(EnvironmentKeys.target)") - var target: String? + // MARK: Project specific - Environment variable overrides - @Option(help: "Override environment variable \(EnvironmentKeys.productModuleName)") - var productModuleName: String? - - @Option(help: "Override environment variable \(EnvironmentKeys.infoPlistFile)") - var infoPlistFile: String? - - @Option(help: "Override environment variable \(EnvironmentKeys.codeSignEntitlements)") - var codeSignEntitlements: String? + @Option(help: "Override environment variable \(EnvironmentKeys.targetName)") + var target: String? +// @Option(help: "Override environment variable \(EnvironmentKeys.productModuleName)") +// var productModuleName: String? +// +// @Option(help: "Override environment variable \(EnvironmentKeys.infoPlistFile)") +// var infoPlistFile: String? +// +// @Option(help: "Override environment variable \(EnvironmentKeys.codeSignEntitlements)") +// var codeSignEntitlements: String? - // MARK: Xcode build - Environment variable overrides + @Option() + var inputFiles: [String] = [] - @Option(help: "Override environment variable \(EnvironmentKeys.builtProductsDir)") - var builtProductsDir: String? + // MARK: Xcode build - Environment variable overrides - @Option(help: "Override environment variable \(EnvironmentKeys.developerDir)") - var developerDir: String? +// @Option(help: "Override environment variable \(EnvironmentKeys.builtProductsDir)") +// var builtProductsDir: String? +// +// @Option(help: "Override environment variable \(EnvironmentKeys.developerDir)") +// var developerDir: String? +// +// @Option(help: "Override environment variable \(EnvironmentKeys.platformDir)") +// var platformDir: String? +// +// @Option(help: "Override environment variable \(EnvironmentKeys.sdkRoot)") +// var sdkRoot: String? +// +// @Option(help: "Override environment variable \(EnvironmentKeys.sourceRoot)") +// var sourceRoot: String? +} - @Option(help: "Override environment variable \(EnvironmentKeys.platformDir)") - var platformDir: String? +extension App { + struct Generate: ParsableCommand { + static var configuration = CommandConfiguration(abstract: "Generates R.generated.swift file") - @Option(help: "Override environment variable \(EnvironmentKeys.sdkRoot)") - var sdkRoot: String? + @OptionGroup var globals: GlobalOptions - @Option(help: "Override environment variable \(EnvironmentKeys.sourceRoot)") - var sourceRoot: String? + @Option(help: "Override environment variable \(EnvironmentKeys.productFilePath)") + var xcodeproj: String? // MARK: Output path argument @Argument(help: "Output path for the generated file") +// @Option(name: .shortAndLong, help: "Output path for the generated file") var outputPath: String mutating func run() throws { let processInfo = ProcessInfo() - let xcodeprojPath = try self.xcodeproj ?? processInfo.environmentVariable(name: EnvironmentKeys.xcodeproj) + let xcodeprojPath = try xcodeproj ?? processInfo.environmentVariable(name: EnvironmentKeys.productFilePath) let xcodeprojURL = URL(fileURLWithPath: xcodeprojPath) - let targetName = try self.getTargetName(xcodeprojURL: xcodeprojURL) - let productModuleName = self.productModuleName ?? processInfo.environment[EnvironmentKeys.productModuleName] - let infoPlistFile = self.infoPlistFile ?? processInfo.environment[EnvironmentKeys.infoPlistFile] - let codeSignEntitlements = self.codeSignEntitlements ?? processInfo.environment[EnvironmentKeys.codeSignEntitlements] + let targetName = try getTargetName(xcodeprojURL: xcodeprojURL) + let productModuleName = processInfo.environment[EnvironmentKeys.productModuleName] + let infoPlistFile = processInfo.environment[EnvironmentKeys.infoPlistFile] + let codeSignEntitlements = processInfo.environment[EnvironmentKeys.codeSignEntitlements] + // If no environment is provided, we're not running inside Xcode, fallback to names let sourceTreeURLs = SourceTreeURLs( - builtProductsDirURL: URL(fileURLWithPath: builtProductsDir ?? processInfo.environment[EnvironmentKeys.builtProductsDir] ?? EnvironmentKeys.builtProductsDir), - developerDirURL: URL(fileURLWithPath: developerDir ?? processInfo.environment[EnvironmentKeys.developerDir] ?? EnvironmentKeys.developerDir), - sourceRootURL: URL(fileURLWithPath: sourceRoot ?? processInfo.environment[EnvironmentKeys.sourceRoot] ?? EnvironmentKeys.sourceRoot), - sdkRootURL: URL(fileURLWithPath: sdkRoot ?? processInfo.environment[EnvironmentKeys.sdkRoot] ?? EnvironmentKeys.sdkRoot), - platformURL: URL(fileURLWithPath: platformDir ?? processInfo.environment[EnvironmentKeys.platformDir] ?? EnvironmentKeys.platformDir) + builtProductsDirURL: URL(fileURLWithPath: processInfo.environment[EnvironmentKeys.builtProductsDir] ?? EnvironmentKeys.builtProductsDir), + developerDirURL: URL(fileURLWithPath: processInfo.environment[EnvironmentKeys.developerDir] ?? EnvironmentKeys.developerDir), + sourceRootURL: URL(fileURLWithPath: processInfo.environment[EnvironmentKeys.sourceRoot] ?? EnvironmentKeys.sourceRoot), + sdkRootURL: URL(fileURLWithPath: processInfo.environment[EnvironmentKeys.sdkRoot] ?? EnvironmentKeys.sdkRoot), + platformURL: URL(fileURLWithPath: processInfo.environment[EnvironmentKeys.platformDir] ?? EnvironmentKeys.platformDir) ) let outputURL = URL(fileURLWithPath: outputPath) // let uiTestOutputURL = generateUITestFile.map(URL.init(fileURLWithPath:)) - let rswiftIgnoreURL = sourceTreeURLs.sourceRootURL.appendingPathComponent(rswiftignore, isDirectory: false) + let rswiftIgnoreURL = sourceTreeURLs.sourceRootURL + .appendingPathComponent(globals.rswiftignore, isDirectory: false) let core = RswiftCore( outputURL: outputURL, - generators: generators.isEmpty ? Generator.allCases : generators, - accessLevel: accessLevel, - importModules: imports, - xcodeprojURL: xcodeprojURL, + generators: globals.generators.isEmpty ? Generator.allCases : globals.generators, + accessLevel: globals.accessLevel, + importModules: globals.imports, targetName: targetName, productModuleName: productModuleName, infoPlistFile: infoPlistFile.map(URL.init(fileURLWithPath:)), @@ -124,9 +135,9 @@ extension App { sourceTreeURLs: sourceTreeURLs ) -// print("RSWIFT", outputURL) + print("RSWIFT inputfiles", globals.inputFiles) do { - try core.developRun() + try core.generateFromXcodeproj(url: xcodeprojURL) } catch let error as ResourceParsingError { throw ValidationError(error.description) } @@ -134,7 +145,7 @@ extension App { func getTargetName(xcodeprojURL: URL) throws -> String { let processInfo = ProcessInfo() - if let targetName = self.target ?? processInfo.environment[EnvironmentKeys.target] { + if let targetName = globals.target ?? processInfo.environment[EnvironmentKeys.targetName] { return targetName } @@ -172,11 +183,10 @@ extension App { struct EnvironmentKeys { static let action = "ACTION" - static let bundleIdentifier = "PRODUCT_BUNDLE_IDENTIFIER" - static let productModuleName = "PRODUCT_MODULE_NAME" - static let target = "TARGET_NAME" - static let xcodeproj = "PROJECT_FILE_PATH" + static let targetName = "TARGET_NAME" static let infoPlistFile = "INFOPLIST_FILE" + static let productFilePath = "PROJECT_FILE_PATH" + static let productModuleName = "PRODUCT_MODULE_NAME" static let codeSignEntitlements = "CODE_SIGN_ENTITLEMENTS" static let builtProductsDir = SourceTreeFolder.buildProductsDir.rawValue From 0e6638eeb44cb5973da79ca8b4aad390358eec74 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Mon, 24 Oct 2022 11:50:01 +0200 Subject: [PATCH 093/161] Don't hardcode UIFont in validation function --- .../FontResource+Generator.swift | 2 +- .../FontResource+Integrations.swift | 20 +++++++++++++++++++ .../Shared/ModuleReference.swift | 3 ++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Sources/RswiftGenerators/FontResource+Generator.swift b/Sources/RswiftGenerators/FontResource+Generator.swift index 96f721f0..41ca4572 100644 --- a/Sources/RswiftGenerators/FontResource+Generator.swift +++ b/Sources/RswiftGenerators/FontResource+Generator.swift @@ -49,7 +49,7 @@ extension FontResource { returnType: .void, valueCodeString: #""" for font in self { - if UIFont(resource: font, size: 42) == nil { throw RswiftResources.ValidationError("[R.swift] Font '\(font.name)' could not be loaded, is '\(font.filename)' added to the UIAppFonts array in this targets Info.plist?") } + if !font.canBeLoaded() { throw RswiftResources.ValidationError("[R.swift] Font '\(font.name)' could not be loaded, is '\(font.filename)' added to the UIAppFonts array in this targets Info.plist?") } } """# ) diff --git a/Sources/RswiftResources/Integrations/FontResource+Integrations.swift b/Sources/RswiftResources/Integrations/FontResource+Integrations.swift index 90a689c0..a7d1e168 100644 --- a/Sources/RswiftResources/Integrations/FontResource+Integrations.swift +++ b/Sources/RswiftResources/Integrations/FontResource+Integrations.swift @@ -10,6 +10,7 @@ import Foundation import SwiftUI + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) extension Font { @@ -38,6 +39,25 @@ extension Font { } +#if os(iOS) || os(tvOS) || os(watchOS) +import UIKit + +extension FontResource { + public func canBeLoaded() -> Bool { + UIFont(name: name, size: 42) != nil + } +} +#elseif os(macOS) +import AppKit + +extension FontResource { + public func canBeLoaded() -> Bool { + NSFont(name: name, size: 42) != nil + } +} +#endif + + #if os(iOS) || os(tvOS) import UIKit diff --git a/Sources/RswiftResources/Shared/ModuleReference.swift b/Sources/RswiftResources/Shared/ModuleReference.swift index a7c0107b..778c0e1b 100644 --- a/Sources/RswiftResources/Shared/ModuleReference.swift +++ b/Sources/RswiftResources/Shared/ModuleReference.swift @@ -38,7 +38,8 @@ public enum ModuleReference: Hashable { } extension ModuleReference { - public static var foundation: ModuleReference { .custom(name: "Foundation") } public static var uiKit: ModuleReference { .custom(name: "UIKit") } + public static var coreText: ModuleReference { .custom(name: "CoreText") } + public static var foundation: ModuleReference { .custom(name: "Foundation") } public static var rswiftResources: ModuleReference { .custom(name: "RswiftResources") } } From 7d3970635512719243adc368e9391c791770b8c1 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Mon, 24 Oct 2022 12:52:49 +0200 Subject: [PATCH 094/161] Move public modifiers --- .../FileResource+Integrations.swift | 12 ++--- .../FontResource+Integrations.swift | 54 +++++++++++-------- .../NibReference+Integrations.swift | 20 +++---- .../ReuseIdentifier+Integrations.swift | 28 +++++----- .../SegueIdentifier+Integrations.swift | 2 +- .../StoryboardReference+Integrations.swift | 14 ++--- 6 files changed, 69 insertions(+), 61 deletions(-) diff --git a/Sources/RswiftResources/Integrations/FileResource+Integrations.swift b/Sources/RswiftResources/Integrations/FileResource+Integrations.swift index 216fe63b..d514f494 100644 --- a/Sources/RswiftResources/Integrations/FileResource+Integrations.swift +++ b/Sources/RswiftResources/Integrations/FileResource+Integrations.swift @@ -9,13 +9,13 @@ import Foundation -public extension FileResource { +extension FileResource { /** Returns the file URL for the given resource (`R.file.*`). - returns: The file URL for the resource file (`R.file.*`) or nil if the file could not be located. */ - func url() -> URL? { + public func url() -> URL? { bundle.url(forResource: name, withExtension: pathExtension) } @@ -25,12 +25,12 @@ public extension FileResource { - returns: The file URL for the resource file (`R.file.*`) or nil if the file could not be located. */ @available(*, renamed: "url()") - func callAsFunction() -> URL? { + public func callAsFunction() -> URL? { url() } } -public extension Bundle { +extension Bundle { /** Returns the file URL for the given resource (`R.file.*`). @@ -38,7 +38,7 @@ public extension Bundle { - returns: The file URL for the resource file (`R.file.*`) or nil if the file could not be located. */ - func url(forResource resource: FileResource) -> URL? { + public func url(forResource resource: FileResource) -> URL? { url(forResource: resource.name, withExtension: resource.pathExtension) } @@ -49,7 +49,7 @@ public extension Bundle { - returns: The full pathname for the resource file (`R.file.*`) or nil if the file could not be located. */ - func path(forResource resource: FileResource) -> String? { + public func path(forResource resource: FileResource) -> String? { path(forResource: resource.name, ofType: resource.pathExtension) } } diff --git a/Sources/RswiftResources/Integrations/FontResource+Integrations.swift b/Sources/RswiftResources/Integrations/FontResource+Integrations.swift index a7d1e168..48a3705e 100644 --- a/Sources/RswiftResources/Integrations/FontResource+Integrations.swift +++ b/Sources/RswiftResources/Integrations/FontResource+Integrations.swift @@ -17,7 +17,7 @@ extension Font { /** Create a custom font from this resource (`R.font.*`) and and size that scales with the body text style. */ - static func custom(_ resource: FontResource, size: CGFloat) -> Font { + public static func custom(_ resource: FontResource, size: CGFloat) -> Font { .custom(resource.name, size: size) } @@ -25,7 +25,7 @@ extension Font { Create a custom font from this resource (`R.font.*`) and a fixed size that does not scale with Dynamic Type. */ @available(macOS 11, iOS 14, tvOS 14, watchOS 7, *) - static func custom(_ resource: FontResource, fixedSize: CGFloat) -> Font { + public static func custom(_ resource: FontResource, fixedSize: CGFloat) -> Font { .custom(resource.name, fixedSize: fixedSize) } @@ -33,32 +33,13 @@ extension Font { Create a custom font from this resource (`R.font.*`) and and size that is relative to the given `textStyle`. */ @available(macOS 11, iOS 14, tvOS 14, watchOS 7, *) - static func custom(_ resource: FontResource, size: CGFloat, relativeTo textStyle: Font.TextStyle) -> Font { + public static func custom(_ resource: FontResource, size: CGFloat, relativeTo textStyle: Font.TextStyle) -> Font { .custom(resource.name, size: size, relativeTo: textStyle) } } -#if os(iOS) || os(tvOS) || os(watchOS) -import UIKit - -extension FontResource { - public func canBeLoaded() -> Bool { - UIFont(name: name, size: 42) != nil - } -} -#elseif os(macOS) -import AppKit - -extension FontResource { - public func canBeLoaded() -> Bool { - NSFont(name: name, size: 42) != nil - } -} -#endif - - -#if os(iOS) || os(tvOS) +#if canImport(UIKit) import UIKit extension FontResource { @@ -91,3 +72,30 @@ public extension UIFont { } } #endif + + +#if canImport(UIKit) +import UIKit + +extension FontResource { + /** + Returns true if the font can be loaded. + Custom fonts may not be loaded if not properly configured in Info.plist + */ + public func canBeLoaded() -> Bool { + UIFont(name: name, size: 42) != nil + } +} +#elseif canImport(AppKit) +import AppKit + +extension FontResource { + /** + Returns true if the font can be loaded. + Custom fonts may not be loaded if not properly configured in Info.plist + */ + public func canBeLoaded() -> Bool { + NSFont(name: name, size: 42) != nil + } +} +#endif diff --git a/Sources/RswiftResources/Integrations/NibReference+Integrations.swift b/Sources/RswiftResources/Integrations/NibReference+Integrations.swift index b8f421ea..711afa9d 100644 --- a/Sources/RswiftResources/Integrations/NibReference+Integrations.swift +++ b/Sources/RswiftResources/Integrations/NibReference+Integrations.swift @@ -12,7 +12,7 @@ import UIKit -public extension NibReferenceContainer { +extension NibReferenceContainer { /** Instantiate the nib to get first object from this nib @@ -20,12 +20,12 @@ public extension NibReferenceContainer { - parameter ownerOrNil: The owner, if the owner parameter is nil, connections to File's Owner are not permitted. - parameter options: Options are identical to the options specified with` -[NSBundle loadNibNamed:owner:options:]` */ - func callAsFunction(withOwner ownerOrNil: Any?, options optionsOrNil: [UINib.OptionsKey : Any]? = nil) -> FirstView? { + public func callAsFunction(withOwner ownerOrNil: Any?, options optionsOrNil: [UINib.OptionsKey : Any]? = nil) -> FirstView? { UINib(nibName: name, bundle: bundle).instantiate(withOwner: ownerOrNil, options: optionsOrNil).first as? FirstView } @available(*, deprecated, message: "renamed to (withOwner:options:)") - func callAsFunction(owner ownerOrNil: Any?, options optionsOrNil: [UINib.OptionsKey : Any]? = nil) -> FirstView? { + public func callAsFunction(owner ownerOrNil: Any?, options optionsOrNil: [UINib.OptionsKey : Any]? = nil) -> FirstView? { UINib(nibName: name, bundle: bundle).instantiate(withOwner: ownerOrNil, options: optionsOrNil).first as? FirstView } @@ -35,12 +35,12 @@ public extension NibReferenceContainer { - parameter ownerOrNil: The owner, if the owner parameter is nil, connections to File's Owner are not permitted. - parameter options: Options are identical to the options specified with` -[NSBundle loadNibNamed:owner:options:]` */ - func firstView(withOwner ownerOrNil: Any?, options optionsOrNil: [UINib.OptionsKey : Any]? = nil) -> FirstView? { + public func firstView(withOwner ownerOrNil: Any?, options optionsOrNil: [UINib.OptionsKey : Any]? = nil) -> FirstView? { UINib(nibName: name, bundle: bundle).instantiate(withOwner: ownerOrNil, options: optionsOrNil).first as? FirstView } @available(*, deprecated, renamed: "firstView(withOwner:options:)") - func firstView(owner ownerOrNil: Any?, options optionsOrNil: [UINib.OptionsKey : Any]? = nil) -> FirstView? { + public func firstView(owner ownerOrNil: Any?, options optionsOrNil: [UINib.OptionsKey : Any]? = nil) -> FirstView? { UINib(nibName: name, bundle: bundle).instantiate(withOwner: ownerOrNil, options: optionsOrNil).first as? FirstView } @@ -52,12 +52,12 @@ public extension NibReferenceContainer { - returns: An array containing the top-level objects from the NIB */ - func instantiate(withOwner ownerOrNil: Any?, options optionsOrNil: [UINib.OptionsKey : Any]? = [:]) -> [Any] { + public func instantiate(withOwner ownerOrNil: Any?, options optionsOrNil: [UINib.OptionsKey : Any]? = [:]) -> [Any] { UINib(nibName: name, bundle: bundle).instantiate(withOwner: ownerOrNil, options: optionsOrNil) } } -public extension UINib { +extension UINib { /** Returns a UINib object initialized to the nib file of the specified resource (`R.nib.*`). @@ -65,12 +65,12 @@ public extension UINib { - returns: The initialized UINib object. An exception is thrown if there were errors during initialization or the nib file could not be located. */ - convenience init(resource: Nib) { + public convenience init(resource: Nib) { self.init(nibName: resource.name, bundle: resource.bundle) } } -public extension UIViewController { +extension UIViewController { /** Returns a newly initialized view controller with the nib resource (`R.nib.*`). @@ -78,7 +78,7 @@ public extension UIViewController { - returns: A newly initialized UIViewController object. */ - convenience init(nib: Nib) { + public convenience init(nib: Nib) { self.init(nibName: nib.name, bundle: nib.bundle) } } diff --git a/Sources/RswiftResources/Integrations/ReuseIdentifier+Integrations.swift b/Sources/RswiftResources/Integrations/ReuseIdentifier+Integrations.swift index 1644ac51..864e2ed3 100644 --- a/Sources/RswiftResources/Integrations/ReuseIdentifier+Integrations.swift +++ b/Sources/RswiftResources/Integrations/ReuseIdentifier+Integrations.swift @@ -12,14 +12,14 @@ import UIKit -public extension UITableView { +extension UITableView { /** Register a `R.nib.*` containing a cell with the table view under it's contained identifier. - parameter resource: A nib resource (`R.nib.*`) containing a table view cell that has a reuse identifier */ - func register(_ resource: Resource) where Resource.Reusable: UITableViewCell { + public func register(_ resource: Resource) where Resource.Reusable: UITableViewCell { register(UINib(resource: resource), forCellReuseIdentifier: resource.identifier) } @@ -28,7 +28,7 @@ public extension UITableView { - parameter resource: A reuse identifier */ - func register(_ resource: Resource) where Resource.Reusable: UITableViewCell { + public func register(_ resource: Resource) where Resource.Reusable: UITableViewCell { register(Resource.Reusable.self, forCellReuseIdentifier: resource.identifier) } @@ -37,7 +37,7 @@ public extension UITableView { - parameter resource: A nib resource (`R.nib.*`) containing a view that has a reuse identifier */ - func registerHeaderFooterView(_ resource: Resource) where Resource.Reusable: UIView { + public func registerHeaderFooterView(_ resource: Resource) where Resource.Reusable: UIView { register(UINib(resource: resource), forHeaderFooterViewReuseIdentifier: resource.identifier) } @@ -46,7 +46,7 @@ public extension UITableView { - parameter resource: A reuse identifier */ - func registerHeaderFooterView(_ resource: Resource) where Resource.Reusable: UITableViewHeaderFooterView { + public func registerHeaderFooterView(_ resource: Resource) where Resource.Reusable: UITableViewHeaderFooterView { register(Resource.Reusable.self, forHeaderFooterViewReuseIdentifier: resource.identifier) } @@ -60,7 +60,7 @@ public extension UITableView { - precondition: You must register a class or nib file using the registerNib: or registerClass:forCellReuseIdentifier: method before calling this method. */ - func dequeueReusableCell(withIdentifier identifier: Identifier, for indexPath: IndexPath) -> Identifier.Reusable? where Identifier.Reusable: UITableViewCell { + public func dequeueReusableCell(withIdentifier identifier: Identifier, for indexPath: IndexPath) -> Identifier.Reusable? where Identifier.Reusable: UITableViewCell { dequeueReusableCell(withIdentifier: identifier.identifier, for: indexPath) as? Identifier.Reusable } @@ -72,20 +72,20 @@ public extension UITableView { - returns: A UITableViewHeaderFooterView object with the associated identifier or nil if no such object exists in the reusable view queue or if it couldn't be cast correctly. */ - func dequeueReusableHeaderFooterView(withIdentifier identifier: Identifier) -> Identifier.Reusable? where Identifier.Reusable: UITableViewHeaderFooterView { + public func dequeueReusableHeaderFooterView(withIdentifier identifier: Identifier) -> Identifier.Reusable? where Identifier.Reusable: UITableViewHeaderFooterView { dequeueReusableHeaderFooterView(withIdentifier: identifier.identifier) as? Identifier.Reusable } } -public extension UICollectionView { +extension UICollectionView { /** Register a `R.nib.*` for use in creating new collection view cells. - parameter resource: A nib resource (`R.nib.*`) containing a object of type UICollectionViewCell that has a reuse identifier */ - func register(_ resource: Resource) where Resource.Reusable: UICollectionViewCell { + public func register(_ resource: Resource) where Resource.Reusable: UICollectionViewCell { register(UINib(resource: resource), forCellWithReuseIdentifier: resource.identifier) } @@ -94,7 +94,7 @@ public extension UICollectionView { - parameter resource: A reuse identifier */ - func register(_ resource: Resource) where Resource.Reusable: UICollectionViewCell { + public func register(_ resource: Resource) where Resource.Reusable: UICollectionViewCell { register(Resource.Reusable.self, forCellWithReuseIdentifier: resource.identifier) } @@ -103,7 +103,7 @@ public extension UICollectionView { - parameter resource: A nib resource (`R.nib.*`) containing a object of type UICollectionReusableView. that has a reuse identifier */ - func register(_ resource: Resource, forSupplementaryViewOfKind kind: String) where Resource.Reusable: UICollectionReusableView { + public func register(_ resource: Resource, forSupplementaryViewOfKind kind: String) where Resource.Reusable: UICollectionReusableView { register(UINib(resource: resource), forSupplementaryViewOfKind: kind, withReuseIdentifier: resource.identifier) } @@ -112,7 +112,7 @@ public extension UICollectionView { - parameter resource: A reuseIdentifier */ - func register(_ resource: Resource, forSupplementaryViewOfKind kind: String) where Resource.Reusable: UICollectionReusableView { + public func register(_ resource: Resource, forSupplementaryViewOfKind kind: String) where Resource.Reusable: UICollectionReusableView { register(Resource.Reusable.self, forSupplementaryViewOfKind: kind, withReuseIdentifier: resource.identifier) } @@ -124,7 +124,7 @@ public extension UICollectionView { - returns: A subclass of UICollectionReusableView or nil if the cast fails. */ - func dequeueReusableCell(withReuseIdentifier identifier: Identifier, for indexPath: IndexPath) -> Identifier.Reusable? where Identifier.Reusable: UICollectionReusableView { + public func dequeueReusableCell(withReuseIdentifier identifier: Identifier, for indexPath: IndexPath) -> Identifier.Reusable? where Identifier.Reusable: UICollectionReusableView { dequeueReusableCell(withReuseIdentifier: identifier.identifier, for: indexPath) as? Identifier.Reusable } @@ -137,7 +137,7 @@ public extension UICollectionView { - returns: A subclass of UICollectionReusableView or nil if the cast fails. */ - func dequeueReusableSupplementaryView(ofKind elementKind: String, withReuseIdentifier identifier: Identifier, for indexPath: IndexPath) -> Identifier.Reusable? where Identifier.Reusable: UICollectionReusableView { + public func dequeueReusableSupplementaryView(ofKind elementKind: String, withReuseIdentifier identifier: Identifier, for indexPath: IndexPath) -> Identifier.Reusable? where Identifier.Reusable: UICollectionReusableView { dequeueReusableSupplementaryView(ofKind: elementKind, withReuseIdentifier: identifier.identifier, for: indexPath) as? Identifier.Reusable } diff --git a/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift b/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift index 10a18932..7bec40ac 100644 --- a/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift +++ b/Sources/RswiftResources/Integrations/SegueIdentifier+Integrations.swift @@ -27,7 +27,7 @@ extension SeguePerformer { */ public func performSegue(withIdentifier identifier: SegueIdentifier, sender: Any?) { performSegue(withIdentifier: identifier.identifier, sender: sender) - } + } } extension SegueIdentifier where Segue: UIStoryboardSegue { diff --git a/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift b/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift index 7ac7c8a6..6306a171 100644 --- a/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift +++ b/Sources/RswiftResources/Integrations/StoryboardReference+Integrations.swift @@ -12,31 +12,31 @@ import Foundation import UIKit -public extension StoryboardReference where Self: InitialControllerContainer { +extension StoryboardReference where Self: InitialControllerContainer { /** Instantiates and returns the initial view controller in the view controller graph. - returns: The initial view controller in the storyboard. */ - func instantiateInitialViewController() -> InitialController? { + public func instantiateInitialViewController() -> InitialController? { UIStoryboard(name: name, bundle: bundle).instantiateInitialViewController() as? InitialController } } -public extension StoryboardViewControllerIdentifier { +extension StoryboardViewControllerIdentifier { /** Instantiates and returns the view controller with the specified resource (`R.storyboard.*.*`). - returns: The view controller corresponding to the specified resource (`R.storyboard.*.*`). If no view controller is associated, this method throws an exception. */ - func callAsFunction() -> ViewController? { + public func callAsFunction() -> ViewController? { UIStoryboard(name: storyboard, bundle: bundle).instantiateViewController(withIdentifier: identifier) as? ViewController } } -public extension UIStoryboard { +extension UIStoryboard { /** Creates and returns a storyboard object for the specified storyboard resource (`R.storyboard.*`) file. @@ -44,7 +44,7 @@ public extension UIStoryboard { - returns: A storyboard object for the specified file. If no storyboard resource file matching name exists, an exception is thrown with description: `Could not find a storyboard named 'XXXXXX' in bundle....` */ - convenience init(resource: Reference) { + public convenience init(resource: Reference) { self.init(name: resource.name, bundle: resource.bundle) } @@ -56,7 +56,7 @@ public extension UIStoryboard { - returns: The view controller corresponding to the specified resource (`R.storyboard.*.*`). If no view controller is associated, this method throws an exception. */ - func instantiateViewController(withIdentifier identifier: StoryboardViewControllerIdentifier) -> ViewController? { + public func instantiateViewController(withIdentifier identifier: StoryboardViewControllerIdentifier) -> ViewController? { self.instantiateViewController(withIdentifier: identifier.identifier) as? ViewController } } From 15ef14b038c1cef0de4fe4e7f192d70c5b249686 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Tue, 25 Oct 2022 11:30:32 +0200 Subject: [PATCH 095/161] Add bundle to FontResource --- .../FontResource+Generator.swift | 18 ++++++++++-------- .../Resources/FontResource+Parser.swift | 6 +++++- Sources/RswiftResources/FontResource.swift | 4 +++- .../ImageResource+Integrations.swift | 3 --- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Sources/RswiftGenerators/FontResource+Generator.swift b/Sources/RswiftGenerators/FontResource+Generator.swift index 41ca4572..917a9a10 100644 --- a/Sources/RswiftGenerators/FontResource+Generator.swift +++ b/Sources/RswiftGenerators/FontResource+Generator.swift @@ -17,16 +17,17 @@ extension FontResource { let groupedResources = resources.grouped(bySwiftIdentifier: { $0.name }) groupedResources.reportWarningsForDuplicatesAndEmpties(source: "font resource", result: "font", warning: warning) - let letbindings = groupedResources.uniques.map { $0.generateLetBinding() } + let vargetters = groupedResources.uniques.map { $0.generateVarGetter() } - let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(letbindings.count) fonts."] + let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(vargetters.count) fonts."] return Struct(comments: comments, name: structName, protocols: [.sequence]) { - if letbindings.count > 0 { - generateMakeIterator(names: letbindings.map(\.name)) + Init.bundle + if vargetters.count > 0 { + generateMakeIterator(names: vargetters.map(\.name)) generateValidate() } - letbindings + vargetters } } @@ -57,11 +58,12 @@ extension FontResource { } extension FontResource { - func generateLetBinding() -> LetBinding { - LetBinding( + func generateVarGetter() -> VarGetter { + VarGetter( comments: ["Font `\(name)`."], name: SwiftIdentifier(name: name), - valueCodeString: "FontResource(name: \"\(name)\", filename: \"\(filename)\")" + typeReference: TypeReference(module: .rswiftResources, rawName: "FontResource"), + valueCodeString: ".init(name: \"\(name)\", bundle: bundle, filename: \"\(filename)\")" ) } } diff --git a/Sources/RswiftParsers/Resources/FontResource+Parser.swift b/Sources/RswiftParsers/Resources/FontResource+Parser.swift index 3c2b3301..cebb9156 100644 --- a/Sources/RswiftParsers/Resources/FontResource+Parser.swift +++ b/Sources/RswiftParsers/Resources/FontResource+Parser.swift @@ -24,6 +24,10 @@ extension FontResource: SupportedExtensions { throw ResourceParsingError("No postscriptName associated to font at \(url)") } - return FontResource(name: postScriptName as String, filename: url.lastPathComponent) + return FontResource( + name: postScriptName as String, + bundle: .temp, + filename: url.lastPathComponent + ) } } diff --git a/Sources/RswiftResources/FontResource.swift b/Sources/RswiftResources/FontResource.swift index 78256fc0..eaec1534 100644 --- a/Sources/RswiftResources/FontResource.swift +++ b/Sources/RswiftResources/FontResource.swift @@ -11,10 +11,12 @@ import Foundation public struct FontResource { public let name: String + public let bundle: Bundle public let filename: String - public init(name: String, filename: String) { + public init(name: String, bundle: Bundle, filename: String) { self.name = name + self.bundle = bundle self.filename = filename } } diff --git a/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift b/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift index 7dd221c4..24e27891 100644 --- a/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift +++ b/Sources/RswiftResources/Integrations/ImageResource+Integrations.swift @@ -46,8 +46,6 @@ extension Image { } -// For some reason, this requires Xcode 14.1, doesn't work in Xcode 14.0 -#if swift(>=5.8) @available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) extension Image { @@ -84,7 +82,6 @@ extension Image { self.init(decorative: resource.name, variableValue: variableValue, bundle: resource.bundle) } } -#endif #if os(iOS) || os(tvOS) From 5768c550509415b85b481bac8ed3a264cb3681a3 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Tue, 25 Oct 2022 11:31:57 +0200 Subject: [PATCH 096/161] Work on plugin --- .../xcshareddata/swiftpm/Package.resolved | 18 ------ .../xcschemes/ResourceApp-watchOS.xcscheme | 2 +- Plugins/RswiftGenerateResources/Plugin.swift | 14 ++++- Sources/RswiftCore/RswiftCore.swift | 12 ++-- Sources/rswift/App.swift | 59 ++++++++++++------- 5 files changed, 59 insertions(+), 46 deletions(-) diff --git a/Examples/RswiftExamples.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/RswiftExamples.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4d30055e..fde89f75 100644 --- a/Examples/RswiftExamples.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/RswiftExamples.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,23 +1,5 @@ { "pins" : [ - { - "identity" : "commander", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kylef/Commander.git", - "state" : { - "revision" : "4a1f2fb82fb6cef613c4a25d2e38f702e4d812c2", - "version" : "0.9.2" - } - }, - { - "identity" : "spectre", - "kind" : "remoteSourceControl", - "location" : "https://github.com/kylef/Spectre.git", - "state" : { - "revision" : "26cc5e9ae0947092c7139ef7ba612e34646086c7", - "version" : "0.10.1" - } - }, { "identity" : "swift-argument-parser", "kind" : "remoteSourceControl", diff --git a/Examples/RwatchApp/RwatchApp.xcodeproj/xcshareddata/xcschemes/ResourceApp-watchOS.xcscheme b/Examples/RwatchApp/RwatchApp.xcodeproj/xcshareddata/xcschemes/ResourceApp-watchOS.xcscheme index 2075d6fb..110162b7 100644 --- a/Examples/RwatchApp/RwatchApp.xcodeproj/xcshareddata/xcschemes/ResourceApp-watchOS.xcscheme +++ b/Examples/RwatchApp/RwatchApp.xcodeproj/xcshareddata/xcschemes/ResourceApp-watchOS.xcscheme @@ -40,7 +40,7 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES" - notificationPayloadFile = "ResourceApp-watchOS-Extension/PushNotificationPayload.apns"> + notificationPayloadFile = "PushNotificationPayload.apns"> String { - let processInfo = ProcessInfo() - if let targetName = globals.target ?? processInfo.environment[EnvironmentKeys.targetName] { + func getXcodeprojURL() throws -> URL { + let xcodeprojPath = try xcodeproj ?? ProcessInfo().environmentVariable(name: EnvironmentKeys.productFilePath) + let xcodeprojURL = URL(fileURLWithPath: xcodeprojPath) + + return xcodeprojURL + } + + func getTargetName() throws -> String { + if let targetName = globals.target ?? ProcessInfo().environment[EnvironmentKeys.targetName] { return targetName } - let targets = try? Xcodeproj(url: xcodeprojURL, warning: { _ in }).allTargets + do { + let xcodeproj = try Xcodeproj(url: getXcodeprojURL(), warning: { _ in }) + let targets = xcodeproj.allTargets + + if let target = targets.first, targets.count == 1 { + return target.name + } - if let targets, let target = targets.first, targets.count == 1 { - return target.name - } + if targets.count > 0 { + let lines = [ + "Missing argument --target", + "Available targets:" + ] + targets.map { "- \($0.name)" } - if let targets, targets.count > 0 { - let lines = [ - "Missing argument --target", - "Available targets:" - ] + targets.map { "- \($0.name)" } + throw ValidationError(lines.joined(separator: "\n")) + } - throw ValidationError(lines.joined(separator: "\n")) + throw ValidationError("Missing argument --target") + } catch { + throw ValidationError("Missing argument --target") } - - throw ValidationError("Missing argument --target") } } } From 7e13028c51245d0fc2b1dca76b123187c1a710fd Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Tue, 25 Oct 2022 17:13:07 +0200 Subject: [PATCH 097/161] Update Plugin code --- Plugins/RswiftGenerateResources/Plugin.swift | 39 +++++++------- Sources/RswiftCore/RswiftCore.swift | 26 ++++++--- .../LocalizableStrings+Generator.swift | 6 ++- Sources/rswift/App.swift | 54 +++++++++---------- 4 files changed, 69 insertions(+), 56 deletions(-) diff --git a/Plugins/RswiftGenerateResources/Plugin.swift b/Plugins/RswiftGenerateResources/Plugin.swift index a1a002ac..3790cf8e 100644 --- a/Plugins/RswiftGenerateResources/Plugin.swift +++ b/Plugins/RswiftGenerateResources/Plugin.swift @@ -22,26 +22,22 @@ struct RswiftGenerateResources: BuildToolPlugin { let rswiftPath = resourcesDirectoryPath.appending(subpath: "R.generated.swift") let sourceFiles = target.sourceFiles + .filter { $0.type == .resource || $0.type == .unknown } .map(\.path.string) let inputFilesArguments = sourceFiles .flatMap { ["--input-files", $0 ] } - Diagnostics.warning("FILES " + sourceFiles.joined(separator: "}, {")) - // let rswift = try context.tool(named: "rswift") return [ -// .prebuildCommand( -// displayName: "My display name 1", -// executable: Path("/Users/tom/Projects/R.swift/.build/debug/rswift"), -// arguments: ["generate", rswiftPath.string, "--target", target.name] + inputFilesArguments, -//// environment: [:], -// outputFilesDirectory: resourcesDirectoryPath -// ), .buildCommand( - displayName: "My display name 1", + displayName: "R.swift generate resources", executable: Path("/Users/tom/Projects/R.swift/.build/debug/rswift"), - arguments: ["generate", rswiftPath.string, "--target", target.name] + inputFilesArguments, + arguments: [ + "generate", rswiftPath.string, + "--input-type", "input-files", + "--bundle-source", "module", + ] + inputFilesArguments, outputFiles: [rswiftPath] ), ] @@ -53,22 +49,27 @@ import XcodeProjectPlugin extension RswiftGenerateResources: XcodeBuildToolPlugin { func createBuildCommands(context: XcodePluginContext, target: XcodeTarget) throws -> [Command] { - Diagnostics.error("\(context)") + let resourcesDirectoryPath = context.pluginWorkDirectory .appending(subpath: target.displayName) .appending(subpath: "Resources") - let rswiftPath = resourcesDirectoryPath.appending(subpath: "R.generated.swift") + try FileManager.default.createDirectory(atPath: resourcesDirectoryPath.string, withIntermediateDirectories: true) - Diagnostics.warning("HELLO WORLD " + target.inputFiles.filter { $0.type == .resource }.map(\.path.string).joined(separator: ", ")) + let rswiftPath = resourcesDirectoryPath.appending(subpath: "R.generated.swift") return [ - .prebuildCommand( - displayName: "My display name 2", + .buildCommand( + displayName: "R.swift generate resources", executable: Path("/Users/tom/Projects/R.swift/.build/debug/rswift"), - arguments: ["generate", rswiftPath.string, "--target", target.displayName], - outputFilesDirectory: resourcesDirectoryPath - ) + arguments: [ + "generate", rswiftPath.string, + "--target", target.displayName, + "--input-type", "xcodeproj", + "--bundle-source", "finder", + ], + outputFiles: [rswiftPath] + ), ] } } diff --git a/Sources/RswiftCore/RswiftCore.swift b/Sources/RswiftCore/RswiftCore.swift index 1b536940..c8fffdd3 100644 --- a/Sources/RswiftCore/RswiftCore.swift +++ b/Sources/RswiftCore/RswiftCore.swift @@ -35,13 +35,17 @@ public enum AccessLevel: String, ExpressibleByArgument { case privateLevel = "private" } +public enum BundleSource: String, ExpressibleByArgument { + case module + case finder +} public struct RswiftCore { let outputURL: URL let generators: [Generator] let accessLevel: AccessLevel + let bundleSource: BundleSource let importModules: [String] - let targetName: String let productModuleName: String? let infoPlistFile: URL? let codeSignEntitlements: URL? @@ -54,8 +58,8 @@ public struct RswiftCore { outputURL: URL, generators: [Generator], accessLevel: AccessLevel, + bundleSource: BundleSource, importModules: [String], - targetName: String, productModuleName: String?, infoPlistFile: URL?, codeSignEntitlements: URL?, @@ -65,8 +69,8 @@ public struct RswiftCore { self.outputURL = outputURL self.generators = generators self.accessLevel = accessLevel + self.bundleSource = bundleSource self.importModules = importModules - self.targetName = targetName self.productModuleName = productModuleName self.infoPlistFile = infoPlistFile self.codeSignEntitlements = codeSignEntitlements @@ -76,7 +80,7 @@ public struct RswiftCore { self.sourceTreeURLs = sourceTreeURLs } - public func generateFromXcodeproj(url xcodeprojURL: URL) throws { + public func generateFromXcodeproj(url xcodeprojURL: URL, targetName: String) throws { let warning: (String) -> Void = { print("warning: [R.swift]", $0) } let xcodeproj = try Xcodeproj(url: xcodeprojURL, warning: warning) @@ -310,7 +314,16 @@ public struct RswiftCore { .map { "import \($0)" } .joined(separator: "\n") - let mainLet = "\(accessLevel == .publicLevel ? "public " : "")let R = _R(bundle: Bundle.module)" + let mainLet: String + switch bundleSource { + case .module: + mainLet = "\(accessLevel == .publicLevel ? "public " : "")let R = _R(bundle: Bundle.module)" + case .finder: + mainLet = """ + private class BundleFinder {} + \(accessLevel == .publicLevel ? "public " : "")let R = _R(bundle: Bundle(for: BundleFinder.self)) + """ + } let str = s.prettyPrint() let code = """ @@ -318,9 +331,6 @@ public struct RswiftCore { \(str) - private class BundleFinder {} - private let classBundle = Bundle(for: BundleFinder.self) - private let resolvedBundle = classBundle.resourceURL.flatMap(Bundle.init(url:)) ?? classBundle \(mainLet) """ try code.write(to: outputURL, atomically: true, encoding: .utf8) diff --git a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift index 8dd07c40..316f7886 100644 --- a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift +++ b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift @@ -51,7 +51,11 @@ extension LocalizableStrings { let strings = computeStringsWithParams(filename: filename, resources: resources, developmentLanguage: developmentLanguage, warning: warning) let vargetters = strings.map { $0.generateVarGetter() } - let functions = strings.filter { $0.params.count > 0 }.map { $0.generateFunction() } + + // only functions with named parameters + let functions = strings + .filter { $0.params.contains { $0.name != nil } } + .map { $0.generateFunction() } let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(vargetters.count) localization keys."] diff --git a/Sources/rswift/App.swift b/Sources/rswift/App.swift index 9a28f967..2c71ff0b 100644 --- a/Sources/rswift/App.swift +++ b/Sources/rswift/App.swift @@ -22,8 +22,16 @@ struct App: ParsableCommand { ) } +enum InputType: String, ExpressibleByArgument { + case xcodeproj + case inputFiles +} + struct GlobalOptions: ParsableArguments { + @Option(help: "The type of input for generation") + var inputType: InputType = .xcodeproj + @Option(help: "Only run specified generators, options: \(Generator.allCases.map(\.rawValue).joined(separator: ", "))", transform: { str in str.components(separatedBy: ",").map { Generator(rawValue: $0)! } }) @@ -44,6 +52,11 @@ struct GlobalOptions: ParsableArguments { // @Option(help: "Override bundle from which resources are loaded") // var hostingBundle: String? + @Option(help: "Paths of files for which resources should be generated") + var inputFiles: [String] = [] + + @Option(help: "Source of default bundle to use") + var bundleSource: BundleSource = .finder // MARK: Project specific - Environment variable overrides @@ -59,9 +72,6 @@ struct GlobalOptions: ParsableArguments { // @Option(help: "Override environment variable \(EnvironmentKeys.codeSignEntitlements)") // var codeSignEntitlements: String? - @Option() - var inputFiles: [String] = [] - // MARK: Xcode build - Environment variable overrides // @Option(help: "Override environment variable \(EnvironmentKeys.builtProductsDir)") @@ -84,22 +94,18 @@ extension App { struct Generate: ParsableCommand { static var configuration = CommandConfiguration(abstract: "Generates R.generated.swift file") - @OptionGroup var globals: GlobalOptions - + @OptionGroup + var globals: GlobalOptions @Option(help: "Override environment variable \(EnvironmentKeys.productFilePath)") var xcodeproj: String? - // MARK: Output path argument - @Argument(help: "Output path for the generated file") -// @Option(name: .shortAndLong, help: "Output path for the generated file") var outputPath: String mutating func run() throws { let processInfo = ProcessInfo() - let targetName = try getTargetName() let productModuleName = processInfo.environment[EnvironmentKeys.productModuleName] let infoPlistFile = processInfo.environment[EnvironmentKeys.infoPlistFile] let codeSignEntitlements = processInfo.environment[EnvironmentKeys.codeSignEntitlements] @@ -123,8 +129,8 @@ extension App { outputURL: outputURL, generators: globals.generators.isEmpty ? Generator.allCases : globals.generators, accessLevel: globals.accessLevel, + bundleSource: globals.bundleSource, importModules: globals.imports, - targetName: targetName, productModuleName: productModuleName, infoPlistFile: infoPlistFile.map(URL.init(fileURLWithPath:)), codeSignEntitlements: codeSignEntitlements.map(URL.init(fileURLWithPath:)), @@ -132,37 +138,29 @@ extension App { sourceTreeURLs: sourceTreeURLs ) - print("YOYO ", processInfo.environment) - do { - let xcodeprojURL = try getXcodeprojURL() - if xcodeprojURL.pathExtension == "xcodeproj" { - try core.generateFromXcodeproj(url: xcodeprojURL) - } else { - print("FILE STARGEDT", outputURL) - print("FILE STARGEDT", FileManager().fileExists(atPath: outputURL.path)) + switch globals.inputType { + case .xcodeproj: + let xcodeprojPath = try xcodeproj ?? ProcessInfo().environmentVariable(name: EnvironmentKeys.productFilePath) + let xcodeprojURL = URL(fileURLWithPath: xcodeprojPath) + let targetName = try getTargetName(xcodeprojURL: xcodeprojURL) + try core.generateFromXcodeproj(url: xcodeprojURL, targetName: targetName) + + case .inputFiles: try core.generateFromFiles(inputFileURLs: globals.inputFiles.map(URL.init(fileURLWithPath:))) - print("FILE GENERATED", outputURL) } } catch let error as ResourceParsingError { throw ValidationError(error.description) } } - func getXcodeprojURL() throws -> URL { - let xcodeprojPath = try xcodeproj ?? ProcessInfo().environmentVariable(name: EnvironmentKeys.productFilePath) - let xcodeprojURL = URL(fileURLWithPath: xcodeprojPath) - - return xcodeprojURL - } - - func getTargetName() throws -> String { + func getTargetName(xcodeprojURL: URL) throws -> String { if let targetName = globals.target ?? ProcessInfo().environment[EnvironmentKeys.targetName] { return targetName } do { - let xcodeproj = try Xcodeproj(url: getXcodeprojURL(), warning: { _ in }) + let xcodeproj = try Xcodeproj(url: xcodeprojURL, warning: { _ in }) let targets = xcodeproj.allTargets if let target = targets.first, targets.count == 1 { From 1abeeeaed6b51662bac4f36fd75e5ec414e54679 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Thu, 27 Oct 2022 16:49:00 +0200 Subject: [PATCH 098/161] Add additionalModuleReferences to Struct --- Sources/RswiftGenerators/LocalizableStrings+Generator.swift | 2 +- Sources/RswiftGenerators/SwiftSyntax/Struct.swift | 4 ++++ Sources/rswift/App.swift | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift index 316f7886..3065b8e4 100644 --- a/Sources/RswiftGenerators/LocalizableStrings+Generator.swift +++ b/Sources/RswiftGenerators/LocalizableStrings+Generator.swift @@ -32,7 +32,7 @@ extension LocalizableStrings { let comments = ["This `\(qualifiedName.value)` struct is generated, and contains static references to \(groupedLocalized.uniques.count) localization tables."] - return Struct(comments: comments, name: structName) { + return Struct(comments: comments, name: structName, additionalModuleReferences: [.rswiftResources]) { Init.bundle for name in groupedLocalized.uniques.map(\.0) { diff --git a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift index a75f5685..19064cc3 100644 --- a/Sources/RswiftGenerators/SwiftSyntax/Struct.swift +++ b/Sources/RswiftGenerators/SwiftSyntax/Struct.swift @@ -371,6 +371,7 @@ public struct Struct { public var funcs: [Function] = [] public var structs: [Struct] = [] public var typealiasses: [TypeAlias] = [] + public var additionalModuleReferences: Set = [] public static var empty: Struct = Struct(name: SwiftIdentifier(name: "empty"), membersBuilder: {}) @@ -380,6 +381,7 @@ public struct Struct { accessControl: AccessControl = AccessControl.none, name: SwiftIdentifier, protocols: [TypeReference] = [], + additionalModuleReferences: Set = [], @StructMembersBuilder membersBuilder: () -> StructMembers ) { self.comments = comments @@ -387,6 +389,7 @@ public struct Struct { self.accessControl = accessControl self.name = name self.protocols = protocols + self.additionalModuleReferences = additionalModuleReferences let members = membersBuilder() self.lets = members.lets @@ -410,6 +413,7 @@ public struct Struct { result.formUnion(funcs.flatMap(\.allModuleReferences)) result.formUnion(structs.flatMap(\.allModuleReferences)) result.formUnion(typealiasses.flatMap(\.allModuleReferences)) + result.formUnion(additionalModuleReferences) return result } diff --git a/Sources/rswift/App.swift b/Sources/rswift/App.swift index 2c71ff0b..9c444532 100644 --- a/Sources/rswift/App.swift +++ b/Sources/rswift/App.swift @@ -23,8 +23,8 @@ struct App: ParsableCommand { } enum InputType: String, ExpressibleByArgument { - case xcodeproj - case inputFiles + case xcodeproj = "xcodeproj" + case inputFiles = "input-files" } struct GlobalOptions: ParsableArguments { From 92890034e0d177f167011e614baf0b2d7cda3182 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Thu, 27 Oct 2022 17:16:57 +0200 Subject: [PATCH 099/161] Remove legacy code --- .../RswiftCoreLegacy/CallInformation.swift | 97 ---- .../EnvironmentValidation.swift | 73 --- ...cessibilityIdentifierStructGenerator.swift | 106 ----- .../AggregatedStructGenerator.swift | 95 ---- .../Generators/AssetSubfolders.swift | 39 -- .../Generators/ColorStructGenerator.swift | 167 ------- .../Generators/FontStructGenerator.swift | 95 ---- .../Generators/ImageStructGenerator.swift | 177 -------- .../Generators/NibStructGenerator.swift | 259 ----------- .../Generators/PropertyListGenerator.swift | 199 -------- .../ResourceFileStructGenerator.swift | 87 ---- .../Generators/ReuseIdentifierGenerator.swift | 59 --- .../Generators/SegueGenerator.swift | 178 -------- .../Generators/StoryboardGenerator.swift | 223 --------- .../Generators/StringsStructGenerator.swift | 390 ---------------- .../Generators/StructGenerator.swift | 29 -- .../Generators/ValidatedStructGenerator.swift | 110 ----- .../ResourceTypes/AssetFolder.swift | 182 -------- .../RswiftCoreLegacy/ResourceTypes/Font.swift | 35 -- .../ResourceTypes/Image.swift | 33 -- .../ResourceTypes/Locale.swift | 70 --- .../ResourceTypes/LocalizableStrings.swift | 185 -------- .../ResourceTypes/NameCatalog.swift | 23 - .../RswiftCoreLegacy/ResourceTypes/Nib.swift | 148 ------ .../ResourceTypes/PropertyList.swift | 31 -- .../ResourceTypes/ResourceFile.swift | 40 -- .../ResourceTypes/Resources.swift | 83 ---- .../ResourceTypes/ReusableContainer.swift | 19 - .../ResourceTypes/Storyboard.swift | 264 ----------- .../ResourceTypes/StringParam.swift | 267 ----------- .../ResourceTypes/Unifiable.swift | 36 -- .../WhiteListedExtensionsResourceType.swift | 25 -- .../ResourceTypes/Xcodeproj.swift | 98 ---- Sources/RswiftCoreLegacy/RswiftCore.swift | 215 --------- .../SwiftTypes/AccessLevel.swift | 25 -- .../RswiftCoreLegacy/SwiftTypes/Class.swift | 20 - .../CodeGenerators/HeaderPrinter.swift | 20 - .../CodeGenerators/ImportPrinter.swift | 37 -- .../SwiftTypes/CodeGenerators/OSPrinter.swift | 32 -- .../CodeGenerators/SwiftCodeConverible.swift | 14 - .../CodeGenerators/TypePrinter.swift | 45 -- .../SwiftTypes/Function.swift | 81 ---- Sources/RswiftCoreLegacy/SwiftTypes/Let.swift | 62 --- .../RswiftCoreLegacy/SwiftTypes/Module.swift | 57 --- .../RswiftCoreLegacy/SwiftTypes/Struct.swift | 104 ----- .../RswiftCoreLegacy/SwiftTypes/Type.swift | 109 ----- .../RswiftCoreLegacy/SwiftTypes/TypeVar.swift | 33 -- .../SwiftTypes/Typealias.swift | 27 -- .../RswiftCoreLegacy/Util/ErrorOutput.swift | 18 - Sources/RswiftCoreLegacy/Util/Glob.swift | 221 --------- .../RswiftCoreLegacy/Util/IgnoreFile.swift | 69 --- .../Util/Struct+InternalProperties.swift | 149 ------ .../Util/SwiftIdentifier.swift | 169 ------- .../Util/TypeSequenceProvider.swift | 18 - .../Util/UtilExtensions.swift | 92 ---- Sources/rswift-legacy/Rswift.swift | 14 - Sources/rswift-legacy/main.swift | 423 ------------------ 57 files changed, 5976 deletions(-) delete mode 100644 Sources/RswiftCoreLegacy/CallInformation.swift delete mode 100644 Sources/RswiftCoreLegacy/EnvironmentValidation.swift delete mode 100644 Sources/RswiftCoreLegacy/Generators/AccessibilityIdentifierStructGenerator.swift delete mode 100644 Sources/RswiftCoreLegacy/Generators/AggregatedStructGenerator.swift delete mode 100644 Sources/RswiftCoreLegacy/Generators/AssetSubfolders.swift delete mode 100644 Sources/RswiftCoreLegacy/Generators/ColorStructGenerator.swift delete mode 100644 Sources/RswiftCoreLegacy/Generators/FontStructGenerator.swift delete mode 100644 Sources/RswiftCoreLegacy/Generators/ImageStructGenerator.swift delete mode 100644 Sources/RswiftCoreLegacy/Generators/NibStructGenerator.swift delete mode 100644 Sources/RswiftCoreLegacy/Generators/PropertyListGenerator.swift delete mode 100644 Sources/RswiftCoreLegacy/Generators/ResourceFileStructGenerator.swift delete mode 100644 Sources/RswiftCoreLegacy/Generators/ReuseIdentifierGenerator.swift delete mode 100644 Sources/RswiftCoreLegacy/Generators/SegueGenerator.swift delete mode 100644 Sources/RswiftCoreLegacy/Generators/StoryboardGenerator.swift delete mode 100644 Sources/RswiftCoreLegacy/Generators/StringsStructGenerator.swift delete mode 100644 Sources/RswiftCoreLegacy/Generators/StructGenerator.swift delete mode 100644 Sources/RswiftCoreLegacy/Generators/ValidatedStructGenerator.swift delete mode 100644 Sources/RswiftCoreLegacy/ResourceTypes/AssetFolder.swift delete mode 100644 Sources/RswiftCoreLegacy/ResourceTypes/Font.swift delete mode 100644 Sources/RswiftCoreLegacy/ResourceTypes/Image.swift delete mode 100644 Sources/RswiftCoreLegacy/ResourceTypes/Locale.swift delete mode 100644 Sources/RswiftCoreLegacy/ResourceTypes/LocalizableStrings.swift delete mode 100644 Sources/RswiftCoreLegacy/ResourceTypes/NameCatalog.swift delete mode 100644 Sources/RswiftCoreLegacy/ResourceTypes/Nib.swift delete mode 100644 Sources/RswiftCoreLegacy/ResourceTypes/PropertyList.swift delete mode 100644 Sources/RswiftCoreLegacy/ResourceTypes/ResourceFile.swift delete mode 100644 Sources/RswiftCoreLegacy/ResourceTypes/Resources.swift delete mode 100644 Sources/RswiftCoreLegacy/ResourceTypes/ReusableContainer.swift delete mode 100644 Sources/RswiftCoreLegacy/ResourceTypes/Storyboard.swift delete mode 100644 Sources/RswiftCoreLegacy/ResourceTypes/StringParam.swift delete mode 100644 Sources/RswiftCoreLegacy/ResourceTypes/Unifiable.swift delete mode 100644 Sources/RswiftCoreLegacy/ResourceTypes/WhiteListedExtensionsResourceType.swift delete mode 100644 Sources/RswiftCoreLegacy/ResourceTypes/Xcodeproj.swift delete mode 100644 Sources/RswiftCoreLegacy/RswiftCore.swift delete mode 100644 Sources/RswiftCoreLegacy/SwiftTypes/AccessLevel.swift delete mode 100644 Sources/RswiftCoreLegacy/SwiftTypes/Class.swift delete mode 100644 Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/HeaderPrinter.swift delete mode 100644 Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/ImportPrinter.swift delete mode 100644 Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/OSPrinter.swift delete mode 100644 Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/SwiftCodeConverible.swift delete mode 100644 Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/TypePrinter.swift delete mode 100644 Sources/RswiftCoreLegacy/SwiftTypes/Function.swift delete mode 100644 Sources/RswiftCoreLegacy/SwiftTypes/Let.swift delete mode 100644 Sources/RswiftCoreLegacy/SwiftTypes/Module.swift delete mode 100644 Sources/RswiftCoreLegacy/SwiftTypes/Struct.swift delete mode 100644 Sources/RswiftCoreLegacy/SwiftTypes/Type.swift delete mode 100644 Sources/RswiftCoreLegacy/SwiftTypes/TypeVar.swift delete mode 100644 Sources/RswiftCoreLegacy/SwiftTypes/Typealias.swift delete mode 100644 Sources/RswiftCoreLegacy/Util/ErrorOutput.swift delete mode 100644 Sources/RswiftCoreLegacy/Util/Glob.swift delete mode 100644 Sources/RswiftCoreLegacy/Util/IgnoreFile.swift delete mode 100644 Sources/RswiftCoreLegacy/Util/Struct+InternalProperties.swift delete mode 100644 Sources/RswiftCoreLegacy/Util/SwiftIdentifier.swift delete mode 100644 Sources/RswiftCoreLegacy/Util/TypeSequenceProvider.swift delete mode 100644 Sources/RswiftCoreLegacy/Util/UtilExtensions.swift delete mode 100644 Sources/rswift-legacy/Rswift.swift delete mode 100644 Sources/rswift-legacy/main.swift diff --git a/Sources/RswiftCoreLegacy/CallInformation.swift b/Sources/RswiftCoreLegacy/CallInformation.swift deleted file mode 100644 index 8687fd7c..00000000 --- a/Sources/RswiftCoreLegacy/CallInformation.swift +++ /dev/null @@ -1,97 +0,0 @@ -// -// CallInformation.swift -// R.swift -// -// Created by Tom Lokhorst on 2017-04-22. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation -import XcodeEdit - -public struct CallInformation { - let outputURL: URL - let uiTestOutputURL: URL? - let rswiftIgnoreURL: URL - let hostingBundle: String? - - let generators: [Generator] - let accessLevel: AccessLevel - let imports: [Module] - - let xcodeprojURL: URL - let targetName: String - let bundleIdentifier: String - let productModuleName: String - let infoPlistFile: URL? - let codeSignEntitlements: URL? - - let builtProductsDirURL: URL - let developerDirURL: URL - let sourceRootURL: URL - let sdkRootURL: URL - let platformURL: URL - - public init( - outputURL: URL, - uiTestOutputURL: URL?, - rswiftIgnoreURL: URL, - hostingBundle: String?, - - generators: [Generator], - accessLevel: AccessLevel, - imports: [Module], - - xcodeprojURL: URL, - targetName: String, - bundleIdentifier: String, - productModuleName: String, - infoPlistFile: URL?, - codeSignEntitlements: URL?, - - builtProductsDirURL: URL, - developerDirURL: URL, - sourceRootURL: URL, - sdkRootURL: URL, - platformURL: URL - ) { - self.outputURL = outputURL - self.uiTestOutputURL = uiTestOutputURL - self.rswiftIgnoreURL = rswiftIgnoreURL - self.hostingBundle = hostingBundle - - self.accessLevel = accessLevel - self.imports = imports - self.generators = generators - - self.xcodeprojURL = xcodeprojURL - self.targetName = targetName - self.bundleIdentifier = bundleIdentifier - self.productModuleName = productModuleName - self.infoPlistFile = infoPlistFile - self.codeSignEntitlements = codeSignEntitlements - - self.builtProductsDirURL = builtProductsDirURL - self.developerDirURL = developerDirURL - self.sourceRootURL = sourceRootURL - self.sdkRootURL = sdkRootURL - self.platformURL = platformURL - } - - - func urlForSourceTreeFolder(_ sourceTreeFolder: SourceTreeFolder) -> URL { - switch sourceTreeFolder { - case .buildProductsDir: - return builtProductsDirURL - case .developerDir: - return developerDirURL - case .sdkRoot: - return sdkRootURL - case .sourceRoot: - return sourceRootURL - case .platformDir: - return platformURL - } - } -} diff --git a/Sources/RswiftCoreLegacy/EnvironmentValidation.swift b/Sources/RswiftCoreLegacy/EnvironmentValidation.swift deleted file mode 100644 index f6ffd837..00000000 --- a/Sources/RswiftCoreLegacy/EnvironmentValidation.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// EnvironmentValidation.swift -// Commander -// -// Created by Tom Lokhorst on 2018-12-12. -// - -import Foundation - -// TODO: "Check XXX output file" codeblocks contain a lot of duplication and are error prone to mix up variables, needs refactor -public func validateRswiftEnvironment( - outputURL: URL, - uiTestOutputURL: URL?, - sourceRootPath: String, - podsRoot: String?, - podsTargetSrcroot: String?, - commandLineArguments: [String]) -> [String] -{ - var errors: [String] = [] - - // Check regular output file - if outputURL.pathExtension != "swift" { - - var error = "Output path must specify a file, it should not be a directory." - if FileManager.default.directoryExists(atPath: outputURL.path) { - let rswiftGeneratedFile = outputURL.appendingPathComponent("R.generated.swift").path - - let commandParts = commandLineArguments - .map { $0.replacingOccurrences(of: outputURL.path, with: rswiftGeneratedFile) } - .map { $0.replacingOccurrences(of: podsTargetSrcroot ?? "", with: "$PODS_TARGET_SRCROOT") } - .map { $0.replacingOccurrences(of: podsRoot ?? "", with: "$PODS_ROOT") } - .map { $0.replacingOccurrences(of: sourceRootPath, with: "$SOURCE_ROOT") } - .map { $0.contains(" ") ? "\"\($0)\"" : $0 } - - error += "\nExample: " + commandParts.joined(separator: " ") - } - - errors.append(error) - } - - // Check UITest output file - if let uiTestOutputURL = uiTestOutputURL { - if uiTestOutputURL.pathExtension != "swift" { - - var error = "Output path for UI test file must specify a file, it should not be a directory." - if FileManager.default.directoryExists(atPath: uiTestOutputURL.path) { - let rswiftGeneratedFile = uiTestOutputURL.appendingPathComponent("R.generated.swift").path - - let commandParts = commandLineArguments - .map { $0.replacingOccurrences(of: uiTestOutputURL.path, with: rswiftGeneratedFile) } - .map { $0.replacingOccurrences(of: podsTargetSrcroot ?? "", with: "$PODS_TARGET_SRCROOT") } - .map { $0.replacingOccurrences(of: podsRoot ?? "", with: "$PODS_ROOT") } - .map { $0.replacingOccurrences(of: sourceRootPath, with: "$SOURCE_ROOT") } - .map { $0.contains(" ") ? "\"\($0)\"" : $0 } - - error += "\nExample: " + commandParts.joined(separator: " ") - } - - errors.append(error) - } - } - - return errors -} - -extension FileManager { - func directoryExists(atPath path: String) -> Bool { - var isDir: ObjCBool = false - let exists = fileExists(atPath: path, isDirectory: &isDir) - - return exists && isDir.boolValue - } -} diff --git a/Sources/RswiftCoreLegacy/Generators/AccessibilityIdentifierStructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/AccessibilityIdentifierStructGenerator.swift deleted file mode 100644 index c233a412..00000000 --- a/Sources/RswiftCoreLegacy/Generators/AccessibilityIdentifierStructGenerator.swift +++ /dev/null @@ -1,106 +0,0 @@ -// -// AccessibilityIdentifierStructGenerator.swift -// R.swift -// -// Created by Mathijs Kadijk on 04/06/2019. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -private protocol AccessibilityIdentifierContainer { - var name: String { get } - var usedAccessibilityIdentifiers: [String] { get } -} - -extension Nib: AccessibilityIdentifierContainer {} -extension Storyboard: AccessibilityIdentifierContainer {} - -struct AccessibilityIdentifierStructGenerator: ExternalOnlyStructGenerator { - private let accessibilityIdentifierContainers: [AccessibilityIdentifierContainer] - - init(nibs: [Nib], storyboards: [Storyboard]) { - accessibilityIdentifierContainers = nibs + storyboards - } - - func generatedStruct(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Struct { - let structName: SwiftIdentifier = "id" - let qualifiedName = prefix + structName - let structsForMergedContainers = accessibilityIdentifierContainers - .grouped(by: { SwiftIdentifier(name: $0.name) }) - .mapValues { - $0.flatMap { $0.usedAccessibilityIdentifiers } - } - .filter { $0.value.count > 0 } - .map { self.structFromContainer(identifier: $0.key, accessibilityIdentifiers: $0.value, at: externalAccessLevel) } - - return Struct( - availables: [], - comments: ["This `\(qualifiedName)` struct is generated, and contains static references to accessibility identifiers."], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: structName), - implements: [], - typealiasses: [], - properties: [], - functions: [], - structs: structsForMergedContainers, - classes: [], - os: [] - ) - } - - private func structFromContainer(identifier: SwiftIdentifier, accessibilityIdentifiers: [String], at externalAccessLevel: AccessLevel) -> Struct { - let groupedAccessibilityIdentifiers = Set(accessibilityIdentifiers) - .array() - .grouped(bySwiftIdentifier: { $0 }) - groupedAccessibilityIdentifiers.printWarningsForDuplicatesAndEmpties(source: "accessibility identifier", result: "accessibility identifier") - - let accessibilityIdentifierLets = groupedAccessibilityIdentifiers - .uniques - .map { letFromAccessibilityIdentifier($0, at: externalAccessLevel) } - - return Struct( - availables: [], - comments: [], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: identifier), - implements: [], - typealiasses: [], - properties: accessibilityIdentifierLets, - functions: [], - structs: [], - classes: [], - os: [] - ) - } - - private func letFromAccessibilityIdentifier(_ accessibilityIdentifier: String, at externalAccessLevel: AccessLevel) -> Let { - return Let( - comments: ["Accessibility identifier `\(accessibilityIdentifier)`."], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: accessibilityIdentifier), - typeDefinition: .specified(._String), - value: "\"\(accessibilityIdentifier)\"" - ) - } -} - -private extension Sequence { - func groupedWithExactDuplicatesAllowed(bySwiftIdentifier identifierSelector: @escaping (Iterator.Element) -> String) -> SwiftNameGroups { - var groupedBy = grouped { SwiftIdentifier(name: identifierSelector($0)) } - let empty = SwiftIdentifier(name: "") - let empties = groupedBy[empty]?.map { "'\(identifierSelector($0))'" }.sorted() - groupedBy[empty] = nil - - let uniques = Array(groupedBy.values.filter { $0.count == 1 }.joined()) - .sorted { identifierSelector($0) < identifierSelector($1) } - let duplicates = groupedBy - .filter { $0.1.count > 1 } - .map { ($0.0, $0.1.map(identifierSelector).sorted()) } - .sorted { $0.0.description < $1.0.description } - - return SwiftNameGroups(uniques: uniques, duplicates: duplicates, empties: empties ?? []) - } -} diff --git a/Sources/RswiftCoreLegacy/Generators/AggregatedStructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/AggregatedStructGenerator.swift deleted file mode 100644 index c65edc0f..00000000 --- a/Sources/RswiftCoreLegacy/Generators/AggregatedStructGenerator.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// AggregatedStructGenerator.swift -// R.swift -// -// Created by Mathijs Kadijk on 05-10-16. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -class AggregatedStructGenerator: StructGenerator { - private let subgenerators: [StructGenerator] - - init(subgenerators: [StructGenerator]) { - self.subgenerators = subgenerators - } - - func generatedStructs(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> StructGenerator.Result { - let structName: SwiftIdentifier = "R" - let qualifiedName = structName - let internalStructName: SwiftIdentifier = "_R" - - let collectedResult = subgenerators - .compactMap { - let result = $0.generatedStructs(at: externalAccessLevel, prefix: qualifiedName) - if result.externalStruct.isEmpty { return nil } - if let internalStruct = result.internalStruct, internalStruct.isEmpty { return nil } - - return result - } - .reduce(StructGeneratorResultCollector()) { collector, result in collector.appending(result) } - .sorted - - let externalStruct = Struct( - availables: [], - comments: ["This `\(qualifiedName)` struct is generated and contains references to static resources."], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: structName), - implements: [], - typealiasses: [], - properties: [], - functions: [], - structs: collectedResult.externalStructs, - classes: [], - os: [] - ) - - let internalStruct = Struct( - availables: [], - comments: [], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: internalStructName), - implements: [], - typealiasses: [], - properties: [], - functions: [], - structs: collectedResult.internalStructs, - classes: [], - os: [] - ) - - return (externalStruct, internalStruct) - } -} - -private struct StructGeneratorResultCollector { - let externalStructs: [Struct] - let internalStructs: [Struct] - - init() { - self.externalStructs = [] - self.internalStructs = [] - } - - private init(externalStructs: [Struct], internalStructs: [Struct]) { - self.externalStructs = externalStructs - self.internalStructs = internalStructs - } - - func appending(_ result: StructGenerator.Result) -> StructGeneratorResultCollector { - return StructGeneratorResultCollector( - externalStructs: externalStructs + [result.externalStruct], - internalStructs: internalStructs + [result.internalStruct].compactMap { $0 } - ) - } - - var sorted: StructGeneratorResultCollector { - return StructGeneratorResultCollector( - externalStructs: externalStructs.sorted(by: { $0.type.name.description < $1.type.name.description }), - internalStructs: internalStructs.sorted(by: { $0.type.name.description < $1.type.name.description }) - ) - } -} - diff --git a/Sources/RswiftCoreLegacy/Generators/AssetSubfolders.swift b/Sources/RswiftCoreLegacy/Generators/AssetSubfolders.swift deleted file mode 100644 index 1b7209e5..00000000 --- a/Sources/RswiftCoreLegacy/Generators/AssetSubfolders.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// AssetSubfolders.swift -// R.swift -// -// Created by Tom Lokhorst on 2017-06-06. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct AssetSubfolders { - let folders: [NamespacedAssetSubfolder] - let duplicates: [NamespacedAssetSubfolder] - - init(all subfolders: [NamespacedAssetSubfolder], assetIdentifiers: [SwiftIdentifier]) { - var dict: [SwiftIdentifier: NamespacedAssetSubfolder] = [:] - - for subfolder in subfolders { - let name = SwiftIdentifier(name: subfolder.name) - if let duplicate = dict[name] { - duplicate.subfolders += subfolder.subfolders - duplicate.imageAssets += subfolder.imageAssets - } else { - dict[name] = subfolder - } - } - - self.folders = dict.values.filter { !assetIdentifiers.contains(SwiftIdentifier(name: $0.name)) } - self.duplicates = dict.values.filter { assetIdentifiers.contains(SwiftIdentifier(name: $0.name)) } - } - - func printWarningsForDuplicates() { - for subfolder in duplicates { - warn("Skipping asset subfolder because symbol '\(subfolder.name)' would conflict with image: \(subfolder.name)") - } - } -} - diff --git a/Sources/RswiftCoreLegacy/Generators/ColorStructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/ColorStructGenerator.swift deleted file mode 100644 index 3fdf216e..00000000 --- a/Sources/RswiftCoreLegacy/Generators/ColorStructGenerator.swift +++ /dev/null @@ -1,167 +0,0 @@ -// -// ColorStructGenerator.swift -// R.swift -// -// Created by Tom Lokhorst on 2017-06-06. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct ColorStructGenerator: ExternalOnlyStructGenerator { - private let assetFolders: [AssetFolder] - - init(assetFolders: [AssetFolder]) { - self.assetFolders = assetFolders - } - - func generatedStruct(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Struct { - let structName: SwiftIdentifier = "color" - let qualifiedName = prefix + structName - let assetFolderColorNames = assetFolders - .flatMap { $0.colorAssets } - - let groupedColors = assetFolderColorNames.grouped(bySwiftIdentifier: { $0 }) - groupedColors.printWarningsForDuplicatesAndEmpties(source: "color", result: "color") - - - let assetSubfolders = AssetSubfolders( - all: assetFolders.flatMap { $0.subfolders }, - assetIdentifiers: groupedColors.uniques.map { SwiftIdentifier(name: $0) }) - - assetSubfolders.printWarningsForDuplicates() - - let structs = assetSubfolders.folders - .map { $0.generatedColorStruct(at: externalAccessLevel, prefix: qualifiedName) } - .filter { !$0.isEmpty } - - let colorLets = groupedColors - .uniques - .map { name in - Let( - comments: ["Color `\(name)`."], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: name), - typeDefinition: .inferred(Type.ColorResource), - value: "Rswift.ColorResource(bundle: R.hostingBundle, name: \"\(name)\")" - ) - } - - return Struct( - availables: [], - comments: ["This `\(qualifiedName)` struct is generated, and contains static references to \(colorLets.count) colors."], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: structName), - implements: [], - typealiasses: [], - properties: colorLets, - functions: groupedColors.uniques.map { [ generateColorFunction(for: $0, at: externalAccessLevel, prefix: qualifiedName), - generateWatchOSColorFunction(for: $0, at: externalAccessLevel, prefix: qualifiedName)] }.flatMap { $0 }, - structs: structs, - classes: [], - os: [] - ) - } - - -} - -private extension NamespacedAssetSubfolder { - func generatedColorStruct(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Struct { - let allFunctions = colorAssets - let groupedFunctions = allFunctions.grouped(bySwiftIdentifier: { $0 }) - - groupedFunctions.printWarningsForDuplicatesAndEmpties(source: "color", result: "color") - - - let assetSubfolders = AssetSubfolders( - all: subfolders, - assetIdentifiers: allFunctions.map { SwiftIdentifier(name: $0) }) - - assetSubfolders.printWarningsForDuplicates() - - let colorPath = resourcePath + (!path.isEmpty ? "/" : "") - let structName = SwiftIdentifier(name: self.name) - let qualifiedName = prefix + structName - let structs = assetSubfolders.folders - .map { $0.generatedColorStruct(at: externalAccessLevel, prefix: qualifiedName) } - .filter { !$0.isEmpty } - - let colorLets = groupedFunctions - .uniques - .map { name in - Let( - comments: ["Color `\(name)`."], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: name), - typeDefinition: .inferred(Type.ColorResource), - value: "Rswift.ColorResource(bundle: R.hostingBundle, name: \"\(colorPath)\(name)\")" - ) - } - - return Struct( - availables: [], - comments: ["This `\(qualifiedName)` struct is generated, and contains static references to \(colorLets.count) colors."], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: structName), - implements: [], - typealiasses: [], - properties: colorLets, - functions: groupedFunctions.uniques.map { [ generateColorFunction(for: $0, at: externalAccessLevel, prefix: qualifiedName), - generateWatchOSColorFunction(for: $0, at: externalAccessLevel, prefix: qualifiedName)] }.flatMap { $0 }, - structs: structs, - classes: [], - os: [] - ) - } -} - -private func generateColorFunction(for name: String, at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Function { - let structName = SwiftIdentifier(name: name) - let qualifiedName = prefix + structName - - return Function( - availables: ["tvOS 11.0, *", "iOS 11.0, *"], - comments: ["`UIColor(named: \"\(name)\", bundle: ..., traitCollection: ...)`"], - accessModifier: externalAccessLevel, - isStatic: true, - name: structName, - generics: nil, - parameters: [ - Function.Parameter( - name: "compatibleWith", - localName: "traitCollection", - type: Type._UITraitCollection.asOptional(), - defaultValue: "nil" - ) - ], - doesThrow: false, - returnType: Type._UIColor.asOptional(), - body: "return UIKit.UIColor(resource: \(qualifiedName), compatibleWith: traitCollection)", - os: ["iOS", "tvOS"] - ) -} - -private func generateWatchOSColorFunction(for name: String, at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Function { - let structName = SwiftIdentifier(name: name) - let qualifiedName = prefix + structName - - return Function( - availables: ["watchOSApplicationExtension 4.0, *"], - comments: ["`UIColor(named: \"\(name)\", bundle: ..., traitCollection: ...)`"], - accessModifier: externalAccessLevel, - isStatic: true, - name: structName, - generics: nil, - parameters: [ - Function.Parameter(name: "_", type: Type._Void, defaultValue: "()") - ], - doesThrow: false, - returnType: Type._UIColor.asOptional(), - body: "return UIKit.UIColor(named: \(qualifiedName).name)", - os: ["watchOS"] - ) -} diff --git a/Sources/RswiftCoreLegacy/Generators/FontStructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/FontStructGenerator.swift deleted file mode 100644 index cafa157c..00000000 --- a/Sources/RswiftCoreLegacy/Generators/FontStructGenerator.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// FontStructGenerator.swift -// R.swift -// -// Created by Mathijs Kadijk on 10-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct FontStructGenerator: ExternalOnlyStructGenerator { - private let fonts: [Font] - - init(fonts: [Font]) { - self.fonts = fonts - } - - func generatedStruct(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Struct { - let structName: SwiftIdentifier = "font" - let qualifiedName = prefix + structName - - let groupedFonts = fonts.grouped(bySwiftIdentifier: { $0.name }) - groupedFonts.printWarningsForDuplicatesAndEmpties(source: "font resource", result: "file") - - let fontTypes = groupedFonts.uniques.map { font -> (Let, Function, String) in - let properties = Let( - comments: ["Font `\(font.name)`."], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: font.name), - typeDefinition: .inferred(Type.FontResource), - value: "Rswift.FontResource(fontName: \"\(font.name)\")" - ) - - let function = Function( - availables: [], - comments: ["`UIFont(name: \"\(font.name)\", size: ...)`"], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: font.name), - generics: nil, - parameters: [ - Function.Parameter(name: "size", type: Type._CGFloat) - ], - doesThrow: false, - returnType: Type._UIFont.asOptional(), - body: "return UIKit.UIFont(resource: \(SwiftIdentifier(name: font.name)), size: size)", - os: [] - ) - - let fontName = qualifiedName + SwiftIdentifier(name: font.name) - let validateLine = "if \(fontName)(size: 42) == nil { throw Rswift.ValidationError(description:\"[R.swift] Font '\(font.name)' could not be loaded, is '\(font.filename)' added to the UIAppFonts array in this targets Info.plist?\") }" - - return (properties, function, validateLine) - } - - var implements: [TypePrinter] = [] - let properties = fontTypes.map { $0.0 } - var functions = fontTypes.map { $0.1 } - let validateLines = fontTypes.map { $0.2 } - - if validateLines.count > 0 { - let validateFunction = Function( - availables: [], - comments: [], - accessModifier: externalAccessLevel, - isStatic: true, - name: "validate", - generics: nil, - parameters: [], - doesThrow: true, - returnType: Type._Void, - body: validateLines.joined(separator: "\n"), - os: [] - ) - functions.append(validateFunction) - implements.append(TypePrinter(type: Type.Validatable)) - } - - return Struct( - availables: [], - comments: ["This `R.font` struct is generated, and contains static references to \(fonts.count) fonts."], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: structName), - implements: implements, - typealiasses: [], - properties: properties, - functions: functions, - structs: [], - classes: [], - os: [] - ) - } -} diff --git a/Sources/RswiftCoreLegacy/Generators/ImageStructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/ImageStructGenerator.swift deleted file mode 100644 index 2ccb17a7..00000000 --- a/Sources/RswiftCoreLegacy/Generators/ImageStructGenerator.swift +++ /dev/null @@ -1,177 +0,0 @@ -// -// ImageStructGenerator.swift -// R.swift -// -// Created by Mathijs Kadijk on 10-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct ImageStructGenerator: ExternalOnlyStructGenerator { - private let assetFolders: [AssetFolder] - private let images: [Image] - - init(assetFolders: [AssetFolder], images: [Image]) { - self.assetFolders = assetFolders - self.images = images - } - - func generatedStruct(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Struct { - let structName: SwiftIdentifier = "image" - let qualifiedName = prefix + structName - let assetFolderImageNames = assetFolders - .flatMap { $0.imageAssets } - - let imagesNames = images - .grouped { $0.name } - .values - .compactMap { $0.first?.name } - - let allFunctions = assetFolderImageNames + imagesNames - let groupedFunctions = allFunctions.grouped(bySwiftIdentifier: { $0 }) - - groupedFunctions.printWarningsForDuplicatesAndEmpties(source: "image", result: "image") - - - let assetSubfolders = AssetSubfolders( - all: assetFolders.flatMap { $0.subfolders }, - assetIdentifiers: allFunctions.map { SwiftIdentifier(name: $0) }) - - assetSubfolders.printWarningsForDuplicates() - - let structs = assetSubfolders.folders - .map { $0.generatedImageStruct(at: externalAccessLevel, prefix: qualifiedName) } - .filter { !$0.isEmpty } - - let imageLets = groupedFunctions - .uniques - .map { name in - Let( - comments: ["Image `\(name)`."], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: name), - typeDefinition: .inferred(Type.ImageResource), - value: "Rswift.ImageResource(bundle: R.hostingBundle, name: \"\(name)\")" - ) - } - - return Struct( - availables: [], - comments: ["This `\(qualifiedName)` struct is generated, and contains static references to \(imageLets.count) images."], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: structName), - implements: [], - typealiasses: [], - properties: imageLets, - functions: groupedFunctions.uniques.map { imageFunction(for: $0, at: externalAccessLevel, prefix: qualifiedName) }, - structs: structs, - classes: [], - os: [] - ) - } - - private func imageFunction(for name: String, at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Function { - let structName = SwiftIdentifier(name: name) - let qualifiedName = prefix + structName - - return Function( - availables: [], - comments: ["`UIImage(named: \"\(name)\", bundle: ..., traitCollection: ...)`"], - accessModifier: externalAccessLevel, - isStatic: true, - name: structName, - generics: nil, - parameters: [ - Function.Parameter( - name: "compatibleWith", - localName: "traitCollection", - type: Type._UITraitCollection.asOptional(), - defaultValue: "nil" - ) - ], - doesThrow: false, - returnType: Type._UIImage.asOptional(), - body: "return UIKit.UIImage(resource: \(qualifiedName), compatibleWith: traitCollection)", - os: ["iOS", "tvOS"] - ) - } -} - -private extension NamespacedAssetSubfolder { - func generatedImageStruct(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Struct { - let allFunctions = imageAssets - let groupedFunctions = allFunctions.grouped(bySwiftIdentifier: { $0 }) - - groupedFunctions.printWarningsForDuplicatesAndEmpties(source: "image", result: "image") - - - let assetSubfolders = AssetSubfolders( - all: subfolders, - assetIdentifiers: allFunctions.map { SwiftIdentifier(name: $0) }) - - assetSubfolders.printWarningsForDuplicates() - - let imagePath = resourcePath + (!path.isEmpty ? "/" : "") - let structName = SwiftIdentifier(name: self.name) - let qualifiedName = prefix + structName - let structs = assetSubfolders.folders - .map { $0.generatedImageStruct(at: externalAccessLevel, prefix: qualifiedName) } - .filter { !$0.isEmpty } - - let imageLets = groupedFunctions - .uniques - .map { name in - Let( - comments: ["Image `\(name)`."], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: name), - typeDefinition: .inferred(Type.ImageResource), - value: "Rswift.ImageResource(bundle: R.hostingBundle, name: \"\(imagePath)\(name)\")" - ) - } - - return Struct( - availables: [], - comments: ["This `\(qualifiedName)` struct is generated, and contains static references to \(imageLets.count) images."], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: structName), - implements: [], - typealiasses: [], - properties: imageLets, - functions: groupedFunctions.uniques.map { imageFunction(for: $0, at: externalAccessLevel, prefix: qualifiedName) }, - structs: structs, - classes: [], - os: [] - ) - } - - private func imageFunction(for name: String, at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Function { - let structName = SwiftIdentifier(name: name) - let qualifiedName = prefix + structName - - return Function( - availables: [], - comments: ["`UIImage(named: \"\(name)\", bundle: ..., traitCollection: ...)`"], - accessModifier: externalAccessLevel, - isStatic: true, - name: structName, - generics: nil, - parameters: [ - Function.Parameter( - name: "compatibleWith", - localName: "traitCollection", - type: Type._UITraitCollection.asOptional(), - defaultValue: "nil" - ) - ], - doesThrow: false, - returnType: Type._UIImage.asOptional(), - body: "return UIKit.UIImage(resource: \(qualifiedName), compatibleWith: traitCollection)", - os: ["iOS", "tvOS"] - ) - } -} diff --git a/Sources/RswiftCoreLegacy/Generators/NibStructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/NibStructGenerator.swift deleted file mode 100644 index e1462824..00000000 --- a/Sources/RswiftCoreLegacy/Generators/NibStructGenerator.swift +++ /dev/null @@ -1,259 +0,0 @@ -// -// NibStructGenerator.swift -// R.swift -// -// Created by Mathijs Kadijk on 10-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -private let Ordinals = [ - (number: 1, word: "first"), - (number: 2, word: "second"), - (number: 3, word: "third"), - (number: 4, word: "fourth"), - (number: 5, word: "fifth"), - (number: 6, word: "sixth"), - (number: 7, word: "seventh"), - (number: 8, word: "eighth"), - (number: 9, word: "ninth"), - (number: 10, word: "tenth"), - (number: 11, word: "eleventh"), - (number: 12, word: "twelfth"), - (number: 13, word: "thirteenth"), - (number: 14, word: "fourteenth"), - (number: 15, word: "fifteenth"), - (number: 16, word: "sixteenth"), - (number: 17, word: "seventeenth"), - (number: 18, word: "eighteenth"), - (number: 19, word: "nineteenth"), - (number: 20, word: "twentieth"), -] - -struct NibStructGenerator: StructGenerator { - private let nibs: [Nib] - - private let instantiateParameters = [ - Function.Parameter(name: "owner", localName: "ownerOrNil", type: Type._AnyObject.asOptional()), - Function.Parameter(name: "options", localName: "optionsOrNil", type: Type(module: .stdLib, name: SwiftIdentifier(rawValue: "[UINib.OptionsKey : Any]"), optional: true), defaultValue: "nil") - ] - - init(nibs: [Nib]) { - self.nibs = nibs - } - - func generatedStructs(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> StructGenerator.Result { - let structName: SwiftIdentifier = "nib" - let qualifiedName = prefix + structName - let groupedNibs = nibs.grouped(bySwiftIdentifier: { $0.name }) - groupedNibs.printWarningsForDuplicatesAndEmpties(source: "xib", result: "file") - - let internalStruct = Struct( - availables: [], - comments: [], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: structName), - implements: [], - typealiasses: [], - properties: [], - functions: [], - structs: groupedNibs - .uniques - .map { nibStruct(for: $0, at: externalAccessLevel) }, - classes: [], - os: ["iOS", "tvOS"] - ) - - let nibProperties: [Let] = groupedNibs - .uniques - .map { nibVar(for: $0, at: externalAccessLevel, prefix: qualifiedName) } - let nibFunctions: [Function] = groupedNibs - .uniques - .flatMap { nib -> [Function] in - let qualifiedCurrentNibName = qualifiedName + SwiftIdentifier(name: nib.name) - - let deprecatedFunction = Function( - availables: ["*, deprecated, message: \"Use UINib(resource: \(qualifiedCurrentNibName)) instead\""], - comments: ["`UINib(name: \"\(nib.name)\", in: bundle)`"], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: nib.name), - generics: nil, - parameters: [ - Function.Parameter(name: "_", type: Type._Void, defaultValue: "()") - ], - doesThrow: false, - returnType: Type._UINib, - body: "return UIKit.UINib(resource: \(qualifiedCurrentNibName))", - os: ["iOS", "tvOS"] - ) - - guard let firstViewInfo = nib.rootViews.first else { return [deprecatedFunction] } - - let newFunction = Function( - availables: [], - comments: [], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: nib.name), - generics: nil, - parameters: instantiateParameters, - doesThrow: false, - returnType: firstViewInfo.asOptional(), - body: "return \(qualifiedCurrentNibName).instantiate(withOwner: ownerOrNil, options: optionsOrNil)[0] as? \(firstViewInfo)", - os: [] - ) - - return [deprecatedFunction, newFunction] - } - - let externalStruct = Struct( - availables: [], - comments: ["This `\(qualifiedName)` struct is generated, and contains static references to \(nibProperties.count) nibs."], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: structName), - implements: [], - typealiasses: [], - properties: nibProperties, - functions: nibFunctions, - structs: [], - classes: [], - os: [] - ) - - return ( - externalStruct, - internalStruct - ) - } - - private func nibVar(for nib: Nib, at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Let { - let structName = SwiftIdentifier(name: "_\(nib.name)") - let qualifiedName = prefix + structName - let structType = Type(module: .host, name: SwiftIdentifier(rawValue: "_\(qualifiedName)")) - return Let( - comments: ["Nib `\(nib.name)`."], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: nib.name), - typeDefinition: .inferred(structType), - value: "\(structType)()" - ) - } - - private func nibStruct(for nib: Nib, at externalAccessLevel: AccessLevel) -> Struct { - let bundleLet = Let( - comments: [], - accessModifier: externalAccessLevel, - isStatic: false, - name: "bundle", - typeDefinition: .inferred(Type._Bundle), - value: "R.hostingBundle" - ) - - let nameVar = Let( - comments: [], - accessModifier: externalAccessLevel, - isStatic: false, - name: "name", - typeDefinition: .inferred(Type._String), - value: "\"\(nib.name)\"" - ) - - let viewFuncs = zip(nib.rootViews, Ordinals) - .map { (view: $0.0, ordinal: $0.1) } - .map { viewInfo -> Function in - let viewIndex = viewInfo.ordinal.number - 1 - let viewTypeString = viewInfo.view.description - return Function( - availables: [], - comments: [], - accessModifier: externalAccessLevel, - isStatic: false, - name: SwiftIdentifier(name: "\(viewInfo.ordinal.word)View"), - generics: nil, - parameters: instantiateParameters, - doesThrow: false, - returnType: viewInfo.view.asOptional(), - body: "return instantiate(withOwner: ownerOrNil, options: optionsOrNil)[\(viewIndex)] as? \(viewTypeString)", - os: [] - ) - } - - let reuseIdentifierProperties: [Let] - let reuseProtocols: [Type] - let reuseTypealiasses: [Typealias] - if let reusable = nib.reusables.first , nib.rootViews.count == 1 && nib.reusables.count == 1 { - reuseIdentifierProperties = [Let( - comments: [], - accessModifier: externalAccessLevel, - isStatic: false, - name: "identifier", - typeDefinition: .inferred(Type._String), - value: "\"\(reusable.identifier)\"" - )] - reuseTypealiasses = [Typealias(accessModifier: externalAccessLevel, alias: "ReusableType", type: reusable.type)] - reuseProtocols = [Type.ReuseIdentifierType] - } else { - reuseIdentifierProperties = [] - reuseTypealiasses = [] - reuseProtocols = [] - } - - // Validation - let validateImagesLines = nib.usedImageIdentifiers.uniqueAndSorted() - .map { nameCatalog -> String in - if nameCatalog.isSystemCatalog { - return "if #available(iOS 13.0, *) { if UIKit.UIImage(systemName: \"\(nameCatalog.name)\") == nil { throw Rswift.ValidationError(description: \"[R.swift] System image named '\(nameCatalog.name)' is used in nib '\(nib.name)', but couldn't be loaded.\") } }" - } else { - return "if UIKit.UIImage(named: \"\(nameCatalog.name)\", in: R.hostingBundle, compatibleWith: nil) == nil { throw Rswift.ValidationError(description: \"[R.swift] Image named '\(nameCatalog.name)' is used in nib '\(nib.name)', but couldn't be loaded.\") }" - } - } - let validateColorLines = nib.usedColorResources.uniqueAndSorted() - .compactMap { nameCatalog -> String? in - if nameCatalog.isSystemCatalog { return nil } - return "if UIKit.UIColor(named: \"\(nameCatalog.name)\", in: R.hostingBundle, compatibleWith: nil) == nil { throw Rswift.ValidationError(description: \"[R.swift] Color named '\(nameCatalog.name)' is used in nib '\(nib.name)', but couldn't be loaded.\") }" - } - let validateColorLinesWithAvailableIf = ["if #available(iOS 11.0, tvOS 11.0, *) {"] + - validateColorLines.map { $0.indent(with: " ") } + - ["}"] - - var validateFunctions: [Function] = [] - var validateImplements: [Type] = [] - if validateImagesLines.count > 0 { - let validateFunction = Function( - availables: [], - comments: [], - accessModifier: externalAccessLevel, - isStatic: true, - name: "validate", - generics: nil, - parameters: [], - doesThrow: true, - returnType: Type._Void, - body: (validateImagesLines + validateColorLinesWithAvailableIf).joined(separator: "\n"), - os: [] - ) - validateFunctions.append(validateFunction) - validateImplements.append(Type.Validatable) - } - - let sanitizedName = SwiftIdentifier(name: nib.name, lowercaseStartingCharacters: false) - - return Struct( - availables: [], - comments: [], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: SwiftIdentifier(name: "_\(sanitizedName)")), - implements: ([Type.NibResourceType] + reuseProtocols + validateImplements).map(TypePrinter.init), - typealiasses: reuseTypealiasses, - properties: [bundleLet, nameVar] + reuseIdentifierProperties, - functions: viewFuncs + validateFunctions, - structs: [], - classes: [], - os: [] - ) - } -} diff --git a/Sources/RswiftCoreLegacy/Generators/PropertyListGenerator.swift b/Sources/RswiftCoreLegacy/Generators/PropertyListGenerator.swift deleted file mode 100644 index 71694561..00000000 --- a/Sources/RswiftCoreLegacy/Generators/PropertyListGenerator.swift +++ /dev/null @@ -1,199 +0,0 @@ -// -// PropertyListGenerator.swift -// R.swift -// -// Created by Tom Lokhorst on 2018-07-07. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct PropertyListGenerator: ExternalOnlyStructGenerator { - private let name: SwiftIdentifier - private let plists: [PropertyList] - private let toplevelKeysWhitelist: [String]? - - init(name: SwiftIdentifier, plists: [PropertyList], toplevelKeysWhitelist: [String]?) { - self.name = name - self.plists = plists - self.toplevelKeysWhitelist = toplevelKeysWhitelist - } - - func generatedStruct(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Struct { - guard let plist = plists.first else { return .empty } - - guard plists.all(where: { $0.url == plist.url }) else { - let configs = plists.map { $0.buildConfigurationName } - warn("Build configurations \(configs) use different \(name) files, this is not yet supported") - return .empty - } - - let contents: PropertyList.Contents - if let whitelist = toplevelKeysWhitelist { - contents = plist.contents.filter { (key, _) in whitelist.contains(key) } - } else { - contents = plist.contents - } - - let qualifiedName = prefix + name - - return Struct( - availables: [], - comments: ["This `\(qualifiedName)` struct is generated, and contains static references to \(contents.count) properties."], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: name), - implements: [], - typealiasses: [], - properties: propertiesFromInfoPlist(contents: contents, path: [], at: externalAccessLevel, printWarnings: false), - functions: [], - structs: structsFromInfoPlist(contents: contents, path: [], at: externalAccessLevel), - classes: [], - os: [] - ) - } - - private func propertiesFromInfoPlist(contents duplicateContents: [String: Any], path: [String], at externalAccessLevel: AccessLevel, printWarnings: Bool) -> [Let] { - let groupedContents = duplicateContents.grouped(bySwiftIdentifier: { $0.key }) - // We do never print the warnings because this method is always called together with `structsFromInfoPlist` that will print the warning. - // If we did print warnings they will be duplicate. - let contents = Dictionary(uniqueKeysWithValues: groupedContents.uniques) - - return contents - .compactMap { (key, value) -> Let? in - switch value { - case let value as Bool: - return Let( - comments: [], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: key), - typeDefinition: .inferred(Type._Bool), - value: "\(value)" - ) - case let value as String: - return propertyFromInfoString(key: key, value: value, path: path, at: externalAccessLevel) - default: - return nil - } - } - } - - private func propertyFromInfoString(key: String, value: String, path: [String], at externalAccessLevel: AccessLevel) -> Let { - - let steps = path.map { "\"\($0.escapedStringLiteral)\"" }.joined(separator: ", ") - - let isKey = key == "_key" - let letValue: String = isKey - ? "\"\(value.escapedStringLiteral)\"" - : "infoPlistString(path: [\(steps)], key: \"\(key.escapedStringLiteral)\") ?? \"\(value.escapedStringLiteral)\"" - - return Let( - comments: [], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: key), - typeDefinition: .inferred(Type._String), - value: letValue - ) - } - - private func structsFromInfoPlist(contents duplicateContents: [String: Any], path: [String], at externalAccessLevel: AccessLevel) -> [Struct] { - let groupedContents = duplicateContents.grouped(bySwiftIdentifier: { $0.key }) - groupedContents.printWarningsForDuplicatesAndEmpties(source: name.description, result: name.description) - let contents = Dictionary(uniqueKeysWithValues: groupedContents.uniques) - - return contents - .compactMap { (key, value) -> Struct? in - var ps = path - ps.append(key) - - switch value { - case let duplicateArray as [String]: - let groupedArray = duplicateArray.grouped(bySwiftIdentifier: { $0 }) - groupedArray.printWarningsForDuplicatesAndEmpties(source: name.description, result: name.description) - let array = groupedArray.uniques - - return Struct( - availables: [], - comments: [], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: SwiftIdentifier(name: key)), - implements: [], - typealiasses: [], - properties: array.map { item in - propertyFromInfoString(key: item, value: item, path: ps, at: externalAccessLevel) - }, - functions: [], - structs: [], - classes: [], - os: [] - ) - - case var dict as [String: Any]: - dict["_key"] = key - - return Struct( - availables: [], - comments: [], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: SwiftIdentifier(name: key)), - implements: [], - typealiasses: [], - properties: propertiesFromInfoPlist(contents: dict, path: ps, at: externalAccessLevel, printWarnings: false), - functions: [], - structs: structsFromInfoPlist(contents: dict, path: ps, at: externalAccessLevel), - classes: [], - os: [] - ) - - case let dicts as [[String: Any]] where arrayOfDictionariesPrimaryKeys.keys.contains(key): - return structForArrayOfDictionaries(key: key, dicts: dicts, path: path, at: externalAccessLevel) - - default: - return nil - } - } - } - - // For arrays of dictionaries we need a primary key. - // This key will be used as a name for the struct in the generated code. - private let arrayOfDictionariesPrimaryKeys: [String: String] = [ - "UIWindowSceneSessionRoleExternalDisplay": "UISceneConfigurationName", - "UIWindowSceneSessionRoleApplication": "UISceneConfigurationName", - "UIApplicationShortcutItems": "UIApplicationShortcutItemType", - "CFBundleDocumentTypes": "CFBundleTypeName", - "CFBundleURLTypes": "CFBundleURLName" - ] - - private func structForArrayOfDictionaries(key: String, dicts: [[String: Any]], path: [String], at externalAccessLevel: AccessLevel) -> Struct { - let kvs = dicts.compactMap { dict -> (String, [String: Any])? in - if - let primaryKey = arrayOfDictionariesPrimaryKeys[key], - let type = dict[primaryKey] as? String - { - return (type, dict) - } - - return nil - } - - var ps = path - ps.append(key) - - let contents = Dictionary(kvs, uniquingKeysWith: { (l, _) in l }) - return Struct( - availables: [], - comments: [], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: SwiftIdentifier(name: key)), - implements: [], - typealiasses: [], - properties: [], - functions: [], - structs: structsFromInfoPlist(contents: contents, path: ps, at: externalAccessLevel), - classes: [], - os: [] - ) - } -} diff --git a/Sources/RswiftCoreLegacy/Generators/ResourceFileStructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/ResourceFileStructGenerator.swift deleted file mode 100644 index 8b1c6e7a..00000000 --- a/Sources/RswiftCoreLegacy/Generators/ResourceFileStructGenerator.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// ResourceFileStructGenerator.swift -// R.swift -// -// Created by Mathijs Kadijk on 10-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct ResourceFileStructGenerator: ExternalOnlyStructGenerator { - private let resourceFiles: [ResourceFile] - - init(resourceFiles: [ResourceFile]) { - self.resourceFiles = resourceFiles - } - - func generatedStruct(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Struct { - let structName: SwiftIdentifier = "file" - let qualifiedName = prefix + structName - let localized = resourceFiles.grouped(by: { $0.fullname }) - let groupedLocalized = localized.grouped(bySwiftIdentifier: { $0.0 }) - - groupedLocalized.printWarningsForDuplicatesAndEmpties(source: "resource file", result: "file") - - // For resource files, the contents of the different locales don't matter, so we just use the first one - let firstLocales = groupedLocalized.uniques.map { ($0.0, Array($0.1.prefix(1))) } - - return Struct( - availables: [], - comments: ["This `\(qualifiedName)` struct is generated, and contains static references to \(firstLocales.count) files."], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: structName), - implements: [], - typealiasses: [], - properties: firstLocales.flatMap { propertiesFromResourceFiles(resourceFiles: $0.1, at: externalAccessLevel) }, - functions: firstLocales.flatMap { functionsFromResourceFiles(resourceFiles: $0.1, at: externalAccessLevel) }, - structs: [], - classes: [], - os: [] - ) - } - - private func propertiesFromResourceFiles(resourceFiles: [ResourceFile], at externalAccessLevel: AccessLevel) -> [Let] { - - return resourceFiles - .map { - return Let( - comments: ["Resource file `\($0.fullname)`."], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: $0.fullname), - typeDefinition: .inferred(Type.FileResource), - value: "Rswift.FileResource(bundle: R.hostingBundle, name: \"\($0.filename)\", pathExtension: \"\($0.pathExtension)\")" - ) - } - } - - private func functionsFromResourceFiles(resourceFiles: [ResourceFile], at externalAccessLevel: AccessLevel) -> [Function] { - - return resourceFiles - .flatMap { resourceFile -> [Function] in - let fullname = resourceFile.fullname - let filename = resourceFile.filename - let pathExtension = resourceFile.pathExtension - - return [ - Function( - availables: [], - comments: ["`bundle.url(forResource: \"\(filename)\", withExtension: \"\(pathExtension)\")`"], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: fullname), - generics: nil, - parameters: [ - Function.Parameter(name: "_", type: Type._Void, defaultValue: "()") - ], - doesThrow: false, - returnType: Type._URL.asOptional(), - body: "let fileResource = R.file.\(SwiftIdentifier(name: fullname))\nreturn fileResource.bundle.url(forResource: fileResource)", - os: [] - ) - ] - } - } -} diff --git a/Sources/RswiftCoreLegacy/Generators/ReuseIdentifierGenerator.swift b/Sources/RswiftCoreLegacy/Generators/ReuseIdentifierGenerator.swift deleted file mode 100644 index 09285e4c..00000000 --- a/Sources/RswiftCoreLegacy/Generators/ReuseIdentifierGenerator.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// ReuseIdentifierStructGenerator.swift -// R.swift -// -// Created by Mathijs Kadijk on 10-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct ReuseIdentifierStructGenerator: ExternalOnlyStructGenerator { - private let reusables: [Reusable] - - init(reusables: [Reusable]) { - self.reusables = reusables - } - - func generatedStruct(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Struct { - let structName: SwiftIdentifier = "reuseIdentifier" - let qualifiedName = prefix + structName - let deduplicatedReusables = reusables - .grouped { $0.hashValue } - .values - .compactMap { $0.first } - - let groupedReusables = deduplicatedReusables.grouped(bySwiftIdentifier: { $0.identifier }) - groupedReusables.printWarningsForDuplicatesAndEmpties(source: "reuseIdentifier", result: "reuseIdentifier") - - let reuseIdentifierProperties = groupedReusables - .uniques - .map { letFromReusable($0, at: externalAccessLevel) } - - return Struct( - availables: [], - comments: ["This `\(qualifiedName)` struct is generated, and contains static references to \(reuseIdentifierProperties.count) reuse identifiers."], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: structName), - implements: [], - typealiasses: [], - properties: reuseIdentifierProperties, - functions: [], - structs: [], - classes: [], - os: [] - ) - } - - private func letFromReusable(_ reusable: Reusable, at externalAccessLevel: AccessLevel) -> Let { - return Let( - comments: ["Reuse identifier `\(reusable.identifier)`."], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: reusable.identifier), - typeDefinition: .specified(Type.ReuseIdentifier.withGenericArgs([reusable.type])), - value: "Rswift.ReuseIdentifier(identifier: \"\(reusable.identifier)\")" - ) - } -} diff --git a/Sources/RswiftCoreLegacy/Generators/SegueGenerator.swift b/Sources/RswiftCoreLegacy/Generators/SegueGenerator.swift deleted file mode 100644 index 3179859f..00000000 --- a/Sources/RswiftCoreLegacy/Generators/SegueGenerator.swift +++ /dev/null @@ -1,178 +0,0 @@ -// -// SegueStructGenerator.swift -// R.swift -// -// Created by Mathijs Kadijk on 10-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -private struct SegueWithInfo { - let segue: Storyboard.Segue - let sourceType: Type - let destinationType: Type - - var groupKey: String { - return "\(segue.identifier)|\(segue.type)|\(sourceType)|\(destinationType)" - } -} - -struct SegueStructGenerator: ExternalOnlyStructGenerator { - private let storyboards: [Storyboard] - - init(storyboards: [Storyboard]) { - self.storyboards = storyboards - } - - func generatedStruct(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Struct { - let structName: SwiftIdentifier = "segue" - let qualifiedName = prefix + structName - - let seguesWithInfo = storyboards.flatMap { storyboard in - storyboard.viewControllers.flatMap { viewController in - viewController.segues.compactMap { segue -> SegueWithInfo? in - guard let destinationType = resolveDestinationTypeForSegue( - segue, - inViewController: viewController, - inStoryboard: storyboard, - allStoryboards: storyboards) - else - { - warn("Destination view controller with id \(segue.destination) for segue \(segue.identifier) in \(viewController.type) not found in storyboard \(storyboard.name). Is this storyboard corrupt?") - return nil - } - - guard !segue.identifier.isEmpty else { - return nil - } - - return SegueWithInfo(segue: segue, sourceType: viewController.type, destinationType: destinationType) - } - } - } - - let deduplicatedSeguesWithInfo = seguesWithInfo - .grouped { $0.groupKey } - .values - .compactMap { $0.first } - - var structs: [Struct] = [] - - for (sourceType, seguesBySourceType) in deduplicatedSeguesWithInfo.grouped(by: { $0.sourceType }) { - let groupedSeguesWithInfo = seguesBySourceType.grouped(bySwiftIdentifier: { $0.segue.identifier }) - - groupedSeguesWithInfo.printWarningsForDuplicatesAndEmpties(source: "segue", container: "for '\(sourceType)'", result: "segue") - - let sts = groupedSeguesWithInfo - .uniques - .grouped { $0.sourceType } - .values - .compactMap { self.seguesWithInfoForSourceTypeToStruct($0, at: externalAccessLevel) } - - structs = structs + sts - } - - return Struct( - availables: [], - comments: ["This `\(qualifiedName)` struct is generated, and contains static references to \(structs.count) view controllers."], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: structName), - implements: [], - typealiasses: [], - properties: [], - functions: [], - structs: structs, - classes: [], - os: ["iOS", "tvOS"] - ) - } - - private func resolveDestinationTypeForSegue(_ segue: Storyboard.Segue, inViewController: Storyboard.ViewController, inStoryboard storyboard: Storyboard, allStoryboards storyboards: [Storyboard]) -> Type? { - if segue.kind == "unwind" { - return Type._UIViewController - } - - let destinationViewControllerType = storyboard.viewControllers - .filter { $0.id == segue.destination } - .first? - .type - - let destinationViewControllerPlaceholderType = storyboard.viewControllerPlaceholders - .filter { $0.id == segue.destination } - .first - .flatMap { storyboard -> Type? in - switch storyboard.resolveWithStoryboards(storyboards) { - case .customBundle: - return Type._UIViewController // Not supported, fallback to UIViewController - case let .resolved(vc): - return vc?.type - } - } - - return destinationViewControllerType ?? destinationViewControllerPlaceholderType - } - - private func seguesWithInfoForSourceTypeToStruct(_ seguesWithInfoForSourceType: [SegueWithInfo], at externalAccessLevel: AccessLevel) -> Struct? { - guard let sourceType = seguesWithInfoForSourceType.first?.sourceType else { return nil } - - let properties = seguesWithInfoForSourceType.map { segueWithInfo -> Let in - let type = Type( - module: "Rswift", - name: "StoryboardSegueIdentifier", - genericArgs: [segueWithInfo.segue.type, segueWithInfo.sourceType, segueWithInfo.destinationType], - optional: false - ) - return Let( - comments: ["Segue identifier `\(segueWithInfo.segue.identifier)`."], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: segueWithInfo.segue.identifier), - typeDefinition: .specified(type), - value: "Rswift.StoryboardSegueIdentifier(identifier: \"\(segueWithInfo.segue.identifier)\")" - ) - } - - let functions = seguesWithInfoForSourceType.map { segueWithInfo -> Function in - Function( - availables: [], - comments: [ - "Optionally returns a typed version of segue `\(segueWithInfo.segue.identifier)`.", - "Returns nil if either the segue identifier, the source, destination, or segue types don't match.", - "For use inside `prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)`." - ], - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: segueWithInfo.segue.identifier), - generics: nil, - parameters: [ - Function.Parameter.init(name: "segue", type: Type._UIStoryboardSegue) - ], - doesThrow: false, - returnType: Type.TypedStoryboardSegueInfo - .asOptional() - .withGenericArgs([segueWithInfo.segue.type, segueWithInfo.sourceType, segueWithInfo.destinationType]), - body: "return Rswift.TypedStoryboardSegueInfo(segueIdentifier: R.segue.\(SwiftIdentifier(name: sourceType.description)).\(SwiftIdentifier(name: segueWithInfo.segue.identifier)), segue: segue)", - os: ["iOS", "tvOS"] - ) - } - - let typeName = SwiftIdentifier(name: sourceType.description) - - return Struct( - availables: [], - comments: ["This struct is generated for `\(sourceType.name)`, and contains static references to \(properties.count) segues."], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: typeName), - implements: [], - typealiasses: [], - properties: properties, - functions: functions, - structs: [], - classes: [], - os: [] - ) - } -} - diff --git a/Sources/RswiftCoreLegacy/Generators/StoryboardGenerator.swift b/Sources/RswiftCoreLegacy/Generators/StoryboardGenerator.swift deleted file mode 100644 index f85fad43..00000000 --- a/Sources/RswiftCoreLegacy/Generators/StoryboardGenerator.swift +++ /dev/null @@ -1,223 +0,0 @@ -// -// StoryboardStructGenerator.swift -// R.swift -// -// Created by Mathijs Kadijk on 10-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct StoryboardStructGenerator: StructGenerator { - private let storyboards: [Storyboard] - - init(storyboards: [Storyboard]) { - self.storyboards = storyboards - } - - func generatedStructs(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> StructGenerator.Result { - let structName: SwiftIdentifier = "storyboard" - let qualifiedName = prefix + structName - let groupedStoryboards = storyboards.grouped(bySwiftIdentifier: { $0.name }) - groupedStoryboards.printWarningsForDuplicatesAndEmpties(source: "storyboard", result: "file") - - let storyboardTypes = groupedStoryboards - .uniques - .map { storyboard -> (Struct, Let, Function) in - let _struct = storyboardStruct(for: storyboard, at: externalAccessLevel, prefix: qualifiedName) - let _storyboardName = qualifiedName + _struct.type.name - - let _property = Let( - comments: ["Storyboard `\(storyboard.name)`."], - accessModifier: externalAccessLevel, - isStatic: true, - name: _struct.type.name, - typeDefinition: .inferred(Type.StoryboardResourceType), - value: "_\(_storyboardName)()" - ) - - let _function = Function( - availables: [], - comments: ["`UIStoryboard(name: \"\(storyboard.name)\", bundle: ...)`"], - accessModifier: externalAccessLevel, - isStatic: true, - name: _struct.type.name, - generics: nil, - parameters: [ - Function.Parameter(name: "_", type: Type._Void, defaultValue: "()") - ], - doesThrow: false, - returnType: Type._UIStoryboard, - body: "return UIKit.UIStoryboard(resource: R.storyboard.\(_struct.type.name))", - os: ["iOS", "tvOS"] - ) - - return (_struct, _property, _function) - } - - let externalStruct = Struct( - availables: [], - comments: ["This `\(qualifiedName)` struct is generated, and contains static references to \(storyboardTypes.count) storyboards."], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: structName), - implements: [], - typealiasses: [], - properties: storyboardTypes.map { $0.1 }, - functions: storyboardTypes.map { $0.2 }, - structs: [], - classes: [], - os: ["iOS", "tvOS"] - ) - - let internalStruct = Struct( - availables: [], - comments: [], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: structName), - implements: [], - typealiasses: [], - properties: [], - functions: [], - structs: storyboardTypes.map { $0.0 }, - classes: [], - os: ["iOS", "tvOS"] - ) - - return ( - externalStruct, - internalStruct - ) - } - - private func storyboardStruct(for storyboard: Storyboard, at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Struct { - let structName = SwiftIdentifier(name: storyboard.name) - let qualifiedName = prefix + structName - - var implements: [TypePrinter] = [] - var typealiasses: [Typealias] = [] - var functions: [Function] = [] - var properties: [Let] = [ - Let(comments: [], accessModifier: externalAccessLevel, isStatic: false, name: "name", typeDefinition: .inferred(Type._String), value: "\"\(storyboard.name)\""), - Let(comments: [], accessModifier: externalAccessLevel, isStatic: false, name: "bundle", typeDefinition: .inferred(Type._Bundle), value: "R.hostingBundle") - ] - - // Initial view controller - if let initialViewController = storyboard.initialViewController { - implements.append(TypePrinter(type: Type.StoryboardResourceWithInitialControllerType)) - typealiasses.append(Typealias(accessModifier: externalAccessLevel, alias: "InitialController", type: initialViewController.type)) - } else { - implements.append(TypePrinter(type: Type.StoryboardResourceType)) - } - - // View controllers with identifiers - let groupedViewControllersWithIdentifier = storyboard.viewControllers - .compactMap { (vc) -> (vc: Storyboard.ViewController, identifier: String)? in - guard let storyboardIdentifier = vc.storyboardIdentifier else { return nil } - return (vc, storyboardIdentifier) - } - .grouped(bySwiftIdentifier: { $0.identifier }) - - for (name, duplicates) in groupedViewControllersWithIdentifier.duplicates { - warn("Skipping \(duplicates.count) view controllers because symbol '\(name)' would be generated for all of these view controller identifiers: \(duplicates.joined(separator: ", "))") - } - - let viewControllersWithResourceProperty = groupedViewControllersWithIdentifier.uniques - .map { arg -> (Storyboard.ViewController, Let) in - let (viewController, identifier) = arg - return ( - viewController, - Let( - comments: [], - accessModifier: externalAccessLevel, - isStatic: false, - name: SwiftIdentifier(name: identifier), - typeDefinition: .inferred(Type.StoryboardViewControllerResource), - value: "\(Type.StoryboardViewControllerResource.name)<\(viewController.type)>(identifier: \"\(identifier)\")" - ) - ) - } - viewControllersWithResourceProperty - .forEach { properties.append($0.1) } - - viewControllersWithResourceProperty - .map { arg in - let (vc, resource) = arg - return Function( - availables: [], - comments: [], - accessModifier: externalAccessLevel, - isStatic: false, - name: resource.name, - generics: nil, - parameters: [ - Function.Parameter(name: "_", type: Type._Void, defaultValue: "()") - ], - doesThrow: false, - returnType: vc.type.asOptional(), - body: "return UIKit.UIStoryboard(resource: self).instantiateViewController(withResource: \(resource.name))", - os: [] - ) - } - .forEach { functions.append($0) } - - // Validation - let validateImagesLines = storyboard.usedImageIdentifiers.uniqueAndSorted() - .map { nameCatalog -> String in - if nameCatalog.isSystemCatalog { - return "if #available(iOS 13.0, *) { if UIKit.UIImage(systemName: \"\(nameCatalog.name)\") == nil { throw Rswift.ValidationError(description: \"[R.swift] System image named '\(nameCatalog.name)' is used in storyboard '\(storyboard.name)', but couldn't be loaded.\") } }" - } else { - return "if UIKit.UIImage(named: \"\(nameCatalog.name)\", in: R.hostingBundle, compatibleWith: nil) == nil { throw Rswift.ValidationError(description: \"[R.swift] Image named '\(nameCatalog.name)' is used in storyboard '\(storyboard.name)', but couldn't be loaded.\") }" - } - } - let validateColorLines = storyboard.usedColorResources.uniqueAndSorted() - .compactMap { nameCatalog -> String? in - if nameCatalog.isSystemCatalog { return nil } - return "if UIKit.UIColor(named: \"\(nameCatalog.name)\", in: R.hostingBundle, compatibleWith: nil) == nil { throw Rswift.ValidationError(description: \"[R.swift] Color named '\(nameCatalog.name)' is used in storyboard '\(storyboard.name)', but couldn't be loaded.\") }" - } - let validateColorLinesWithAvailableIf = ["if #available(iOS 11.0, tvOS 11.0, *) {"] + - validateColorLines.map { $0.indent(with: " ") } + - ["}"] - let validateViewControllersLines = groupedViewControllersWithIdentifier.uniques - .compactMap { arg -> String? in - let (vc, _) = arg - guard let storyboardName = vc.storyboardIdentifier else { return nil } - let storyboardIdentifier = SwiftIdentifier(name: storyboardName) - return "if _\(qualifiedName)().\(storyboardIdentifier)() == nil { throw Rswift.ValidationError(description:\"[R.swift] ViewController with identifier '\(storyboardIdentifier)' could not be loaded from storyboard '\(storyboard.name)' as '\(vc.type)'.\") }" - } - let validateLines = validateImagesLines + validateColorLinesWithAvailableIf + validateViewControllersLines - - if validateLines.count > 0 { - let validateFunction = Function( - availables: [], - comments: [], - accessModifier: externalAccessLevel, - isStatic: true, - name: "validate", - generics: nil, - parameters: [], - doesThrow: true, - returnType: Type._Void, - body: validateLines.joined(separator: "\n"), - os: [] - ) - functions.append(validateFunction) - implements.append(TypePrinter(type: Type.Validatable)) - } - - // Return - return Struct( - availables: [], - comments: [], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: structName), - implements: implements, - typealiasses: typealiasses, - properties: properties, - functions: functions, - structs: [], - classes: [], - os: ["iOS", "tvOS"] - ) - } -} diff --git a/Sources/RswiftCoreLegacy/Generators/StringsStructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/StringsStructGenerator.swift deleted file mode 100644 index fd87d269..00000000 --- a/Sources/RswiftCoreLegacy/Generators/StringsStructGenerator.swift +++ /dev/null @@ -1,390 +0,0 @@ -// -// StringsStructGenerator.swift -// R.swift -// -// Created by Nolan Warner on 2016/02/23. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct StringsStructGenerator: ExternalOnlyStructGenerator { - private let localizableStrings: [LocalizableStrings] - private let developmentLanguage: String - - init(localizableStrings: [LocalizableStrings], developmentLanguage: String) { - self.localizableStrings = localizableStrings - self.developmentLanguage = developmentLanguage - } - - func generatedStruct(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Struct { - let structName: SwiftIdentifier = "string" - let qualifiedName = prefix + structName - let localized = localizableStrings.grouped(by: { $0.filename }) - let groupedLocalized = localized.grouped(bySwiftIdentifier: { $0.0 }) - - groupedLocalized.printWarningsForDuplicatesAndEmpties(source: "strings file", result: "file") - - let structs = groupedLocalized.uniques.compactMap { arg -> Struct? in - let (key, value) = arg - return stringStructFromLocalizableStrings(filename: key, strings: value, at: externalAccessLevel, prefix: qualifiedName) - } - - return Struct( - availables: [], - comments: ["This `\(qualifiedName)` struct is generated, and contains static references to \(groupedLocalized.uniques.count) localization tables."], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: structName), - implements: [], - typealiasses: [], - properties: [], - functions: [], - structs: structs, - classes: [], - os: [] - ) - } - - private func stringStructFromLocalizableStrings(filename: String, strings: [LocalizableStrings], at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Struct? { - - let structName = SwiftIdentifier(name: filename) - let qualifiedName = prefix + structName - - let params = computeParams(filename: filename, strings: strings) - - return Struct( - availables: [], - comments: ["This `\(qualifiedName)` struct is generated, and contains static references to \(params.count) localization keys."], - accessModifier: externalAccessLevel, - type: Type(module: .host, name: structName), - implements: [], - typealiasses: [], - properties: params.map { stringLet(values: $0, at: externalAccessLevel) }, - functions: params.map { stringFunction(values: $0, at: externalAccessLevel) }, - structs: [], - classes: [], - os: [] - ) - } - - // Ahem, this code is a bit of a mess. It might need cleaning up... ;-) - // Maybe when we pick up this issue: https://github.com/mac-cain13/R.swift/issues/136 - private func computeParams(filename: String, strings: [LocalizableStrings]) -> [StringValues] { - - var allParams: [String: [(Locale, String, [StringParam])]] = [:] - let primaryLanguage: String - let primaryKeys: Set? - let bases = strings.filter { $0.locale.isBase } - let developments = strings.filter { $0.locale.language == developmentLanguage } - - if !bases.isEmpty { - primaryKeys = Set(bases.flatMap { $0.dictionary.keys }) - primaryLanguage = "Base" - } else if !developments.isEmpty { - primaryKeys = Set(developments.flatMap { $0.dictionary.keys }) - primaryLanguage = developmentLanguage - } else { - primaryKeys = nil - primaryLanguage = developmentLanguage - } - - // Warnings about duplicates and empties - for ls in strings { - let filenameLocale = ls.locale.withFilename(filename) - let groupedKeys = ls.dictionary.keys.grouped(bySwiftIdentifier: { $0 }) - - groupedKeys.printWarningsForDuplicatesAndEmpties(source: "string", container: "in \(filenameLocale)", result: "key") - - // Save uniques - for key in groupedKeys.uniques { - if let (params, commentValue) = ls.dictionary[key] { - if let _ = allParams[key] { - allParams[key]?.append((ls.locale, commentValue, params)) - } - else { - allParams[key] = [(ls.locale, commentValue, params)] - } - } - } - } - - // Warnings about missing translations - for (locale, lss) in strings.grouped(by: { $0.locale }) { - let filenameLocale = locale.withFilename(filename) - let sourceKeys = primaryKeys ?? Set(allParams.keys) - - let missing = sourceKeys.subtracting(lss.flatMap { $0.dictionary.keys }) - - if missing.isEmpty { - continue - } - - let paddedKeys = missing.sorted().map { "'\($0)'" } - let paddedKeysString = paddedKeys.joined(separator: ", ") - - warn("Strings file \(filenameLocale) is missing translations for keys: \(paddedKeysString)") - } - - // Warnings about extra translations - for (locale, lss) in strings.grouped(by: { $0.locale }) { - let filenameLocale = locale.withFilename(filename) - let sourceKeys = primaryKeys ?? Set(allParams.keys) - - let usedKeys = Set(lss.flatMap { $0.dictionary.keys }) - let extra = usedKeys.subtracting(sourceKeys) - - if extra.isEmpty { - continue - } - - let paddedKeys = extra.sorted().map { "'\($0)'" } - let paddedKeysString = paddedKeys.joined(separator: ", ") - - warn("Strings file \(filenameLocale) has extra translations (not in \(primaryLanguage)) for keys: \(paddedKeysString)") - } - - // Only include translation if it exists in the primary language - func includeTranslation(_ key: String) -> Bool { - if let primaryKeys = primaryKeys { - return primaryKeys.contains(key) - } - - return true - } - - var results: [StringValues] = [] - var badFormatSpecifiersKeys = Set() - - let filteredSortedParams = allParams - .map { $0 } - .filter { includeTranslation($0.0) } - .sorted(by: { $0.0 < $1.0 }) - - // Unify format specifiers - for (key, keyParams) in filteredSortedParams { - var params: [StringParam] = [] - var areCorrectFormatSpecifiers = true - - for (locale, _, ps) in keyParams { - if ps.contains(where: { $0.spec == FormatSpecifier.topType }) { - let name = locale.withFilename(filename) - warn("Skipping string \(key) in \(name), not all format specifiers are consecutive") - - areCorrectFormatSpecifiers = false - } - } - - if !areCorrectFormatSpecifiers { continue } - - for (_, _, ps) in keyParams { - if let unified = params.unify(ps) { - params = unified - } - else { - badFormatSpecifiersKeys.insert(key) - - areCorrectFormatSpecifiers = false - } - } - - if !areCorrectFormatSpecifiers { continue } - - let vals = keyParams.map { ($0.0, $0.1) } - let values = StringValues(key: key, params: params, tableName: filename, values: vals, developmentLanguage: developmentLanguage) - results.append(values) - } - - for badKey in badFormatSpecifiersKeys.sorted() { - let fewParams = allParams.filter { $0.0 == badKey }.map { $0.1 } - - if let params = fewParams.first { - let locales = params.compactMap { $0.0.localeDescription }.joined(separator: ", ") - warn("Skipping string for key \(badKey) (\(filename)), format specifiers don't match for all locales: \(locales)") - } - } - - return results - } - - private func stringLet(values: StringValues, at externalAccessLevel: AccessLevel) -> Let { - let escapedKey = values.key.escapedStringLiteral - let locales = values.values - .map { $0.0 } - .compactMap { $0.localeDescription } - .map { "\"\($0)\"" } - .joined(separator: ", ") - - return Let( - comments: values.comments, - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: values.key), - typeDefinition: .inferred(Type.StringResource), - value: "Rswift.StringResource(key: \"\(escapedKey)\", tableName: \"\(values.tableName)\", bundle: R.hostingBundle, locales: [\(locales)], comment: nil)" - ) - } - - private func stringFunction(values: StringValues, at externalAccessLevel: AccessLevel) -> Function { - if values.params.isEmpty { - return stringFunctionNoParams(for: values, at: externalAccessLevel) - } - else { - return stringFunctionParams(for: values, at: externalAccessLevel) - } - } - - private func stringFunctionNoParams(for values: StringValues, at externalAccessLevel: AccessLevel) -> Function { - - return Function( - availables: [], - comments: values.comments, - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: values.key), - generics: nil, - parameters: [ - Function.Parameter( - name: "preferredLanguages", - type: Type._Array.withGenericArgs([Type._String]).asOptional(), - defaultValue: "nil" - ) - ], - doesThrow: false, - returnType: Type._String, - body: """ - guard let preferredLanguages = preferredLanguages else { - return \(values.swiftCode(bundle: "hostingBundle")) - } - - guard let (_, bundle) = localeBundle(tableName: "\(values.tableName)", preferredLanguages: preferredLanguages) else { - return "\(values.key.escapedStringLiteral)" - } - - return \(values.swiftCode(bundle: "bundle")) - """, - os: [] - ) - } - - private func stringFunctionParams(for values: StringValues, at externalAccessLevel: AccessLevel) -> Function { - - var params = values.params.enumerated().map { arg -> Function.Parameter in - let (ix, param) = arg - let argumentLabel = param.name ?? "_" - let valueName = "value\(ix + 1)" - - return Function.Parameter(name: argumentLabel, localName: valueName, type: param.spec.type) - } - - let args = params.map { $0.localName ?? $0.name }.joined(separator: ", ") - - let prefereredLanguages = Function.Parameter( - name: "preferredLanguages", - type: Type._Array.withGenericArgs([Type._String]).asOptional(), - defaultValue: "nil" - ) - params.append(prefereredLanguages) - - return Function( - availables: [], - comments: values.comments, - accessModifier: externalAccessLevel, - isStatic: true, - name: SwiftIdentifier(name: values.key), - generics: nil, - parameters: params, - doesThrow: false, - returnType: Type._String, - body: """ - guard let preferredLanguages = preferredLanguages else { - let format = \(values.swiftCode(bundle: "hostingBundle")) - return String(format: format, locale: applicationLocale, \(args)) - } - - guard let (locale, bundle) = localeBundle(tableName: "\(values.tableName)", preferredLanguages: preferredLanguages) else { - return "\(values.key.escapedStringLiteral)" - } - - let format = \(values.swiftCode(bundle: "bundle")) - return String(format: format, locale: locale, \(args)) - """, - os: [] - ) - } - -} - -extension Locale { - func withFilename(_ filename: String) -> String { - switch self { - case .none: - return "'\(filename)'" - case .base: - return "'\(filename)' (Base)" - case .language(let language): - return "'\(filename)' (\(language))" - } - } -} - -private struct StringValues { - let key: String - let params: [StringParam] - let tableName: String - let values: [(Locale, String)] - let developmentLanguage: String - - func swiftCode(bundle: String) -> String { - let escapedKey = key.escapedStringLiteral - - var valueArgument: String = "" - if let value = baseLanguageValue { - valueArgument = ", value: \"\(value.escapedStringLiteral)\"" - } - - if tableName == "Localizable" { - return "NSLocalizedString(\"\(escapedKey)\", bundle: \(bundle)\(valueArgument), comment: \"\")" - } - else { - return "NSLocalizedString(\"\(escapedKey)\", tableName: \"\(tableName)\", bundle: \(bundle)\(valueArgument), comment: \"\")" - } - } - - var baseLanguageValue: String? { - return values.filter { $0.0.isBase }.map { $0.1 }.first - } - - private var primaryLanguageValues: [(Locale, String)] { - return values.filter { $0.0.isBase } + values.filter { $0.0.language == developmentLanguage } - } - - var comments: [String] { - var results: [String] = [] - - let anyNone = values.contains { $0.0.isNone } - let vs = primaryLanguageValues + values - - if let (locale, value) = vs.first { - if let localeDescription = locale.localeDescription { - let str = "\(localeDescription) translation: \(value)".commentString - results.append(str) - } - else { - let str = "Value: \(value)".commentString - results.append(str) - } - } - - if !anyNone { - if !results.isEmpty { - results.append("") - } - - let locales = values.compactMap { $0.0.localeDescription } - results.append("Locales: \(locales.joined(separator: ", "))") - } - - return results - } -} diff --git a/Sources/RswiftCoreLegacy/Generators/StructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/StructGenerator.swift deleted file mode 100644 index 9247f01d..00000000 --- a/Sources/RswiftCoreLegacy/Generators/StructGenerator.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// StructGenerator.swift -// R.swift -// -// Created by Mathijs Kadijk on 06-09-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -protocol StructGenerator { - typealias Result = (externalStruct: Struct, internalStruct: Struct?) - - func generatedStructs(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Result -} - -protocol ExternalOnlyStructGenerator: StructGenerator { - func generatedStruct(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> Struct -} - -extension ExternalOnlyStructGenerator { - func generatedStructs(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> StructGenerator.Result { - return ( - generatedStruct(at: externalAccessLevel, prefix: prefix), - nil - ) - } -} diff --git a/Sources/RswiftCoreLegacy/Generators/ValidatedStructGenerator.swift b/Sources/RswiftCoreLegacy/Generators/ValidatedStructGenerator.swift deleted file mode 100644 index 8e8af7bd..00000000 --- a/Sources/RswiftCoreLegacy/Generators/ValidatedStructGenerator.swift +++ /dev/null @@ -1,110 +0,0 @@ -// -// ValidatedStructGenerator.swift -// R.swift -// -// Created by Mathijs Kadijk on 05-10-16. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -class ValidatedStructGenerator: StructGenerator { - private let validationSubject: StructGenerator.Result - - init(validationSubject: StructGenerator.Result) { - self.validationSubject = validationSubject - } - - func generatedStructs(at externalAccessLevel: AccessLevel, prefix: SwiftIdentifier) -> StructGenerator.Result { - - let internalStruct = validationSubject.internalStruct? - .addingChildStructValidationMethods(at: externalAccessLevel) - - let validationFunctionBody: String - if let internalStruct = internalStruct, internalStruct.implements.map({ $0.type }).contains(Type.Validatable) { - validationFunctionBody = OSPrinter(code: "try \(internalStruct.type).validate()", supportedOS: internalStruct.os).swiftCode - } else { - validationFunctionBody = "// There are no resources to validate" - } - - let validationStruct = Struct( - availables: [], - comments: [], - accessModifier: .filePrivate, - type: Type(module: .host, name: "intern"), - implements: [TypePrinter(type: Type.Validatable)], - typealiasses: [], - properties: [], - functions: [ - Function( - availables: [], - comments: [], - accessModifier: .filePrivate, - isStatic: true, - name: "validate", - generics: nil, - parameters: [], - doesThrow: true, - returnType: Type._Void, - body: validationFunctionBody, - os: [] - ) - ], - structs: [], - classes: [], - os: [] - ) - - var externalStruct = validationSubject.externalStruct - externalStruct.structs.append(validationStruct) - - return ( - externalStruct.addingChildStructValidationMethods(at: externalAccessLevel), - internalStruct - ) - } -} - -private extension Struct { - /// Implements the Validatable protocol on this and child struct if one or more childs already implement the - /// Validatable protocol. The newly created validate methods will call their child validate methods. - func addingChildStructValidationMethods(at externalAccessLevel: AccessLevel) -> Struct { - if implements.map({ $0.type }).contains(Type.Validatable) { - return self - } - - let childStructs = structs - .map { $0.addingChildStructValidationMethods(at: externalAccessLevel) } - - let validatableStructs = childStructs - .filter { $0.implements.map({ $0.type }).contains(Type.Validatable) } - - guard validatableStructs.count > 0 else { - return self - } - - var outputStruct = self - outputStruct.structs = childStructs - outputStruct.implements.append(TypePrinter(type: Type.Validatable)) - outputStruct.functions.append( - Function( - availables: [], - comments: [], - accessModifier: externalAccessLevel, - isStatic: true, - name: "validate", - generics: nil, - parameters: [], - doesThrow: true, - returnType: Type._Void, - body: validatableStructs - .map { OSPrinter(code: "try \($0.type).validate()", supportedOS: $0.os).swiftCode } - .joined(separator: "\n"), - os: [] - ) - ) - - return outputStruct - } -} diff --git a/Sources/RswiftCoreLegacy/ResourceTypes/AssetFolder.swift b/Sources/RswiftCoreLegacy/ResourceTypes/AssetFolder.swift deleted file mode 100644 index b34b2ac5..00000000 --- a/Sources/RswiftCoreLegacy/ResourceTypes/AssetFolder.swift +++ /dev/null @@ -1,182 +0,0 @@ -// -// AssetFolder.swift -// R.swift -// -// Created by Mathijs Kadijk on 09-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -// Note: "appiconset" is not loadable by default, so it's not included here -private let imageExtensions: Set = ["launchimage", "imageset", "imagestack", "symbolset"] - -private let colorExtensions: Set = ["colorset"] - -// Ignore everything in folders with these extensions -private let ignoredExtensions: Set = ["brandassets", "imagestacklayer"] - -// Files checked for asset folder and subfolder properties -private let assetPropertiesFilenames: Array<(fileName: String, fileExtension: String)> = [("Contents","json")] - -struct AssetFolder: WhiteListedExtensionsResourceType, NamespacedAssetSubfolderType { - static let supportedExtensions: Set = ["xcassets"] - - let url: URL - let name: String - let path = "" - let resourcePath = "" - var imageAssets: [String] - var colorAssets: [String] - var subfolders: [NamespacedAssetSubfolder] - - init(url: URL, fileManager: FileManager) throws { - self.url = url - try AssetFolder.throwIfUnsupportedExtension(url.pathExtension) - - guard let filename = url.filename else { - throw ResourceParsingError.parsingFailed("Couldn't extract filename from URL: \(url)") - } - name = filename - - // Browse asset directory recursively and list only the assets folders - var imageAssetURLs = [URL]() - var colorAssetURLs = [URL]() - var namespaces = [URL]() - let enumerator = fileManager.enumerator(at: url, includingPropertiesForKeys: nil, options: .skipsHiddenFiles, errorHandler: nil) - if let enumerator = enumerator { - for case let fileURL as URL in enumerator { - let pathExtension = fileURL.pathExtension - if fileURL.providesNamespace() { - namespaces.append(fileURL.namespaceURL) - } - if imageExtensions.contains(pathExtension) { - imageAssetURLs.append(fileURL) - } - if colorExtensions.contains(pathExtension) { - colorAssetURLs.append(fileURL) - } - if ignoredExtensions.contains(pathExtension) { - enumerator.skipDescendants() - } - } - } - - subfolders = [] - imageAssets = [] - colorAssets = [] - namespaces.sort { $0.absoluteString < $1.absoluteString } - for namespace in namespaces.map(NamespacedAssetSubfolder.init) { - populateSubfolders(subfolder: namespace) - } - - for assetURL in imageAssetURLs { - populateImageAssets(asset: assetURL) - } - - for assetURL in colorAssetURLs { - populateColorAssets(asset: assetURL) - } - } -} - -protocol NamespacedAssetSubfolderType { - var url: URL { get } - var path: String { get } - var resourcePath: String { get } - var imageAssets: [String] { get set } - var colorAssets: [String] { get set } - var subfolders: [NamespacedAssetSubfolder] { get set } -} - -extension NamespacedAssetSubfolderType { - mutating func populateSubfolders(subfolder: NamespacedAssetSubfolder) { - if var parent = subfolders.first(where: { subfolder.isSubfolderOf($0) }) { - parent.populateSubfolders(subfolder: subfolder) - } else { - let name = SwiftIdentifier(name: subfolder.name) - let resourceName = SwiftIdentifier(rawValue: subfolder.name) - subfolder.path = path != "" ? "\(path).\(name)" : "\(name)" - subfolder.resourcePath = resourcePath != "" ? "\(resourcePath)/\(resourceName)" : "\(resourceName)" - subfolders.append(subfolder) - } - } - - mutating func populateImageAssets(asset: URL) { - if var parent = subfolders.first(where: { asset.matches($0.url) }) { - parent.populateImageAssets(asset: asset) - } else { - if let filename = asset.filename { - imageAssets.append(filename) - } - } - } - - mutating func populateColorAssets(asset: URL) { - if var parent = subfolders.first(where: { asset.matches($0.url) }) { - parent.populateColorAssets(asset: asset) - } else { - if let filename = asset.filename { - colorAssets.append(filename) - } - } - } - - func isSubfolderOf(_ subfolder: NamespacedAssetSubfolder) -> Bool { - return url.absoluteString != subfolder.url.absoluteString && url.matches(subfolder.url) - } -} - -class NamespacedAssetSubfolder: NamespacedAssetSubfolderType { - let url: URL - let name: String - var path: String = "" - var resourcePath: String = "" - var imageAssets: [String] = [] - var colorAssets: [String] = [] - var subfolders: [NamespacedAssetSubfolder] = [] - - init(url: URL) { - self.url = url - self.name = url.namespace - } -} - -fileprivate extension URL { - - func providesNamespace() -> Bool { - guard isFileURL else { return false } - - let isPropertiesFile = assetPropertiesFilenames.contains(where: { arg -> Bool in - let (fileName, fileExtension) = arg - guard let pathFilename = self.filename else { - return false - } - let pathExtension = self.pathExtension - return pathFilename == fileName && pathExtension == fileExtension - }) - - guard isPropertiesFile else { return false } - guard let data = try? Data(contentsOf: self) else { return false } - guard let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) else { return false } - guard let dict = json as? [String: Any] else { return false } - guard let properties = dict["properties"] as? [String: Any] else { return false } - guard let providesNamespace = properties["provides-namespace"] as? Bool else { return false } - - return providesNamespace - } - - var namespace: String { - return lastPathComponent - } - - var namespaceURL: URL { - return deletingLastPathComponent() - } - - // Returns whether self is descendant of namespace - func matches(_ namespace: URL) -> Bool { - return self.absoluteString.hasPrefix(namespace.absoluteString) - } -} diff --git a/Sources/RswiftCoreLegacy/ResourceTypes/Font.swift b/Sources/RswiftCoreLegacy/ResourceTypes/Font.swift deleted file mode 100644 index 45b48250..00000000 --- a/Sources/RswiftCoreLegacy/ResourceTypes/Font.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// Font.swift -// R.swift -// -// Created by Mathijs Kadijk on 09-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation -import CoreGraphics - -struct Font: WhiteListedExtensionsResourceType { - static let supportedExtensions: Set = ["otf", "ttf"] - - let filename: String - let name: String - - init(url: URL) throws { - try Font.throwIfUnsupportedExtension(url.pathExtension) - - filename = url.lastPathComponent - - guard let dataProvider = CGDataProvider(url: url as CFURL) else { - throw ResourceParsingError.parsingFailed("Unable to create data provider for font at \(url)") - } - - let font = CGFont(dataProvider) - guard let postScriptName = font?.postScriptName else { - throw ResourceParsingError.parsingFailed("No postscriptName associated to font at \(url)") - } - - name = postScriptName as String - } -} diff --git a/Sources/RswiftCoreLegacy/ResourceTypes/Image.swift b/Sources/RswiftCoreLegacy/ResourceTypes/Image.swift deleted file mode 100644 index e024fb95..00000000 --- a/Sources/RswiftCoreLegacy/ResourceTypes/Image.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// Image.swift -// R.swift -// -// Created by Mathijs Kadijk on 09-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct Image: WhiteListedExtensionsResourceType { - // See "Supported Image Formats" on https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIImage_Class/ - static let supportedExtensions: Set = ["tiff", "tif", "jpg", "jpeg", "gif", "png", "bmp", "bmpf", "ico", "cur", "xbm"] - - let name: String - - init(url: URL) throws { - try Image.throwIfUnsupportedExtension(url.pathExtension) - - let filename = url.lastPathComponent - let pathExtension = url.pathExtension - guard filename.count > 0 && pathExtension.count > 0 else { - throw ResourceParsingError.parsingFailed("Filename and/or extension could not be parsed from URL: \(url.absoluteString)") - } - - let extensions = Image.supportedExtensions.joined(separator: "|") - let regex = try! NSRegularExpression(pattern: "(~(ipad|iphone))?(@[2,3]x)?\\.(\(extensions))$", options: .caseInsensitive) - let fullFileNameRange = NSRange(location: 0, length: filename.count) - let pathExtensionToUse = (pathExtension == "png") ? "" : ".\(pathExtension)" - name = regex.stringByReplacingMatches(in: filename, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: fullFileNameRange, withTemplate: pathExtensionToUse) - } -} diff --git a/Sources/RswiftCoreLegacy/ResourceTypes/Locale.swift b/Sources/RswiftCoreLegacy/ResourceTypes/Locale.swift deleted file mode 100644 index 3ee384d0..00000000 --- a/Sources/RswiftCoreLegacy/ResourceTypes/Locale.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// Locale.swift -// R.swift -// -// Created by Tom Lokhorst on 2016-04-24. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -enum Locale { - case none - case base // Older projects use a "Base" locale - case language(String) - - var isNone: Bool { - if case .none = self { - return true - } - - return false - } - - var isBase: Bool { - if case .base = self { - return true - } - - return false - } - - var language: String? { - if case .language(let language) = self { - return language - } - - return nil - } -} - -extension Locale: Hashable { - init(url: URL) { - if let localeComponent = url.pathComponents.dropLast().last , localeComponent.hasSuffix(".lproj") { - let lang = localeComponent.replacingOccurrences(of: ".lproj", with: "") - - if lang == "Base" { - self = .base - } else { - self = .language(lang) - } - } - else { - self = .none - } - } - - var localeDescription: String? { - switch self { - case .none: - return nil - - case .base: - return "Base" - - case .language(let language): - return language - } - } -} diff --git a/Sources/RswiftCoreLegacy/ResourceTypes/LocalizableStrings.swift b/Sources/RswiftCoreLegacy/ResourceTypes/LocalizableStrings.swift deleted file mode 100644 index 3ee746b6..00000000 --- a/Sources/RswiftCoreLegacy/ResourceTypes/LocalizableStrings.swift +++ /dev/null @@ -1,185 +0,0 @@ -// -// LocalizableStrings.swift -// R.swift -// -// Created by Tom Lokhorst on 2016-04-24. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct LocalizableStrings : WhiteListedExtensionsResourceType { - static let supportedExtensions: Set = ["strings", "stringsdict"] - - let filename: String - let locale: Locale - let dictionary: [String : (params: [StringParam], commentValue: String)] - - init(filename: String, locale: Locale, dictionary: [String : (params: [StringParam], commentValue: String)]) { - self.filename = filename - self.locale = locale - self.dictionary = dictionary - } - - init(url: URL) throws { - try LocalizableStrings.throwIfUnsupportedExtension(url.pathExtension) - - guard let filename = url.filename else { - throw ResourceParsingError.parsingFailed("Couldn't extract filename without extension from URL: \(url)") - } - - // Get locale from url (second to last component) - let locale = Locale(url: url) - - // Check to make sure url can be parsed as a dictionary - guard let nsDictionary = NSDictionary(contentsOf: url) else { - throw ResourceParsingError.parsingFailed("File could not be parsed as a strings file: \(url.absoluteString)") - } - - // Parse dicts from NSDictionary - let dictionary: [String : (params: [StringParam], commentValue: String)] - switch url.pathExtension { - case "strings": - dictionary = try parseStrings(nsDictionary, source: locale.withFilename("\(filename).strings")) - case "stringsdict": - dictionary = try parseStringsdict(nsDictionary, source: locale.withFilename("\(filename).stringsdict")) - default: - throw ResourceParsingError.unsupportedExtension(givenExtension: url.pathExtension, supportedExtensions: LocalizableStrings.supportedExtensions) - } - - self.filename = filename - self.locale = locale - self.dictionary = dictionary - } -} - -private func parseStrings(_ nsDictionary: NSDictionary, source: String) throws -> [String : (params: [StringParam], commentValue: String)] { - var dictionary: [String : (params: [StringParam], commentValue: String)] = [:] - - for (key, obj) in nsDictionary { - if let - key = key as? String, - let val = obj as? String - { - var params: [StringParam] = [] - - for part in FormatPart.formatParts(formatString: val) { - switch part { - case .reference: - throw ResourceParsingError.parsingFailed("Non-specifier reference in \(source): \(key) = \(val)") - - case .spec(let formatSpecifier): - params.append(StringParam(name: nil, spec: formatSpecifier)) - } - } - - - dictionary[key] = (params, val) - } - else { - throw ResourceParsingError.parsingFailed("Non-string value in \(source): \(key) = \(obj)") - } - } - - return dictionary -} - -private func parseStringsdict(_ nsDictionary: NSDictionary, source: String) throws -> [String : (params: [StringParam], commentValue: String)] { - - var dictionary: [String : (params: [StringParam], commentValue: String)] = [:] - - for (key, obj) in nsDictionary { - if let - key = key as? String, - let dict = obj as? [String: AnyObject] - { - guard let localizedFormat = dict["NSStringLocalizedFormatKey"] as? String else { - continue - } - - do { - let params = try parseStringsdictParams(localizedFormat, dict: dict) - dictionary[key] = (params, localizedFormat) - } - catch ResourceParsingError.parsingFailed(let message) { - warn("\(message) in '\(key)' \(source)") - } - } - else { - throw ResourceParsingError.parsingFailed("Non-dict value in \(source): \(key) = \(obj)") - } - } - - return dictionary -} - -private func parseStringsdictParams(_ format: String, dict: [String: AnyObject]) throws -> [StringParam] { - - var params: [StringParam] = [] - - let parts = FormatPart.formatParts(formatString: format) - for part in parts { - switch part { - case .reference(let reference): - params += try lookup(key: reference, in: dict) - - case .spec(let formatSpecifier): - params.append(StringParam(name: nil, spec: formatSpecifier)) - } - } - - return params -} - -func lookup(key: String, in dict: [String: AnyObject], processedReferences: [String] = []) throws -> [StringParam] { - var processedReferences = processedReferences - - if processedReferences.contains(key) { - throw ResourceParsingError.parsingFailed("Cyclic reference '\(key)'") - } - - processedReferences.append(key) - - guard let obj = dict[key], let nested = obj as? [String: AnyObject] else { - throw ResourceParsingError.parsingFailed("Missing reference '\(key)'") - } - - guard let formatSpecType = nested["NSStringFormatSpecTypeKey"] as? String, - let formatValueType = nested["NSStringFormatValueTypeKey"] as? String - , formatSpecType == "NSStringPluralRuleType" - else { - throw ResourceParsingError.parsingFailed("Incorrect reference '\(key)'") - } - guard let formatSpecifier = FormatSpecifier(formatString: formatValueType) - else { - throw ResourceParsingError.parsingFailed("Incorrect reference format specifier \"\(formatValueType)\" for '\(key)'") - } - - var results = [StringParam(name: nil, spec: formatSpecifier)] - - let stringValues = nested.values.compactMap { $0 as? String }.sorted() - - for stringValue in stringValues { - var alternative: [StringParam] = [] - let parts = FormatPart.formatParts(formatString: stringValue) - for part in parts { - switch part { - case .reference(let reference): - alternative += try lookup(key: reference, in: dict, processedReferences: processedReferences) - - case .spec(let formatSpecifier): - alternative.append(StringParam(name: key, spec: formatSpecifier)) - } - } - - if let unified = results.unify(alternative) { - results = unified - } - else { - throw ResourceParsingError.parsingFailed("Can't unify '\(key)'") - } - } - - return results -} diff --git a/Sources/RswiftCoreLegacy/ResourceTypes/NameCatalog.swift b/Sources/RswiftCoreLegacy/ResourceTypes/NameCatalog.swift deleted file mode 100644 index e3711bd3..00000000 --- a/Sources/RswiftCoreLegacy/ResourceTypes/NameCatalog.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// NameCatalog.swift -// RswiftCore -// -// Created by Tom Lokhorst on 2020-05-08. -// - -import Foundation - -struct NameCatalog: Hashable, Comparable { - let name: String - let catalog: String? - - var isSystemCatalog: Bool { - return - catalog == "System" // for colors - || catalog == "system" // for images - } - - static func < (lhs: NameCatalog, rhs: NameCatalog) -> Bool { - lhs.name < rhs.name - } -} diff --git a/Sources/RswiftCoreLegacy/ResourceTypes/Nib.swift b/Sources/RswiftCoreLegacy/ResourceTypes/Nib.swift deleted file mode 100644 index 6c05da77..00000000 --- a/Sources/RswiftCoreLegacy/ResourceTypes/Nib.swift +++ /dev/null @@ -1,148 +0,0 @@ -// -// Nib.swift -// R.swift -// -// Created by Mathijs Kadijk on 09-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -private let ElementNameToTypeMapping = [ - // TODO: Should contain all standard view elements, like button -> UIButton, view -> UIView etc - "view": Type._UIView, - "tableViewCell": Type._UITableViewCell, - "collectionViewCell": Type._UICollectionViewCell, - "collectionReusableView": Type._UICollectionReusableView -] - -struct Nib: WhiteListedExtensionsResourceType, ReusableContainer { - static let supportedExtensions: Set = ["xib"] - - let name: String - let rootViews: [Type] - let reusables: [Reusable] - let usedImageIdentifiers: [NameCatalog] - let usedColorResources: [NameCatalog] - let usedAccessibilityIdentifiers: [String] - - init(url: URL) throws { - try Nib.throwIfUnsupportedExtension(url.pathExtension) - - guard let filename = url.filename else { - throw ResourceParsingError.parsingFailed("Couldn't extract filename from URL: \(url)") - } - name = filename - - guard let parser = XMLParser(contentsOf: url) else { - throw ResourceParsingError.parsingFailed("Couldn't load file at: '\(url)'") - } - - let parserDelegate = NibParserDelegate() - parser.delegate = parserDelegate - - guard parser.parse() else { - throw ResourceParsingError.parsingFailed("Invalid XML in file at: '\(url)'") - } - - rootViews = parserDelegate.rootViews - reusables = parserDelegate.reusables - usedImageIdentifiers = parserDelegate.usedImageIdentifiers - usedColorResources = parserDelegate.usedColorReferences - usedAccessibilityIdentifiers = parserDelegate.usedAccessibilityIdentifiers - } -} - -internal class NibParserDelegate: NSObject, XMLParserDelegate { - let ignoredRootViewElements = ["placeholder"] - var rootViews: [Type] = [] - var reusables: [Reusable] = [] - var usedImageIdentifiers: [NameCatalog] = [] - var usedColorReferences: [NameCatalog] = [] - var usedAccessibilityIdentifiers: [String] = [] - - // State - var isObjectsTagOpened = false; - var levelSinceObjectsTagOpened = 0; - - @objc func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) { - if isObjectsTagOpened { - levelSinceObjectsTagOpened += 1 - } - if elementName == "objects" { - isObjectsTagOpened = true - } - - switch elementName { - case "image": - if let imageIdentifier = attributeDict["name"] { - usedImageIdentifiers.append(NameCatalog(name: imageIdentifier, catalog: attributeDict["catalog"])) - } - - case "color": - if let colorName = attributeDict["name"] { - usedColorReferences.append(NameCatalog(name: colorName, catalog: attributeDict["catalog"])) - } - - case "accessibility": - if let accessibilityIdentifier = attributeDict["identifier"] { - usedAccessibilityIdentifiers.append(accessibilityIdentifier) - } - - case "userDefinedRuntimeAttribute": - if let accessibilityIdentifier = attributeDict["value"], "accessibilityIdentifier" == attributeDict["keyPath"] && "string" == attributeDict["type"] { - usedAccessibilityIdentifiers.append(accessibilityIdentifier) - } - - default: - if let rootView = viewWithAttributes(attributeDict, elementName: elementName), - levelSinceObjectsTagOpened == 1 && ignoredRootViewElements.allSatisfy({ $0 != elementName }) { - rootViews.append(rootView) - } - if let reusable = reusableFromAttributes(attributeDict, elementName: elementName) { - reusables.append(reusable) - } - } - } - - @objc func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { - switch elementName { - case "objects": - isObjectsTagOpened = false; - - default: - if isObjectsTagOpened { - levelSinceObjectsTagOpened -= 1 - } - } - } - - func viewWithAttributes(_ attributeDict: [String : String], elementName: String) -> Type? { - let customModuleProvider = attributeDict["customModuleProvider"] - let customModule = (customModuleProvider == "target") ? nil : attributeDict["customModule"] - let customClass = attributeDict["customClass"] - let customType = customClass - .map { SwiftIdentifier(name: $0, lowercaseStartingCharacters: false) } - .map { Type(module: Module(name: customModule), name: $0, optional: false) } - - return customType ?? ElementNameToTypeMapping[elementName] ?? Type._UIView - } - - func reusableFromAttributes(_ attributeDict: [String : String], elementName: String) -> Reusable? { - guard let reuseIdentifier = attributeDict["reuseIdentifier"] , reuseIdentifier != "" else { - return nil - } - - let customModuleProvider = attributeDict["customModuleProvider"] - let customModule = (customModuleProvider == "target") ? nil : attributeDict["customModule"] - let customClass = attributeDict["customClass"] - let customType = customClass - .map { SwiftIdentifier(name: $0, lowercaseStartingCharacters: false) } - .map { Type(module: Module(name: customModule), name: $0, optional: false) } - - let type = customType ?? ElementNameToTypeMapping[elementName] ?? Type._UIView - - return Reusable(identifier: reuseIdentifier, type: type) - } -} diff --git a/Sources/RswiftCoreLegacy/ResourceTypes/PropertyList.swift b/Sources/RswiftCoreLegacy/ResourceTypes/PropertyList.swift deleted file mode 100644 index d00170b9..00000000 --- a/Sources/RswiftCoreLegacy/ResourceTypes/PropertyList.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// PropertyList.swift -// R.swift -// -// Created by Tom Lokhorst on 2018-07-08. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct PropertyList { - typealias Contents = [String: Any] - - let buildConfigurationName: String - let contents: Contents - let url: URL - - init(buildConfigurationName: String, url: URL) throws { - guard - let nsDictionary = NSDictionary(contentsOf: url), - let dictionary = nsDictionary as? [String: Any] - else { - throw ResourceParsingError.parsingFailed("File could not be parsed as InfoPlist from URL: \(url.absoluteString)") - } - - self.buildConfigurationName = buildConfigurationName - self.contents = dictionary - self.url = url - } -} diff --git a/Sources/RswiftCoreLegacy/ResourceTypes/ResourceFile.swift b/Sources/RswiftCoreLegacy/ResourceTypes/ResourceFile.swift deleted file mode 100644 index 1da89b71..00000000 --- a/Sources/RswiftCoreLegacy/ResourceTypes/ResourceFile.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// ResourceFile.swift -// R.swift -// -// Created by Mathijs Kadijk on 09-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct ResourceFile { - // These are all extensions of resources that are passed to some special compiler step and not directly available runtime - static let unsupportedExtensions: Set = [ - AssetFolder.supportedExtensions, - Storyboard.supportedExtensions, - Nib.supportedExtensions, - LocalizableStrings.supportedExtensions, - ] - .reduce([]) { $0.union($1) } - - let fullname: String - let filename: String - let pathExtension: String - - init(url: URL) throws { - pathExtension = url.pathExtension - if ResourceFile.unsupportedExtensions.contains(pathExtension) { - throw ResourceParsingError.unsupportedExtension(givenExtension: pathExtension, supportedExtensions: ["*"]) - } - - let fullname = url.lastPathComponent - guard let filename = url.filename else { - throw ResourceParsingError.parsingFailed("Couldn't extract filename from URL: \(url)") - } - - self.fullname = fullname - self.filename = filename - } -} diff --git a/Sources/RswiftCoreLegacy/ResourceTypes/Resources.swift b/Sources/RswiftCoreLegacy/ResourceTypes/Resources.swift deleted file mode 100644 index 8f762c1f..00000000 --- a/Sources/RswiftCoreLegacy/ResourceTypes/Resources.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// Resources.swift -// R.swift -// -// Created by Mathijs Kadijk on 08-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -enum ResourceParsingError: Error { - case unsupportedExtension(givenExtension: String?, supportedExtensions: Set) - case parsingFailed(String) -} - -struct Resources { - let assetFolders: [AssetFolder] - let images: [Image] - let fonts: [Font] - let nibs: [Nib] - let storyboards: [Storyboard] - let resourceFiles: [ResourceFile] - let localizableStrings: [LocalizableStrings] - - let reusables: [Reusable] - - init(resourceURLs: [URL], fileManager: FileManager) { - - var assetFolders = [AssetFolder]() - var images = [Image]() - var fonts = [Font]() - var nibs = [Nib]() - var storyboards = [Storyboard]() - var resourceFiles = [ResourceFile]() - var localizableStrings = [LocalizableStrings]() - - resourceURLs.forEach { url in - if let nib = tryResourceParsing({ try Nib(url: url) }) { - nibs.append(nib) - } else if let image = tryResourceParsing({ try Image(url: url) }) { - images.append(image) - } else if let asset = tryResourceParsing({ try AssetFolder(url: url, fileManager: fileManager) }) { - assetFolders.append(asset) - } else if let font = tryResourceParsing({ try Font(url: url) }) { - fonts.append(font) - } else if let storyboard = tryResourceParsing({ try Storyboard(url: url) }) { - storyboards.append(storyboard) - } else if let localizableString = tryResourceParsing({ try LocalizableStrings(url: url) }) { - localizableStrings.append(localizableString) - } - - // All previous assets can also possibly be used as files - if let resourceFile = tryResourceParsing({ try ResourceFile(url: url) }) { - resourceFiles.append(resourceFile) - } - } - - self.assetFolders = assetFolders - self.images = images - self.fonts = fonts - self.nibs = nibs - self.storyboards = storyboards - self.resourceFiles = resourceFiles - self.localizableStrings = localizableStrings - - reusables = (nibs.map { $0 as ReusableContainer } + storyboards.map { $0 as ReusableContainer }) - .flatMap { $0.reusables } - } -} - -private func tryResourceParsing(_ parse: () throws -> T) -> T? { - do { - return try parse() - } catch let ResourceParsingError.parsingFailed(humanReadableError) { - warn(humanReadableError) - return nil - } catch ResourceParsingError.unsupportedExtension { - return nil - } catch { - return nil - } -} diff --git a/Sources/RswiftCoreLegacy/ResourceTypes/ReusableContainer.swift b/Sources/RswiftCoreLegacy/ResourceTypes/ReusableContainer.swift deleted file mode 100644 index 20591c52..00000000 --- a/Sources/RswiftCoreLegacy/ResourceTypes/ReusableContainer.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// ReusableContainer.swift -// R.swift -// -// Created by Mathijs Kadijk on 10-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct Reusable: Hashable { - let identifier: String - let type: Type -} - -protocol ReusableContainer { - var reusables: [Reusable] { get } -} diff --git a/Sources/RswiftCoreLegacy/ResourceTypes/Storyboard.swift b/Sources/RswiftCoreLegacy/ResourceTypes/Storyboard.swift deleted file mode 100644 index 9639e52b..00000000 --- a/Sources/RswiftCoreLegacy/ResourceTypes/Storyboard.swift +++ /dev/null @@ -1,264 +0,0 @@ -// -// Storyboard.swift -// R.swift -// -// Created by Mathijs Kadijk on 09-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -private let ElementNameToTypeMapping = [ - "viewController": Type._UIViewController, - "tableViewCell": Type(module: "UIKit", name: "UITableViewCell"), - "tabBarController": Type(module: "UIKit", name: "UITabBarController"), - "glkViewController": Type(module: "GLKit", name: "GLKViewController"), - "pageViewController": Type(module: "UIKit", name: "UIPageViewController"), - "tableViewController": Type(module: "UIKit", name: "UITableViewController"), - "splitViewController": Type(module: "UIKit", name: "UISplitViewController"), - "navigationController": Type(module: "UIKit", name: "UINavigationController"), - "avPlayerViewController": Type(module: "AVKit", name: "AVPlayerViewController"), - "collectionViewController": Type(module: "UIKit", name: "UICollectionViewController"), -] - -struct Storyboard: WhiteListedExtensionsResourceType, ReusableContainer { - static let supportedExtensions: Set = ["storyboard"] - - let name: String - private let initialViewControllerIdentifier: String? - let viewControllers: [ViewController] - let viewControllerPlaceholders: [ViewControllerPlaceholder] - let usedAccessibilityIdentifiers: [String] - let usedImageIdentifiers: [NameCatalog] - let usedColorResources: [NameCatalog] - let reusables: [Reusable] - - var initialViewController: ViewController? { - return viewControllers - .filter { $0.id == self.initialViewControllerIdentifier } - .first - } - - init(url: URL) throws { - try Storyboard.throwIfUnsupportedExtension(url.pathExtension) - - guard let filename = url.filename else { - throw ResourceParsingError.parsingFailed("Couldn't extract filename from URL: \(url)") - } - name = filename - - guard let parser = XMLParser(contentsOf: url) else { - throw ResourceParsingError.parsingFailed("Couldn't load file at: '\(url)'") - } - - let parserDelegate = StoryboardParserDelegate() - parser.delegate = parserDelegate - - guard parser.parse() else { - throw ResourceParsingError.parsingFailed("Invalid XML in file at: '\(url)'") - } - - initialViewControllerIdentifier = parserDelegate.initialViewControllerIdentifier - viewControllers = parserDelegate.viewControllers - viewControllerPlaceholders = parserDelegate.viewControllerPlaceholders - usedAccessibilityIdentifiers = parserDelegate.usedAccessibilityIdentifiers - usedImageIdentifiers = parserDelegate.usedImageIdentifiers - usedColorResources = parserDelegate.usedColorReferences - reusables = parserDelegate.reusables - } - - struct ViewController { - let id: String - let storyboardIdentifier: String? - let type: Type - private(set) var segues: [Segue] - - fileprivate mutating func add(_ segue: Segue) { - segues.append(segue) - } - } - - struct ViewControllerPlaceholder { - enum ResolvedResult { - case customBundle - case resolved(ViewController?) - } - - let id: String - let storyboardName: String? - let referencedIdentifier: String? - let bundleIdentifier: String? - - func resolveWithStoryboards(_ storyboards: [Storyboard]) -> ResolvedResult { - if nil != bundleIdentifier { - // Can't resolve storyboard in other bundles - return .customBundle - } - - guard let storyboardName = storyboardName else { - // Storyboard reference without a storyboard defined?! - return .resolved(nil) - } - - let storyboard = storyboards - .filter { $0.name == storyboardName } - - guard let referencedIdentifier = referencedIdentifier else { - return .resolved(storyboard.first?.initialViewController) - } - - return .resolved(storyboard - .flatMap { - $0.viewControllers.filter { $0.storyboardIdentifier == referencedIdentifier } - } - .first) - } - } - - struct Segue { - let identifier: String - let type: Type - let destination: String - let kind: String - } -} - -private class StoryboardParserDelegate: NSObject, XMLParserDelegate { - var initialViewControllerIdentifier: String? - var viewControllers: [Storyboard.ViewController] = [] - var viewControllerPlaceholders: [Storyboard.ViewControllerPlaceholder] = [] - var usedImageIdentifiers: [NameCatalog] = [] - var usedColorReferences: [NameCatalog] = [] - var usedAccessibilityIdentifiers: [String] = [] - var reusables: [Reusable] = [] - - // State - var currentViewController: Storyboard.ViewController? - - @objc func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) { - switch elementName { - case "document": - if let initialViewController = attributeDict["initialViewController"] { - initialViewControllerIdentifier = initialViewController - } - - case "segue": - let customModuleProvider = attributeDict["customModuleProvider"] - let customModule = (customModuleProvider == "target") ? nil : attributeDict["customModule"] - let customClass = attributeDict["customClass"] - let customType = customClass - .map { SwiftIdentifier(name: $0, lowercaseStartingCharacters: false) } - .map { Type(module: Module(name: customModule), name: $0, optional: false) } - - if let customType = customType , attributeDict["kind"] != "custom" , attributeDict["kind"] != "unwind" { - warn("Set the segue of class \(customType) with identifier '\(attributeDict["identifier"] ?? "-no identifier-")' to type custom, using segue subclasses with other types can cause crashes on iOS 8 and lower.") - } - - if let segueIdentifier = attributeDict["identifier"], - let destination = attributeDict["destination"], - let kind = attributeDict["kind"] - { - let type = customType ?? Type._UIStoryboardSegue - - let segue = Storyboard.Segue(identifier: segueIdentifier, type: type, destination: destination, kind: kind) - currentViewController?.add(segue) - } - - case "image": - if let imageIdentifier = attributeDict["name"] { - usedImageIdentifiers.append(NameCatalog(name: imageIdentifier, catalog: attributeDict["catalog"])) - } - - case "color": - if let colorName = attributeDict["name"] { - usedColorReferences.append(NameCatalog(name: colorName, catalog: attributeDict["catalog"])) - } - - case "accessibility": - if let accessibilityIdentifier = attributeDict["identifier"] { - usedAccessibilityIdentifiers.append(accessibilityIdentifier) - } - - case "userDefinedRuntimeAttribute": - if let accessibilityIdentifier = attributeDict["value"], "accessibilityIdentifier" == attributeDict["keyPath"] && "string" == attributeDict["type"] { - usedAccessibilityIdentifiers.append(accessibilityIdentifier) - } - - case "viewControllerPlaceholder": - if let id = attributeDict["id"] , attributeDict["sceneMemberID"] == "viewController" { - let placeholder = Storyboard.ViewControllerPlaceholder( - id: id, - storyboardName: attributeDict["storyboardName"], - referencedIdentifier: attributeDict["referencedIdentifier"], - bundleIdentifier: attributeDict["bundleIdentifier"] - ) - viewControllerPlaceholders.append(placeholder) - } - - default: - if let viewController = viewControllerFromAttributes(attributeDict, elementName: elementName) { - currentViewController = viewController - } - - if let reusable = reusableFromAttributes(attributeDict, elementName: elementName) { - reusables.append(reusable) - } - } - } - - @objc func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { - - // We keep the current view controller open to collect segues until the closing scene: - // - // - // ... - // - // - // - // - if elementName == "scene" { - if let currentViewController = currentViewController { - viewControllers.append(currentViewController) - self.currentViewController = nil - } - } - } - - func viewControllerFromAttributes(_ attributeDict: [String : String], elementName: String) -> Storyboard.ViewController? { - guard let id = attributeDict["id"] , attributeDict["sceneMemberID"] == "viewController" else { - return nil - } - - let storyboardIdentifier = attributeDict["storyboardIdentifier"] - - let customModuleProvider = attributeDict["customModuleProvider"] - let customModule = (customModuleProvider == "target") ? nil : attributeDict["customModule"] - let customClass = attributeDict["customClass"] - let customType = customClass - .map { SwiftIdentifier(name: $0, lowercaseStartingCharacters: false) } - .map { Type(module: Module(name: customModule), name: $0, optional: false) } - - let type = customType ?? ElementNameToTypeMapping[elementName] ?? Type._UIViewController - - return Storyboard.ViewController(id: id, storyboardIdentifier: storyboardIdentifier, type: type, segues: []) - } - - func reusableFromAttributes(_ attributeDict: [String : String], elementName: String) -> Reusable? { - guard let reuseIdentifier = attributeDict["reuseIdentifier"] , reuseIdentifier != "" else { - return nil - } - - let customModuleProvider = attributeDict["customModuleProvider"] - let customModule = (customModuleProvider == "target") ? nil : attributeDict["customModule"] - let customClass = attributeDict["customClass"] - let customType = customClass - .map { SwiftIdentifier(name: $0, lowercaseStartingCharacters: false) } - .map { Type(module: Module(name: customModule), name: $0, optional: false) } - - let type = customType ?? ElementNameToTypeMapping[elementName] ?? Type._UIView - - return Reusable(identifier: reuseIdentifier, type: type) - } -} - diff --git a/Sources/RswiftCoreLegacy/ResourceTypes/StringParam.swift b/Sources/RswiftCoreLegacy/ResourceTypes/StringParam.swift deleted file mode 100644 index 9bf1ad75..00000000 --- a/Sources/RswiftCoreLegacy/ResourceTypes/StringParam.swift +++ /dev/null @@ -1,267 +0,0 @@ -// -// StringParam.swift -// R.swift -// -// Created by Tom Lokhorst on 2016-04-18. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// -// Parts of the content of this file are loosly based on StringsFileParser.swift from SwiftGen/GenumKit. -// We don't feel this is a "substantial portion of the Software" so are not including their MIT license, -// eventhough we would like to give credit where credit is due by referring to SwiftGen thanking Olivier -// Halligon for creating SwiftGen and GenumKit. -// -// See: https://github.com/AliSoftware/SwiftGen/blob/master/GenumKit/Parsers/StringsFileParser.swift -// - -import Foundation - -struct StringParam : Equatable, Unifiable { - let name: String? - let spec: FormatSpecifier - - func unify(_ other: StringParam) -> StringParam? { - if let name = name, let otherName = other.name , name != otherName { - return nil - } - - if let spec = spec.unify(other.spec) { - return StringParam(name: name ?? other.name, spec: spec) - } - - return nil - } -} - -func ==(lhs: StringParam, rhs: StringParam) -> Bool { - return lhs.name == rhs.name && lhs.spec == rhs.spec -} - -enum FormatPart: Unifiable { - case spec(FormatSpecifier) - case reference(String) - - var formatSpecifier: FormatSpecifier? { - switch self { - case .spec(let formatSpecifier): - return formatSpecifier - - case .reference: - return nil - } - } - - static func formatParts(formatString: String) -> [FormatPart] { - return createFormatParts(formatString) - } - - func unify(_ other: FormatPart) -> FormatPart? { - switch (self, other) { - case let (.spec(l), .spec(r)): - if let spec = l.unify(r) { - return .spec(spec) - } - else { - return nil - } - - case let (.reference(l), .reference(r)) where l == r: - return .reference(l) - - default: - return nil - } - } -} - -// https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html#//apple_ref/doc/uid/TP40004265-SW1 -enum FormatSpecifier { - case object - case double - case int - case uInt - case character - case cStringPointer - case voidPointer - case topType - - var type: Type { - switch self { - case .object: - return ._String - case .double: - return ._Double - case .int: - return ._Int - case .uInt: - return ._UInt - case .character: - return ._Character - case .cStringPointer: - return ._CStringPointer - case .voidPointer: - return ._VoidPointer - case .topType: - return ._Any - } - } -} - -extension FormatSpecifier : Unifiable { - - // Convenience initializer, uses last character of string, - // ignoring lengt modifiers, e.g. "lld" - init?(formatString string: String) { - guard let last = string.last else { - return nil - } - - self.init(formatChar: last) - } - - init?(formatChar char: Swift.Character) { - let lcChar = Swift.String(char).lowercased().first! - switch lcChar { - case "@": - self = .object - case "a", "e", "f", "g": - self = .double - case "d", "i": - self = .int - case "o", "u", "x": - self = .uInt - case "c": - self = .character - case "s": - self = .cStringPointer - case "p": - self = .voidPointer - default: - return nil - } - } - - func unify(_ other: FormatSpecifier) -> FormatSpecifier? { - if self == .topType { - return other - } - - if other == .topType { - return self - } - - if self == other { - return self - } - - return nil - } -} - -private let referenceRegEx: NSRegularExpression = { - do { - return try NSRegularExpression(pattern: "#@([^@]+)@", options: [.caseInsensitive]) - } catch { - fatalError("Error building the regular expression used to match reference") - } -}() - -private let formatTypesRegEx: NSRegularExpression = { - let pattern_int = "(?:h|hh|l|ll|q|z|t|j)?([dioux])" // %d/%i/%o/%u/%x with their optional length modifiers like in "%lld" - let pattern_float = "[aefg]" - let position = "([1-9]\\d*\\$)?" // like in "%3$" to make positional specifiers - let precision = "[-+]?\\d*(?:\\.\\d*)?" // precision like in "%1.2f" or "%012.10" - let reference = "#@([^@]+)@" // reference to NSStringFormatSpecType in .stringsdict - do { - return try NSRegularExpression(pattern: "(? [.Spec(.Int), .Spec(.String), .Reference("named")] -private func createFormatParts(_ formatString: String) -> [FormatPart] { - let nsString = formatString as NSString - let range = NSRange(location: 0, length: nsString.length) - - // Extract the list of chars (conversion specifiers) and their optional positional specifier - let chars = formatTypesRegEx.matches(in: formatString, options: [], range: range).map { match -> (String, Int?) in - let range: NSRange - if match.range(at: 3).location != NSNotFound { - // [dioux] are in range #3 because in #2 there may be length modifiers (like in "lld") - range = match.range(at: 3) - } else { - // otherwise, no length modifier, the conversion specifier is in #2 - range = match.range(at: 2) - } - let char = nsString.substring(with: range) - - let posRange = match.range(at: 1) - if posRange.location == NSNotFound { - // No positional specifier - return (char, nil) - } else { - // Remove the "$" at the end of the positional specifier, and convert to Int - let posRange1 = NSRange(location: posRange.location, length: posRange.length-1) - let pos = nsString.substring(with: posRange1) - return (char, Int(pos)) - } - } - - // Build up params array - var params = [FormatPart]() - var nextNonPositional = 1 - for (str, pos) in chars { - let insertionPos: Int - if let pos = pos { - insertionPos = pos - } - else { - insertionPos = nextNonPositional - nextNonPositional += 1 - } - - let param: FormatPart? - - if let reference = referenceRegEx.firstSubstring(input: str) { - param = FormatPart.reference(reference) - } - else if let char = str.first, let fs = FormatSpecifier(formatChar: char) - { - param = FormatPart.spec(fs) - } - else { - param = nil - } - - if let param = param { - if insertionPos > 0 { - while params.count <= insertionPos - 1 { - params.append(FormatPart.spec(FormatSpecifier.topType)) - } - - params[insertionPos - 1] = param - } - } - } - - return params -} - -extension NSRegularExpression { - fileprivate func firstSubstring(input: String) -> String? { - let nsInput = input as NSString - let inputRange = NSMakeRange(0, nsInput.length) - - guard let match = self.firstMatch(in: input, options: [], range: inputRange) else { - return nil - } - - guard match.numberOfRanges > 0 else { - return nil - } - - let range = match.range(at: 1) - return nsInput.substring(with: range) - } -} diff --git a/Sources/RswiftCoreLegacy/ResourceTypes/Unifiable.swift b/Sources/RswiftCoreLegacy/ResourceTypes/Unifiable.swift deleted file mode 100644 index 8b9aca59..00000000 --- a/Sources/RswiftCoreLegacy/ResourceTypes/Unifiable.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// Unifiable.swift -// R.swift -// -// Created by Tom Lokhorst on 2016-04-30. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -protocol Unifiable { - func unify(_ other: Self) -> Self? -} - -extension Array where Element : Unifiable { - func unify(_ other: [Element]) -> [Element]? { - var result = self - - for (ix, right) in other.enumerated() { - if let left = result[safe: ix] { - if let unified = left.unify(right) { - result[ix] = unified - } - else { - return nil - } - } - else { - result.append(right) - } - } - - return result - } -} diff --git a/Sources/RswiftCoreLegacy/ResourceTypes/WhiteListedExtensionsResourceType.swift b/Sources/RswiftCoreLegacy/ResourceTypes/WhiteListedExtensionsResourceType.swift deleted file mode 100644 index 4bcc70a2..00000000 --- a/Sources/RswiftCoreLegacy/ResourceTypes/WhiteListedExtensionsResourceType.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// ResourceType.swift -// R.swift -// -// Created by Mathijs Kadijk on 10-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -protocol WhiteListedExtensionsResourceType { - static var supportedExtensions: Set { get } -} - -extension WhiteListedExtensionsResourceType { - // Convenience function to check if the path extension is supported, but feels a bit dirty since it throws and takes an optional - static func throwIfUnsupportedExtension(_ pathExtension: String?) throws { - let pathExtension = pathExtension ?? "" - - if !supportedExtensions.contains(pathExtension.lowercased()) { - throw ResourceParsingError.unsupportedExtension(givenExtension: pathExtension, supportedExtensions: supportedExtensions) - } - } -} diff --git a/Sources/RswiftCoreLegacy/ResourceTypes/Xcodeproj.swift b/Sources/RswiftCoreLegacy/ResourceTypes/Xcodeproj.swift deleted file mode 100644 index 23b0b283..00000000 --- a/Sources/RswiftCoreLegacy/ResourceTypes/Xcodeproj.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// Xcodeproj.swift -// R.swift -// -// Created by Mathijs Kadijk on 09-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation -import XcodeEdit - -struct BuildConfiguration { - let name: String -} - -struct Xcodeproj: WhiteListedExtensionsResourceType { - static let supportedExtensions: Set = ["xcodeproj"] - - private let projectFile: XCProjectFile - - let developmentLanguage: String - - init(url: URL) throws { - try Xcodeproj.throwIfUnsupportedExtension(url.pathExtension) - let projectFile: XCProjectFile - - // Parse project file - do { - do { - projectFile = try XCProjectFile(xcodeprojURL: url) - } - catch let error as ProjectFileError { - warn(error.localizedDescription) - - projectFile = try XCProjectFile(xcodeprojURL: url, ignoreReferenceErrors: true) - } - } - catch { - throw ResourceParsingError.parsingFailed("Project file at '\(url)' could not be parsed, is this a valid Xcode project file ending in *.xcodeproj?\n\(error.localizedDescription)") - } - - self.projectFile = projectFile - self.developmentLanguage = projectFile.project.developmentRegion - } - - private func findTarget(name: String) throws -> PBXTarget { - // Look for target in project file - let allTargets = projectFile.project.targets.compactMap { $0.value } - guard let target = allTargets.filter({ $0.name == name }).first else { - let availableTargets = allTargets.compactMap { $0.name }.joined(separator: ", ") - throw ResourceParsingError.parsingFailed("Target '\(name)' not found in project file, available targets are: \(availableTargets)") - } - - return target - } - - func resourcePaths(forTarget targetName: String) throws -> [Path] { - let target = try findTarget(name: targetName) - - let resourcesFileRefs = target.buildPhases - .compactMap { $0.value as? PBXResourcesBuildPhase } - .flatMap { $0.files } - .compactMap { $0.value?.fileRef } - - let fileRefPaths = resourcesFileRefs - .compactMap { $0.value as? PBXFileReference } - .compactMap { $0.fullPath } - - let variantGroupPaths = resourcesFileRefs - .compactMap { $0.value as? PBXVariantGroup } - .flatMap { $0.fileRefs } - .compactMap { $0.value?.fullPath } - - return fileRefPaths + variantGroupPaths - } - - func scriptBuildPhases(forTarget targetName: String) throws -> [PBXShellScriptBuildPhase] { - let target = try findTarget(name: targetName) - - return target.buildPhases - .compactMap { $0.value as? PBXShellScriptBuildPhase } - } - - func buildConfigurations(forTarget targetName: String) throws -> [BuildConfiguration] { - let target = try findTarget(name: targetName) - - guard let buildConfigurationList = target.buildConfigurationList.value else { return [] } - - let buildConfigurations = buildConfigurationList.buildConfigurations - .compactMap { $0.value } - .compactMap { configuration -> BuildConfiguration? in - return BuildConfiguration(name: configuration.name) - } - - return buildConfigurations - } -} diff --git a/Sources/RswiftCoreLegacy/RswiftCore.swift b/Sources/RswiftCoreLegacy/RswiftCore.swift deleted file mode 100644 index 6e209853..00000000 --- a/Sources/RswiftCoreLegacy/RswiftCore.swift +++ /dev/null @@ -1,215 +0,0 @@ -// -// RswiftCore.swift -// R.swift -// -// Created by Tom Lokhorst on 2017-04-22. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation -import XcodeEdit - -public typealias RswiftGenerator = Generator -public enum Generator: String, CaseIterable { - case image - case string - case color - case file - case font - case nib - case segue - case storyboard - case reuseIdentifier - case entitlements - case info - case id -} - -public struct RswiftCore { - private let callInformation: CallInformation - - public init(_ callInformation: CallInformation) { - self.callInformation = callInformation - } - - public func run() throws { - do { - let xcodeproj = try Xcodeproj(url: callInformation.xcodeprojURL) - let ignoreFile = (try? IgnoreFile(ignoreFileURL: callInformation.rswiftIgnoreURL)) ?? IgnoreFile() - - printWarningAboutDependencyAnalysis(for: try xcodeproj.scriptBuildPhases(forTarget: callInformation.targetName)) - - let buildConfigurations = try xcodeproj.buildConfigurations(forTarget: callInformation.targetName) - - let resourceURLs = try xcodeproj.resourcePaths(forTarget: callInformation.targetName) - .map { path in path.url(with: callInformation.urlForSourceTreeFolder) } - .compactMap { $0 } - .filter { !ignoreFile.matches(url: $0) } - - let resources = Resources(resourceURLs: resourceURLs, fileManager: FileManager.default) - let infoPlistWhitelist = ["UIApplicationShortcutItems", "UIApplicationSceneManifest", "NSUserActivityTypes", "NSExtension"] - - var structGenerators: [StructGenerator] = [] - if callInformation.generators.contains(.image) { - structGenerators.append(ImageStructGenerator(assetFolders: resources.assetFolders, images: resources.images)) - } - if callInformation.generators.contains(.color) { - structGenerators.append(ColorStructGenerator(assetFolders: resources.assetFolders)) - } - if callInformation.generators.contains(.font) { - structGenerators.append(FontStructGenerator(fonts: resources.fonts)) - } - if callInformation.generators.contains(.segue) { - structGenerators.append(SegueStructGenerator(storyboards: resources.storyboards)) - } - if callInformation.generators.contains(.storyboard) { - structGenerators.append(StoryboardStructGenerator(storyboards: resources.storyboards)) - } - if callInformation.generators.contains(.nib) { - structGenerators.append(NibStructGenerator(nibs: resources.nibs)) - } - if callInformation.generators.contains(.reuseIdentifier) { - structGenerators.append(ReuseIdentifierStructGenerator(reusables: resources.reusables)) - } - if callInformation.generators.contains(.file) { - structGenerators.append(ResourceFileStructGenerator(resourceFiles: resources.resourceFiles)) - } - if callInformation.generators.contains(.string) { - structGenerators.append(StringsStructGenerator(localizableStrings: resources.localizableStrings, developmentLanguage: xcodeproj.developmentLanguage)) - } - if callInformation.generators.contains(.id) { - structGenerators.append(AccessibilityIdentifierStructGenerator(nibs: resources.nibs, storyboards: resources.storyboards)) - } - if callInformation.generators.contains(.info) { - - let infoPlists = buildConfigurations.compactMap { config -> PropertyList? in - guard let infoPlistFile = callInformation.infoPlistFile else { return nil } - return loadPropertyList(name: config.name, url: infoPlistFile, callInformation: callInformation) - } - - structGenerators.append(PropertyListGenerator(name: "info", plists: infoPlists, toplevelKeysWhitelist: infoPlistWhitelist)) - } - if callInformation.generators.contains(.entitlements) { - - let entitlements = buildConfigurations.compactMap { config -> PropertyList? in - guard let codeSignEntitlement = callInformation.codeSignEntitlements else { return nil } - return loadPropertyList(name: config.name, url: codeSignEntitlement, callInformation: callInformation) - } - - structGenerators.append(PropertyListGenerator(name: "entitlements", plists: entitlements, toplevelKeysWhitelist: nil)) - } - - // Generate regular R file - let fileContents = generateRegularFileContents(resources: resources, generators: structGenerators) - writeIfChanged(contents: fileContents, toURL: callInformation.outputURL) - - // Generate UITest R file - if let uiTestOutputURL = callInformation.uiTestOutputURL { - let uiTestFileContents = generateUITestFileContents(resources: resources, generators: [ - AccessibilityIdentifierStructGenerator(nibs: resources.nibs, storyboards: resources.storyboards) - ]) - writeIfChanged(contents: uiTestFileContents, toURL: uiTestOutputURL) - } - - } catch let error as ResourceParsingError { - switch error { - case let .parsingFailed(description): - fail(description) - - case let .unsupportedExtension(givenExtension, supportedExtensions): - let joinedSupportedExtensions = supportedExtensions.joined(separator: ", ") - fail("File extension '\(String(describing: givenExtension))' is not one of the supported extensions: \(joinedSupportedExtensions)") - } - - exit(EXIT_FAILURE) - } - } - - private func printWarningAboutDependencyAnalysis(for scriptBuildPhases: [PBXShellScriptBuildPhase]) { - let outputFiles = [ - callInformation.outputURL.path, - callInformation.outputURL.path.replacingOccurrences(of: callInformation.sourceRootURL.path, with: "$SOURCE_ROOT"), - callInformation.outputURL.path.replacingOccurrences(of: callInformation.sourceRootURL.path, with: "$SRCROOT"), - callInformation.outputURL.path.replacingOccurrences(of: callInformation.sourceRootURL.path, with: "$(SOURCE_ROOT)"), - callInformation.outputURL.path.replacingOccurrences(of: callInformation.sourceRootURL.path, with: "$(SRCROOT)"), - ] - - let rswiftPhase = scriptBuildPhases.first { buildPhase in - let outputs = buildPhase.outputPaths ?? [] - let script = buildPhase.shellScript - - return script.contains("rswift") && outputs.contains { outputFiles.contains($0) } - } - - if let rswiftPhase = rswiftPhase { - if rswiftPhase.alwaysOutOfDate != true { - warn("In R.swift Run Script build phase, disable \"Based on dependency analysis\"") - } - } - } - - private func generateRegularFileContents(resources: Resources, generators: [StructGenerator]) -> String { - let aggregatedResult = AggregatedStructGenerator(subgenerators: generators) - .generatedStructs(at: callInformation.accessLevel, prefix: "") - - let (externalStructWithoutProperties, internalStruct) = ValidatedStructGenerator(validationSubject: aggregatedResult) - .generatedStructs(at: callInformation.accessLevel, prefix: "") - - let externalStruct = externalStructWithoutProperties - .addingInternalProperties(forBundleIdentifier: callInformation.bundleIdentifier, hostingBundle: callInformation.hostingBundle) - - let codeConvertibles: [SwiftCodeConverible?] = [ - HeaderPrinter(), - ImportPrinter( - modules: callInformation.imports, - extractFrom: [externalStruct, internalStruct], - exclude: [Module.custom(name: callInformation.productModuleName)] - ), - externalStruct, - internalStruct - ] - - return codeConvertibles - .compactMap { $0?.swiftCode } - .joined(separator: "\n\n") - + "\n" // Newline at end of file - } - - private func generateUITestFileContents(resources: Resources, generators: [StructGenerator]) -> String { - let (externalStruct, _) = AggregatedStructGenerator(subgenerators: generators) - .generatedStructs(at: callInformation.accessLevel, prefix: "") - - let codeConvertibles: [SwiftCodeConverible?] = [ - HeaderPrinter(), - externalStruct - ] - - return codeConvertibles - .compactMap { $0?.swiftCode } - .joined(separator: "\n\n") - + "\n" // Newline at end of file - } -} - -private func loadPropertyList(name: String, url: URL, callInformation: CallInformation) -> PropertyList? { - do { - return try PropertyList(buildConfigurationName: name, url: url) - } catch let ResourceParsingError.parsingFailed(humanReadableError) { - warn(humanReadableError) - return nil - } - catch { - return nil - } -} - -private func writeIfChanged(contents: String, toURL outputURL: URL) { - let currentFileContents = try? String(contentsOf: outputURL, encoding: .utf8) - guard currentFileContents != contents else { return } - do { - try contents.write(to: outputURL, atomically: true, encoding: .utf8) - } catch { - fail(error.localizedDescription) - } -} diff --git a/Sources/RswiftCoreLegacy/SwiftTypes/AccessLevel.swift b/Sources/RswiftCoreLegacy/SwiftTypes/AccessLevel.swift deleted file mode 100644 index 46036454..00000000 --- a/Sources/RswiftCoreLegacy/SwiftTypes/AccessLevel.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// AccessLevel.swift -// R.swift -// -// Created by Mathijs Kadijk on 09-01-16. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -public enum AccessLevel: String, SwiftCodeConverible { - case publicLevel = "public" - case internalLevel = "internal" - case filePrivate = "fileprivate" - case privateLevel = "private" - - var swiftCode: String { - if self == .internalLevel { - return "" - } - - return "\(self.rawValue) " - } -} diff --git a/Sources/RswiftCoreLegacy/SwiftTypes/Class.swift b/Sources/RswiftCoreLegacy/SwiftTypes/Class.swift deleted file mode 100644 index 9633a656..00000000 --- a/Sources/RswiftCoreLegacy/SwiftTypes/Class.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// Class.swift -// R.swift -// -// Created by Mathijs Kadijk on 10-12-16. -// Copyright © 2016 Mathijs Kadijk. All rights reserved. -// - -import Foundation - -struct Class: SwiftCodeConverible { - let accessModifier: AccessLevel - let type: Type - - var swiftCode: String { - let accessModifierString = accessModifier.swiftCode - - return "\(accessModifierString)class \(type) {}" - } -} diff --git a/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/HeaderPrinter.swift b/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/HeaderPrinter.swift deleted file mode 100644 index a6e1cc95..00000000 --- a/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/HeaderPrinter.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// HeaderPrinter.swift -// R.swift -// -// Created by Mathijs Kadijk on 06-10-16. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -/// Prints a static header for the generated file -struct HeaderPrinter: SwiftCodeConverible { - let swiftCode = [ - "//", - "// This is a generated file, do not edit!", - "// Generated by R.swift, see https://github.com/mac-cain13/R.swift", - "//", - ].joined(separator: "\n") -} diff --git a/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/ImportPrinter.swift b/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/ImportPrinter.swift deleted file mode 100644 index 5330457b..00000000 --- a/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/ImportPrinter.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// ImportPrinter.swift -// R.swift -// -// Created by Mathijs Kadijk on 06-10-16. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -/// Prints all import statements for the modules used in the given structs -struct ImportPrinter: SwiftCodeConverible { - let swiftCode: String - - init(modules: [Module], extractFrom structs: [Struct?], exclude excludedModules: Set) { - let extractedModules = structs - .compactMap { $0 } - .flatMap(getUsedTypes) - .map { $0.type.module } - - let extractedModulesArray = Set(extractedModules) - .subtracting(excludedModules) - .subtracting(modules) - .filter { $0.isCustom } - .sorted { $0.description < $1.description } - - // Note that the modules specified to the --import flag are always specified first - // See: https://github.com/mac-cain13/R.swift/issues/534 - var modulesToImport = modules - modulesToImport.append(contentsOf: extractedModulesArray) - - swiftCode = modulesToImport - .map { "import \($0)" } - .joined(separator: "\n") - } -} diff --git a/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/OSPrinter.swift b/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/OSPrinter.swift deleted file mode 100644 index d0885859..00000000 --- a/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/OSPrinter.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// OSPrinter.swift -// RswiftCore -// -// Created by Lammert Westerhoff on 28/08/2018. -// - -import Foundation - -/// Prints the code wrapped inside #if os(...) / #endif preprocessors if the code is only supported by certain operating systems -struct OSPrinter: SwiftCodeConverible { - let swiftCode: String - - init(code: String, supportedOS: [String]) { - guard supportedOS.count > 0 else { - swiftCode = code - return - } - - let preprocessorStartString = supportedOS.enumerated().reduce("") { result, item in - let (index, os) = item - var result = result - if index == 0 { - result += "#if " - } else { - result += " || " - } - return result + "os(\(os))" - } - swiftCode = "\(preprocessorStartString)\n\(code)\n#endif" - } -} diff --git a/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/SwiftCodeConverible.swift b/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/SwiftCodeConverible.swift deleted file mode 100644 index e9ebbe8b..00000000 --- a/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/SwiftCodeConverible.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// SwiftCodeConverible.swift -// R.swift -// -// Created by Mathijs Kadijk on 14-01-16. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -protocol SwiftCodeConverible { - var swiftCode: String { get } -} diff --git a/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/TypePrinter.swift b/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/TypePrinter.swift deleted file mode 100644 index 720db635..00000000 --- a/Sources/RswiftCoreLegacy/SwiftTypes/CodeGenerators/TypePrinter.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// TypePrinter.swift -// R.swift -// -// Created by Mathijs Kadijk on 14-01-16. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct TypePrinter: SwiftCodeConverible, UsedTypesProvider { - let type: Type - - var usedTypes: [UsedType] { - return type.usedTypes - } - - var swiftCode: String { - let optionalSuffix = type.optional ? "?" : "" - let args = type.genericArgs.map { $0.description }.joined(separator: ", ") - - let withoutModule: String - if type.genericArgs.isEmpty { - withoutModule = "\(type.name)" - } else if type.name == Type._Tuple.name { - withoutModule = "(\(args))" - } else if type.name == Type._Array.name { - withoutModule = "[\(args)]" - } else { - withoutModule = "\(type.name)<\(args)>" - } - - if case let .custom(name: moduleName) = type.module { - return "\(moduleName).\(withoutModule)\(optionalSuffix)" - } else { - return "\(withoutModule)\(optionalSuffix)" - } - } - - init(type: Type) { - self.type = type - } -} - diff --git a/Sources/RswiftCoreLegacy/SwiftTypes/Function.swift b/Sources/RswiftCoreLegacy/SwiftTypes/Function.swift deleted file mode 100644 index fd972190..00000000 --- a/Sources/RswiftCoreLegacy/SwiftTypes/Function.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// Function.swift -// R.swift -// -// Created by Mathijs Kadijk on 10-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct Function: UsedTypesProvider, SwiftCodeConverible { - let availables: [String] - let comments: [String] - let accessModifier: AccessLevel - let isStatic: Bool - let name: SwiftIdentifier - let generics: String? - let parameters: [Parameter] - let doesThrow: Bool - let returnType: Type - let body: String - let os: [String] - - var usedTypes: [UsedType] { - return [ - returnType.usedTypes, - parameters.flatMap(getUsedTypes), - ] - .joined() - .array() - } - - var swiftCode: String { - let commentsString = comments.map { $0.isEmpty ? "///\n" : "/// \($0)\n" }.joined(separator: "") - let availablesString = availables.map { "@available(\($0))\n" }.joined(separator: "") - let accessModifierString = accessModifier.swiftCode - let staticString = isStatic ? "static " : "" - let genericsString = generics.map { "<\($0)>" } ?? "" - let parameterString = parameters.map { $0.description }.joined(separator: ", ") - let throwString = doesThrow ? " throws" : "" - let returnString = Type._Void == returnType ? "" : " -> \(returnType)" - let bodyString = body.indent(with: " ") - - return OSPrinter(code: "\(commentsString)\(availablesString)\(accessModifierString)\(staticString)func \(name)\(genericsString)(\(parameterString))\(throwString)\(returnString) {\n\(bodyString)\n}", supportedOS: os).swiftCode - } - - struct Parameter: UsedTypesProvider, CustomStringConvertible { - let name: String - let localName: String? - let type: Type - let defaultValue: String? - - var usedTypes: [UsedType] { - return type.usedTypes - } - - var swiftIdentifier: SwiftIdentifier { - return SwiftIdentifier(name: name, lowercaseStartingCharacters: true) - } - - var description: String { - let definition = localName.map({ "\(swiftIdentifier) \($0): \(type)" }) ?? "\(swiftIdentifier): \(type)" - return defaultValue.map({ "\(definition) = \($0)" }) ?? definition - } - - init(name: String, type: Type, defaultValue: String? = nil) { - self.name = name - self.localName = nil - self.type = type - self.defaultValue = defaultValue - } - - init(name: String, localName: String?, type: Type, defaultValue: String? = nil) { - self.name = name - self.localName = localName - self.type = type - self.defaultValue = defaultValue - } - } -} diff --git a/Sources/RswiftCoreLegacy/SwiftTypes/Let.swift b/Sources/RswiftCoreLegacy/SwiftTypes/Let.swift deleted file mode 100644 index 70f6506a..00000000 --- a/Sources/RswiftCoreLegacy/SwiftTypes/Let.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// Let.swift -// R.swift -// -// Created by Mathijs Kadijk on 05-01-16. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -enum TypeDefinition: UsedTypesProvider { - case specified(Type) - case inferred(Type?) - - var type: Type? { - switch self { - case let .specified(type): return type - case let .inferred(type): return type - } - } - - var usedTypes: [UsedType] { - return type?.usedTypes ?? [] - } -} - -struct Let: UsedTypesProvider, SwiftCodeConverible { - let comments: [String] - let accessModifier: AccessLevel - let isStatic: Bool - let name: SwiftIdentifier - let typeDefinition: TypeDefinition - let value: String - - init(comments: [String], accessModifier: AccessLevel, isStatic: Bool, name: SwiftIdentifier, typeDefinition: TypeDefinition, value: String) { - self.comments = comments - self.accessModifier = accessModifier - self.isStatic = isStatic - self.name = name - self.typeDefinition = typeDefinition - self.value = value - } - - var usedTypes: [UsedType] { - return typeDefinition.usedTypes - } - - var swiftCode: String { - let commentsString = comments.map { $0.isEmpty ? "///\n" : "/// \($0)\n" }.joined(separator: "") - let accessModifierString = accessModifier.swiftCode - let staticString = isStatic ? "static " : "" - - let typeString: String - switch typeDefinition { - case let .specified(type): typeString = ": \(type)" - case .inferred: typeString = "" - } - - return "\(commentsString)\(accessModifierString)\(staticString)let \(name)\(typeString) = \(value)" - } -} diff --git a/Sources/RswiftCoreLegacy/SwiftTypes/Module.swift b/Sources/RswiftCoreLegacy/SwiftTypes/Module.swift deleted file mode 100644 index 7b98e99a..00000000 --- a/Sources/RswiftCoreLegacy/SwiftTypes/Module.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// Module.swift -// R.swift -// -// Created by Mathijs Kadijk on 11-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -public enum Module: ExpressibleByStringLiteral, CustomStringConvertible, Hashable { - case host - case stdLib - case custom(name: String) - - public typealias UnicodeScalarLiteralType = StringLiteralType - public typealias ExtendedGraphemeClusterLiteralType = StringLiteralType - - public var description: String { - switch self { - case .host: return "" - case .stdLib: return "" - case let .custom(name: name): return name - } - } - - var isCustom: Bool { - switch self { - case .custom: - return true - default: - return false - } - } - - public init(name: String?, fallback: Module = .host) { - let cleaned = name?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" - self = cleaned.isEmpty ? fallback : .custom(name: cleaned) - } - - public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) { - self = .custom(name: value) - } - - public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) { - self = .custom(name: value) - } - - public init(stringLiteral value: StringLiteralType) { - self = .custom(name: value) - } - - static public func ==(lhs: Module, rhs: Module) -> Bool { - return lhs.hashValue == rhs.hashValue - } -} diff --git a/Sources/RswiftCoreLegacy/SwiftTypes/Struct.swift b/Sources/RswiftCoreLegacy/SwiftTypes/Struct.swift deleted file mode 100644 index 3d870c3c..00000000 --- a/Sources/RswiftCoreLegacy/SwiftTypes/Struct.swift +++ /dev/null @@ -1,104 +0,0 @@ -// -// Struct.swift -// R.swift -// -// Created by Mathijs Kadijk on 10-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct Struct: UsedTypesProvider, SwiftCodeConverible { - let availables: [String] - let comments: [String] - let accessModifier: AccessLevel - let type: Type - var implements: [TypePrinter] - let typealiasses: [Typealias] - var properties: [Let] - var functions: [Function] - var structs: [Struct] - var classes: [Class] - let os: [String] - - var isEmpty: Bool { - return properties.isEmpty - && functions.isEmpty - && (structs.isEmpty || structs.all { $0.isEmpty }) - && classes.isEmpty - } - - var usedTypes: [UsedType] { - return [ - type.usedTypes, - implements.flatMap(getUsedTypes), - typealiasses.flatMap(getUsedTypes), - properties.flatMap(getUsedTypes), - functions.flatMap(getUsedTypes), - structs.flatMap(getUsedTypes), - ] - .joined() - .array() - } - - var swiftCode: String { - let commentsString = comments.map { $0.isEmpty ? "///\n" : "/// \($0)\n" }.joined(separator: "") - let availablesString = availables.map { "@available(\($0))\n" }.joined(separator: "") - let accessModifierString = accessModifier.swiftCode - let implementsString = implements.count > 0 ? ": " + implements.map { $0.swiftCode }.joined(separator: ", ") : "" - - let typealiasString = typealiasses - .sorted { $0.alias < $1.alias } - .map { $0.description } - .joined(separator: "\n") - - let varsString = properties - .map { $0.swiftCode } - .sorted() - .map { $0.description } - .joined(separator: "\n") - - let functionsString = functions - .map { $0.swiftCode } - .sorted() - .map { $0.description } - .joined(separator: "\n\n") - - let structsString = structs - .map { $0.swiftCode } - .sorted() - .map { $0.description } - .joined(separator: "\n\n") - - let classesString = classes - .map { $0.swiftCode } - .sorted() - .map { $0.description } - .joined(separator: "\n\n") - - // File private `init`, so that struct can't be initialized from the outside world - let fileprivateInit = "fileprivate init() {}" - - let bodyComponents = [typealiasString, varsString, functionsString, structsString, classesString, fileprivateInit].filter { $0 != "" } - let bodyString = bodyComponents.joined(separator: "\n\n").indent(with: " ") - - return OSPrinter(code: "\(commentsString)\(availablesString)\(accessModifierString)struct \(type)\(implementsString) {\n\(bodyString)\n}", supportedOS: os).swiftCode - } - - static var empty: Struct { - return Struct( - availables: [], - comments: [], - accessModifier: .publicLevel, - type: Type(module: .host, name: "empty"), - implements: [], - typealiasses: [], - properties: [], - functions: [], - structs: [], - classes: [], - os: [] - ) - } -} diff --git a/Sources/RswiftCoreLegacy/SwiftTypes/Type.swift b/Sources/RswiftCoreLegacy/SwiftTypes/Type.swift deleted file mode 100644 index de2ad8dd..00000000 --- a/Sources/RswiftCoreLegacy/SwiftTypes/Type.swift +++ /dev/null @@ -1,109 +0,0 @@ -// -// Type.swift -// R.swift -// -// Created by Mathijs Kadijk on 10-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct UsedType: Hashable { - let type: Type - - fileprivate init(type: Type) { - self.type = type - } -} - -struct Type: UsedTypesProvider, CustomStringConvertible, Hashable { - static let _Void = Type(module: .stdLib, name: "Void") - static let _Any = Type(module: .stdLib, name: "Any") - static let _AnyObject = Type(module: .stdLib, name: "AnyObject") - static let _String = Type(module: .stdLib, name: "String") - static let _Bool = Type(module: .stdLib, name: "Bool") - static let _Array = Type(module: .stdLib, name: "Array") - static let _Tuple = Type(module: .stdLib, name: "_TUPLE_") - static let _Int = Type(module: .stdLib, name: "Int") - static let _UInt = Type(module: .stdLib, name: "UInt") - static let _Double = Type(module: .stdLib, name: "Double") - static let _Character = Type(module: .stdLib, name: "Character") - static let _CStringPointer = Type(module: .stdLib, name: SwiftIdentifier(rawValue: "UnsafePointer")) - static let _VoidPointer = Type(module: .stdLib, name: SwiftIdentifier(rawValue: "UnsafePointer")) - static let _URL = Type(module: "Foundation", name: "URL") - static let _Bundle = Type(module: "Foundation", name: "Bundle") - static let _Locale = Type(module: "Foundation", name: "Locale") - static let _UINib = Type(module: "UIKit", name: "UINib") - static let _UIView = Type(module: "UIKit", name: "UIView") - static let _UIImage = Type(module: "UIKit", name: "UIImage") - static let _UIStoryboard = Type(module: "UIKit", name: "UIStoryboard") - static let _UITableViewCell = Type(module: "UIKit", name: "UITableViewCell") - static let _UICollectionViewCell = Type(module: "UIKit", name: "UICollectionViewCell") - static let _UICollectionReusableView = Type(module: "UIKit", name: "UICollectionReusableView") - static let _UIStoryboardSegue = Type(module: "UIKit", name: "UIStoryboardSegue") - static let _UITraitCollection = Type(module: "UIKit", name: "UITraitCollection") - static let _UIViewController = Type(module: "UIKit", name: "UIViewController") - static let _UIFont = Type(module: "UIKit", name: "UIFont") - static let _UIColor = Type(module: "UIKit", name: "UIColor") - static let _CGFloat = Type(module: .stdLib, name: "CGFloat") - static let _CVarArgType = Type(module: .stdLib, name: "CVarArgType...") - - static let ReuseIdentifier = Type(module: "Rswift", name: "ReuseIdentifier", genericArgs: [TypeVar(description: "T", usedTypes: [])]) - static let ReuseIdentifierType = Type(module: "Rswift", name: "ReuseIdentifierType") - static let StoryboardResourceType = Type(module: "Rswift", name: "StoryboardResourceType") - static let StoryboardResourceWithInitialControllerType = Type(module: "Rswift", name: "StoryboardResourceWithInitialControllerType") - static let StoryboardViewControllerResource = Type(module: "Rswift", name: "StoryboardViewControllerResource") - static let NibResourceType = Type(module: "Rswift", name: "NibResourceType") - static let FileResource = Type(module: "Rswift", name: "FileResource") - static let FontResource = Type(module: "Rswift", name: "FontResource") - static let ColorResource = Type(module: "Rswift", name: "ColorResource") - static let ImageResource = Type(module: "Rswift", name: "ImageResource") - static let StringResource = Type(module: "Rswift", name: "StringResource") - static let Strings = Type(module: "Rswift", name: "Strings") - static let Validatable = Type(module: "Rswift", name: "Validatable") - static let TypedStoryboardSegueInfo = Type(module: "Rswift", name: "TypedStoryboardSegueInfo", genericArgs: [TypeVar(description: "Segue", usedTypes: []), TypeVar(description: "Source", usedTypes: []), TypeVar(description: "Destination", usedTypes: [])]) - - let module: Module - let name: SwiftIdentifier - let genericArgs: [TypeVar] - let optional: Bool - - var usedTypes: [UsedType] { - return [UsedType(type: self)] + genericArgs.flatMap(getUsedTypes) - } - - var description: String { - return TypePrinter(type: self).swiftCode - } - - init(module: Module, name: SwiftIdentifier, genericArgs: [TypeVar] = [], optional: Bool = false) { - self.module = module - self.name = name - self.genericArgs = genericArgs - self.optional = optional - } - - init(module: Module, name: SwiftIdentifier, genericArgs: [Type], optional: Bool = false) { - self.module = module - self.name = name - self.genericArgs = genericArgs.map(TypeVar.init) - self.optional = optional - } - - func asOptional() -> Type { - return Type(module: module, name: name, genericArgs: genericArgs, optional: true) - } - - func asNonOptional() -> Type { - return Type(module: module, name: name, genericArgs: genericArgs, optional: false) - } - - func withGenericArgs(_ genericArgs: [TypeVar]) -> Type { - return Type(module: module, name: name, genericArgs: genericArgs, optional: optional) - } - - func withGenericArgs(_ genericArgs: [Type]) -> Type { - return Type(module: module, name: name, genericArgs: genericArgs, optional: optional) - } -} diff --git a/Sources/RswiftCoreLegacy/SwiftTypes/TypeVar.swift b/Sources/RswiftCoreLegacy/SwiftTypes/TypeVar.swift deleted file mode 100644 index a028b81e..00000000 --- a/Sources/RswiftCoreLegacy/SwiftTypes/TypeVar.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// TypeVar.swift -// R.swift -// -// Created by Mathijs Kadijk on 22-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct TypeVar: UsedTypesProvider, CustomStringConvertible, Hashable { - let description: String - let usedTypes: [UsedType] - - init(type: Type) { - assert(type.genericArgs.count == 0, "TypeVars may not have generic args") - - description = type.description - usedTypes = [type] - .map { $0.withGenericArgs([] as [TypeVar]) } // Defensively handle if there are generic types - .flatMap(getUsedTypes) - } - - init(description: String, usedTypes: [Type]) { - assert(usedTypes.flatMap { $0.genericArgs }.count == 0, "TypeVars may not have generic args") - - self.description = description - self.usedTypes = usedTypes - .map { $0.withGenericArgs([] as [TypeVar]) } // Defensively handle if there are generic types - .flatMap(getUsedTypes) - } -} diff --git a/Sources/RswiftCoreLegacy/SwiftTypes/Typealias.swift b/Sources/RswiftCoreLegacy/SwiftTypes/Typealias.swift deleted file mode 100644 index 307c5fb1..00000000 --- a/Sources/RswiftCoreLegacy/SwiftTypes/Typealias.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Typealias.swift -// R.swift -// -// Created by Mathijs Kadijk on 10-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct Typealias: UsedTypesProvider, CustomStringConvertible { - let accessModifier: AccessLevel - let alias: String - let type: Type? - - var usedTypes: [UsedType] { - return type?.usedTypes ?? [] - } - - var description: String { - let accessModifierString = accessModifier.swiftCode - let typeString = type.map { " = \($0)" } ?? "" - - return "\(accessModifierString)typealias \(alias)\(typeString)" - } -} diff --git a/Sources/RswiftCoreLegacy/Util/ErrorOutput.swift b/Sources/RswiftCoreLegacy/Util/ErrorOutput.swift deleted file mode 100644 index 95061bb7..00000000 --- a/Sources/RswiftCoreLegacy/Util/ErrorOutput.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// ErrorOutput.swift -// R.swift -// -// Created by Mathijs Kadijk on 11-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -public func warn(_ warning: String) { - print("warning: [R.swift] \(warning)") -} - -public func fail(_ error: String) { - print("error: [R.swift] \(error)") -} diff --git a/Sources/RswiftCoreLegacy/Util/Glob.swift b/Sources/RswiftCoreLegacy/Util/Glob.swift deleted file mode 100644 index 0ba31a77..00000000 --- a/Sources/RswiftCoreLegacy/Util/Glob.swift +++ /dev/null @@ -1,221 +0,0 @@ -// -// Created by Eric Firestone on 3/22/16. -// Copyright © 2016 Square, Inc. All rights reserved. -// Released under the Apache v2 License. -// -// Adapted from https://gist.github.com/blakemerryman/76312e1cbf8aec248167 -// Adapted from https://gist.github.com/efirestone/ce01ae109e08772647eb061b3bb387c3 - - -import Foundation - - -public let GlobBehaviorBashV3 = Glob.Behavior( - supportsGlobstar: false, - includesFilesFromRootOfGlobstar: false, - includesDirectoriesInResults: true, - includesFilesInResultsIfTrailingSlash: false -) -public let GlobBehaviorBashV4 = Glob.Behavior( - supportsGlobstar: true, // Matches Bash v4 with "shopt -s globstar" option - includesFilesFromRootOfGlobstar: true, - includesDirectoriesInResults: true, - includesFilesInResultsIfTrailingSlash: false -) -public let GlobBehaviorGradle = Glob.Behavior( - supportsGlobstar: true, - includesFilesFromRootOfGlobstar: true, - includesDirectoriesInResults: false, - includesFilesInResultsIfTrailingSlash: true -) - - -/** - Finds files on the file system using pattern matching. - */ -public class Glob: Collection { - - /** - * Different glob implementations have different behaviors, so the behavior of this - * implementation is customizable. - */ - public struct Behavior { - // If true then a globstar ("**") causes matching to be done recursively in subdirectories. - // If false then "**" is treated the same as "*" - let supportsGlobstar: Bool - - // If true the results from the directory where the globstar is declared will be included as well. - // For example, with the pattern "dir/**/*.ext" the fie "dir/file.ext" would be included if this - // property is true, and would be omitted if it's false. - let includesFilesFromRootOfGlobstar: Bool - - // If false then the results will not include directory entries. This does not affect recursion depth. - let includesDirectoriesInResults: Bool - - // If false and the last characters of the pattern are "**/" then only directories are returned in the results. - let includesFilesInResultsIfTrailingSlash: Bool - } - - public static var defaultBehavior = GlobBehaviorBashV4 - - public static let defaultBlacklistedDirectories = ["node_modules", "Pods"] - - private var isDirectoryCache = [String: Bool]() - - public let behavior: Behavior - public let blacklistedDirectories: [String] - var paths = [String]() - public var startIndex: Int { return paths.startIndex } - public var endIndex: Int { return paths.endIndex } - - /// Initialize a glob - /// - /// - Parameters: - /// - pattern: The pattern to use when building the list of matching directories. - /// - behavior: See individual descriptions on `Glob.Behavior` values. - /// - blacklistedDirectories: An array of directories to ignore at the root level of the project. - public init(pattern: String, behavior: Behavior = Glob.defaultBehavior, blacklistedDirectories: [String] = defaultBlacklistedDirectories) { - - self.behavior = behavior - self.blacklistedDirectories = blacklistedDirectories - - var adjustedPattern = pattern - let hasTrailingGlobstarSlash = pattern.hasSuffix("**/") - var includeFiles = !hasTrailingGlobstarSlash - - if behavior.includesFilesInResultsIfTrailingSlash { - includeFiles = true - if hasTrailingGlobstarSlash { - // Grab the files too. - adjustedPattern += "*" - } - } - - let patterns = behavior.supportsGlobstar ? expandGlobstar(pattern: adjustedPattern) : [adjustedPattern] - - for pattern in patterns { - var gt = glob_t() - if executeGlob(pattern: pattern, gt: >) { - populateFiles(gt: gt, includeFiles: includeFiles) - } - - globfree(>) - } - - paths = Array(Set(paths)).sorted { lhs, rhs in - lhs.compare(rhs) != ComparisonResult.orderedDescending - } - - clearCaches() - } - - // MARK: Subscript Support - - public subscript(i: Int) -> String { - return paths[i] - } - - // MARK: Protocol of IndexableBase - - public func index(after i: Glob.Index) -> Glob.Index { - return i + 1 - } - - // MARK: Private - - private var globalFlags = GLOB_TILDE | GLOB_BRACE | GLOB_MARK - - private func executeGlob(pattern: UnsafePointer, gt: UnsafeMutablePointer) -> Bool { - return 0 == glob(pattern, globalFlags, nil, gt) - } - - private func expandGlobstar(pattern: String) -> [String] { - guard pattern.contains("**") else { - return [pattern] - } - - var results = [String]() - var parts = pattern.components(separatedBy: "**") - let firstPart = parts.removeFirst() - var lastPart = parts.joined(separator: "**") - - let fileManager = FileManager.default - - var directories: [String] - - do { - directories = try fileManager.contentsOfDirectory(atPath: firstPart).compactMap { subpath -> [String]? in - if blacklistedDirectories.contains(subpath) { - return nil - } - let firstLevelPath = NSString(string: firstPart).appendingPathComponent(subpath) - if isDirectory(path: firstLevelPath) { - var subDirs: [String] = try fileManager.subpathsOfDirectory(atPath: firstLevelPath).compactMap { subpath -> String? in - let fullPath = NSString(string: firstLevelPath).appendingPathComponent(subpath) - return isDirectory(path: fullPath) ? fullPath : nil - } - subDirs.append(firstLevelPath) - return subDirs - } else { - return nil - } - }.joined().array() - } catch { - directories = [] - print("Error parsing file system item: \(error)") - } - - if behavior.includesFilesFromRootOfGlobstar { - // Check the base directory for the glob star as well. - directories.insert(firstPart, at: 0) - - // Include the globstar root directory ("dir/") in a pattern like "dir/**" or "dir/**/" - if lastPart.isEmpty { - results.append(firstPart) - } - } - - if lastPart.isEmpty { - lastPart = "*" - } - for directory in directories { - let partiallyResolvedPattern = NSString(string: directory).appendingPathComponent(lastPart) - results.append(contentsOf: expandGlobstar(pattern: partiallyResolvedPattern)) - } - - return results - } - - private func isDirectory(path: String) -> Bool { - if let isDirectory = isDirectoryCache[path] { - return isDirectory - } - - var isDirectoryBool = ObjCBool(false) - let isDirectory = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectoryBool) && isDirectoryBool.boolValue - isDirectoryCache[path] = isDirectory - - return isDirectory - } - - private func clearCaches() { - isDirectoryCache.removeAll() - } - - private func populateFiles(gt: glob_t, includeFiles: Bool) { - let includeDirectories = behavior.includesDirectoriesInResults - - for i in 0.. Bool { - // Check for empty line - if potentialPattern.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { return false } - - // Check for commented line - if potentialPattern.trimmingCharacters(in: .whitespacesAndNewlines).first == "#" { return false } - - return true - } - - static private func isExplicitlyIncludedPattern(potentialPattern: String) -> Bool { - // Check for explicitly included line - guard potentialPattern.trimmingCharacters(in: .whitespacesAndNewlines).first == "!" else { return false } - - return true - } - - static private func expandPattern(_ pattern: String, workingDirectory: URL) -> [URL] { - let globPattern = workingDirectory.path + "/" + pattern // This is a glob pattern, so we don't use URL here - let filePaths = IgnoreFile.listFilePaths(pattern: globPattern) - let urls = filePaths.map { URL(fileURLWithPath: $0).standardizedFileURL } - - return urls - } - - static private func listFilePaths(pattern: String) -> [String] { - guard !pattern.isEmpty else { - return [] - } - - return Glob(pattern: pattern).paths - } - - func matches(url: URL) -> Bool { - return ignoredURLs.contains(url) && !explicitlyIncludedURLs.contains(url) - } -} diff --git a/Sources/RswiftCoreLegacy/Util/Struct+InternalProperties.swift b/Sources/RswiftCoreLegacy/Util/Struct+InternalProperties.swift deleted file mode 100644 index 7cc9eaae..00000000 --- a/Sources/RswiftCoreLegacy/Util/Struct+InternalProperties.swift +++ /dev/null @@ -1,149 +0,0 @@ -// -// Struct+InternalProperties.swift -// R.swift -// -// Created by Mathijs Kadijk on 06-10-16. -// Copyright © 2016 Mathijs Kadijk. All rights reserved. -// - -import Foundation - -extension Struct { - func addingInternalProperties(forBundleIdentifier bundleIdentifier: String, hostingBundle: String? = nil) -> Struct { - let hostingBundleValue: String - if let bundleName = hostingBundle, !bundleName.isEmpty { - hostingBundleValue = "Bundle(for: R.Class.self).path(forResource: \"\(bundleName)\", ofType: \"bundle\").flatMap(Bundle.init(path:)) ?? Bundle(for: R.Class.self)" - } else { - hostingBundleValue = "Bundle(for: R.Class.self)" - } - - let internalProperties = [ - Let( - comments: [], - accessModifier: .filePrivate, - isStatic: true, - name: "hostingBundle", - typeDefinition: .inferred(Type._Bundle), - value: hostingBundleValue), - Let( - comments: [], - accessModifier: .filePrivate, - isStatic: true, - name: "applicationLocale", - typeDefinition: .inferred(Type._Locale), - value: "hostingBundle.preferredLocalizations.first.flatMap { Locale(identifier: $0) } ?? Locale.current") - ] - - let internalClasses = [ - Class(accessModifier: .filePrivate, type: Type(module: .host, name: "Class")) - ] - - let internalFunctions = [ - Function( - availables: [], - comments: ["Load string from Info.plist file"], - accessModifier: .filePrivate, - isStatic: true, - name: "infoPlistString", - generics: nil, - parameters: [ - .init(name: "path", type: Type._Array.withGenericArgs([Type._String])), - .init(name: "key", type: Type._String) - ], - doesThrow: false, - returnType: Type._String.asOptional(), - body: """ - var dict = hostingBundle.infoDictionary - for step in path { - guard let obj = dict?[step] as? [String: Any] else { return nil } - dict = obj - } - return dict?[key] as? String - """, - os: [] - ), - Function( - availables: [], - comments: ["Find first language and bundle for which the table exists"], - accessModifier: .filePrivate, - isStatic: true, - name: "localeBundle", - generics: nil, - parameters: [ - .init(name: "tableName", type: Type._String), - .init(name: "preferredLanguages", type: Type._Array.withGenericArgs([Type._String])) - ], - doesThrow: false, - returnType: Type._Tuple.withGenericArgs([Type._Locale, Type._Bundle]).asOptional(), - body: """ - // Filter preferredLanguages to localizations, use first locale - var languages = preferredLanguages - .map { Locale(identifier: $0) } - .prefix(1) - .flatMap { locale -> [String] in - if hostingBundle.localizations.contains(locale.identifier) { - if let language = locale.languageCode, hostingBundle.localizations.contains(language) { - return [locale.identifier, language] - } else { - return [locale.identifier] - } - } else if let language = locale.languageCode, hostingBundle.localizations.contains(language) { - return [language] - } else { - return [] - } - } - - // If there's no languages, use development language as backstop - if languages.isEmpty { - if let developmentLocalization = hostingBundle.developmentLocalization { - languages = [developmentLocalization] - } - } else { - // Insert Base as second item (between locale identifier and languageCode) - languages.insert("Base", at: 1) - - // Add development language as backstop - if let developmentLocalization = hostingBundle.developmentLocalization { - languages.append(developmentLocalization) - } - } - - // Find first language for which table exists - // Note: key might not exist in chosen language (in that case, key will be shown) - for language in languages { - if let lproj = hostingBundle.url(forResource: language, withExtension: "lproj"), - let lbundle = Bundle(url: lproj) - { - let strings = lbundle.url(forResource: tableName, withExtension: "strings") - let stringsdict = lbundle.url(forResource: tableName, withExtension: "stringsdict") - - if strings != nil || stringsdict != nil { - return (Locale(identifier: language), lbundle) - } - } - } - - // If table is available in main bundle, don't look for localized resources - let strings = hostingBundle.url(forResource: tableName, withExtension: "strings", subdirectory: nil, localization: nil) - let stringsdict = hostingBundle.url(forResource: tableName, withExtension: "stringsdict", subdirectory: nil, localization: nil) - - if strings != nil || stringsdict != nil { - return (applicationLocale, hostingBundle) - } - - // If table is not found for requested languages, key will be shown - return nil - """, - os: [] - ) - ] - - var externalStruct = self - externalStruct.properties.append(contentsOf: internalProperties) - externalStruct.functions.append(contentsOf: internalFunctions) - externalStruct.classes.append(contentsOf: internalClasses) - - return externalStruct - } -} diff --git a/Sources/RswiftCoreLegacy/Util/SwiftIdentifier.swift b/Sources/RswiftCoreLegacy/Util/SwiftIdentifier.swift deleted file mode 100644 index eeaab328..00000000 --- a/Sources/RswiftCoreLegacy/Util/SwiftIdentifier.swift +++ /dev/null @@ -1,169 +0,0 @@ -// -// SwiftIdentifier.swift -// R.swift -// -// Created by Mathijs Kadijk on 11-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -private let numberPrefixRegex = try! NSRegularExpression(pattern: "^[0-9]+") -private let upperCasedPrefixRegex = try! NSRegularExpression(pattern: "^([A-Z]+)(?=[^a-z]{1})") - -/* - Disallowed characters: whitespace, mathematical symbols, arrows, private-use and invalid Unicode points, line- and boxdrawing characters - Special rules: Can't begin with a number - */ -struct SwiftIdentifier : CustomStringConvertible, Hashable { - let description: String - - init(name: String, lowercaseStartingCharacters: Bool = true) { - // Remove all blacklisted characters from the name and uppercase the character after a blacklisted character - var nameComponents = name.components(separatedBy: blacklistedCharacters) - let firstComponent = nameComponents.remove(at: 0) - let cleanedSwiftName = nameComponents.reduce(firstComponent) { $0 + $1.uppercaseFirstCharacter } - - // Remove numbers at the start of the name - let sanitizedSwiftName = numberPrefixRegex.stringByReplacingMatches(in: cleanedSwiftName, options: [], range: cleanedSwiftName.fullRange, withTemplate: "") - - // Lowercase the start of the name - let capitalizedSwiftName = lowercaseStartingCharacters ? SwiftIdentifier.lowercasePrefix(sanitizedSwiftName) : sanitizedSwiftName - - // Escape the name if it is a keyword - if SwiftKeywords.contains(capitalizedSwiftName) { - description = "`\(capitalizedSwiftName)`" - } else { - description = capitalizedSwiftName - } - } - - init(rawValue: String) { - description = rawValue - } - - private static func lowercasePrefix(_ name: String) -> String { - let prefixRange = upperCasedPrefixRegex.rangeOfFirstMatch(in: name, options: [], range: name.fullRange) - - if prefixRange.location == NSNotFound { - return name.lowercaseFirstCharacter - } else { - let lowercasedPrefix = (name as NSString).substring(with: prefixRange).lowercased() - return (name as NSString).replacingCharacters(in: prefixRange, with: lowercasedPrefix) - } - } - - static func +(lhs: SwiftIdentifier, rhs: SwiftIdentifier) -> SwiftIdentifier { - return SwiftIdentifier(rawValue: "\(lhs.description).\(rhs.description)") - } -} - -extension SwiftIdentifier : ExpressibleByStringLiteral { - typealias StringLiteralType = String - typealias UnicodeScalarLiteralType = String - typealias ExtendedGraphemeClusterLiteralType = String - - init(stringLiteral value: StringLiteralType) { - description = value - - if self != SwiftIdentifier(name: value, lowercaseStartingCharacters: false) { - assertionFailure("'\(value)' not a correct SwiftIdentifier") - } - } - - init(unicodeScalarLiteral value: StringLiteralType) { - description = value - } - - init(extendedGraphemeClusterLiteral value: StringLiteralType) { - description = value - } - -} - -struct SwiftNameGroups { - let uniques: [T] - let duplicates: [(SwiftIdentifier, [String])] // Identifiers that result in duplicate Swift names - let empties: [String] // Identifiers (wrapped in quotes) that result in empty swift names - - func printWarningsForDuplicatesAndEmpties(source: String, container: String? = nil, result: String) { - - let sourceSingular = [source, container].compactMap { $0 }.joined(separator: " ") - let sourcePlural = ["\(source)s", container].compactMap { $0 }.joined(separator: " ") - - let resultSingular = result - let resultPlural = "\(result)s" - - for (sanitizedName, dups) in duplicates { - warn("Skipping \(dups.count) \(sourcePlural) because symbol '\(sanitizedName)' would be generated for all of these \(resultPlural): \(dups.joined(separator: ", "))") - } - - if let empty = empties.first , empties.count == 1 { - warn("Skipping 1 \(sourceSingular) because no swift identifier can be generated for \(resultSingular): \(empty)") - } - else if empties.count > 1 { - warn("Skipping \(empties.count) \(sourcePlural) because no swift identifier can be generated for all of these \(resultPlural): \(empties.joined(separator: ", "))") - } - } -} - -extension Sequence { - func grouped(bySwiftIdentifier identifierSelector: @escaping (Iterator.Element) -> String) -> SwiftNameGroups { - var groupedBy = grouped { SwiftIdentifier(name: identifierSelector($0)) } - let empty = SwiftIdentifier(name: "") - let empties = groupedBy[empty]?.map { "'\(identifierSelector($0))'" }.sorted() - groupedBy[empty] = nil - - let uniques = Array(groupedBy.values.filter { $0.count == 1 }.joined()) - .sorted { identifierSelector($0) < identifierSelector($1) } - let duplicates = groupedBy - .filter { $0.1.count > 1 } - .map { ($0.0, $0.1.map(identifierSelector).sorted()) } - .sorted { $0.0.description < $1.0.description } - - return SwiftNameGroups(uniques: uniques, duplicates: duplicates, empties: empties ?? []) - } -} - -private let blacklistedCharacters: CharacterSet = { - let blacklist = NSMutableCharacterSet(charactersIn: "") - blacklist.formUnion(with: CharacterSet.whitespacesAndNewlines) - blacklist.formUnion(with: CharacterSet.punctuationCharacters) - blacklist.formUnion(with: CharacterSet.symbols) - blacklist.formUnion(with: CharacterSet.illegalCharacters) - blacklist.formUnion(with: CharacterSet.controlCharacters) - blacklist.removeCharacters(in: "_") - - // Emoji ranges, roughly based on http://www.unicode.org/Public/emoji/1.0//emoji-data.txt - [ - 0x2600...0x27BF, - 0x1F300...0x1F6FF, - 0x1F900...0x1F9FF, - 0x1F1E6...0x1F1FF, - ].forEach { - let range = NSRange(location: $0.lowerBound, length: $0.upperBound - $0.lowerBound) - blacklist.removeCharacters(in: range) - } - - return blacklist as CharacterSet -}() - -// Based on https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID413 -private let SwiftKeywords = [ - // Keywords used in declarations - "associatedtype", "class", "deinit", "enum", "extension", "fileprivate", "func", "import", "init", "inout", "internal", "let", "open", "operator", "private", "protocol", "public", "static", "struct", "subscript", "typealias", "var", - - // Keywords used in statements - "break", "case", "continue", "default", "defer", "do", "else", "fallthrough", "for", "guard", "if", "in", "repeat", "return", "switch", "where", "while", - - // Keywords used in expressions and types - "as", "Any", "catch", "false", "is", "nil", "rethrows", "super", "self", "Self", "throw", "throws", "true", "try", - - // Keywords that begin with a number sign (#) - "#available", "#colorLiteral", "#column", "#else", "#elseif", "#endif", "#error", "#file", "#fileLiteral", "#function", "#if", "#imageLiteral", "#line", "#selector", "#sourceLocation", "#warning", - - // Keywords from Swift 2 that are still reserved - "__COLUMN__", "__FILE__", "__FUNCTION__", "__LINE__", -] - diff --git a/Sources/RswiftCoreLegacy/Util/TypeSequenceProvider.swift b/Sources/RswiftCoreLegacy/Util/TypeSequenceProvider.swift deleted file mode 100644 index 2da022c6..00000000 --- a/Sources/RswiftCoreLegacy/Util/TypeSequenceProvider.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// UsedTypesProvider.swift -// R.swift -// -// Created by Mathijs Kadijk on 16-12-15. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -protocol UsedTypesProvider { - var usedTypes: [UsedType] { get } -} - -func getUsedTypes(from provider: UsedTypesProvider) -> [UsedType] { - return provider.usedTypes -} diff --git a/Sources/RswiftCoreLegacy/Util/UtilExtensions.swift b/Sources/RswiftCoreLegacy/Util/UtilExtensions.swift deleted file mode 100644 index af9d377a..00000000 --- a/Sources/RswiftCoreLegacy/Util/UtilExtensions.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// util.swift -// R.swift -// -// Created by Mathijs Kadijk on 12-12-14. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -// MARK: Array operations - -extension Array { - subscript (safe index: Int) -> Element? { - return indices ~= index ? self[index] : nil - } -} - -extension Array where Element: Comparable, Element: Hashable { - func uniqueAndSorted() -> [Element] { - return Set(self).array().sorted() - } -} - -// MARK: Sequence operations - -extension Sequence { - func grouped(by keyForValue: (Element) -> Key) -> [Key: [Element]] { - return Dictionary(grouping: self, by: keyForValue) - } - - func all(where predicate: (Element) throws -> Bool) rethrows -> Bool { - return !(try contains(where: { !(try predicate($0)) })) - } - - func array() -> [Element] { - return Array(self) - } -} - -// MARK: String operations - -extension String { - var lowercaseFirstCharacter: String { - if self.count <= 1 { return self.lowercased() } - let index = self.index(startIndex, offsetBy: 1) - return self[.. String { - return self - .components(separatedBy: "\n") - .map { line in line .isEmpty ? "" : "\(indentation)\(line)" } - .joined(separator: "\n") - } - - var fullRange: NSRange { - return NSRange(location: 0, length: self.count) - } - - var escapedStringLiteral: String { - return self - .replacingOccurrences(of: "\\", with: "\\\\") - .replacingOccurrences(of: "\"", with: "\\\"") - .replacingOccurrences(of: "\t", with: "\\t") - .replacingOccurrences(of: "\r", with: "\\r") - .replacingOccurrences(of: "\n", with: "\\n") - } - - var commentString: String { - return self - .replacingOccurrences(of: "\r\n", with: " ") - .replacingOccurrences(of: "\r", with: " ") - .replacingOccurrences(of: "\n", with: " ") - } -} - -// MARK: URL operations - -extension URL { - var filename: String? { - let filename = deletingPathExtension().lastPathComponent - return filename.count == 0 ? nil : filename - } -} diff --git a/Sources/rswift-legacy/Rswift.swift b/Sources/rswift-legacy/Rswift.swift deleted file mode 100644 index 91a1c055..00000000 --- a/Sources/rswift-legacy/Rswift.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Rswift.swift -// R.swift -// -// Created by Tom Lokhorst on 2017-04-22. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation - -struct Rswift { - static let version = "Unknown" -} diff --git a/Sources/rswift-legacy/main.swift b/Sources/rswift-legacy/main.swift deleted file mode 100644 index 39d40ef6..00000000 --- a/Sources/rswift-legacy/main.swift +++ /dev/null @@ -1,423 +0,0 @@ -// -// main.swift -// R.swift -// -// Created by Mathijs Kadijk on 11-12-14. -// From: https://github.com/mac-cain13/R.swift -// License: MIT License -// - -import Foundation -import Commander -import RswiftCoreLegacy -import XcodeEdit - -// Argument convertibles -extension AccessLevel: ArgumentConvertible, CustomStringConvertible { - public init(parser: ArgumentParser) throws { - guard let value = parser.shift() else { throw ArgumentError.missingValue(argument: nil) } - guard let level = AccessLevel(rawValue: value) else { throw ArgumentError.invalidType(value: value, type: "AccessLevel", argument: nil) } - - self = level - } - - public var description: String { - return rawValue - } -} - -enum PrintCommandArguments: String, ArgumentConvertible { - case portable - case complete - - public init(parser: ArgumentParser) throws { - guard let value = parser.shift() else { throw ArgumentError.missingValue(argument: nil) } - guard let level = PrintCommandArguments(rawValue: value) else { throw ArgumentError.invalidType(value: value, type: "PrintCommandArguments", argument: nil) } - - self = level - } - - var description: String { - return rawValue - } -} - -extension ProcessInfo { - func environmentVariable(name: String) throws -> String { - guard let value = self.environment[name] else { throw ArgumentError.missingValue(argument: name) } - return value - } - - func scriptInputFiles() throws -> [String] { - let scriptInputFileCountString = try environmentVariable(name: EnvironmentKeys.scriptInputFileCount) - guard let scriptInputFileCount = Int(scriptInputFileCountString) else { - throw ArgumentError.invalidType(value: scriptInputFileCountString, type: "Int", argument: EnvironmentKeys.scriptInputFileCount) - } - - return try (0.. [String] { - let scriptOutputFileCountString = try environmentVariable(name: EnvironmentKeys.scriptOutputFileCount) - guard let scriptOutputFileCount = Int(scriptOutputFileCountString) else { - throw ArgumentError.invalidType(value: scriptOutputFileCountString, type: "Int", argument: EnvironmentKeys.scriptOutputFileCount) - } - - return try (0.. String { - return "SCRIPT_INPUT_FILE_\(number)" - } - - static func scriptOutputFile(number: Int) -> String { - return "SCRIPT_OUTPUT_FILE_\(number)" - } -} - -// Options grouped in struct for readability -struct CommanderOptions { - static let generators = Option("generators", default: "", description: "Only run specified generators, comma seperated") - static let uiTest = Option("generateUITestFile", default: "", description: "Output path for an extra generated file that contains resources commonly used in UI tests such as accessibility identifiers") - static let importModules = Option("import", default: "", description: "Add extra modules as import in the generated file, comma seperated") - static let accessLevel = Option("accessLevel", default: AccessLevel.internalLevel, description: "The access level [public|internal] to use for the generated R-file") - static let rswiftIgnore = Option("rswiftignore", default: ".rswiftignore", description: "Path to pattern file that describes files that should be ignored") - static let hostingBundle: Option = Option("hostingBundle", default: nil, description: "Override bundle from which resources are loaded") - - // Project specific - Environment variable overrides - static let xcodeproj: Option = Option("xcodeproj", default: nil, description: "Defaults to environment variable \(EnvironmentKeys.xcodeproj)") - static let target: Option = Option("target", default: nil, description: "Defaults to environment variable \(EnvironmentKeys.target)") - static let bundleIdentifier: Option = Option("bundleIdentifier", default: nil, description: "Defaults to environment variable \(EnvironmentKeys.bundleIdentifier)") - static let productModuleName: Option = Option("productModuleName", default: nil, description: "Defaults to environment variable \(EnvironmentKeys.productModuleName)") - static let infoPlistFile: Option = Option("infoPlistFile", default: nil, description: "Defaults to environment variable \(EnvironmentKeys.infoPlistFile)") - static let codeSignEntitlements: Option = Option("codeSignEntitlements", default: nil, description: "Defaults to environment variable \(EnvironmentKeys.codeSignEntitlements)") - - // Xcode build - Environment variable overrides - static let builtProductsDir: Option = Option("builtProductsDir", default: nil, description: "Defaults to environment variable \(EnvironmentKeys.builtProductsDir)") - static let developerDir: Option = Option("developerDir", default: nil, description: "Defaults to environment variable \(EnvironmentKeys.developerDir)") - static let platformDir: Option = Option("platformDir", default: nil, description: "Defaults to environment variable \(EnvironmentKeys.platformDir)") - static let sdkRoot: Option = Option("sdkRoot", default: nil, description: "Defaults to environment variable \(EnvironmentKeys.sdkRoot)") - static let sourceRoot: Option = Option("sourceRoot", default: nil, description: "Defaults to environment variable \(EnvironmentKeys.sourceRoot)") - - // Print-command - static let arguments: Option = Option("arguments", default: .portable, description: "Which arguments are printed [portable|complete]") -} - -// Options grouped in struct for readability -struct CommanderArguments { - static let outputPath = Argument("outputPath", description: "Output path for the generated file") -} - -func parseGenerators(_ generatorNames: String) -> ([RswiftGenerator], [String]) { - var generators: [Generator] = [] - var unknowns: [String] = [] - - let parts = generatorNames.components(separatedBy: ",") - .map { $0.trimmingCharacters(in: CharacterSet.whitespaces) } - .filter { !$0.isEmpty } - - for part in parts { - if let generator = RswiftGenerator(rawValue: part) { - generators.append(generator) - } else { - unknowns.append(part) - } - } - - return (generators, unknowns) -} - -func parseValidateGenerators(_ generatorNames: String) -> [RswiftGenerator] { - let (knownGenerators, unknownGenerators) = parseGenerators(generatorNames) - if !unknownGenerators.isEmpty { - warn("Unknown generator options: \(unknownGenerators.joined(separator: ", "))") - if knownGenerators.isEmpty { - warn("No known generators, falling back to all generators") - } - } - - return knownGenerators.isEmpty ? RswiftGenerator.allCases : knownGenerators -} - -func parseModules(_ importModules: String) -> [Module] { - return importModules - .components(separatedBy: ",") - .map { $0.trimmingCharacters(in: CharacterSet.whitespaces) } - .filter { !$0.isEmpty } - .map { Module.custom(name: $0) } -} - -func escapePath(_ path: String) -> String { - path.replacingOccurrences(of: " ", with: "\\ ") -} - -let generate = command( - CommanderOptions.generators, - CommanderOptions.uiTest, - CommanderOptions.importModules, - CommanderOptions.accessLevel, - CommanderOptions.rswiftIgnore, - CommanderOptions.hostingBundle, - - CommanderOptions.xcodeproj, - CommanderOptions.target, - CommanderOptions.bundleIdentifier, - CommanderOptions.productModuleName, - CommanderOptions.infoPlistFile, - CommanderOptions.codeSignEntitlements, - - CommanderOptions.builtProductsDir, - CommanderOptions.developerDir, - CommanderOptions.platformDir, - CommanderOptions.sdkRoot, - CommanderOptions.sourceRoot, - - CommanderArguments.outputPath -) { - generatorNames, - uiTestOutputPath, - importModules, - accessLevel, - rswiftIgnore, - hostingBundle, - - xcodeprojOption, - targetOption, - bundleIdentifierOption, - productModuleNameOption, - infoPlistFileOption, - codeSignEntitlementsOption, - - builtProductsDirOption, - developerDirOption, - platformDirOption, - sdkRootOption, - sourceRootOption, - - outputPath in - - let processInfo = ProcessInfo() - - if let action = try? processInfo.environmentVariable(name: EnvironmentKeys.action), action == "indexbuild" { - warn("Not generating code during index build") - exit(EXIT_SUCCESS) - } - - if let scriptInputFile = try? processInfo.environmentVariable(name: EnvironmentKeys.scriptInputFile(number: 0)), - scriptInputFile.hasSuffix("/rswift-lastrun") - { - warn("For updating to R.swift 6.0, read our migration guide: https://github.com/mac-cain13/R.swift/blob/master/Documentation/Migration.md") - } - - let xcodeprojPath = try xcodeprojOption ?? processInfo.environmentVariable(name: EnvironmentKeys.xcodeproj) - let targetName = try targetOption ?? processInfo.environmentVariable(name: EnvironmentKeys.target) - let bundleIdentifier = try bundleIdentifierOption ?? processInfo.environmentVariable(name: EnvironmentKeys.bundleIdentifier) - let productModuleName = try productModuleNameOption ?? processInfo.environmentVariable(name: EnvironmentKeys.productModuleName) - let infoPlistFile = infoPlistFileOption ?? processInfo.environment[EnvironmentKeys.infoPlistFile] - let codeSignEntitlements = codeSignEntitlementsOption ?? processInfo.environment[EnvironmentKeys.codeSignEntitlements] - - let builtProductsDirPath = try builtProductsDirOption ?? processInfo.environmentVariable(name: EnvironmentKeys.builtProductsDir) - let developerDirPath = try developerDirOption ?? processInfo.environmentVariable(name: EnvironmentKeys.developerDir) - let sourceRootPath = try sourceRootOption ?? processInfo.environmentVariable(name: EnvironmentKeys.sourceRoot) - let sdkRootPath = try sdkRootOption ?? processInfo.environmentVariable(name: EnvironmentKeys.sdkRoot) - let platformPath = try platformDirOption ?? processInfo.environmentVariable(name: EnvironmentKeys.platformDir) - - let outputURL = URL(fileURLWithPath: outputPath) - let uiTestOutputURL = uiTestOutputPath.count > 0 ? URL(fileURLWithPath: uiTestOutputPath) : nil - let rswiftIgnoreURL = URL(fileURLWithPath: sourceRootPath).appendingPathComponent(rswiftIgnore, isDirectory: false) - let generators = parseValidateGenerators(generatorNames) - let modules = parseModules(importModules) - - let errors = validateRswiftEnvironment( - outputURL: outputURL, - uiTestOutputURL: uiTestOutputURL, - sourceRootPath: sourceRootPath, - podsRoot: processInfo.environment["PODS_ROOT"], - podsTargetSrcroot: processInfo.environment["PODS_TARGET_SRCROOT"], - commandLineArguments: CommandLine.arguments) - - guard errors.isEmpty else { - for error in errors { - fail(error) - } - exit(EXIT_FAILURE) - } - - let callInformation = CallInformation( - outputURL: outputURL, - uiTestOutputURL: uiTestOutputURL, - rswiftIgnoreURL: rswiftIgnoreURL, - hostingBundle: hostingBundle, - - generators: generators, - accessLevel: accessLevel, - imports: modules, - - xcodeprojURL: URL(fileURLWithPath: xcodeprojPath), - targetName: targetName, - bundleIdentifier: bundleIdentifier, - productModuleName: productModuleName, - infoPlistFile: infoPlistFile.map { URL(fileURLWithPath: $0) }, - codeSignEntitlements: codeSignEntitlements.map { URL(fileURLWithPath: $0) }, - - builtProductsDirURL: URL(fileURLWithPath: builtProductsDirPath), - developerDirURL: URL(fileURLWithPath: developerDirPath), - sourceRootURL: URL(fileURLWithPath: sourceRootPath), - sdkRootURL: URL(fileURLWithPath: sdkRootPath), - platformURL: URL(fileURLWithPath: platformPath) - ) - - try RswiftCore(callInformation).run() -} - -let printCommand = command( - CommanderOptions.arguments, - - CommanderOptions.generators, - CommanderOptions.uiTest, - CommanderOptions.importModules, - CommanderOptions.accessLevel, - CommanderOptions.rswiftIgnore, - CommanderOptions.hostingBundle, - - CommanderArguments.outputPath -) { - arguments, - - generatorNames, - uiTestOutputPath, - importModules, - accessLevel, - rswiftIgnore, - hostingBundle, - - outputPath in - - let processInfo = ProcessInfo() - - let targetName = try processInfo.environmentVariable(name: EnvironmentKeys.target) - let bundleIdentifier = try processInfo.environmentVariable(name: EnvironmentKeys.bundleIdentifier) - let productModuleName = try processInfo.environmentVariable(name: EnvironmentKeys.productModuleName) - let infoPlistFile = processInfo.environment[EnvironmentKeys.infoPlistFile] - let codeSignEntitlements = processInfo.environment[EnvironmentKeys.codeSignEntitlements] - - let xcodeprojPath = try processInfo.environmentVariable(name: EnvironmentKeys.xcodeproj) - let builtProductsDirPath = try processInfo.environmentVariable(name: EnvironmentKeys.builtProductsDir) - let developerDirPath = try processInfo.environmentVariable(name: EnvironmentKeys.developerDir) - let sourceRootPath = try processInfo.environmentVariable(name: EnvironmentKeys.sourceRoot) - let sdkRootPath = try processInfo.environmentVariable(name: EnvironmentKeys.sdkRoot) - let platformPath = try processInfo.environmentVariable(name: EnvironmentKeys.platformDir) - - var args: [String] = ["generate"] - - // Add args that differ from defaults - if generatorNames != CommanderOptions.generators.default { - args.append("--\(CommanderOptions.generators.name) \(generatorNames)") - } - if uiTestOutputPath != CommanderOptions.uiTest.default { - args.append("--\(CommanderOptions.uiTest.name) \(escapePath(uiTestOutputPath))") - } - if importModules != CommanderOptions.importModules.default { - args.append("--\(CommanderOptions.importModules.name) \(importModules)") - } - if accessLevel != CommanderOptions.accessLevel.default { - args.append("--\(CommanderOptions.accessLevel.name) \(accessLevel.rawValue)") - } - if rswiftIgnore != CommanderOptions.rswiftIgnore.default { - args.append("--\(CommanderOptions.rswiftIgnore.name) \(rswiftIgnore)") - } - if let hostingBundle = hostingBundle { - args.append("--\(CommanderOptions.hostingBundle.name) \(hostingBundle)") - } - - // Add args for environment variables - args.append("--\(CommanderOptions.target.name) \(escapePath(targetName))") - args.append("--\(CommanderOptions.bundleIdentifier.name) \(escapePath(bundleIdentifier))") - args.append("--\(CommanderOptions.productModuleName.name) \(escapePath(productModuleName))") - if let infoPlistFile = infoPlistFile { - args.append("--\(CommanderOptions.infoPlistFile.name) \(escapePath(infoPlistFile))") - } - if let codeSignEntitlements = codeSignEntitlements { - args.append("--\(CommanderOptions.codeSignEntitlements.name) \(escapePath(codeSignEntitlements))") - } - - let rswiftCommand: String - - switch arguments { - case .portable: - let libraryDirPath = try processInfo.environmentVariable(name: "USER_LIBRARY_DIR") - rswiftCommand = CommandLine.arguments.first?.replacingOccurrences(of: "\(sourceRootPath)/", with: "") ?? "rswift" - - args.append("--\(CommanderOptions.xcodeproj.name) \(escapePath(xcodeprojPath.replacingOccurrences(of: "\(sourceRootPath)/", with: "")))") - args.append("--\(CommanderOptions.developerDir.name) $(xcrun xcode-select --print-path)") - args.append("--\(CommanderOptions.platformDir.name) $(xcrun xcode-select --print-path)\(platformPath.replacingOccurrences(of: developerDirPath, with: ""))") - args.append("--\(CommanderOptions.sdkRoot.name) $(xcrun xcode-select --print-path)\(sdkRootPath.replacingOccurrences(of: developerDirPath, with: ""))") - args.append("--\(CommanderOptions.builtProductsDir.name) \(builtProductsDirPath.replacingOccurrences(of: libraryDirPath, with: "~/Library"))") - args.append("--\(CommanderOptions.sourceRoot.name) .") - - args.append(escapePath(outputPath.replacingOccurrences(of: "\(sourceRootPath)/", with: ""))) - - case .complete: - rswiftCommand = CommandLine.arguments.first ?? "rswift" - - args.append("--\(CommanderOptions.xcodeproj.name) \(escapePath(xcodeprojPath))") - args.append("--\(CommanderOptions.developerDir.name) \(escapePath(developerDirPath))") - args.append("--\(CommanderOptions.platformDir.name) \(escapePath(platformPath))") - args.append("--\(CommanderOptions.sdkRoot.name) \(escapePath(sdkRootPath))") - args.append("--\(CommanderOptions.builtProductsDir.name) \(escapePath(builtProductsDirPath))") - args.append("--\(CommanderOptions.sourceRoot.name) \(escapePath(sourceRootPath))") - - args.append(escapePath(outputPath)) - } - - print("\(rswiftCommand) \(args.joined(separator: " \\\n "))") - print("error: R.swift command logged (see build log)") -} - -struct FailErrorsCommand: CommandType { - let original: CommandType - - func run(_ parser: ArgumentParser) throws { - do { - try original.run(parser) - } catch { - fail("\(error)") - exit(EXIT_FAILURE) - } - } -} - -// Start parsing the launch arguments -let group = Group() -group.addCommand("generate", "Generates R.generated.swift file", FailErrorsCommand(original: generate)) -group.addCommand("print-command", "Prints the command rswift for use in CLI", FailErrorsCommand(original: printCommand)) -group.run(Rswift.version) From a069a63a8b3e689bcad6d86e76fa27f7a748f03f Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 28 Oct 2022 14:32:03 +0200 Subject: [PATCH 100/161] Bump year --- License | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/License b/License index a8f6f49d..f448d743 100644 --- a/License +++ b/License @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014-2020 Mathijs Kadijk +Copyright (c) 2014-2022 Mathijs Kadijk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From c52abe1db22b3f471eff17c4bcc125294c0f7c1c Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 28 Oct 2022 18:11:40 +0200 Subject: [PATCH 101/161] Update R.swift.podspec --- R.swift.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R.swift.podspec b/R.swift.podspec index d46049de..e2c3df0e 100644 --- a/R.swift.podspec +++ b/R.swift.podspec @@ -21,14 +21,14 @@ Pod::Spec.new do |spec| spec.social_media_url = "https://twitter.com/mac_cain13" spec.requires_arc = true - spec.source = { :http => "https://github.com/mac-cain13/R.swift/releases/download/v#{spec.version}/rswift-v#{spec.version}.zip" } + spec.source = { :http => "https://github.com/mac-cain13/R.swift/releases/download/#{spec.version}/rswift-#{spec.version}.zip" } spec.swift_version = "5.1" spec.ios.deployment_target = '9.0' spec.tvos.deployment_target = '9.0' spec.watchos.deployment_target = '2.2' - spec.dependency "R.swift.Library", "~> 5.3.0" + spec.dependency "R.swift.Library", "~> 5.4.0" spec.preserve_paths = "rswift" From 602c85a76501368480be7ef2f9c558f3181db1ce Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Fri, 28 Oct 2022 18:32:54 +0200 Subject: [PATCH 102/161] Update release.yml --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 881d8fc8..7c650762 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,7 +74,7 @@ jobs: - name: Publish to Cocoapods run: | - export POD_VERSION=$(echo $TAG_NAME | cut -c2-) + export POD_VERSION=$TAG_NAME pod trunk push env: TAG_NAME: ${{ github.event.release.tag_name }} From b771809f7db3887f084e5f251afde3e21a885e11 Mon Sep 17 00:00:00 2001 From: Tom Lokhorst Date: Mon, 31 Oct 2022 14:30:01 +0100 Subject: [PATCH 103/161] Update example storyboards --- .../ResourceApp/Base.lproj/Main.storyboard | 22 ++++++------ .../Base.lproj/Secondary.storyboard | 36 ++++++++++--------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/Examples/ResourceApp/ResourceApp/Base.lproj/Main.storyboard b/Examples/ResourceApp/ResourceApp/Base.lproj/Main.storyboard index 304eb384..c84176f9 100644 --- a/Examples/ResourceApp/ResourceApp/Base.lproj/Main.storyboard +++ b/Examples/ResourceApp/ResourceApp/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -38,7 +38,7 @@ - + @@ -114,12 +114,12 @@ - + -