├── Screens ├── 1.png └── 2.png ├── Examples ├── SwiftScript │ ├── Example │ │ ├── MixedUp3.png │ │ ├── NotUsing.png │ │ ├── MixedUp3@2x.png │ │ ├── MixedUp3@3x.png │ │ ├── NotUsing@2x.png │ │ ├── Assets.xcassets │ │ │ ├── A │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Section │ │ │ │ ├── Contents.json │ │ │ │ └── DuplicatedImage1.imageset │ │ │ │ │ ├── DuplicatedImage50.png │ │ │ │ │ ├── DuplicatedImage75.png │ │ │ │ │ └── Contents.json │ │ │ ├── BadRaster.imageset │ │ │ │ ├── a1.png │ │ │ │ ├── a2.png │ │ │ │ └── Contents.json │ │ │ ├── MixedUp1.imageset │ │ │ │ ├── 1_1.png │ │ │ │ ├── 1_2.png │ │ │ │ ├── 1_3.png │ │ │ │ └── Contents.json │ │ │ ├── MixedUp2.imageset │ │ │ │ ├── 2_2.png │ │ │ │ ├── 2_3.png │ │ │ │ └── Contents.json │ │ │ ├── BigImages.imageset │ │ │ │ ├── Bookmark.png │ │ │ │ ├── Bookmark(3).png │ │ │ │ └── Contents.json │ │ │ ├── BigRastor.imageset │ │ │ │ ├── BigRastor.png │ │ │ │ └── Contents.json │ │ │ ├── BigVector.imageset │ │ │ │ ├── BigVector.pdf │ │ │ │ └── Contents.json │ │ │ ├── TwoVectors.imageset │ │ │ │ ├── TwoVector1.pdf │ │ │ │ ├── TwoVector2.pdf │ │ │ │ └── Contents.json │ │ │ ├── FalsePdf.imageset │ │ │ │ ├── status_failed.pdf │ │ │ │ └── Contents.json │ │ │ ├── MixedBookmark.imageset │ │ │ │ ├── Bookmark.pdf │ │ │ │ ├── Bookmark.png │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Lite-Icon-icon.png │ │ │ │ └── Contents.json │ │ │ ├── NotFoundFile.imageset │ │ │ │ ├── NotUsingImage.png │ │ │ │ └── Contents.json │ │ │ ├── Folder │ │ │ │ ├── Contents.json │ │ │ │ └── DuplicatedImage3.imageset │ │ │ │ │ ├── DuplicateImage3.pdf │ │ │ │ │ └── Contents.json │ │ │ ├── NotUsingImage.imageset │ │ │ │ ├── NotUsingImage100.png │ │ │ │ ├── NotUsingImage50.png │ │ │ │ └── Contents.json │ │ │ ├── DuplicatedImage2.imageset │ │ │ │ ├── DuplicateImage2.pdf │ │ │ │ └── Contents.json │ │ │ ├── TruePng.imageset │ │ │ │ ├── icons8-Cute Color-Connect.png │ │ │ │ ├── icons8-Cute Color-Connect100.png │ │ │ │ └── Contents.json │ │ │ ├── Duplicate.imageset │ │ │ │ ├── icons8-Color Glass-Connect.pdf │ │ │ │ └── Contents.json │ │ │ ├── DuplicatedImage4.imageset │ │ │ │ ├── DuplicatedImage50.png │ │ │ │ ├── DuplicatedImage75.png │ │ │ │ └── Contents.json │ │ │ ├── TruePdf.imageset │ │ │ │ ├── icons8-Color Glass-Bookmark.pdf │ │ │ │ └── Contents.json │ │ │ ├── FalseSVG.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── graphic.svg │ │ │ ├── Empty.imageset │ │ │ │ └── Contents.json │ │ │ ├── checkSVG.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── CheckSVG.svg │ │ │ └── AccentColor.colorset │ │ │ │ └── Contents.json │ │ ├── NotDuplicated.png │ │ ├── NotDuplicated@2x.png │ │ ├── NotDuplicated@3x.png │ │ ├── Preview Content │ │ │ └── Preview Assets.xcassets │ │ │ │ └── Contents.json │ │ ├── ExampleApp.swift │ │ ├── ContentView.swift │ │ └── Generated │ │ │ └── Assets+Generated.swift │ ├── Example.xcodeproj │ │ └── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── Podfile │ ├── swiftgen.yml │ ├── imagelinter.yaml │ └── SwiftGen │ │ ├── fonts.stencil │ │ └── assets.stencil └── SPMPlugin │ ├── Example │ ├── Modules │ │ └── Images │ │ │ ├── Sources │ │ │ └── Images │ │ │ │ ├── Resources │ │ │ │ ├── Assets.xcassets │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── A │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Section │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ ├── DuplicatedImage1.imageset │ │ │ │ │ │ │ ├── DuplicatedImage50.png │ │ │ │ │ │ │ ├── DuplicatedImage75.png │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ │ └── snake-case_image.imageset │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Folder │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ └── DuplicatedImage3.imageset │ │ │ │ │ │ │ ├── DuplicateImage3.pdf │ │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── BadRaster.imageset │ │ │ │ │ │ ├── a1.png │ │ │ │ │ │ ├── a2.png │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── MixedUp1.imageset │ │ │ │ │ │ ├── 1_1.png │ │ │ │ │ │ ├── 1_2.png │ │ │ │ │ │ ├── 1_3.png │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── MixedUp2.imageset │ │ │ │ │ │ ├── 2_2.png │ │ │ │ │ │ ├── 2_3.png │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── BigImages.imageset │ │ │ │ │ │ ├── Bookmark.png │ │ │ │ │ │ ├── Bookmark(3).png │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── BigRastor.imageset │ │ │ │ │ │ ├── BigRastor.png │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── BigVector.imageset │ │ │ │ │ │ ├── BigVector.pdf │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── FalsePdf.imageset │ │ │ │ │ │ ├── status_failed.pdf │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── MixedBookmark.imageset │ │ │ │ │ │ ├── Bookmark.pdf │ │ │ │ │ │ ├── Bookmark.png │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── TwoVectors.imageset │ │ │ │ │ │ ├── TwoVector1.pdf │ │ │ │ │ │ ├── TwoVector2.pdf │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ │ ├── Lite-Icon-icon.png │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── NotFoundFile.imageset │ │ │ │ │ │ ├── NotUsingImage.png │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── NotUsingImage.imageset │ │ │ │ │ │ ├── NotUsingImage100.png │ │ │ │ │ │ ├── NotUsingImage50.png │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── DuplicatedImage2.imageset │ │ │ │ │ │ ├── DuplicateImage2.pdf │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── TruePng.imageset │ │ │ │ │ │ ├── icons8-Cute Color-Connect.png │ │ │ │ │ │ ├── icons8-Cute Color-Connect100.png │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── DuplicatedImage4.imageset │ │ │ │ │ │ ├── DuplicatedImage50.png │ │ │ │ │ │ ├── DuplicatedImage75.png │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── TruePdf.imageset │ │ │ │ │ │ ├── icons8-Color Glass-Bookmark.pdf │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Duplicate.imageset │ │ │ │ │ │ ├── icons8-Color Glass-Connect.pdf │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── FalseSVG.imageset │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ └── graphic.svg │ │ │ │ │ ├── Empty.imageset │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── checkSVG.imageset │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ └── CheckSVG.svg │ │ │ │ │ └── AccentColor.colorset │ │ │ │ │ │ └── Contents.json │ │ │ │ ├── MixedUp3.png │ │ │ │ ├── NotUsing.png │ │ │ │ ├── MixedUp3@2x.png │ │ │ │ ├── MixedUp3@3x.png │ │ │ │ ├── NotDuplicated.png │ │ │ │ ├── NotUsing@2x.png │ │ │ │ ├── NotDuplicated@2x.png │ │ │ │ └── NotDuplicated@3x.png │ │ │ │ ├── imagelinter.yml │ │ │ │ ├── ContentView.swift │ │ │ │ └── Generated │ │ │ │ └── Assets+Generated.swift │ │ │ ├── Package.swift │ │ │ └── imagelinter.yaml │ └── ExampleApp.swift │ ├── Example.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── project.pbxproj │ ├── Podfile │ ├── swiftgen.yml │ └── SwiftGen │ ├── fonts.stencil │ └── assets.stencil ├── Sources ├── Imagelinter │ └── EmptyFile.swift └── ImagelinterExec │ ├── SwiftGen.swift │ ├── main.swift │ └── ImageInfo.swift ├── Scripts ├── release.sh ├── MakeImageLinter.swift ├── releasenotes.sh └── build.sh ├── Package.swift ├── LICENSE ├── Plugins └── ImagelinterPlugin │ └── plugin.swift ├── .gitignore ├── CHANGELOG.md └── README.md /Screens/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Screens/1.png -------------------------------------------------------------------------------- /Screens/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Screens/2.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/MixedUp3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/MixedUp3.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/NotUsing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/NotUsing.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/MixedUp3@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/MixedUp3@2x.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/MixedUp3@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/MixedUp3@3x.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/NotUsing@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/NotUsing@2x.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/A/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/NotDuplicated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/NotDuplicated.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/Section/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/NotDuplicated@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/NotDuplicated@2x.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/NotDuplicated@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/NotDuplicated@3x.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/imagelinter.yml: -------------------------------------------------------------------------------- 1 | isCheckingDuplicatedByContent: true 2 | relativeImagesPaths: 3 | - /Resources 4 | relativeSourcePath: 5 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/A/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/Section/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/BadRaster.imageset/a1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/BadRaster.imageset/a1.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/BadRaster.imageset/a2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/BadRaster.imageset/a2.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/MixedUp1.imageset/1_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/MixedUp1.imageset/1_1.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/MixedUp1.imageset/1_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/MixedUp1.imageset/1_2.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/MixedUp1.imageset/1_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/MixedUp1.imageset/1_3.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/MixedUp2.imageset/2_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/MixedUp2.imageset/2_2.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/MixedUp2.imageset/2_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/MixedUp2.imageset/2_3.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/BigImages.imageset/Bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/BigImages.imageset/Bookmark.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/MixedUp3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/MixedUp3.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/NotUsing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/NotUsing.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/BigImages.imageset/Bookmark(3).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/BigImages.imageset/Bookmark(3).png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/BigRastor.imageset/BigRastor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/BigRastor.imageset/BigRastor.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/BigVector.imageset/BigVector.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/BigVector.imageset/BigVector.pdf -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/TwoVectors.imageset/TwoVector1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/TwoVectors.imageset/TwoVector1.pdf -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/TwoVectors.imageset/TwoVector2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/TwoVectors.imageset/TwoVector2.pdf -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/FalsePdf.imageset/status_failed.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/FalsePdf.imageset/status_failed.pdf -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/MixedBookmark.imageset/Bookmark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/MixedBookmark.imageset/Bookmark.pdf -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/MixedBookmark.imageset/Bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/MixedBookmark.imageset/Bookmark.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/MixedUp3@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/MixedUp3@2x.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/MixedUp3@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/MixedUp3@3x.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/NotDuplicated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/NotDuplicated.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/NotUsing@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/NotUsing@2x.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/AppIcon.appiconset/Lite-Icon-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/AppIcon.appiconset/Lite-Icon-icon.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/NotFoundFile.imageset/NotUsingImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/NotFoundFile.imageset/NotUsingImage.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/NotDuplicated@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/NotDuplicated@2x.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/NotDuplicated@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/NotDuplicated@3x.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/Folder/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "properties" : { 7 | "provides-namespace" : true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/NotUsingImage.imageset/NotUsingImage100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/NotUsingImage.imageset/NotUsingImage100.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/NotUsingImage.imageset/NotUsingImage50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/NotUsingImage.imageset/NotUsingImage50.png -------------------------------------------------------------------------------- /Sources/Imagelinter/EmptyFile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyFile.swift 3 | // 4 | // 5 | // Created by Sergey Balalaev on 02.04.2024. 6 | // 7 | 8 | // This empty file needs for fix the error with init library contented only Plugin 9 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/DuplicatedImage2.imageset/DuplicateImage2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/DuplicatedImage2.imageset/DuplicateImage2.pdf -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/TruePng.imageset/icons8-Cute Color-Connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/TruePng.imageset/icons8-Cute Color-Connect.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/Duplicate.imageset/icons8-Color Glass-Connect.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/Duplicate.imageset/icons8-Color Glass-Connect.pdf -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/DuplicatedImage4.imageset/DuplicatedImage50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/DuplicatedImage4.imageset/DuplicatedImage50.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/DuplicatedImage4.imageset/DuplicatedImage75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/DuplicatedImage4.imageset/DuplicatedImage75.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/TruePdf.imageset/icons8-Color Glass-Bookmark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/TruePdf.imageset/icons8-Color Glass-Bookmark.pdf -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/TruePng.imageset/icons8-Cute Color-Connect100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/TruePng.imageset/icons8-Cute Color-Connect100.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/Folder/DuplicatedImage3.imageset/DuplicateImage3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/Folder/DuplicatedImage3.imageset/DuplicateImage3.pdf -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/Section/DuplicatedImage1.imageset/DuplicatedImage50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/Section/DuplicatedImage1.imageset/DuplicatedImage50.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/Section/DuplicatedImage1.imageset/DuplicatedImage75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SwiftScript/Example/Assets.xcassets/Section/DuplicatedImage1.imageset/DuplicatedImage75.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/Folder/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "properties" : { 7 | "provides-namespace" : true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/BadRaster.imageset/a1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/BadRaster.imageset/a1.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/BadRaster.imageset/a2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/BadRaster.imageset/a2.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/MixedUp1.imageset/1_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/MixedUp1.imageset/1_1.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/MixedUp1.imageset/1_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/MixedUp1.imageset/1_2.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/MixedUp1.imageset/1_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/MixedUp1.imageset/1_3.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/MixedUp2.imageset/2_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/MixedUp2.imageset/2_2.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/MixedUp2.imageset/2_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/MixedUp2.imageset/2_3.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/BigImages.imageset/Bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/BigImages.imageset/Bookmark.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/BigRastor.imageset/BigRastor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/BigRastor.imageset/BigRastor.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/BigVector.imageset/BigVector.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/BigVector.imageset/BigVector.pdf -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/BigImages.imageset/Bookmark(3).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/BigImages.imageset/Bookmark(3).png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/FalsePdf.imageset/status_failed.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/FalsePdf.imageset/status_failed.pdf -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/MixedBookmark.imageset/Bookmark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/MixedBookmark.imageset/Bookmark.pdf -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/MixedBookmark.imageset/Bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/MixedBookmark.imageset/Bookmark.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/TwoVectors.imageset/TwoVector1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/TwoVectors.imageset/TwoVector1.pdf -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/TwoVectors.imageset/TwoVector2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/TwoVectors.imageset/TwoVector2.pdf -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/AppIcon.appiconset/Lite-Icon-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/AppIcon.appiconset/Lite-Icon-icon.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/FalseSVG.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "graphic.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/NotFoundFile.imageset/NotUsingImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/NotFoundFile.imageset/NotUsingImage.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/FalsePdf.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "status_failed.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/NotUsingImage.imageset/NotUsingImage100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/NotUsingImage.imageset/NotUsingImage100.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/NotUsingImage.imageset/NotUsingImage50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/NotUsingImage.imageset/NotUsingImage50.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/DuplicatedImage2.imageset/DuplicateImage2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/DuplicatedImage2.imageset/DuplicateImage2.pdf -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/TruePng.imageset/icons8-Cute Color-Connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/TruePng.imageset/icons8-Cute Color-Connect.png -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/DuplicatedImage2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "DuplicateImage2.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/TruePdf.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-Color Glass-Bookmark.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/DuplicatedImage4.imageset/DuplicatedImage50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/DuplicatedImage4.imageset/DuplicatedImage50.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/DuplicatedImage4.imageset/DuplicatedImage75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/DuplicatedImage4.imageset/DuplicatedImage75.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/TruePdf.imageset/icons8-Color Glass-Bookmark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/TruePdf.imageset/icons8-Color Glass-Bookmark.pdf -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/Folder/DuplicatedImage3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "DuplicateImage3.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/Duplicate.imageset/icons8-Color Glass-Connect.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/Duplicate.imageset/icons8-Color Glass-Connect.pdf -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/TruePng.imageset/icons8-Cute Color-Connect100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/TruePng.imageset/icons8-Cute Color-Connect100.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/Folder/DuplicatedImage3.imageset/DuplicateImage3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/Folder/DuplicatedImage3.imageset/DuplicateImage3.pdf -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/FalseSVG.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "graphic.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/Section/DuplicatedImage1.imageset/DuplicatedImage50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/Section/DuplicatedImage1.imageset/DuplicatedImage50.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/Section/DuplicatedImage1.imageset/DuplicatedImage75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ByteriX/ImageLinter/HEAD/Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/Section/DuplicatedImage1.imageset/DuplicatedImage75.png -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/FalsePdf.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "status_failed.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | platform :ios, '14.0' 3 | 4 | inhibit_all_warnings! 5 | 6 | target 'Example' do 7 | # Comment the next line if you don't want to use dynamic frameworks 8 | use_frameworks! 9 | 10 | pod "SwiftGen" 11 | 12 | 13 | end 14 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/DuplicatedImage2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "DuplicateImage2.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | platform :ios, '14.0' 3 | 4 | inhibit_all_warnings! 5 | 6 | target 'Example' do 7 | # Comment the next line if you don't want to use dynamic frameworks 8 | use_frameworks! 9 | 10 | pod "SwiftGen" 11 | 12 | 13 | end 14 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/Section/snake-case_image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "just_icon.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/TruePdf.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-Color Glass-Bookmark.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/Folder/DuplicatedImage3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "DuplicateImage3.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/ExampleApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleApp.swift 3 | // Example 4 | // 5 | // Created by Sergey Balalaev on 27.09.2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct ExampleApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Lite-Icon-icon.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/ExampleApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleApp.swift 3 | // Example 4 | // 5 | // Created by Sergey Balalaev on 27.09.2022. 6 | // 7 | 8 | import SwiftUI 9 | import Images 10 | 11 | @main 12 | struct ExampleApp: App { 13 | var body: some Scene { 14 | WindowGroup { 15 | ContentView() 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/swiftgen.yml: -------------------------------------------------------------------------------- 1 | 2 | 3 | input_dir: Example/ 4 | output_dir: Example/Generated/ 5 | 6 | ## see https://www.raywenderlich.com/23709326-swiftgen-tutorial-for-ios 7 | 8 | xcassets: 9 | inputs: 10 | - Assets.xcassets 11 | - *.png 12 | outputs: 13 | ##templateName: swift5 14 | templatePath: SwiftGen/assets.stencil 15 | output: Assets+Generated.swift 16 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Lite-Icon-icon.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Examples/SwiftScript/swiftgen.yml: -------------------------------------------------------------------------------- 1 | 2 | 3 | input_dir: Example/ 4 | output_dir: Example/Generated/ 5 | 6 | ## see https://www.raywenderlich.com/23709326-swiftgen-tutorial-for-ios 7 | 8 | xcassets: 9 | inputs: 10 | - Assets.xcassets 11 | - *.png 12 | outputs: 13 | ##templateName: swift5 14 | templatePath: SwiftGen/assets.stencil 15 | output: Assets+Generated.swift 16 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/Empty.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/Empty.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/checkSVG.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "CheckSVG.svg", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/BigRastor.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "BigRastor.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/BigVector.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "BigVector.pdf", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/NotFoundFile.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "NotUsingImage50.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/Duplicate.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "icons8-Color Glass-Connect.pdf", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.000", 9 | "green" : "0.319", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/BadRaster.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "a1.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "a2.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/MixedUp2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "2_3.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "2_2.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/BigRastor.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "BigRastor.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/BigVector.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "BigVector.pdf", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/checkSVG.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "CheckSVG.svg", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/BigImages.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "Bookmark(3).png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "Bookmark.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/MixedBookmark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "Bookmark.pdf", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "Bookmark.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/NotFoundFile.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "NotUsingImage50.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/TwoVectors.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "TwoVector1.pdf", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "TwoVector2.pdf", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.000", 9 | "green" : "0.319", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/Duplicate.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "icons8-Color Glass-Connect.pdf", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/NotUsingImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "NotUsingImage50.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "NotUsingImage100.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/MixedUp1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "1_2.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "1_1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "1_3.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/BadRaster.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "a1.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "a2.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/MixedUp2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "2_3.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "2_2.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/DuplicatedImage4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "DuplicatedImage50.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "DuplicatedImage75.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/Section/DuplicatedImage1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "DuplicatedImage50.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "DuplicatedImage75.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/TruePng.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-Cute Color-Connect.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "icons8-Cute Color-Connect100.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/BigImages.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "Bookmark(3).png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "Bookmark.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/MixedBookmark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "Bookmark.pdf", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "Bookmark.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/TwoVectors.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "TwoVector1.pdf", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "TwoVector2.pdf", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/NotUsingImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "NotUsingImage50.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "NotUsingImage100.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/DuplicatedImage4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "DuplicatedImage50.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "DuplicatedImage75.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/MixedUp1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "1_2.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "1_1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "1_3.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/Section/DuplicatedImage1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "DuplicatedImage50.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "DuplicatedImage75.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/TruePng.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icons8-Cute Color-Connect.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "icons8-Cute Color-Connect100.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Example 4 | // 5 | // Created by Sergey Balalaev on 27.09.2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | var body: some View { 12 | VStack { 13 | Image("TruePng") 14 | .imageScale(.large) 15 | .foregroundColor(.accentColor) 16 | Image("TruePdf") 17 | 18 | if let image = UIImage(named: "FalsePdf") { 19 | Image(uiImage: image) 20 | } 21 | 22 | Image("checkSVG") 23 | } 24 | .padding() 25 | } 26 | } 27 | 28 | struct ContentView_Previews: PreviewProvider { 29 | static var previews: some View { 30 | ContentView() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Images", 8 | platforms: [.iOS("13.0")], 9 | products: [ 10 | .library( 11 | name: "Images", 12 | targets: ["Images"] 13 | ), 14 | ], 15 | dependencies: [ 16 | .package(path: "../../../../..") 17 | ], 18 | targets: [ 19 | .target( 20 | name: "Images", 21 | dependencies: [ 22 | ], 23 | plugins: [ 24 | .plugin(name: "ImagelinterPlugin", package: "Imagelinter"), 25 | ] 26 | ) 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /Scripts/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | checkExit(){ 4 | echo "$1\n" 5 | if [ $? != 0 ]; then 6 | echo "Building failed: $1\n" 7 | exit 1 8 | fi 9 | } 10 | 11 | CHANGELOG=$(cat CHANGELOG.md) 12 | VERSION_REGEX='## *\[([.0-9]*)\]' 13 | CURRENT_VERSION="" 14 | if [[ $CHANGELOG =~ $VERSION_REGEX ]]; then 15 | CURRENT_VERSION=${BASH_REMATCH[1]} 16 | fi 17 | 18 | echo "Release version $CURRENT_VERSION detected" 19 | 20 | APP_CONFIG_PATH="./build.config" 21 | echo "CURRENT_VERSION=$CURRENT_VERSION" > "$APP_CONFIG_PATH" 22 | 23 | checkExit "Start to make ImageLinter.swift script" 24 | 25 | swift Scripts/MakeImageLinter.swift -version "$CURRENT_VERSION" 26 | 27 | checkExit "Finished ImageLinter.swift script" 28 | 29 | git add "ImageLinter.swift" 30 | git commit -m "Release $CURRENT_VERSION version with updating Swift script" 31 | 32 | checkExit "Version $CURRENT_VERSION commited" 33 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.8 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Imagelinter", 8 | platforms: [.iOS(.v12), .macOS(.v11)], 9 | products: [ 10 | .library( 11 | name: "Imagelinter", 12 | targets: ["Imagelinter"] 13 | ), 14 | .plugin( 15 | name: "ImagelinterPlugin", 16 | targets: ["ImagelinterPlugin"] 17 | ), 18 | ], 19 | dependencies: [ 20 | ], 21 | targets: [ 22 | .target( 23 | name: "Imagelinter", 24 | dependencies: [] 25 | ), 26 | .executableTarget( 27 | name: "ImagelinterExec", 28 | dependencies: [ ] 29 | ), 30 | .plugin(name: "ImagelinterPlugin", capability: .buildTool(), dependencies: ["ImagelinterExec"]) 31 | ] 32 | ) 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ByteriX 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Example 4 | // 5 | // Created by Sergey Balalaev on 27.09.2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct ContentView: View { 11 | 12 | public init() {} 13 | 14 | public var body: some View { 15 | VStack { 16 | Image("TruePng") 17 | .imageScale(.large) 18 | .foregroundColor(.accentColor) 19 | Image("TruePdf") 20 | 21 | if let image = UIImage(named: "FalsePdf") { 22 | Image(uiImage: image) 23 | } 24 | 25 | Image(uiImage: #imageLiteral(resourceName: "checkSVG")) 26 | 27 | Image("NotFoundedImage") 28 | 29 | // SwiftGen ussing: 30 | // Asset.Folder.duplicatedImage3.image 31 | // Asset.duplicatedImage1.image 32 | // Asset.snakeCaseImage.name 33 | 34 | Image("snake-case_image") 35 | } 36 | .padding() 37 | } 38 | } 39 | 40 | struct ContentView_Previews: PreviewProvider { 41 | static var previews: some View { 42 | ContentView() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Examples/SwiftScript/imagelinter.yaml: -------------------------------------------------------------------------------- 1 | 2 | isEnabled: true 3 | relativeImagesPath: / 4 | relativeSourcePath: / 5 | usingTypes: 6 | - case: uiKit 7 | - case: swiftUI 8 | - case: swiftGen 9 | enumName: Asset 10 | - case: custom 11 | pattern: "(.*)".image 12 | isSwiftGen: false 13 | - case: custom 14 | pattern: "(.*)".image 15 | isSwiftGen: true 16 | - case: custom 17 | pattern: "(.*)".name 18 | ignoredUnusedImages: 19 | - temp 20 | ignoredUndefinedImages: 21 | - temp 22 | rastorExtensions: 23 | - png 24 | - jpg 25 | - jpeg 26 | vectorExtensions: 27 | - pdf 28 | - svg 29 | sourcesExtensions: 30 | - swift 31 | - mm 32 | resourcesExtensions: 33 | - storyboard 34 | - xib 35 | isAllFilesErrorShowing: false 36 | maxVectorFileSize: 30000 37 | maxVectorImageSize: 38 | width: 100 39 | height: 100 40 | maxRastorFileSize: 300000 41 | maxRastorImageSize: 42 | width: 300 43 | height: 300 44 | isCheckingFileSize: true 45 | isCheckingImageSize: true 46 | isCheckingPdfVector: true 47 | isCheckingSvgVector: true 48 | isCheckingScaleSize: true 49 | isCheckingDuplicatedByName: true 50 | isCheckingDuplicatedByContent: true 51 | targetPlatforms: 52 | - iOS 53 | - iPadOS 54 | - macOS 55 | - tvOS 56 | - visionOS 57 | - watchOS 58 | -------------------------------------------------------------------------------- /Scripts/MakeImageLinter.swift: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env xcrun --sdk macosx swift 2 | 3 | import Foundation 4 | import AppKit 5 | 6 | let version = UserDefaults.standard.string(forKey: "version")! 7 | 8 | let path = "Sources/ImagelinterExec" 9 | let sourceFileEnumerator = FileManager.default.enumerator(atPath: path) 10 | let startStrings = """ 11 | #!/usr/bin/env xcrun --sdk macosx swift 12 | 13 | import Foundation 14 | import AppKit 15 | 16 | /** 17 | ImageLinter.swift 18 | version \(version) 19 | 20 | Created by Sergey Balalaev on 23.09.22. 21 | Copyright (c) 2022-2025 ByteriX. All rights reserved. 22 | 23 | Using from build phase: 24 | ${SRCROOT}/Scripts/ImageLinter.swift 25 | */ 26 | 27 | 28 | """ 29 | 30 | var saveString = startStrings 31 | 32 | while let sourceFileName = sourceFileEnumerator?.nextObject() as? String { 33 | let fileExtension = (sourceFileName as NSString).pathExtension.uppercased() 34 | let filePath = "\(path)/\(sourceFileName)" 35 | // checks the extension to source 36 | if fileExtension.contains("SWIFT") { 37 | if let string = try? String(contentsOfFile: filePath, encoding: .utf8) { 38 | saveString.append(string) 39 | } 40 | } 41 | } 42 | 43 | try saveString.data(using: .utf8)?.write(to: URL(fileURLWithPath: "Imagelinter.swift")) 44 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/checkSVG.imageset/CheckSVG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/checkSVG.imageset/CheckSVG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/imagelinter.yaml: -------------------------------------------------------------------------------- 1 | 2 | isEnabled: true 3 | relativeImagesPath: /Sources/Images/Resources/Error 4 | relativeSourcePath: / 5 | relativeSourcePaths: 6 | - /Sources1 7 | - /Sources2 8 | usingTypes: 9 | - case: uiKit 10 | - case: uiKitLiteral 11 | - case: swiftUI 12 | - case: swiftGen 13 | enumName: Asset 14 | - case: custom 15 | pattern: "(.*)".image 16 | isSwiftGen: false 17 | - case: custom 18 | pattern: "(.*)".image 19 | isSwiftGen: true 20 | - case: custom 21 | pattern: "(.*)".name 22 | ignoredUnusedImages: 23 | - temp 24 | ignoredUndefinedImages: 25 | - temp 26 | rastorExtensions: 27 | - png 28 | - jpg 29 | - jpeg 30 | vectorExtensions: 31 | - pdf 32 | - svg 33 | sourcesExtensions: 34 | - swift 35 | - mm 36 | resourcesExtensions: 37 | - storyboard 38 | - xib 39 | isAllFilesErrorShowing: false 40 | maxVectorFileSize: 30000 41 | maxVectorImageSize: 42 | width: 100 43 | height: 100 44 | maxRastorFileSize: 300000 45 | maxRastorImageSize: 46 | width: 300 47 | height: 300 48 | isCheckingFileSize: true 49 | isCheckingImageSize: true 50 | isCheckingPdfVector: true 51 | isCheckingSvgVector: true 52 | isCheckingScaleSize: true 53 | isCheckingDuplicatedByName: true 54 | isCheckingDuplicatedByContent: true 55 | targetPlatforms: 56 | - iOS 57 | - iPadOS 58 | - macOS 59 | - tvOS 60 | - visionOS 61 | - watchOS 62 | -------------------------------------------------------------------------------- /Plugins/ImagelinterPlugin/plugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // plugin.swift 3 | // 4 | // 5 | // Created by Sergey Balalaev on 02.04.2024. 6 | // 7 | 8 | import PackagePlugin 9 | 10 | @main 11 | struct LocalinterPlugin: BuildToolPlugin { 12 | func createBuildCommands(context: PackagePlugin.PluginContext, target: Target) throws -> [PackagePlugin.Command] { 13 | let executable = try context.tool(named: "ImagelinterExec").path 14 | 15 | return [ 16 | .buildCommand( 17 | displayName: "Running Imagelinter", 18 | executable: executable, 19 | arguments: [ 20 | "--settingsPath", target.directory.string 21 | ] 22 | ), 23 | ] 24 | } 25 | } 26 | 27 | #if canImport(XcodeProjectPlugin) 28 | import XcodeProjectPlugin 29 | 30 | extension LocalinterPlugin: XcodeBuildToolPlugin { 31 | func createBuildCommands(context: XcodeProjectPlugin.XcodePluginContext, target: XcodeProjectPlugin.XcodeTarget) throws -> [PackagePlugin.Command] { 32 | let executable = try context.tool(named: "ImagelinterExec").path 33 | 34 | return [ 35 | .buildCommand( 36 | displayName: "Running Imagelinter", 37 | executable: executable, 38 | arguments: [ 39 | "--settingsPath", context.xcodeProject.directory.string 40 | ] 41 | ), 42 | ] 43 | } 44 | } 45 | #endif 46 | -------------------------------------------------------------------------------- /Sources/ImagelinterExec/SwiftGen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftGen.swift 3 | // 4 | // 5 | // Created by Sergey Balalaev on 18.04.2024. 6 | // 7 | 8 | import Foundation 9 | 10 | extension String { 11 | 12 | static let snakeSeporators = "-_" 13 | 14 | func lowercasedFirstLetter() -> String { 15 | return prefix(1).lowercased() + dropFirst() 16 | } 17 | 18 | func uppercasedFirstLetter() -> String { 19 | return prefix(1).uppercased() + dropFirst() 20 | } 21 | } 22 | 23 | 24 | fileprivate extension Array where Self.Element == String { 25 | func swiftGenFolders() -> [Self.Element] { 26 | guard let last = last else { 27 | return self 28 | } 29 | var result: [Self.Element] = dropLast() 30 | result.append(last.lowercasedFirstLetter()) 31 | return result 32 | } 33 | 34 | func swiftGenCamel() -> [Self.Element] { 35 | guard let first = first else { 36 | return self 37 | } 38 | var result: [Self.Element] = dropFirst().map { $0.uppercasedFirstLetter() } 39 | result.insert(first, at: 0) 40 | return result 41 | } 42 | } 43 | 44 | extension String { 45 | func swiftGenCamel() -> String { 46 | return self 47 | .trimmingCharacters(in: CharacterSet(charactersIn: Self.snakeSeporators)) 48 | .split(whereSeparator: { char in 49 | Self.snakeSeporators.contains { $0 == char } 50 | }) 51 | .map { String($0) } 52 | .swiftGenCamel() 53 | .joined(separator: "") 54 | } 55 | } 56 | 57 | extension String { 58 | func swiftGenKey() -> String { 59 | return self 60 | .split(separator: "/") 61 | .map { String($0).swiftGenCamel() } 62 | .swiftGenFolders() 63 | .joined(separator: ".") 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Scripts/releasenotes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | checkExit(){ 4 | if [ $? != 0 ]; then 5 | echo "Building failed: $1\n" 6 | exit 1 7 | fi 8 | } 9 | 10 | CHANGELOG=$(cat CHANGELOG.md) 11 | VERSION_REGEX='##[[:space:]]*\[([.0-9]*)\]' 12 | CURRENT_VERSION="" 13 | if [[ $CHANGELOG =~ $VERSION_REGEX ]]; then 14 | CURRENT_VERSION=${BASH_REMATCH[1]} 15 | fi 16 | 17 | echo "Release version $CURRENT_VERSION detected" 18 | 19 | checkExit "Version detection" 20 | 21 | #perl scripts/releasenotes.pl 22 | #VERSION_MESSAGE=$(perl scripts/releasenotes.pl) 23 | 24 | VERSION_MESSAGE= 25 | 26 | VERSION_MESSAGE=$( 27 | perl -l - $cnt <<'EOF' 28 | 29 | open my $fh, '<', 'CHANGELOG.md' or die "Can't open file $!"; 30 | my $CHANGELOG = do { local $/; <$fh> }; 31 | 32 | if ($CHANGELOG =~ m/## *\[([.0-9]*)\]/) { 33 | $CURRENT_VERSION="$1" 34 | } 35 | 36 | if ($CHANGELOG =~ m/## *\[$CURRENT_VERSION\].*\s+((.|\s)*?)\s+## *\[[.0-9]*\]/) { 37 | print "$1" 38 | } 39 | 40 | EOF 41 | ) 42 | 43 | checkExit "Release notes detection" 44 | 45 | echo "Release notes:" 46 | echo "$VERSION_MESSAGE" 47 | 48 | echo "Start upload release to GitHub" 49 | 50 | # GitHub CLI api 51 | # https://cli.github.com/manual/gh_api 52 | # I change '$CURRENT_VERSION' to "$CURRENT_VERSION" 53 | # If you need auth GitHub on Runner: 54 | # 1. install gh: `brew install gh` 55 | # 2. Start interactive setup `gh auth login` 56 | # 3. More: https://cli.github.com/manual/gh 57 | # About create Release API: https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#create-a-release 58 | 59 | gh api \ 60 | --method POST \ 61 | -H "Accept: application/vnd.github+json" \ 62 | -H "X-GitHub-Api-Version: 2022-11-28" \ 63 | /repos/ByteriX/Imagelinter/releases \ 64 | -f tag_name="$CURRENT_VERSION" \ 65 | -f target_commitish='main' \ 66 | -f name="$CURRENT_VERSION" \ 67 | -f body="$VERSION_MESSAGE" \ 68 | -F draft=false \ 69 | -F prerelease=false \ 70 | -F generate_release_notes=false 71 | 72 | 73 | checkExit "GitHub release upload" 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | build.config 92 | -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Assets.xcassets/FalseSVG.imageset/graphic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Resources/Assets.xcassets/FalseSVG.imageset/graphic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # Changelog 3 | 4 | Any significant changes made to this project will be documented in this file. 5 | 6 | ## [2.2.1] - 2025-11-29 7 | 8 | #### Added 9 | 10 | - #13 new case .uiKitLiteral for support image literals. 11 | - an example to use image literal. 12 | - #14 description about --settingsPath param to Readme. 13 | 14 | ## [2.2.0] - 2025-11-27 15 | 16 | #### Added 17 | 18 | - Addition the param relativeSourcePaths as extention of the relativeSourcePath. Before relativeSourcePath worked with issue. 19 | - Addition the same param relativeImagesPaths as extention of the relativeImagesPath. 20 | 21 | ## [2.1.2] - 2025-09-25 22 | 23 | #### Fixed 24 | 25 | - Reading the binary vector files. 26 | - Improvements vector files error messages. 27 | 28 | ## [2.1.1] - 2025-05-24 29 | 30 | #### Fixed 31 | 32 | - Examples for the SPM plugin using and the Swift Script using. 33 | 34 | ## [2.1.0] - 2025-05-24 35 | 36 | #### Added 37 | 38 | - Updating script version `ImageLinter.swift` from release script. 39 | - Actualization both examples for using with a Plugin and a Script. 40 | 41 | ## [2.0.3] - 2024-04-23 42 | 43 | #### Added 44 | 45 | - Supporting Settings with more yaml/yml extension from Root Library/target with inherited. Priority: target/root, yaml/yml. 46 | - Documentation of Settings file format. 47 | 48 | ## [2.0.2] - 2024-04-21 49 | 50 | #### Fixed 51 | 52 | - SwiftGen type options with using .name property. 53 | 54 | #### Added 55 | 56 | - To Custom type `isSwiftGen` attribute. 57 | 58 | ## [2.0.1] - 2024-04-18 59 | 60 | #### Fixed 61 | 62 | - Standart both SwiftGen image using. 63 | - #10 SwiftGen variation with name contains '-' and '_' symbols. 64 | 65 | ## [2.0.0] - 2024-04-10 66 | 67 | #### Added 68 | 69 | - Supporting SPM plugin. 70 | - Settings from YAML. 71 | - Changelog. 72 | 73 | #### Fixed 74 | 75 | - Documentation with intallation and setup settings sections. 76 | 77 | ## [1.7.0] - 2023-07-14 78 | 79 | #### Added 80 | 81 | - #3 added checking for platform target 82 | 83 | ## [1.6.1] - 2023-07-13 84 | 85 | #### Fixed 86 | 87 | - Readme: ImageLinter call with image and source path from command line 88 | - Example: new case with not found image. 89 | - #8 issue: duplicated errors. 90 | 91 | ## [1.6.0] - 2023-07-10 92 | 93 | #### Added 94 | 95 | - Image and source pathes from command line getting 96 | 97 | ## [1.5.1] - 2023-07-09 98 | 99 | #### Fixed 100 | 101 | - Commented error for scaled images. 102 | 103 | ## [1.5.0] - 2023-07-09 104 | 105 | #### Added 106 | 107 | - Analyse from sources and resources. 108 | - Properties sourcesExtensions, resourcesExtensions, isCheckingImageSize. 109 | 110 | ## [1.4.0] - 2022-11-21 111 | 112 | #### Added 113 | 114 | - Supporting checking of rastor in SVG. 115 | 116 | ## [1.3.1] - 2022-11-21 117 | 118 | #### Fixed 119 | 120 | - Supporting SVG format. 121 | 122 | ## [1.3.0] - 2022-11-21 123 | 124 | #### Added 125 | 126 | - Descriptions to Readme. 127 | - Seporated maxVectorImageSize and maxRastorImageSize. 128 | - Supporting SVG format. 129 | 130 | #### Fixed 131 | 132 | - Renamed maxVectorFileSize and maxRastorFileSize from maxPdfSize and maxPngSize 133 | 134 | ## [1.2.3] - 2022-10-07 135 | 136 | #### Added 137 | 138 | - Search empty and broken asset images. 139 | 140 | ## [1.2.2] - 2022-10-06 141 | 142 | #### Added 143 | 144 | - Image name to error message. 145 | 146 | #### Fixed 147 | 148 | - Bug with regular expressions. 149 | - Full support SwiftGen. 150 | 151 | ## [1.2.1] - 2022-10-05 152 | 153 | #### Added 154 | 155 | - Image file filter by extensions. 156 | 157 | #### Fixed 158 | 159 | - Supporting folder from Asset. 160 | 161 | ## [1.2.0] - 2022-10-04 162 | 163 | #### Added 164 | 165 | - Checking duplication images by content 166 | - More Examples error images. 167 | - UsingType settings with uiKit value. 168 | - Search undefined images. 169 | - Checking image size. 170 | - ignoredUnusedImages for excude errors. 171 | 172 | ## [1.1.0] - 2022-09-28 173 | 174 | #### Added 175 | 176 | - Searching unused images. 177 | - Checking duplication images by name 178 | - UsingType settings with swiftUI, swiftGen and custom pattern of search. 179 | 180 | ## [1.0.0] - 2022-09-24 181 | 182 | #### Added 183 | 184 | - Checking file size. 185 | - PDF vector validation. 186 | - Showing error in XCode. 187 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImageLinter 2 | 3 | Check image files and resources for Swift 4 | 5 | ## Script allows 6 | 7 | 1. Checking size of vector(PDF) and rastor(PNG/JPEG) files 8 | 2. Catch raster from PDF 9 | 3. Checking unused image files 10 | 4. Search undefined images 11 | 5. Comparing scaled images size 12 | 6. Checking duplicate images by name 13 | 7. Checking duplicate images by content (but identical) 14 | 8. Search empty and broken asset images 15 | 9. Analysis scales of images with dependency on platforms target 16 | 17 | ![](Screens/1.png) 18 | 19 | ## Accessibility 20 | 21 | 1. Possible analyse the sources (swift, Objective-C files) and resources (Storyboard, xib files) 22 | 2. Support Assets and files with @Xx notation 23 | 3. vector/rastor diffenition and you can limit use formats by PNG, JPG, PDF, SVG, etc formats 24 | 4. Support any use notation: SwiftUI, UIKit, SwiftGen, and custom Regex 25 | 5. You can ignore any images 26 | 6. Any settings for generation errors or warnings 27 | 28 | ## Install 29 | 30 | ### Swift Package Manager (SPM) 31 | 32 | The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. `Imagelinter` supports its use on supported platforms as plugin tool. 33 | 34 | Once you have your Swift package set up, adding `Imagelinter` as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`. Then you need call from your target plugin like this: 35 | 36 | ```swift 37 | 38 | dependencies: [ 39 | .Package(url: "https://github.com/ByteriX/Imagelinter.git", majorVersion: 2) 40 | ], 41 | targets: [ 42 | .target( 43 | name: "YourTarget", 44 | plugins: [ 45 | .plugin(name: "ImagelinterPlugin", package: "Imagelinter"), 46 | ] 47 | ) 48 | ] 49 | 50 | ``` 51 | 52 | ### Swift script from Build Phases 53 | 54 | 1. Just copy ImageLinter.swift to project. 55 | 2. Exclude from "Build Phases" -> "Compile Sources" 56 | 3. Add to "Build Phases" run script: 57 | ```bash 58 | ${SRCROOT}/ImageLinter.swift 59 | ``` 60 | ![](Screens/2.png) 61 | 62 | ## Settings: 63 | 64 | You need to add a settings file named `imagelinter.yaml` or/and `imagelinter.yml` to a `target` or/and `the root of the library` `dir` of the package. 65 | imagesPath and sourcePath are calculated from dir of this package. 66 | 67 | Supports more settings files with rewrite properties with priority: first a `target` then `the root of library `dir`, first `imagelinter.yaml` then `imagelinter.yml`. 68 | If you want to have a custom path to settings file, you can use `--settingsPath [path to your imagelinter.yaml or imagelinter.yml file]` command line param from script call only on build phase. In the plugin this would be set to target dirrectory all time. 69 | 70 | ### Example of Settings file format 71 | 72 | ```yaml 73 | isEnabled: true 74 | relativeImagesPath: /Sources/Images/Resources 75 | relativeImagesPaths: 76 | - /Module1/res 77 | - /Module2/res 78 | relativeSourcePath: /Sources/Code 79 | relativeSourcePaths: 80 | - /Module1/src 81 | - /Module2/src 82 | usingTypes: 83 | - case: uiKit 84 | - case: uiKitLiteral 85 | - case: swiftUI 86 | - case: swiftGen 87 | enumName: Asset 88 | - case: custom 89 | pattern: "(.*)".name 90 | isSwiftGen: true 91 | - case: custom 92 | pattern: "(.*)".image 93 | ignoredUnusedImages: 94 | - temp 95 | ignoredUndefinedImages: 96 | - temp 97 | rastorExtensions: 98 | - png 99 | - jpg 100 | - jpeg 101 | vectorExtensions: 102 | - pdf 103 | - svg 104 | sourcesExtensions: 105 | - swift 106 | - mm 107 | resourcesExtensions: 108 | - storyboard 109 | - xib 110 | isAllFilesErrorShowing: false 111 | maxVectorFileSize: 10000 112 | maxVectorImageSize: 113 | width: 100 114 | height: 100 115 | maxRastorFileSize: 300000 116 | maxRastorImageSize: 117 | width: 300 118 | height: 300 119 | isCheckingFileSize: true 120 | isCheckingImageSize: true 121 | isCheckingPdfVector: true 122 | isCheckingSvgVector: true 123 | isCheckingScaleSize: true 124 | isCheckingDuplicatedByName: true 125 | isCheckingDuplicatedByContent: true 126 | targetPlatforms: 127 | - iOS 128 | - iPadOS 129 | - macOS 130 | - tvOS 131 | - visionOS 132 | - watchOS 133 | ``` 134 | 135 | ## Example 136 | 137 | You can review ![Examples project](Examples) 138 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/SwiftGen/fonts.stencil: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen 3 | 4 | {% if families %} 5 | {% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} 6 | {% set fontType %}{{param.fontTypeName|default:"FontConvertible"}}{% endset %} 7 | #if os(macOS) 8 | import AppKit.NSFont 9 | #elseif os(iOS) || os(tvOS) || os(watchOS) 10 | import UIKit 11 | import SwiftUI 12 | #endif 13 | 14 | // Deprecated typealiases 15 | //@available(*, deprecated, renamed: "{{fontType}}.Font", message: "This typealias will be removed in SwiftGen 7.0") 16 | //{{accessModifier}} typealias {{param.fontAliasName|default:"Font"}} = {{fontType}}.Font 17 | 18 | // swiftlint:disable superfluous_disable_command 19 | // swiftlint:disable file_length 20 | 21 | // MARK: - Fonts 22 | 23 | // swiftlint:disable identifier_name line_length type_body_length 24 | {% macro transformPath path %}{% filter removeNewlines %} 25 | {% if param.preservePath %} 26 | {{path}} 27 | {% else %} 28 | {{path|basename}} 29 | {% endif %} 30 | {% endfilter %}{% endmacro %} 31 | {{accessModifier}} enum {{param.enumName|default:"FontFamily"}} { 32 | {% for family in families %} 33 | {{accessModifier}} enum {{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { 34 | {% for font in family.fonts %} 35 | {{accessModifier}} static let {{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{fontType}}(name: "{{font.name}}", family: "{{family.name}}", path: "{% call transformPath font.path %}") 36 | {% endfor %} 37 | {{accessModifier}} static let all: [{{fontType}}] = [{% for font in family.fonts %}{{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{{ ", " if not forloop.last }}{% endfor %}] 38 | } 39 | {% endfor %} 40 | {{accessModifier}} static let allCustomFonts: [{{fontType}}] = [{% for family in families %}{{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.all{{ ", " if not forloop.last }}{% endfor %}].flatMap { $0 } 41 | {{accessModifier}} static func registerAllCustomFonts() { 42 | allCustomFonts.forEach { $0.register() } 43 | } 44 | } 45 | // swiftlint:enable identifier_name line_length type_body_length 46 | 47 | // MARK: - Implementation Details 48 | 49 | public extension Font { 50 | init(uiFont: UIFont) { 51 | self = Font(uiFont as CTFont) 52 | } 53 | } 54 | 55 | {{accessModifier}} struct {{fontType}} { 56 | {{accessModifier}} let name: String 57 | {{accessModifier}} let family: String 58 | {{accessModifier}} let path: String 59 | 60 | #if os(macOS) 61 | {{accessModifier}} typealias Font = NSFont 62 | #elseif os(iOS) || os(tvOS) || os(watchOS) 63 | //{{accessModifier}} typealias Font = UIFont 64 | #endif 65 | 66 | {{accessModifier}} func font(size: CGFloat) -> Font { 67 | guard let font = UIFont(font: self, size: size) else { 68 | fatalError("Unable to initialize font '\(name)' (\(family))") 69 | } 70 | return Font(uiFont: font) 71 | } 72 | 73 | {{accessModifier}} func register() { 74 | // swiftlint:disable:next conditional_returns_on_newline 75 | guard let url = url else { return } 76 | CTFontManagerRegisterFontsForURL(url as CFURL, .process, nil) 77 | } 78 | 79 | fileprivate var url: URL? { 80 | // swiftlint:disable:next implicit_return 81 | {% if param.lookupFunction %} 82 | return {{param.lookupFunction}}(name, family, path) 83 | {% else %} 84 | return {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil) 85 | {% endif %} 86 | } 87 | } 88 | 89 | {{accessModifier}} extension UIFont { 90 | convenience init?(font: {{fontType}}, size: CGFloat) { 91 | #if os(iOS) || os(tvOS) || os(watchOS) 92 | if !UIFont.fontNames(forFamilyName: font.family).contains(font.name) { 93 | font.register() 94 | } 95 | #elseif os(macOS) 96 | if let url = font.url, CTFontManagerGetScopeForURL(url as CFURL) == .none { 97 | font.register() 98 | } 99 | #endif 100 | 101 | self.init(name: font.name, size: size) 102 | } 103 | } 104 | {% if not param.bundle and not param.lookupFunction %} 105 | 106 | // swiftlint:disable convenience_type 107 | private final class BundleToken { 108 | static let bundle: Bundle = { 109 | #if SWIFT_PACKAGE 110 | return Bundle.module 111 | #else 112 | return Bundle(for: BundleToken.self) 113 | #endif 114 | }() 115 | } 116 | // swiftlint:enable convenience_type 117 | {% endif %} 118 | {% else %} 119 | // No fonts found 120 | {% endif %} -------------------------------------------------------------------------------- /Examples/SwiftScript/SwiftGen/fonts.stencil: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen 3 | 4 | {% if families %} 5 | {% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} 6 | {% set fontType %}{{param.fontTypeName|default:"FontConvertible"}}{% endset %} 7 | #if os(macOS) 8 | import AppKit.NSFont 9 | #elseif os(iOS) || os(tvOS) || os(watchOS) 10 | import UIKit 11 | import SwiftUI 12 | #endif 13 | 14 | // Deprecated typealiases 15 | //@available(*, deprecated, renamed: "{{fontType}}.Font", message: "This typealias will be removed in SwiftGen 7.0") 16 | //{{accessModifier}} typealias {{param.fontAliasName|default:"Font"}} = {{fontType}}.Font 17 | 18 | // swiftlint:disable superfluous_disable_command 19 | // swiftlint:disable file_length 20 | 21 | // MARK: - Fonts 22 | 23 | // swiftlint:disable identifier_name line_length type_body_length 24 | {% macro transformPath path %}{% filter removeNewlines %} 25 | {% if param.preservePath %} 26 | {{path}} 27 | {% else %} 28 | {{path|basename}} 29 | {% endif %} 30 | {% endfilter %}{% endmacro %} 31 | {{accessModifier}} enum {{param.enumName|default:"FontFamily"}} { 32 | {% for family in families %} 33 | {{accessModifier}} enum {{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { 34 | {% for font in family.fonts %} 35 | {{accessModifier}} static let {{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{fontType}}(name: "{{font.name}}", family: "{{family.name}}", path: "{% call transformPath font.path %}") 36 | {% endfor %} 37 | {{accessModifier}} static let all: [{{fontType}}] = [{% for font in family.fonts %}{{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{{ ", " if not forloop.last }}{% endfor %}] 38 | } 39 | {% endfor %} 40 | {{accessModifier}} static let allCustomFonts: [{{fontType}}] = [{% for family in families %}{{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.all{{ ", " if not forloop.last }}{% endfor %}].flatMap { $0 } 41 | {{accessModifier}} static func registerAllCustomFonts() { 42 | allCustomFonts.forEach { $0.register() } 43 | } 44 | } 45 | // swiftlint:enable identifier_name line_length type_body_length 46 | 47 | // MARK: - Implementation Details 48 | 49 | public extension Font { 50 | init(uiFont: UIFont) { 51 | self = Font(uiFont as CTFont) 52 | } 53 | } 54 | 55 | {{accessModifier}} struct {{fontType}} { 56 | {{accessModifier}} let name: String 57 | {{accessModifier}} let family: String 58 | {{accessModifier}} let path: String 59 | 60 | #if os(macOS) 61 | {{accessModifier}} typealias Font = NSFont 62 | #elseif os(iOS) || os(tvOS) || os(watchOS) 63 | //{{accessModifier}} typealias Font = UIFont 64 | #endif 65 | 66 | {{accessModifier}} func font(size: CGFloat) -> Font { 67 | guard let font = UIFont(font: self, size: size) else { 68 | fatalError("Unable to initialize font '\(name)' (\(family))") 69 | } 70 | return Font(uiFont: font) 71 | } 72 | 73 | {{accessModifier}} func register() { 74 | // swiftlint:disable:next conditional_returns_on_newline 75 | guard let url = url else { return } 76 | CTFontManagerRegisterFontsForURL(url as CFURL, .process, nil) 77 | } 78 | 79 | fileprivate var url: URL? { 80 | // swiftlint:disable:next implicit_return 81 | {% if param.lookupFunction %} 82 | return {{param.lookupFunction}}(name, family, path) 83 | {% else %} 84 | return {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil) 85 | {% endif %} 86 | } 87 | } 88 | 89 | {{accessModifier}} extension UIFont { 90 | convenience init?(font: {{fontType}}, size: CGFloat) { 91 | #if os(iOS) || os(tvOS) || os(watchOS) 92 | if !UIFont.fontNames(forFamilyName: font.family).contains(font.name) { 93 | font.register() 94 | } 95 | #elseif os(macOS) 96 | if let url = font.url, CTFontManagerGetScopeForURL(url as CFURL) == .none { 97 | font.register() 98 | } 99 | #endif 100 | 101 | self.init(name: font.name, size: size) 102 | } 103 | } 104 | {% if not param.bundle and not param.lookupFunction %} 105 | 106 | // swiftlint:disable convenience_type 107 | private final class BundleToken { 108 | static let bundle: Bundle = { 109 | #if SWIFT_PACKAGE 110 | return Bundle.module 111 | #else 112 | return Bundle(for: BundleToken.self) 113 | #endif 114 | }() 115 | } 116 | // swiftlint:enable convenience_type 117 | {% endif %} 118 | {% else %} 119 | // No fonts found 120 | {% endif %} -------------------------------------------------------------------------------- /Examples/SwiftScript/Example/Generated/Assets+Generated.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen 3 | 4 | #if os(macOS) 5 | import AppKit 6 | #elseif os(iOS) 7 | import SwiftUI 8 | #elseif os(tvOS) || os(watchOS) 9 | import UIKit 10 | #endif 11 | 12 | // Deprecated typealiases 13 | @available(*, deprecated, renamed: "ColorAsset.Color", message: "This typealias will be removed in SwiftGen 7.0") 14 | internal typealias AssetColorTypeAlias = ColorAsset.Color 15 | @available(*, deprecated, renamed: "ImageAsset.Image", message: "This typealias will be removed in SwiftGen 7.0") 16 | internal typealias AssetImageTypeAlias = ImageAsset.Image 17 | 18 | // swiftlint:disable superfluous_disable_command file_length implicit_return 19 | 20 | // MARK: - Asset Catalogs 21 | 22 | // swiftlint:disable identifier_name line_length nesting type_body_length type_name 23 | internal enum Asset { 24 | internal static let accentColor = ColorAsset(name: "AccentColor") 25 | internal static let badRaster = ImageAsset(name: "BadRaster") 26 | internal static let bigRastor = ImageAsset(name: "BigRastor") 27 | internal static let bigVector = ImageAsset(name: "BigVector") 28 | internal static let duplicate = ImageAsset(name: "Duplicate") 29 | internal static let duplicatedImage2 = ImageAsset(name: "DuplicatedImage2") 30 | internal static let duplicatedImage4 = ImageAsset(name: "DuplicatedImage4") 31 | internal static let empty = ImageAsset(name: "Empty") 32 | internal static let falsePdf = ImageAsset(name: "FalsePdf") 33 | internal static let falseSVG = ImageAsset(name: "FalseSVG") 34 | internal enum Folder { 35 | internal static let duplicatedImage3 = ImageAsset(name: "Folder/DuplicatedImage3") 36 | } 37 | internal static let mixedBookmark = ImageAsset(name: "MixedBookmark") 38 | internal static let mixedUp1 = ImageAsset(name: "MixedUp1") 39 | internal static let mixedUp2 = ImageAsset(name: "MixedUp2") 40 | internal static let notFoundFile = ImageAsset(name: "NotFoundFile") 41 | internal static let notUsingImage = ImageAsset(name: "NotUsingImage") 42 | internal static let duplicatedImage1 = ImageAsset(name: "DuplicatedImage1") 43 | internal static let truePdf = ImageAsset(name: "TruePdf") 44 | internal static let truePng = ImageAsset(name: "TruePng") 45 | internal static let twoVectors = ImageAsset(name: "TwoVectors") 46 | internal static let checkSVG = ImageAsset(name: "checkSVG") 47 | } 48 | // swiftlint:enable identifier_name line_length nesting type_body_length type_name 49 | 50 | // MARK: - Implementation Details 51 | 52 | internal final class ColorAsset { 53 | internal fileprivate(set) var name: String 54 | 55 | #if os(macOS) 56 | internal typealias Color = NSColor 57 | #elseif os(iOS) || os(tvOS) || os(watchOS) 58 | internal typealias Color = SwiftUI.Color 59 | internal typealias UIColor = UIKit.UIColor 60 | #endif 61 | 62 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) 63 | internal private(set) lazy var color: Color = { 64 | guard let color = Color(asset: self) else { 65 | fatalError("Unable to load color asset named \(name).") 66 | } 67 | return color 68 | }() 69 | 70 | internal private(set) lazy var uiColor: UIColor = { 71 | guard let color = UIColor(asset: self) else { 72 | fatalError("Unable to load color asset named \(name).") 73 | } 74 | return color 75 | }() 76 | /* 77 | #if os(iOS) || os(tvOS) 78 | @available(iOS 11.0, tvOS 11.0, *) 79 | internal func color(compatibleWith traitCollection: UITraitCollection) -> Color { 80 | let bundle = BundleToken.bundle 81 | guard let color = Color(named: name, bundle: bundle, compatibleWith: traitCollection) else { 82 | fatalError("Unable to load color asset named \(name).") 83 | } 84 | return color 85 | } 86 | #endif 87 | */ 88 | 89 | fileprivate init(name: String) { 90 | self.name = name 91 | } 92 | } 93 | 94 | internal extension ColorAsset.Color { 95 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) 96 | init?(asset: ColorAsset) { 97 | let bundle = BundleToken.bundle 98 | #if os(iOS) || os(tvOS) 99 | self.init(asset.name, bundle: bundle) 100 | #elseif os(macOS) 101 | self.init(named: NSColor.Name(asset.name), bundle: bundle) 102 | #elseif os(watchOS) 103 | self.init(named: asset.name) 104 | #endif 105 | } 106 | } 107 | internal extension ColorAsset.UIColor { 108 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) 109 | convenience init?(asset: ColorAsset) { 110 | let bundle = BundleToken.bundle 111 | #if os(iOS) || os(tvOS) 112 | self.init(named: asset.name, in: bundle, compatibleWith: nil) 113 | #elseif os(macOS) 114 | self.init(named: NSColor.Name(asset.name), bundle: bundle) 115 | #elseif os(watchOS) 116 | self.init(named: asset.name) 117 | #endif 118 | } 119 | } 120 | 121 | internal struct ImageAsset { 122 | internal fileprivate(set) var name: String 123 | 124 | #if os(macOS) 125 | internal typealias Image = NSImage 126 | #elseif os(iOS) || os(tvOS) || os(watchOS) 127 | internal typealias Image = SwiftUI.Image 128 | #endif 129 | 130 | @available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *) 131 | internal var image: Image { 132 | let bundle = BundleToken.bundle 133 | #if os(iOS) || os(tvOS) 134 | let image = Image(name, bundle: bundle) 135 | #elseif os(macOS) 136 | let name = NSImage.Name(self.name) 137 | let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name) 138 | #elseif os(watchOS) 139 | let image = Image(named: name) 140 | #endif 141 | //guard let result = image else { 142 | // fatalError("Unable to load image asset named \(name).") 143 | //} 144 | return image 145 | } 146 | 147 | @available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *) 148 | internal var uiImage: UIImage { 149 | let bundle = BundleToken.bundle 150 | #if os(iOS) || os(tvOS) 151 | let image = UIImage(named: name, in: bundle, compatibleWith: nil) 152 | #elseif os(macOS) 153 | let name = NSImage.Name(self.name) 154 | let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name) 155 | #elseif os(watchOS) 156 | let image = Image(named: name) 157 | #endif 158 | guard let image = image else { 159 | fatalError("Unable to load image asset named \(name).") 160 | } 161 | return image 162 | } 163 | /* 164 | #if os(iOS) || os(tvOS) 165 | @available(iOS 8.0, tvOS 9.0, *) 166 | internal func image(compatibleWith traitCollection: UITraitCollection) -> Image { 167 | let bundle = BundleToken.bundle 168 | guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else { 169 | fatalError("Unable to load image asset named \(name).") 170 | } 171 | return result 172 | } 173 | #endif 174 | */ 175 | } 176 | 177 | internal extension ImageAsset.Image { 178 | @available(iOS 8.0, tvOS 9.0, watchOS 2.0, *) 179 | @available(macOS, deprecated, 180 | message: "This initializer is unsafe on macOS, please use the ImageAsset.image property") 181 | init?(asset: ImageAsset) { 182 | #if os(iOS) || os(tvOS) 183 | let bundle = BundleToken.bundle 184 | self.init(asset.name, bundle: bundle) 185 | #elseif os(macOS) 186 | self.init(named: NSImage.Name(asset.name)) 187 | #elseif os(watchOS) 188 | self.init(named: asset.name) 189 | #endif 190 | } 191 | } 192 | 193 | // swiftlint:disable convenience_type 194 | private final class BundleToken { 195 | static let bundle: Bundle = { 196 | #if SWIFT_PACKAGE 197 | return Bundle.module 198 | #else 199 | return Bundle(for: BundleToken.self) 200 | #endif 201 | }() 202 | } 203 | // swiftlint:enable convenience_type 204 | 205 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example/Modules/Images/Sources/Images/Generated/Assets+Generated.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen 3 | 4 | #if os(macOS) 5 | import AppKit 6 | #elseif os(iOS) 7 | import SwiftUI 8 | #elseif os(tvOS) || os(watchOS) 9 | import UIKit 10 | #endif 11 | 12 | // Deprecated typealiases 13 | @available(*, deprecated, renamed: "ColorAsset.Color", message: "This typealias will be removed in SwiftGen 7.0") 14 | internal typealias AssetColorTypeAlias = ColorAsset.Color 15 | @available(*, deprecated, renamed: "ImageAsset.Image", message: "This typealias will be removed in SwiftGen 7.0") 16 | internal typealias AssetImageTypeAlias = ImageAsset.Image 17 | 18 | // swiftlint:disable superfluous_disable_command file_length implicit_return 19 | 20 | // MARK: - Asset Catalogs 21 | 22 | // swiftlint:disable identifier_name line_length nesting type_body_length type_name 23 | internal enum Asset { 24 | internal static let accentColor = ColorAsset(name: "AccentColor") 25 | internal static let badRaster = ImageAsset(name: "BadRaster") 26 | internal static let bigRastor = ImageAsset(name: "BigRastor") 27 | internal static let bigVector = ImageAsset(name: "BigVector") 28 | internal static let duplicate = ImageAsset(name: "Duplicate") 29 | internal static let duplicatedImage2 = ImageAsset(name: "DuplicatedImage2") 30 | internal static let duplicatedImage4 = ImageAsset(name: "DuplicatedImage4") 31 | internal static let empty = ImageAsset(name: "Empty") 32 | internal static let falsePdf = ImageAsset(name: "FalsePdf") 33 | internal static let falseSVG = ImageAsset(name: "FalseSVG") 34 | internal enum Folder { 35 | internal static let duplicatedImage3 = ImageAsset(name: "Folder/DuplicatedImage3") 36 | } 37 | internal static let mixedBookmark = ImageAsset(name: "MixedBookmark") 38 | internal static let mixedUp1 = ImageAsset(name: "MixedUp1") 39 | internal static let mixedUp2 = ImageAsset(name: "MixedUp2") 40 | internal static let notFoundFile = ImageAsset(name: "NotFoundFile") 41 | internal static let notUsingImage = ImageAsset(name: "NotUsingImage") 42 | internal static let duplicatedImage1 = ImageAsset(name: "DuplicatedImage1") 43 | internal static let truePdf = ImageAsset(name: "TruePdf") 44 | internal static let truePng = ImageAsset(name: "TruePng") 45 | internal static let twoVectors = ImageAsset(name: "TwoVectors") 46 | internal static let checkSVG = ImageAsset(name: "checkSVG") 47 | } 48 | // swiftlint:enable identifier_name line_length nesting type_body_length type_name 49 | 50 | // MARK: - Implementation Details 51 | 52 | internal final class ColorAsset { 53 | internal fileprivate(set) var name: String 54 | 55 | #if os(macOS) 56 | internal typealias Color = NSColor 57 | #elseif os(iOS) || os(tvOS) || os(watchOS) 58 | internal typealias Color = SwiftUI.Color 59 | internal typealias UIColor = UIKit.UIColor 60 | #endif 61 | 62 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) 63 | internal private(set) lazy var color: Color = { 64 | guard let color = Color(asset: self) else { 65 | fatalError("Unable to load color asset named \(name).") 66 | } 67 | return color 68 | }() 69 | 70 | internal private(set) lazy var uiColor: UIColor = { 71 | guard let color = UIColor(asset: self) else { 72 | fatalError("Unable to load color asset named \(name).") 73 | } 74 | return color 75 | }() 76 | /* 77 | #if os(iOS) || os(tvOS) 78 | @available(iOS 11.0, tvOS 11.0, *) 79 | internal func color(compatibleWith traitCollection: UITraitCollection) -> Color { 80 | let bundle = BundleToken.bundle 81 | guard let color = Color(named: name, bundle: bundle, compatibleWith: traitCollection) else { 82 | fatalError("Unable to load color asset named \(name).") 83 | } 84 | return color 85 | } 86 | #endif 87 | */ 88 | 89 | fileprivate init(name: String) { 90 | self.name = name 91 | } 92 | } 93 | 94 | internal extension ColorAsset.Color { 95 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) 96 | init?(asset: ColorAsset) { 97 | let bundle = BundleToken.bundle 98 | #if os(iOS) || os(tvOS) 99 | self.init(asset.name, bundle: bundle) 100 | #elseif os(macOS) 101 | self.init(named: NSColor.Name(asset.name), bundle: bundle) 102 | #elseif os(watchOS) 103 | self.init(named: asset.name) 104 | #endif 105 | } 106 | } 107 | internal extension ColorAsset.UIColor { 108 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) 109 | convenience init?(asset: ColorAsset) { 110 | let bundle = BundleToken.bundle 111 | #if os(iOS) || os(tvOS) 112 | self.init(named: asset.name, in: bundle, compatibleWith: nil) 113 | #elseif os(macOS) 114 | self.init(named: NSColor.Name(asset.name), bundle: bundle) 115 | #elseif os(watchOS) 116 | self.init(named: asset.name) 117 | #endif 118 | } 119 | } 120 | 121 | internal struct ImageAsset { 122 | internal fileprivate(set) var name: String 123 | 124 | #if os(macOS) 125 | internal typealias Image = NSImage 126 | #elseif os(iOS) || os(tvOS) || os(watchOS) 127 | internal typealias Image = SwiftUI.Image 128 | #endif 129 | 130 | @available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *) 131 | internal var image: Image { 132 | let bundle = BundleToken.bundle 133 | #if os(iOS) || os(tvOS) 134 | let image = Image(name, bundle: bundle) 135 | #elseif os(macOS) 136 | let name = NSImage.Name(self.name) 137 | let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name) 138 | #elseif os(watchOS) 139 | let image = Image(named: name) 140 | #endif 141 | //guard let result = image else { 142 | // fatalError("Unable to load image asset named \(name).") 143 | //} 144 | return image 145 | } 146 | 147 | @available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *) 148 | internal var uiImage: UIImage { 149 | let bundle = BundleToken.bundle 150 | #if os(iOS) || os(tvOS) 151 | let image = UIImage(named: name, in: bundle, compatibleWith: nil) 152 | #elseif os(macOS) 153 | let name = NSImage.Name(self.name) 154 | let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name) 155 | #elseif os(watchOS) 156 | let image = Image(named: name) 157 | #endif 158 | guard let image = image else { 159 | fatalError("Unable to load image asset named \(name).") 160 | } 161 | return image 162 | } 163 | /* 164 | #if os(iOS) || os(tvOS) 165 | @available(iOS 8.0, tvOS 9.0, *) 166 | internal func image(compatibleWith traitCollection: UITraitCollection) -> Image { 167 | let bundle = BundleToken.bundle 168 | guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else { 169 | fatalError("Unable to load image asset named \(name).") 170 | } 171 | return result 172 | } 173 | #endif 174 | */ 175 | } 176 | 177 | internal extension ImageAsset.Image { 178 | @available(iOS 8.0, tvOS 9.0, watchOS 2.0, *) 179 | @available(macOS, deprecated, 180 | message: "This initializer is unsafe on macOS, please use the ImageAsset.image property") 181 | init?(asset: ImageAsset) { 182 | #if os(iOS) || os(tvOS) 183 | let bundle = BundleToken.bundle 184 | self.init(asset.name, bundle: bundle) 185 | #elseif os(macOS) 186 | self.init(named: NSImage.Name(asset.name)) 187 | #elseif os(watchOS) 188 | self.init(named: asset.name) 189 | #endif 190 | } 191 | } 192 | 193 | // swiftlint:disable convenience_type 194 | private final class BundleToken { 195 | static let bundle: Bundle = { 196 | #if SWIFT_PACKAGE 197 | return Bundle.module 198 | #else 199 | return Bundle(for: BundleToken.self) 200 | #endif 201 | }() 202 | } 203 | // swiftlint:enable convenience_type 204 | 205 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C37E670E2BBCA0620057DAFC /* Images in Resources */ = {isa = PBXBuildFile; fileRef = C37E670C2BBCA0610057DAFC /* Images */; }; 11 | C37E67112BBCA0D00057DAFC /* Images in Frameworks */ = {isa = PBXBuildFile; productRef = C37E67102BBCA0D00057DAFC /* Images */; }; 12 | C3F03CC928E37B90004CFE58 /* ExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F03CC828E37B90004CFE58 /* ExampleApp.swift */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXFileReference section */ 16 | C31C728D2BBC935600113564 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../../README.md; sourceTree = ""; }; 17 | C37E670C2BBCA0610057DAFC /* Images */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Images; sourceTree = ""; }; 18 | C3F03CC528E37B90004CFE58 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 19 | C3F03CC828E37B90004CFE58 /* ExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleApp.swift; sourceTree = ""; }; 20 | /* End PBXFileReference section */ 21 | 22 | /* Begin PBXFrameworksBuildPhase section */ 23 | C3F03CC228E37B90004CFE58 /* Frameworks */ = { 24 | isa = PBXFrameworksBuildPhase; 25 | buildActionMask = 2147483647; 26 | files = ( 27 | C37E67112BBCA0D00057DAFC /* Images in Frameworks */, 28 | ); 29 | runOnlyForDeploymentPostprocessing = 0; 30 | }; 31 | /* End PBXFrameworksBuildPhase section */ 32 | 33 | /* Begin PBXGroup section */ 34 | C37E670D2BBCA0610057DAFC /* Modules */ = { 35 | isa = PBXGroup; 36 | children = ( 37 | C37E670C2BBCA0610057DAFC /* Images */, 38 | ); 39 | path = Modules; 40 | sourceTree = ""; 41 | }; 42 | C37E670F2BBCA0D00057DAFC /* Frameworks */ = { 43 | isa = PBXGroup; 44 | children = ( 45 | ); 46 | name = Frameworks; 47 | sourceTree = ""; 48 | }; 49 | C3F03CBC28E37B90004CFE58 = { 50 | isa = PBXGroup; 51 | children = ( 52 | C31C728D2BBC935600113564 /* README.md */, 53 | C3F03CC728E37B90004CFE58 /* Example */, 54 | C3F03CC628E37B90004CFE58 /* Products */, 55 | C37E670F2BBCA0D00057DAFC /* Frameworks */, 56 | ); 57 | sourceTree = ""; 58 | }; 59 | C3F03CC628E37B90004CFE58 /* Products */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | C3F03CC528E37B90004CFE58 /* Example.app */, 63 | ); 64 | name = Products; 65 | sourceTree = ""; 66 | }; 67 | C3F03CC728E37B90004CFE58 /* Example */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | C37E670D2BBCA0610057DAFC /* Modules */, 71 | C3F03CC828E37B90004CFE58 /* ExampleApp.swift */, 72 | ); 73 | path = Example; 74 | sourceTree = ""; 75 | }; 76 | /* End PBXGroup section */ 77 | 78 | /* Begin PBXNativeTarget section */ 79 | C3F03CC428E37B90004CFE58 /* Example */ = { 80 | isa = PBXNativeTarget; 81 | buildConfigurationList = C3F03CD328E37B92004CFE58 /* Build configuration list for PBXNativeTarget "Example" */; 82 | buildPhases = ( 83 | C3F03CC128E37B90004CFE58 /* Sources */, 84 | C3F03CC228E37B90004CFE58 /* Frameworks */, 85 | C3F03CC328E37B90004CFE58 /* Resources */, 86 | ); 87 | buildRules = ( 88 | ); 89 | dependencies = ( 90 | ); 91 | name = Example; 92 | packageProductDependencies = ( 93 | C37E67102BBCA0D00057DAFC /* Images */, 94 | ); 95 | productName = Example; 96 | productReference = C3F03CC528E37B90004CFE58 /* Example.app */; 97 | productType = "com.apple.product-type.application"; 98 | }; 99 | /* End PBXNativeTarget section */ 100 | 101 | /* Begin PBXProject section */ 102 | C3F03CBD28E37B90004CFE58 /* Project object */ = { 103 | isa = PBXProject; 104 | attributes = { 105 | BuildIndependentTargetsInParallel = 1; 106 | LastSwiftUpdateCheck = 1400; 107 | LastUpgradeCheck = 1400; 108 | TargetAttributes = { 109 | C3F03CC428E37B90004CFE58 = { 110 | CreatedOnToolsVersion = 14.0; 111 | }; 112 | }; 113 | }; 114 | buildConfigurationList = C3F03CC028E37B90004CFE58 /* Build configuration list for PBXProject "Example" */; 115 | compatibilityVersion = "Xcode 14.0"; 116 | developmentRegion = en; 117 | hasScannedForEncodings = 0; 118 | knownRegions = ( 119 | en, 120 | Base, 121 | ); 122 | mainGroup = C3F03CBC28E37B90004CFE58; 123 | packageReferences = ( 124 | ); 125 | productRefGroup = C3F03CC628E37B90004CFE58 /* Products */; 126 | projectDirPath = ""; 127 | projectRoot = ""; 128 | targets = ( 129 | C3F03CC428E37B90004CFE58 /* Example */, 130 | ); 131 | }; 132 | /* End PBXProject section */ 133 | 134 | /* Begin PBXResourcesBuildPhase section */ 135 | C3F03CC328E37B90004CFE58 /* Resources */ = { 136 | isa = PBXResourcesBuildPhase; 137 | buildActionMask = 2147483647; 138 | files = ( 139 | C37E670E2BBCA0620057DAFC /* Images in Resources */, 140 | ); 141 | runOnlyForDeploymentPostprocessing = 0; 142 | }; 143 | /* End PBXResourcesBuildPhase section */ 144 | 145 | /* Begin PBXSourcesBuildPhase section */ 146 | C3F03CC128E37B90004CFE58 /* Sources */ = { 147 | isa = PBXSourcesBuildPhase; 148 | buildActionMask = 2147483647; 149 | files = ( 150 | C3F03CC928E37B90004CFE58 /* ExampleApp.swift in Sources */, 151 | ); 152 | runOnlyForDeploymentPostprocessing = 0; 153 | }; 154 | /* End PBXSourcesBuildPhase section */ 155 | 156 | /* Begin XCBuildConfiguration section */ 157 | C3F03CD128E37B92004CFE58 /* Debug */ = { 158 | isa = XCBuildConfiguration; 159 | buildSettings = { 160 | ALWAYS_SEARCH_USER_PATHS = NO; 161 | CLANG_ANALYZER_NONNULL = YES; 162 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 163 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 164 | CLANG_ENABLE_MODULES = YES; 165 | CLANG_ENABLE_OBJC_ARC = YES; 166 | CLANG_ENABLE_OBJC_WEAK = YES; 167 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 168 | CLANG_WARN_BOOL_CONVERSION = YES; 169 | CLANG_WARN_COMMA = YES; 170 | CLANG_WARN_CONSTANT_CONVERSION = YES; 171 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 172 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 173 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 174 | CLANG_WARN_EMPTY_BODY = YES; 175 | CLANG_WARN_ENUM_CONVERSION = YES; 176 | CLANG_WARN_INFINITE_RECURSION = YES; 177 | CLANG_WARN_INT_CONVERSION = YES; 178 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 179 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 180 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 181 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 182 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 183 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 184 | CLANG_WARN_STRICT_PROTOTYPES = YES; 185 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 186 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 187 | CLANG_WARN_UNREACHABLE_CODE = YES; 188 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 189 | COPY_PHASE_STRIP = NO; 190 | DEBUG_INFORMATION_FORMAT = dwarf; 191 | ENABLE_STRICT_OBJC_MSGSEND = YES; 192 | ENABLE_TESTABILITY = YES; 193 | GCC_C_LANGUAGE_STANDARD = gnu11; 194 | GCC_DYNAMIC_NO_PIC = NO; 195 | GCC_NO_COMMON_BLOCKS = YES; 196 | GCC_OPTIMIZATION_LEVEL = 0; 197 | GCC_PREPROCESSOR_DEFINITIONS = ( 198 | "DEBUG=1", 199 | "$(inherited)", 200 | ); 201 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 202 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 203 | GCC_WARN_UNDECLARED_SELECTOR = YES; 204 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 205 | GCC_WARN_UNUSED_FUNCTION = YES; 206 | GCC_WARN_UNUSED_VARIABLE = YES; 207 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 208 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 209 | MTL_FAST_MATH = YES; 210 | ONLY_ACTIVE_ARCH = YES; 211 | SDKROOT = iphoneos; 212 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 213 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 214 | }; 215 | name = Debug; 216 | }; 217 | C3F03CD228E37B92004CFE58 /* Release */ = { 218 | isa = XCBuildConfiguration; 219 | buildSettings = { 220 | ALWAYS_SEARCH_USER_PATHS = NO; 221 | CLANG_ANALYZER_NONNULL = YES; 222 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 223 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 224 | CLANG_ENABLE_MODULES = YES; 225 | CLANG_ENABLE_OBJC_ARC = YES; 226 | CLANG_ENABLE_OBJC_WEAK = YES; 227 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 228 | CLANG_WARN_BOOL_CONVERSION = YES; 229 | CLANG_WARN_COMMA = YES; 230 | CLANG_WARN_CONSTANT_CONVERSION = YES; 231 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 232 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 233 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 234 | CLANG_WARN_EMPTY_BODY = YES; 235 | CLANG_WARN_ENUM_CONVERSION = YES; 236 | CLANG_WARN_INFINITE_RECURSION = YES; 237 | CLANG_WARN_INT_CONVERSION = YES; 238 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 239 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 240 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 241 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 242 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 243 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 244 | CLANG_WARN_STRICT_PROTOTYPES = YES; 245 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 246 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 247 | CLANG_WARN_UNREACHABLE_CODE = YES; 248 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 249 | COPY_PHASE_STRIP = NO; 250 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 251 | ENABLE_NS_ASSERTIONS = NO; 252 | ENABLE_STRICT_OBJC_MSGSEND = YES; 253 | GCC_C_LANGUAGE_STANDARD = gnu11; 254 | GCC_NO_COMMON_BLOCKS = YES; 255 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 256 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 257 | GCC_WARN_UNDECLARED_SELECTOR = YES; 258 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 259 | GCC_WARN_UNUSED_FUNCTION = YES; 260 | GCC_WARN_UNUSED_VARIABLE = YES; 261 | IPHONEOS_DEPLOYMENT_TARGET = 16.0; 262 | MTL_ENABLE_DEBUG_INFO = NO; 263 | MTL_FAST_MATH = YES; 264 | SDKROOT = iphoneos; 265 | SWIFT_COMPILATION_MODE = wholemodule; 266 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 267 | VALIDATE_PRODUCT = YES; 268 | }; 269 | name = Release; 270 | }; 271 | C3F03CD428E37B92004CFE58 /* Debug */ = { 272 | isa = XCBuildConfiguration; 273 | buildSettings = { 274 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 275 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 276 | CODE_SIGN_STYLE = Automatic; 277 | CURRENT_PROJECT_VERSION = 1; 278 | DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\""; 279 | ENABLE_PREVIEWS = YES; 280 | GENERATE_INFOPLIST_FILE = YES; 281 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 282 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 283 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 284 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 285 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 286 | LD_RUNPATH_SEARCH_PATHS = ( 287 | "$(inherited)", 288 | "@executable_path/Frameworks", 289 | ); 290 | MARKETING_VERSION = 1.0; 291 | PRODUCT_BUNDLE_IDENTIFIER = Byterix.Example; 292 | PRODUCT_NAME = "$(TARGET_NAME)"; 293 | SWIFT_EMIT_LOC_STRINGS = YES; 294 | SWIFT_VERSION = 5.0; 295 | TARGETED_DEVICE_FAMILY = "1,2"; 296 | }; 297 | name = Debug; 298 | }; 299 | C3F03CD528E37B92004CFE58 /* Release */ = { 300 | isa = XCBuildConfiguration; 301 | buildSettings = { 302 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 303 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 304 | CODE_SIGN_STYLE = Automatic; 305 | CURRENT_PROJECT_VERSION = 1; 306 | DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\""; 307 | ENABLE_PREVIEWS = YES; 308 | GENERATE_INFOPLIST_FILE = YES; 309 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 310 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 311 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 312 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 313 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 314 | LD_RUNPATH_SEARCH_PATHS = ( 315 | "$(inherited)", 316 | "@executable_path/Frameworks", 317 | ); 318 | MARKETING_VERSION = 1.0; 319 | PRODUCT_BUNDLE_IDENTIFIER = Byterix.Example; 320 | PRODUCT_NAME = "$(TARGET_NAME)"; 321 | SWIFT_EMIT_LOC_STRINGS = YES; 322 | SWIFT_VERSION = 5.0; 323 | TARGETED_DEVICE_FAMILY = "1,2"; 324 | }; 325 | name = Release; 326 | }; 327 | /* End XCBuildConfiguration section */ 328 | 329 | /* Begin XCConfigurationList section */ 330 | C3F03CC028E37B90004CFE58 /* Build configuration list for PBXProject "Example" */ = { 331 | isa = XCConfigurationList; 332 | buildConfigurations = ( 333 | C3F03CD128E37B92004CFE58 /* Debug */, 334 | C3F03CD228E37B92004CFE58 /* Release */, 335 | ); 336 | defaultConfigurationIsVisible = 0; 337 | defaultConfigurationName = Release; 338 | }; 339 | C3F03CD328E37B92004CFE58 /* Build configuration list for PBXNativeTarget "Example" */ = { 340 | isa = XCConfigurationList; 341 | buildConfigurations = ( 342 | C3F03CD428E37B92004CFE58 /* Debug */, 343 | C3F03CD528E37B92004CFE58 /* Release */, 344 | ); 345 | defaultConfigurationIsVisible = 0; 346 | defaultConfigurationName = Release; 347 | }; 348 | /* End XCConfigurationList section */ 349 | 350 | /* Begin XCSwiftPackageProductDependency section */ 351 | C37E67102BBCA0D00057DAFC /* Images */ = { 352 | isa = XCSwiftPackageProductDependency; 353 | productName = Images; 354 | }; 355 | /* End XCSwiftPackageProductDependency section */ 356 | }; 357 | rootObject = C3F03CBD28E37B90004CFE58 /* Project object */; 358 | } 359 | -------------------------------------------------------------------------------- /Examples/SPMPlugin/SwiftGen/assets.stencil: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen 3 | 4 | {% if catalogs %} 5 | {% set enumName %}{{param.enumName|default:"Asset"}}{% endset %} 6 | {% set arResourceGroupType %}{{param.arResourceGroupTypeName|default:"ARResourceGroupAsset"}}{% endset %} 7 | {% set colorType %}{{param.colorTypeName|default:"ColorAsset"}}{% endset %} 8 | {% set dataType %}{{param.dataTypeName|default:"DataAsset"}}{% endset %} 9 | {% set imageType %}{{param.imageTypeName|default:"ImageAsset"}}{% endset %} 10 | {% set symbolType %}{{param.symbolTypeName|default:"SymbolAsset"}}{% endset %} 11 | {% set forceNamespaces %}{{param.forceProvidesNamespaces|default:"false"}}{% endset %} 12 | {% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} 13 | #if os(macOS) 14 | import AppKit 15 | #elseif os(iOS) 16 | {% if resourceCount.arresourcegroup > 0 %} 17 | import ARKit 18 | {% endif %} 19 | import SwiftUI 20 | #elseif os(tvOS) || os(watchOS) 21 | import UIKit 22 | #endif 23 | 24 | // Deprecated typealiases 25 | {% if resourceCount.color > 0 %} 26 | @available(*, deprecated, renamed: "{{colorType}}.Color", message: "This typealias will be removed in SwiftGen 7.0") 27 | {{accessModifier}} typealias {{param.colorAliasName|default:"AssetColorTypeAlias"}} = {{colorType}}.Color 28 | {% endif %} 29 | {% if resourceCount.image > 0 %} 30 | @available(*, deprecated, renamed: "{{imageType}}.Image", message: "This typealias will be removed in SwiftGen 7.0") 31 | {{accessModifier}} typealias {{param.imageAliasName|default:"AssetImageTypeAlias"}} = {{imageType}}.Image 32 | {% endif %} 33 | 34 | // swiftlint:disable superfluous_disable_command file_length implicit_return 35 | 36 | // MARK: - Asset Catalogs 37 | 38 | {% macro enumBlock assets %} 39 | {% call casesBlock assets %} 40 | {% if param.allValues %} 41 | 42 | // swiftlint:disable trailing_comma 43 | {% if resourceCount.arresourcegroup > 0 %} 44 | {{accessModifier}} static let allResourceGroups: [{{arResourceGroupType}}] = [ 45 | {% filter indent:2 %}{% call allValuesBlock assets "arresourcegroup" "" %}{% endfilter %} 46 | ] 47 | {% endif %} 48 | {% if resourceCount.color > 0 %} 49 | {{accessModifier}} static let allColors: [{{colorType}}] = [ 50 | {% filter indent:2 %}{% call allValuesBlock assets "color" "" %}{% endfilter %} 51 | ] 52 | {% endif %} 53 | {% if resourceCount.data > 0 %} 54 | {{accessModifier}} static let allDataAssets: [{{dataType}}] = [ 55 | {% filter indent:2 %}{% call allValuesBlock assets "data" "" %}{% endfilter %} 56 | ] 57 | {% endif %} 58 | {% if resourceCount.image > 0 %} 59 | {{accessModifier}} static let allImages: [{{imageType}}] = [ 60 | {% filter indent:2 %}{% call allValuesBlock assets "image" "" %}{% endfilter %} 61 | ] 62 | {% endif %} 63 | {% if resourceCount.symbol > 0 %} 64 | {{accessModifier}} static let allSymbols: [{{symbolType}}] = [ 65 | {% filter indent:2 %}{% call allValuesBlock assets "symbol" "" %}{% endfilter %} 66 | ] 67 | {% endif %} 68 | // swiftlint:enable trailing_comma 69 | {% endif %} 70 | {% endmacro %} 71 | {% macro casesBlock assets %} 72 | {% for asset in assets %} 73 | {% if asset.type == "arresourcegroup" %} 74 | {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{arResourceGroupType}}(name: "{{asset.value}}") 75 | {% elif asset.type == "color" %} 76 | {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{colorType}}(name: "{{asset.value}}") 77 | {% elif asset.type == "data" %} 78 | {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{dataType}}(name: "{{asset.value}}") 79 | {% elif asset.type == "image" %} 80 | {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{imageType}}(name: "{{asset.value}}") 81 | {% elif asset.type == "symbol" %} 82 | {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{symbolType}}(name: "{{asset.value}}") 83 | {% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %} 84 | {{accessModifier}} enum {{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { 85 | {% filter indent:2 %}{% call casesBlock asset.items %}{% endfilter %} 86 | } 87 | {% elif asset.items %} 88 | {% call casesBlock asset.items %} 89 | {% endif %} 90 | {% endfor %} 91 | {% endmacro %} 92 | {% macro allValuesBlock assets filter prefix %} 93 | {% for asset in assets %} 94 | {% if asset.type == filter %} 95 | {{prefix}}{{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}, 96 | {% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %} 97 | {% set prefix2 %}{{prefix}}{{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.{% endset %} 98 | {% call allValuesBlock asset.items filter prefix2 %} 99 | {% elif asset.items %} 100 | {% call allValuesBlock asset.items filter prefix %} 101 | {% endif %} 102 | {% endfor %} 103 | {% endmacro %} 104 | // swiftlint:disable identifier_name line_length nesting type_body_length type_name 105 | {{accessModifier}} enum {{enumName}} { 106 | {% if catalogs.count > 1 or param.forceFileNameEnum %} 107 | {% for catalog in catalogs %} 108 | {{accessModifier}} enum {{catalog.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { 109 | {% filter indent:2 %}{% call enumBlock catalog.assets %}{% endfilter %} 110 | } 111 | {% endfor %} 112 | {% else %} 113 | {% call enumBlock catalogs.first.assets %} 114 | {% endif %} 115 | } 116 | // swiftlint:enable identifier_name line_length nesting type_body_length type_name 117 | 118 | // MARK: - Implementation Details 119 | {% if resourceCount.arresourcegroup > 0 %} 120 | 121 | {{accessModifier}} struct {{arResourceGroupType}} { 122 | {{accessModifier}} fileprivate(set) var name: String 123 | 124 | #if os(iOS) 125 | @available(iOS 11.3, *) 126 | {{accessModifier}} var referenceImages: Set { 127 | return ARReferenceImage.referenceImages(in: self) 128 | } 129 | 130 | @available(iOS 12.0, *) 131 | {{accessModifier}} var referenceObjects: Set { 132 | return ARReferenceObject.referenceObjects(in: self) 133 | } 134 | #endif 135 | } 136 | 137 | #if os(iOS) 138 | @available(iOS 11.3, *) 139 | {{accessModifier}} extension ARReferenceImage { 140 | static func referenceImages(in asset: {{arResourceGroupType}}) -> Set { 141 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 142 | return referenceImages(inGroupNamed: asset.name, bundle: bundle) ?? Set() 143 | } 144 | } 145 | 146 | @available(iOS 12.0, *) 147 | {{accessModifier}} extension ARReferenceObject { 148 | static func referenceObjects(in asset: {{arResourceGroupType}}) -> Set { 149 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 150 | return referenceObjects(inGroupNamed: asset.name, bundle: bundle) ?? Set() 151 | } 152 | } 153 | #endif 154 | {% endif %} 155 | {% if resourceCount.color > 0 %} 156 | 157 | {{accessModifier}} final class {{colorType}} { 158 | {{accessModifier}} fileprivate(set) var name: String 159 | 160 | #if os(macOS) 161 | {{accessModifier}} typealias Color = NSColor 162 | #elseif os(iOS) || os(tvOS) || os(watchOS) 163 | {{accessModifier}} typealias Color = SwiftUI.Color 164 | {{accessModifier}} typealias UIColor = UIKit.UIColor 165 | #endif 166 | 167 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) 168 | {{accessModifier}} private(set) lazy var color: Color = { 169 | guard let color = Color(asset: self) else { 170 | fatalError("Unable to load color asset named \(name).") 171 | } 172 | return color 173 | }() 174 | 175 | {{accessModifier}} private(set) lazy var uiColor: UIColor = { 176 | guard let color = UIColor(asset: self) else { 177 | fatalError("Unable to load color asset named \(name).") 178 | } 179 | return color 180 | }() 181 | /* 182 | #if os(iOS) || os(tvOS) 183 | @available(iOS 11.0, tvOS 11.0, *) 184 | {{accessModifier}} func color(compatibleWith traitCollection: UITraitCollection) -> Color { 185 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 186 | guard let color = Color(named: name, bundle: bundle, compatibleWith: traitCollection) else { 187 | fatalError("Unable to load color asset named \(name).") 188 | } 189 | return color 190 | } 191 | #endif 192 | */ 193 | 194 | fileprivate init(name: String) { 195 | self.name = name 196 | } 197 | } 198 | 199 | {{accessModifier}} extension {{colorType}}.Color { 200 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) 201 | init?(asset: {{colorType}}) { 202 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 203 | #if os(iOS) || os(tvOS) 204 | self.init(asset.name, bundle: bundle) 205 | #elseif os(macOS) 206 | self.init(named: NSColor.Name(asset.name), bundle: bundle) 207 | #elseif os(watchOS) 208 | self.init(named: asset.name) 209 | #endif 210 | } 211 | } 212 | {{accessModifier}} extension {{colorType}}.UIColor { 213 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) 214 | convenience init?(asset: {{colorType}}) { 215 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 216 | #if os(iOS) || os(tvOS) 217 | self.init(named: asset.name, in: bundle, compatibleWith: nil) 218 | #elseif os(macOS) 219 | self.init(named: NSColor.Name(asset.name), bundle: bundle) 220 | #elseif os(watchOS) 221 | self.init(named: asset.name) 222 | #endif 223 | } 224 | } 225 | {% endif %} 226 | {% if resourceCount.data > 0 %} 227 | 228 | {{accessModifier}} struct {{dataType}} { 229 | {{accessModifier}} fileprivate(set) var name: String 230 | 231 | @available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *) 232 | {{accessModifier}} var data: NSDataAsset { 233 | guard let data = NSDataAsset(asset: self) else { 234 | fatalError("Unable to load data asset named \(name).") 235 | } 236 | return data 237 | } 238 | } 239 | 240 | @available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *) 241 | {{accessModifier}} extension NSDataAsset { 242 | convenience init?(asset: {{dataType}}) { 243 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 244 | #if os(iOS) || os(tvOS) || os(watchOS) 245 | self.init(name: asset.name, bundle: bundle) 246 | #elseif os(macOS) 247 | self.init(name: NSDataAsset.Name(asset.name), bundle: bundle) 248 | #endif 249 | } 250 | } 251 | {% endif %} 252 | {% if resourceCount.image > 0 %} 253 | 254 | {{accessModifier}} struct {{imageType}} { 255 | {{accessModifier}} fileprivate(set) var name: String 256 | 257 | #if os(macOS) 258 | {{accessModifier}} typealias Image = NSImage 259 | #elseif os(iOS) || os(tvOS) || os(watchOS) 260 | {{accessModifier}} typealias Image = SwiftUI.Image 261 | #endif 262 | 263 | @available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *) 264 | {{accessModifier}} var image: Image { 265 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 266 | #if os(iOS) || os(tvOS) 267 | let image = Image(name, bundle: bundle) 268 | #elseif os(macOS) 269 | let name = NSImage.Name(self.name) 270 | let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name) 271 | #elseif os(watchOS) 272 | let image = Image(named: name) 273 | #endif 274 | //guard let result = image else { 275 | // fatalError("Unable to load image asset named \(name).") 276 | //} 277 | return image 278 | } 279 | 280 | @available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *) 281 | {{accessModifier}} var uiImage: UIImage { 282 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 283 | #if os(iOS) || os(tvOS) 284 | let image = UIImage(named: name, in: bundle, compatibleWith: nil) 285 | #elseif os(macOS) 286 | let name = NSImage.Name(self.name) 287 | let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name) 288 | #elseif os(watchOS) 289 | let image = Image(named: name) 290 | #endif 291 | guard let image = image else { 292 | fatalError("Unable to load image asset named \(name).") 293 | } 294 | return image 295 | } 296 | /* 297 | #if os(iOS) || os(tvOS) 298 | @available(iOS 8.0, tvOS 9.0, *) 299 | {{accessModifier}} func image(compatibleWith traitCollection: UITraitCollection) -> Image { 300 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 301 | guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else { 302 | fatalError("Unable to load image asset named \(name).") 303 | } 304 | return result 305 | } 306 | #endif 307 | */ 308 | } 309 | 310 | {{accessModifier}} extension {{imageType}}.Image { 311 | @available(iOS 8.0, tvOS 9.0, watchOS 2.0, *) 312 | @available(macOS, deprecated, 313 | message: "This initializer is unsafe on macOS, please use the {{imageType}}.image property") 314 | init?(asset: {{imageType}}) { 315 | #if os(iOS) || os(tvOS) 316 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 317 | self.init(asset.name, bundle: bundle) 318 | #elseif os(macOS) 319 | self.init(named: NSImage.Name(asset.name)) 320 | #elseif os(watchOS) 321 | self.init(named: asset.name) 322 | #endif 323 | } 324 | } 325 | {% endif %} 326 | {% if resourceCount.symbol > 0 %} 327 | 328 | {{accessModifier}} struct {{symbolType}} { 329 | {{accessModifier}} fileprivate(set) var name: String 330 | 331 | #if os(iOS) || os(tvOS) || os(watchOS) 332 | @available(iOS 13.0, tvOS 13.0, watchOS 6.0, *) 333 | {{accessModifier}} typealias Configuration = SwiftUI.Image.SymbolConfiguration 334 | {{accessModifier}} typealias Image = SwiftUI.Image 335 | 336 | @available(iOS 12.0, tvOS 12.0, watchOS 5.0, *) 337 | {{accessModifier}} var image: Image { 338 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 339 | #if os(iOS) || os(tvOS) 340 | let image = Image(named: name, in: bundle, compatibleWith: nil) 341 | #elseif os(watchOS) 342 | let image = Image(named: name) 343 | #endif 344 | guard let result = image else { 345 | fatalError("Unable to load symbol asset named \(name).") 346 | } 347 | return result 348 | } 349 | 350 | @available(iOS 13.0, tvOS 13.0, watchOS 6.0, *) 351 | {{accessModifier}} func image(with configuration: Configuration) -> Image { 352 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 353 | guard let result = Image(named: name, in: bundle, with: configuration) else { 354 | fatalError("Unable to load symbol asset named \(name).") 355 | } 356 | return result 357 | } 358 | #endif 359 | } 360 | {% endif %} 361 | {% if not param.bundle %} 362 | 363 | // swiftlint:disable convenience_type 364 | private final class BundleToken { 365 | static let bundle: Bundle = { 366 | #if SWIFT_PACKAGE 367 | return Bundle.module 368 | #else 369 | return Bundle(for: BundleToken.self) 370 | #endif 371 | }() 372 | } 373 | // swiftlint:enable convenience_type 374 | {% endif %} 375 | {% else %} 376 | // No assets found 377 | {% endif %} 378 | 379 | -------------------------------------------------------------------------------- /Examples/SwiftScript/SwiftGen/assets.stencil: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen 3 | 4 | {% if catalogs %} 5 | {% set enumName %}{{param.enumName|default:"Asset"}}{% endset %} 6 | {% set arResourceGroupType %}{{param.arResourceGroupTypeName|default:"ARResourceGroupAsset"}}{% endset %} 7 | {% set colorType %}{{param.colorTypeName|default:"ColorAsset"}}{% endset %} 8 | {% set dataType %}{{param.dataTypeName|default:"DataAsset"}}{% endset %} 9 | {% set imageType %}{{param.imageTypeName|default:"ImageAsset"}}{% endset %} 10 | {% set symbolType %}{{param.symbolTypeName|default:"SymbolAsset"}}{% endset %} 11 | {% set forceNamespaces %}{{param.forceProvidesNamespaces|default:"false"}}{% endset %} 12 | {% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} 13 | #if os(macOS) 14 | import AppKit 15 | #elseif os(iOS) 16 | {% if resourceCount.arresourcegroup > 0 %} 17 | import ARKit 18 | {% endif %} 19 | import SwiftUI 20 | #elseif os(tvOS) || os(watchOS) 21 | import UIKit 22 | #endif 23 | 24 | // Deprecated typealiases 25 | {% if resourceCount.color > 0 %} 26 | @available(*, deprecated, renamed: "{{colorType}}.Color", message: "This typealias will be removed in SwiftGen 7.0") 27 | {{accessModifier}} typealias {{param.colorAliasName|default:"AssetColorTypeAlias"}} = {{colorType}}.Color 28 | {% endif %} 29 | {% if resourceCount.image > 0 %} 30 | @available(*, deprecated, renamed: "{{imageType}}.Image", message: "This typealias will be removed in SwiftGen 7.0") 31 | {{accessModifier}} typealias {{param.imageAliasName|default:"AssetImageTypeAlias"}} = {{imageType}}.Image 32 | {% endif %} 33 | 34 | // swiftlint:disable superfluous_disable_command file_length implicit_return 35 | 36 | // MARK: - Asset Catalogs 37 | 38 | {% macro enumBlock assets %} 39 | {% call casesBlock assets %} 40 | {% if param.allValues %} 41 | 42 | // swiftlint:disable trailing_comma 43 | {% if resourceCount.arresourcegroup > 0 %} 44 | {{accessModifier}} static let allResourceGroups: [{{arResourceGroupType}}] = [ 45 | {% filter indent:2 %}{% call allValuesBlock assets "arresourcegroup" "" %}{% endfilter %} 46 | ] 47 | {% endif %} 48 | {% if resourceCount.color > 0 %} 49 | {{accessModifier}} static let allColors: [{{colorType}}] = [ 50 | {% filter indent:2 %}{% call allValuesBlock assets "color" "" %}{% endfilter %} 51 | ] 52 | {% endif %} 53 | {% if resourceCount.data > 0 %} 54 | {{accessModifier}} static let allDataAssets: [{{dataType}}] = [ 55 | {% filter indent:2 %}{% call allValuesBlock assets "data" "" %}{% endfilter %} 56 | ] 57 | {% endif %} 58 | {% if resourceCount.image > 0 %} 59 | {{accessModifier}} static let allImages: [{{imageType}}] = [ 60 | {% filter indent:2 %}{% call allValuesBlock assets "image" "" %}{% endfilter %} 61 | ] 62 | {% endif %} 63 | {% if resourceCount.symbol > 0 %} 64 | {{accessModifier}} static let allSymbols: [{{symbolType}}] = [ 65 | {% filter indent:2 %}{% call allValuesBlock assets "symbol" "" %}{% endfilter %} 66 | ] 67 | {% endif %} 68 | // swiftlint:enable trailing_comma 69 | {% endif %} 70 | {% endmacro %} 71 | {% macro casesBlock assets %} 72 | {% for asset in assets %} 73 | {% if asset.type == "arresourcegroup" %} 74 | {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{arResourceGroupType}}(name: "{{asset.value}}") 75 | {% elif asset.type == "color" %} 76 | {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{colorType}}(name: "{{asset.value}}") 77 | {% elif asset.type == "data" %} 78 | {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{dataType}}(name: "{{asset.value}}") 79 | {% elif asset.type == "image" %} 80 | {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{imageType}}(name: "{{asset.value}}") 81 | {% elif asset.type == "symbol" %} 82 | {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{symbolType}}(name: "{{asset.value}}") 83 | {% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %} 84 | {{accessModifier}} enum {{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { 85 | {% filter indent:2 %}{% call casesBlock asset.items %}{% endfilter %} 86 | } 87 | {% elif asset.items %} 88 | {% call casesBlock asset.items %} 89 | {% endif %} 90 | {% endfor %} 91 | {% endmacro %} 92 | {% macro allValuesBlock assets filter prefix %} 93 | {% for asset in assets %} 94 | {% if asset.type == filter %} 95 | {{prefix}}{{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}, 96 | {% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %} 97 | {% set prefix2 %}{{prefix}}{{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.{% endset %} 98 | {% call allValuesBlock asset.items filter prefix2 %} 99 | {% elif asset.items %} 100 | {% call allValuesBlock asset.items filter prefix %} 101 | {% endif %} 102 | {% endfor %} 103 | {% endmacro %} 104 | // swiftlint:disable identifier_name line_length nesting type_body_length type_name 105 | {{accessModifier}} enum {{enumName}} { 106 | {% if catalogs.count > 1 or param.forceFileNameEnum %} 107 | {% for catalog in catalogs %} 108 | {{accessModifier}} enum {{catalog.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { 109 | {% filter indent:2 %}{% call enumBlock catalog.assets %}{% endfilter %} 110 | } 111 | {% endfor %} 112 | {% else %} 113 | {% call enumBlock catalogs.first.assets %} 114 | {% endif %} 115 | } 116 | // swiftlint:enable identifier_name line_length nesting type_body_length type_name 117 | 118 | // MARK: - Implementation Details 119 | {% if resourceCount.arresourcegroup > 0 %} 120 | 121 | {{accessModifier}} struct {{arResourceGroupType}} { 122 | {{accessModifier}} fileprivate(set) var name: String 123 | 124 | #if os(iOS) 125 | @available(iOS 11.3, *) 126 | {{accessModifier}} var referenceImages: Set { 127 | return ARReferenceImage.referenceImages(in: self) 128 | } 129 | 130 | @available(iOS 12.0, *) 131 | {{accessModifier}} var referenceObjects: Set { 132 | return ARReferenceObject.referenceObjects(in: self) 133 | } 134 | #endif 135 | } 136 | 137 | #if os(iOS) 138 | @available(iOS 11.3, *) 139 | {{accessModifier}} extension ARReferenceImage { 140 | static func referenceImages(in asset: {{arResourceGroupType}}) -> Set { 141 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 142 | return referenceImages(inGroupNamed: asset.name, bundle: bundle) ?? Set() 143 | } 144 | } 145 | 146 | @available(iOS 12.0, *) 147 | {{accessModifier}} extension ARReferenceObject { 148 | static func referenceObjects(in asset: {{arResourceGroupType}}) -> Set { 149 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 150 | return referenceObjects(inGroupNamed: asset.name, bundle: bundle) ?? Set() 151 | } 152 | } 153 | #endif 154 | {% endif %} 155 | {% if resourceCount.color > 0 %} 156 | 157 | {{accessModifier}} final class {{colorType}} { 158 | {{accessModifier}} fileprivate(set) var name: String 159 | 160 | #if os(macOS) 161 | {{accessModifier}} typealias Color = NSColor 162 | #elseif os(iOS) || os(tvOS) || os(watchOS) 163 | {{accessModifier}} typealias Color = SwiftUI.Color 164 | {{accessModifier}} typealias UIColor = UIKit.UIColor 165 | #endif 166 | 167 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) 168 | {{accessModifier}} private(set) lazy var color: Color = { 169 | guard let color = Color(asset: self) else { 170 | fatalError("Unable to load color asset named \(name).") 171 | } 172 | return color 173 | }() 174 | 175 | {{accessModifier}} private(set) lazy var uiColor: UIColor = { 176 | guard let color = UIColor(asset: self) else { 177 | fatalError("Unable to load color asset named \(name).") 178 | } 179 | return color 180 | }() 181 | /* 182 | #if os(iOS) || os(tvOS) 183 | @available(iOS 11.0, tvOS 11.0, *) 184 | {{accessModifier}} func color(compatibleWith traitCollection: UITraitCollection) -> Color { 185 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 186 | guard let color = Color(named: name, bundle: bundle, compatibleWith: traitCollection) else { 187 | fatalError("Unable to load color asset named \(name).") 188 | } 189 | return color 190 | } 191 | #endif 192 | */ 193 | 194 | fileprivate init(name: String) { 195 | self.name = name 196 | } 197 | } 198 | 199 | {{accessModifier}} extension {{colorType}}.Color { 200 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) 201 | init?(asset: {{colorType}}) { 202 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 203 | #if os(iOS) || os(tvOS) 204 | self.init(asset.name, bundle: bundle) 205 | #elseif os(macOS) 206 | self.init(named: NSColor.Name(asset.name), bundle: bundle) 207 | #elseif os(watchOS) 208 | self.init(named: asset.name) 209 | #endif 210 | } 211 | } 212 | {{accessModifier}} extension {{colorType}}.UIColor { 213 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) 214 | convenience init?(asset: {{colorType}}) { 215 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 216 | #if os(iOS) || os(tvOS) 217 | self.init(named: asset.name, in: bundle, compatibleWith: nil) 218 | #elseif os(macOS) 219 | self.init(named: NSColor.Name(asset.name), bundle: bundle) 220 | #elseif os(watchOS) 221 | self.init(named: asset.name) 222 | #endif 223 | } 224 | } 225 | {% endif %} 226 | {% if resourceCount.data > 0 %} 227 | 228 | {{accessModifier}} struct {{dataType}} { 229 | {{accessModifier}} fileprivate(set) var name: String 230 | 231 | @available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *) 232 | {{accessModifier}} var data: NSDataAsset { 233 | guard let data = NSDataAsset(asset: self) else { 234 | fatalError("Unable to load data asset named \(name).") 235 | } 236 | return data 237 | } 238 | } 239 | 240 | @available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *) 241 | {{accessModifier}} extension NSDataAsset { 242 | convenience init?(asset: {{dataType}}) { 243 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 244 | #if os(iOS) || os(tvOS) || os(watchOS) 245 | self.init(name: asset.name, bundle: bundle) 246 | #elseif os(macOS) 247 | self.init(name: NSDataAsset.Name(asset.name), bundle: bundle) 248 | #endif 249 | } 250 | } 251 | {% endif %} 252 | {% if resourceCount.image > 0 %} 253 | 254 | {{accessModifier}} struct {{imageType}} { 255 | {{accessModifier}} fileprivate(set) var name: String 256 | 257 | #if os(macOS) 258 | {{accessModifier}} typealias Image = NSImage 259 | #elseif os(iOS) || os(tvOS) || os(watchOS) 260 | {{accessModifier}} typealias Image = SwiftUI.Image 261 | #endif 262 | 263 | @available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *) 264 | {{accessModifier}} var image: Image { 265 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 266 | #if os(iOS) || os(tvOS) 267 | let image = Image(name, bundle: bundle) 268 | #elseif os(macOS) 269 | let name = NSImage.Name(self.name) 270 | let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name) 271 | #elseif os(watchOS) 272 | let image = Image(named: name) 273 | #endif 274 | //guard let result = image else { 275 | // fatalError("Unable to load image asset named \(name).") 276 | //} 277 | return image 278 | } 279 | 280 | @available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *) 281 | {{accessModifier}} var uiImage: UIImage { 282 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 283 | #if os(iOS) || os(tvOS) 284 | let image = UIImage(named: name, in: bundle, compatibleWith: nil) 285 | #elseif os(macOS) 286 | let name = NSImage.Name(self.name) 287 | let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name) 288 | #elseif os(watchOS) 289 | let image = Image(named: name) 290 | #endif 291 | guard let image = image else { 292 | fatalError("Unable to load image asset named \(name).") 293 | } 294 | return image 295 | } 296 | /* 297 | #if os(iOS) || os(tvOS) 298 | @available(iOS 8.0, tvOS 9.0, *) 299 | {{accessModifier}} func image(compatibleWith traitCollection: UITraitCollection) -> Image { 300 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 301 | guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else { 302 | fatalError("Unable to load image asset named \(name).") 303 | } 304 | return result 305 | } 306 | #endif 307 | */ 308 | } 309 | 310 | {{accessModifier}} extension {{imageType}}.Image { 311 | @available(iOS 8.0, tvOS 9.0, watchOS 2.0, *) 312 | @available(macOS, deprecated, 313 | message: "This initializer is unsafe on macOS, please use the {{imageType}}.image property") 314 | init?(asset: {{imageType}}) { 315 | #if os(iOS) || os(tvOS) 316 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 317 | self.init(asset.name, bundle: bundle) 318 | #elseif os(macOS) 319 | self.init(named: NSImage.Name(asset.name)) 320 | #elseif os(watchOS) 321 | self.init(named: asset.name) 322 | #endif 323 | } 324 | } 325 | {% endif %} 326 | {% if resourceCount.symbol > 0 %} 327 | 328 | {{accessModifier}} struct {{symbolType}} { 329 | {{accessModifier}} fileprivate(set) var name: String 330 | 331 | #if os(iOS) || os(tvOS) || os(watchOS) 332 | @available(iOS 13.0, tvOS 13.0, watchOS 6.0, *) 333 | {{accessModifier}} typealias Configuration = SwiftUI.Image.SymbolConfiguration 334 | {{accessModifier}} typealias Image = SwiftUI.Image 335 | 336 | @available(iOS 12.0, tvOS 12.0, watchOS 5.0, *) 337 | {{accessModifier}} var image: Image { 338 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 339 | #if os(iOS) || os(tvOS) 340 | let image = Image(named: name, in: bundle, compatibleWith: nil) 341 | #elseif os(watchOS) 342 | let image = Image(named: name) 343 | #endif 344 | guard let result = image else { 345 | fatalError("Unable to load symbol asset named \(name).") 346 | } 347 | return result 348 | } 349 | 350 | @available(iOS 13.0, tvOS 13.0, watchOS 6.0, *) 351 | {{accessModifier}} func image(with configuration: Configuration) -> Image { 352 | let bundle = {{param.bundle|default:"BundleToken.bundle"}} 353 | guard let result = Image(named: name, in: bundle, with: configuration) else { 354 | fatalError("Unable to load symbol asset named \(name).") 355 | } 356 | return result 357 | } 358 | #endif 359 | } 360 | {% endif %} 361 | {% if not param.bundle %} 362 | 363 | // swiftlint:disable convenience_type 364 | private final class BundleToken { 365 | static let bundle: Bundle = { 366 | #if SWIFT_PACKAGE 367 | return Bundle.module 368 | #else 369 | return Bundle(for: BundleToken.self) 370 | #endif 371 | }() 372 | } 373 | // swiftlint:enable convenience_type 374 | {% endif %} 375 | {% else %} 376 | // No assets found 377 | {% endif %} 378 | 379 | -------------------------------------------------------------------------------- /Sources/ImagelinterExec/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // 4 | // 5 | // Created by Sergey Balalaev on 02.04.2024. 6 | // 7 | 8 | import Foundation 9 | import AppKit 10 | 11 | // MARK: begin of settings the script 12 | 13 | let settings = Settings() 14 | 15 | // MARK: end of settings the script 16 | 17 | let startDate = Date() 18 | 19 | let imageSetExtensions = settings.rastorExtensions.union(settings.vectorExtensions) 20 | 21 | struct RegexPattern { 22 | let pattern: NSRegularExpression 23 | let isSwiftGen: Bool 24 | } 25 | var sourcesRegex: [RegexPattern] = [] 26 | var isSwiftGen = false 27 | 28 | private func addSourceRegexPattern(pattern: String, isSwiftGen: Bool) { 29 | guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { 30 | printError(filePath: #file, message: "Not right pattern for regex: \(pattern)", line: #line) 31 | return 32 | } 33 | sourcesRegex.append(RegexPattern(pattern: regex, isSwiftGen: isSwiftGen)) 34 | } 35 | 36 | for usingType in settings.usingTypes { 37 | switch usingType { 38 | case .custom(let pattern, let isSwiftGen): 39 | addSourceRegexPattern(pattern: pattern, isSwiftGen: isSwiftGen) 40 | case .swiftUI: 41 | addSourceRegexPattern(pattern: #"\bImage\(\s*"(.*)"\s*\)"#, isSwiftGen: false) 42 | case .uiKit: 43 | addSourceRegexPattern(pattern: #"\bUIImage\(\s*named:\s*"(.*)"\s*\)"#, isSwiftGen: false) 44 | case .uiKitLiteral: 45 | addSourceRegexPattern(pattern: ##"\#imageLiteral\(\s*resourceName:\s*"(.*)"\s*\)"##, isSwiftGen: false) 46 | case .swiftGen(let enumName): 47 | addSourceRegexPattern(pattern: enumName + 48 | #"\s*\.((?:\.*[A-Z]{1}[A-z0-9]*)*)\s*((?:\.*[a-z]{1}[A-z0-9]*))(?:\s*\.image|\s*\.uiImage|\s*\.name)"#, isSwiftGen: true) 49 | isSwiftGen = true 50 | } 51 | } 52 | 53 | let allImageScales = (1...3) 54 | var targetScales: Set = [] 55 | for targetPlatform in settings.targetPlatforms { 56 | switch targetPlatform { 57 | case .iPadOS, .visionOS, .watchOS: 58 | targetScales.insert(2) 59 | case .iOS: 60 | targetScales.insert(2) 61 | targetScales.insert(3) 62 | case .macOS, .tvOS: 63 | targetScales.insert(1) 64 | targetScales.insert(2) 65 | } 66 | } 67 | if targetScales.count < 1 { 68 | print("\(CommandLine.arguments[0]):\(#line): error: targetPlatforms should have one or more values. It need for detect quality of images.") 69 | } 70 | 71 | // MARK: detection resources of images 72 | 73 | 74 | 75 | var warningsCount = 0 76 | var errorsCount = 0 77 | 78 | // MARK: start analyze 79 | 80 | if settings.isEnabled == false { 81 | print("\(CommandLine.arguments[0]):\(#line): warning: images checking cancelled") 82 | exit(000) 83 | } 84 | 85 | func printError(filePath: String, message: String, 86 | line: Int? = nil, isWarning: Bool = false) { 87 | var result = filePath 88 | if let line = line { 89 | result += ":\(line): " 90 | } else { 91 | result += ": " 92 | } 93 | result += isWarning ? "warning: " : "error: " 94 | print(result + message) 95 | if isWarning { 96 | warningsCount += 1 97 | } else { 98 | errorsCount += 1 99 | } 100 | } 101 | 102 | extension String { 103 | var linesCount: Int { 104 | return reduce(into: 1) { count, letter in 105 | if letter == "\n" { // This treats CRLF as one "letter", contrary to UnicodeScalars 106 | count += 1 107 | } 108 | } 109 | } 110 | 111 | var scale: Int? { 112 | guard (self as NSString).contains("x") else { 113 | return nil 114 | } 115 | return Int(dropLast(1)) 116 | } 117 | } 118 | 119 | extension NSImage { 120 | var pixelSize: NSSize? { 121 | if let rep = representations.first { 122 | let size = NSSize(width: rep.pixelsWide, height: rep.pixelsHigh) 123 | return size 124 | } 125 | return nil 126 | } 127 | } 128 | 129 | extension CGImage { 130 | var png: Data? { 131 | guard let mutableData = CFDataCreateMutable(nil, 0), 132 | let destination = CGImageDestinationCreateWithData(mutableData, "public.png" as CFString, 1, nil) else { return nil } 133 | CGImageDestinationAddImage(destination, self, nil) 134 | guard CGImageDestinationFinalize(destination) else { return nil } 135 | return mutableData as Data 136 | } 137 | } 138 | 139 | func fileSize(fromPath path: String) -> UInt64 { 140 | let size: Any? = try? FileManager.default.attributesOfItem(atPath: path)[FileAttributeKey.size] 141 | guard let fileSize = size as? UInt64 else { 142 | printError(filePath: path, message: "Not read file size") 143 | return 0 144 | } 145 | return fileSize 146 | } 147 | 148 | func covertToString(fileSize: UInt64) -> String { 149 | ByteCountFormatter().string(fromByteCount: Int64(fileSize)) 150 | } 151 | 152 | let pdfRasterPattern = #".*\/[Ii]mage.*"# 153 | let pdfRasterRegex = try? NSRegularExpression(pattern: pdfRasterPattern, options: []) 154 | let svgRasterPattern = #".* settings.maxVectorFileSize { 177 | printError( 178 | filePath: imageFilePath, 179 | message: "File size (\(covertToString(fileSize: fileSize))) of the image is very biggest. Max file size is \(covertToString(fileSize: settings.maxVectorFileSize)). Found for image '\(imageInfo.name)'" 180 | ) 181 | } 182 | 183 | if settings.isCheckingPdfVector || settings.isCheckingSvgVector { 184 | if !FileManager.default.isReadableFile(atPath: imageFilePath) { 185 | printError(filePath: imageFilePath, message: "Can not read file with path: '\(imageFilePath)'") 186 | } else { 187 | do { 188 | let string = try String(contentsOfFile: imageFilePath, encoding: .isoLatin1) 189 | 190 | let range = NSRange(location: 0, length: string.count) 191 | if settings.isCheckingPdfVector, pdfRasterRegex?.firstMatch(in: string, options: [], range: range) != nil { 192 | printError(filePath: imageFilePath, message: "PDF File is not a pure vector. Found for image '\(imageInfo.name)'") 193 | } 194 | if settings.isCheckingSvgVector, svgRasterRegex?.firstMatch(in: string, options: [], range: range) != nil { 195 | printError(filePath: imageFilePath, message: "SVG File is not a pure vector. Found for image '\(imageInfo.name)'") 196 | } 197 | } catch let error { 198 | printError(filePath: imageFilePath, message: "Can not parse Vector file. Found for image '\(imageInfo.name)' with error: `\(error.localizedDescription)`") 199 | } 200 | } 201 | } 202 | } else if settings.rastorExtensions.contains(fileExtension) { 203 | if settings.isCheckingFileSize, fileSize > settings.maxRastorFileSize { 204 | printError( 205 | filePath: imageFilePath, 206 | message: "File size (\(covertToString(fileSize: fileSize))) of the image is very biggest. Max file size is \(covertToString(fileSize: settings.maxRastorFileSize)). Found for image '\(imageInfo.name)'" 207 | ) 208 | } 209 | } 210 | } 211 | } else if imageFileName.hasSuffix(imagesetExtension) { 212 | let fileEnumerator = FileManager.default.enumerator(atPath: imageFilePath) 213 | var files: Set = [] 214 | while let fileName = fileEnumerator?.nextObject() as? String { 215 | files.insert(fileName) 216 | } 217 | let name = ((imageFileName as NSString).lastPathComponent as NSString).deletingPathExtension 218 | if let content = load(AssetContents.self, dir: imagesPath, for: imageFileName) { 219 | let contentFileNames = Set(content.images.compactMap { $0.filename }) 220 | if contentFileNames.isEmpty { 221 | printError(filePath: imageFileName, message: "Empty asset with name '\(name)'") 222 | } 223 | let notFoundFile = contentFileNames.subtracting(files) 224 | for file in notFoundFile { 225 | printError(filePath: imageFileName, message: "Not found file '\(file)' for Asset with name '\(name)'") 226 | } 227 | } else { 228 | printError(filePath: imageFileName, message: "Empty folder for Asset with name '\(name)'") 229 | } 230 | } 231 | } 232 | } 233 | 234 | // MARK: - detect unused Images 235 | 236 | 237 | var usedImages: [String] = [] 238 | var usedImagesFromSwiftGen: [String] = [] 239 | let resourcesRegex = try! NSRegularExpression(pattern: #"<\bimage name="(.[A-z0-9]*)""#, options: []) 240 | 241 | print("source folders: \(settings.relativeSourcePaths)") 242 | for relativeSourcePath in settings.relativeSourcePaths { 243 | let sourcePath = settings.dir + relativeSourcePath 244 | print("source path: \(sourcePath)") 245 | // Search all using 246 | let sourceFileEnumerator = FileManager.default.enumerator(atPath: sourcePath) 247 | while let sourceFileName = sourceFileEnumerator?.nextObject() as? String { 248 | let fileExtension = (sourceFileName as NSString).pathExtension.uppercased() 249 | let filePath = sourcePath + sourceFileName 250 | // checks the extension to source 251 | if settings.sourcesExtensions.contains(fileExtension) { 252 | if let string = try? String(contentsOfFile: filePath, encoding: .utf8) { 253 | let range = NSRange(location: 0, length: (string as NSString).length) 254 | sourcesRegex.forEach{ regex in 255 | regex.pattern.enumerateMatches( 256 | in: string, 257 | options: [], 258 | range: range) { result, _, _ in 259 | addUsedImage(from: string, result: result, path: filePath, isSwiftGen: regex.isSwiftGen) 260 | } 261 | } 262 | } 263 | } else if settings.resourcesExtensions.contains(fileExtension) { // checks the extension to resource 264 | if let string = try? String(contentsOfFile: filePath, encoding: .utf8) { 265 | let range = NSRange(location: 0, length: (string as NSString).length) 266 | resourcesRegex.enumerateMatches(in: string, 267 | options: [], 268 | range: range) { result, _, _ in 269 | addUsedImage(from: string, result: result, path: filePath) 270 | } 271 | } 272 | } 273 | } 274 | } 275 | 276 | func addUsedImage(from string: String, result: NSTextCheckingResult?, path: String, isSwiftGen: Bool = false) { 277 | guard let result = result, result.numberOfRanges > 0 else { 278 | return 279 | } 280 | // first range is matching, all next is groups 281 | let value = (1...result.numberOfRanges - 1).map { index in 282 | (string as NSString).substring(with: result.range(at: index)) 283 | }.joined() 284 | var foundedImage: Any? = nil 285 | if isSwiftGen { 286 | usedImagesFromSwiftGen.append(value) 287 | foundedImage = foundedSwiftGenMirrorImages[value] 288 | } else { 289 | usedImages.append(value) 290 | foundedImage = foundedImages[value] 291 | } 292 | 293 | if foundedImage == nil, settings.ignoredUndefinedImages.contains(value) == false { 294 | let line = (string as NSString).substring(with: NSRange(location: 0, length: result.range(at: 0).location)).linesCount 295 | 296 | printError(filePath: path, message: "Not found image with name '\(value)'", line: line) 297 | } 298 | } 299 | 300 | let standartUnusedImages = Set(foundedImages.keys).subtracting(usedImages).subtracting(settings.ignoredUnusedImages) 301 | let swiftGenUnusedImages = Set(foundedSwiftGenMirrorImages.keys).subtracting(usedImagesFromSwiftGen).subtracting(settings.ignoredUnusedImages) 302 | let unusedImages = Set(standartUnusedImages).intersection(swiftGenUnusedImages.compactMap {foundedSwiftGenMirrorImages[$0]} ) 303 | 304 | for unusedImage in unusedImages { 305 | if let imageInfo = foundedImages[unusedImage] { 306 | imageInfo.error(with: "File unused from code. Found for image '\(imageInfo.name)'") 307 | } 308 | } 309 | 310 | let images: [ImageInfo] = foundedImages.values.map { $0 } 311 | for imageInfo in images { 312 | if settings.isCheckingDuplicatedByName { 313 | imageInfo.checkDuplicateByName() 314 | } 315 | if settings.isCheckingScaleSize { 316 | imageInfo.checkImageSizeAndDetectType() 317 | } 318 | if settings.isCheckingDuplicatedByContent { 319 | if let data = imageInfo.calculateData() { 320 | imageInfo.hash = "\(data.count)" 321 | } 322 | } 323 | } 324 | 325 | if settings.isCheckingDuplicatedByContent { 326 | for (index, imageInfo1) in images.enumerated() { 327 | for i in index + 1.. 0 { 347 | exit(1) 348 | } 349 | -------------------------------------------------------------------------------- /Scripts/build.sh: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # build.sh 4 | # version 2.4.2 5 | # 6 | # Created by Sergey Balalaev on 20.08.15. 7 | # Copyright (c) 2015-2023 ByteriX. All rights reserved. 8 | # 9 | 10 | PROJECT_NAME="" 11 | CONFIGURATION_NAME="Release" 12 | SCHEME_NAME="" 13 | SETUP_VERSION=auto 14 | IS_PODS_INIT=false 15 | IS_TAG_VERSION=false 16 | HAS_BITCODE=false 17 | HAS_TESTING=false 18 | HAS_IPA_BUILD=true 19 | IS_APP_STORE=false 20 | OUTPUT_NAME="" 21 | TEAM_ID="" 22 | TEST_DESTINATION="platform=iOS Simulator,name=iPhone 13,OS=15.0" 23 | TEST_PLAN="" 24 | SRC_DIR="${PWD}" 25 | 26 | EXPORT_PLIST="" 27 | PROVISIONING_PROFILE="" #reserver 28 | 29 | USERNAME="" 30 | PASSWORD="" 31 | API_KEY="" 32 | API_ISSUER="" 33 | AUTH_KEY="" 34 | 35 | GOOGLE_SERVICE_PLIST="" 36 | 37 | # get parameters of script 38 | 39 | POSITIONAL=() 40 | 41 | #if [ "$#" -le 3 ] && [ "$1" != "-h" ]; then 42 | # echo -e '\nSomething is missing... Type "./build -h" without the quotes to find out more...\n' 43 | # exit 0 44 | #fi 45 | 46 | while [[ $# -gt 0 ]] 47 | do 48 | key="$1" 49 | 50 | case $key in 51 | -p|--project) 52 | PROJECT_NAME="$2" 53 | shift # past argument 54 | shift # past value 55 | ;; 56 | -c|--configuration) 57 | CONFIGURATION_NAME="$2" 58 | shift # past argument 59 | shift # past value 60 | ;; 61 | -s|--scheme) 62 | SCHEME_NAME="$2" 63 | shift # past argument 64 | shift # past value 65 | ;; 66 | -u|--user) 67 | USERNAME="$2" 68 | PASSWORD="$3" 69 | if [ "$PASSWORD" == "" ]; then 70 | echo "ERROR: $1 need 2 parameters" 71 | exit 72 | fi 73 | IS_APP_STORE=true 74 | shift # past argument 75 | shift # past value 1 76 | shift # past value 2 77 | ;; 78 | -key|--key) 79 | API_KEY="$2" 80 | API_ISSUER="$3" 81 | AUTH_KEY="$4" 82 | if [ "$AUTH_KEY" == "" ]; then 83 | echo "ERROR: $1 need 3 parameters" 84 | exit 85 | fi 86 | IS_APP_STORE=true 87 | shift # past argument 88 | shift # past value 1 89 | shift # past value 2 90 | shift # past value 3 91 | ;; 92 | -v|--version) 93 | SETUP_VERSION="$2" 94 | shift # past argument 95 | shift # past value 96 | ;; 97 | -o|--output) 98 | OUTPUT_NAME=$2 99 | shift # past argument 100 | shift # past value 101 | ;; 102 | -t|--team) 103 | TEAM_ID=$2 104 | shift # past argument 105 | shift # past value 106 | ;; 107 | -ep|--exportPlist) 108 | EXPORT_PLIST="$2" 109 | shift # past argument 110 | shift # past value 111 | ;; 112 | -gsp|--googlePlist) 113 | GOOGLE_SERVICE_PLIST=$2 114 | shift # past argument 115 | shift # past value 116 | ;; 117 | -ip|--initPods) 118 | IS_PODS_INIT=true 119 | shift # past argument 120 | ;; 121 | -at|--addTag) 122 | IS_TAG_VERSION=true 123 | shift # past argument 124 | ;; 125 | -bc|--bitcode) 126 | HAS_BITCODE=true 127 | shift # past argument 128 | ;; 129 | -test|--test) 130 | TEST_VALUE="" 131 | if [[ ! "$2" =~ ^- ]]; then 132 | TEST_DESTINATION="$2" 133 | TEST_VALUE="$2" 134 | fi 135 | HAS_TESTING=true 136 | HAS_IPA_BUILD=false 137 | shift # past argument 138 | if [ "$TEST_VALUE" != "" ]; then 139 | shift # past value 140 | fi 141 | ;; 142 | -tp|--testPlan) 143 | TEST_PLAN="$2" 144 | shift # past argument 145 | shift # past value 146 | ;; 147 | -h|--help) 148 | echo "" 149 | echo "Help for call build script with parameters:" 150 | echo " -p, --project : name of project or workspase. Requered param." 151 | echo " -c, --configuration : name of configuration. Default is Release." 152 | echo " -s, --scheme : name of target scheme. Default is the same as project name." 153 | echo " -u, --user : 2 params: login password. It specialized user, who created in Connection of developer programm. If defined then App will be uploaded to Store." 154 | echo " -key, --key : 3 params: apiKey, apiIssuer and path to key with p8. It specialized user, who created in Connection of developer programm. If defined then App will be uploaded to Store." 155 | echo " -v, --version : number of bundle version of the App. If has 'auto' value then will be detected from tags. Default auto." 156 | echo " -o, --output : name of out ipa file. Default is SchemeName.ipa." 157 | echo " -t, --team : team identifier of your developer program for a upload IPA to Connection AppSore. If defined -ep doesn't meater and export plist will created automaticle." 158 | echo " -ep, --exportPlist : export plist file. When team is empty has default value of AdHoc.plist or AppStore.plist when defined -t/--team." 159 | echo " -gsp, --googlePlist : path to GoogleService.plist with information for sending to Firebase/Crashlytics" 160 | echo " -ip, --initPods : If selected then will update Pods as is as from 'Pods.lock' in a start. Default is not selected." 161 | echo " -at, --addTag : If selected then will add Tag after build. Default is not selected." 162 | echo " -bc, --bitcode : If selected then will export with bitcode (when defined team). Default is not selected." 163 | echo " -test, --test : If selected then will build and run tests for special destination who you can choise. Default is not selected. Example of destination: 'platform=iOS Simulator,name=iPhone 12,OS=14.0'" 164 | echo " -tp, --testPlan : If selected then with -test will use selected test plane after that" 165 | echo "" 166 | echo "Emample: sh build.sh -p ProjectName -ip -t --version auto\n\n" 167 | exit 0 168 | ;; 169 | *) 170 | shift 171 | ;; 172 | esac 173 | done 174 | set -- "${POSITIONAL[@]}" # restore positional parameters 175 | 176 | # Common Settings 177 | 178 | 179 | # Initalize 180 | 181 | APP_BUILD_PATH="${SRC_DIR}/.build" 182 | BUILD_DIR="${APP_BUILD_PATH}/xcode" 183 | APP_CURRENT_BUILD_PATH="${APP_BUILD_PATH}/Current" 184 | APP_CONFIG_PATH="./build.config" 185 | BUILD_VERSION_TAG_GROUP_NAME="build" 186 | PROJECT_PLIST="${PROJECT_NAME}/Resources/Plists/Info.plist" 187 | 188 | # Setup skiped parameters init 189 | 190 | if [ "$PROJECT_NAME" == "" ]; then 191 | echo "ERROR: Expected project name from build parameters. Please read the help (call with -h or -help)." 192 | exit 1 193 | fi 194 | if [ "$SCHEME_NAME" == "" ]; then 195 | SCHEME_NAME=$PROJECT_NAME 196 | fi 197 | if [ "$OUTPUT_NAME" == "" ]; then 198 | OUTPUT_NAME="${SCHEME_NAME}" 199 | fi 200 | if [ "$EXPORT_PLIST" == "" ]; then 201 | if [ "$USERNAME" == "" ]; then 202 | EXPORT_PLIST="./AdHoc.plist" 203 | else 204 | EXPORT_PLIST="./AppStore.plist" 205 | fi 206 | fi 207 | 208 | if [ -f "${PROJECT_NAME}.xcworkspace/contents.xcworkspacedata" ]; then 209 | XCODE_PROJECT="-workspace ${PROJECT_NAME}.xcworkspace" 210 | echo "Using for workspace: ${XCODE_PROJECT}\n" 211 | else 212 | XCODE_PROJECT="-project ${PROJECT_NAME}.xcodeproj" 213 | echo "Start for project: ${XCODE_PROJECT}\n" 214 | fi 215 | 216 | # Setup version 217 | 218 | if [ "$SETUP_VERSION" == "auto" ]; then 219 | echo "Pulling all tags from remote" 220 | git fetch --tags --force 221 | 222 | # get templated version number as max from git tags: 223 | VERSION_TAG=$(git tag --list "${BUILD_VERSION_TAG_GROUP_NAME}/*" | awk "/$BUILD_VERSION_TAG_GROUP_NAME\/[0-9.]+$"'/{print $0}' | sed -n "s/$BUILD_VERSION_TAG_GROUP_NAME\/\(\S*\)/\1/p" | awk '{if(min==""){min=$1}; if(max==""){max=$1}; if($1>max) {max=$1}; if($1 "$APP_CONFIG_PATH" 234 | else 235 | echo "CURRENT_PROJECT_VERSION=$SETUP_VERSION" > "$APP_CONFIG_PATH" 236 | fi 237 | 238 | . "$APP_CONFIG_PATH" 239 | 240 | # Create execution 241 | 242 | checkExit(){ 243 | if [ $? != 0 ]; then 244 | echo "Building failed\n" 245 | exit 1 246 | fi 247 | } 248 | 249 | # Functions 250 | 251 | clearCurrentBuild(){ 252 | rm -rf "${BUILD_DIR}" 253 | rm -r -f -d "${APP_CURRENT_BUILD_PATH}" 254 | } 255 | 256 | createIPA() 257 | { 258 | local CONFIGURATION_NAME=$1 259 | local SCHEME_NAME=$2 260 | local EXPORT_PLIST=$3 261 | local PROVISIONING_PROFILE=$4 262 | 263 | local ACTION="clean archive" 264 | APP_DIR="${BUILD_DIR}/${CONFIGURATION_NAME}-iphoneos" 265 | APP="${APP_DIR}/${PROJECT_NAME}.app" 266 | ARCHIVE_PATH="${BUILD_DIR}/${SCHEME_NAME}.xcarchive" 267 | 268 | clearCurrentBuild 269 | mkdir -p "${APP_CURRENT_BUILD_PATH}" 270 | 271 | PROVISIONING_PROFILE_PARAMS="" 272 | if [ "${PROVISIONING_PROFILE}" != "" ]; then 273 | PROVISIONING_PROFILE_PARAMS="PROVISIONING_PROFILE=${PROVISIONING_PROFILE}" 274 | fi 275 | 276 | xcodebuild \ 277 | -allowProvisioningUpdates \ 278 | -configuration ${CONFIGURATION_NAME} $XCODE_PROJECT \ 279 | -scheme ${SCHEME_NAME} \ 280 | -sdk iphoneos \ 281 | -xcconfig "${APP_CONFIG_PATH}" BUILD_DIR="${BUILD_DIR}" \ 282 | -archivePath "${ARCHIVE_PATH}" $PROVISIONING_PROFILE_PARAMS $ACTION 283 | 284 | if [ TEAM_ID != "" ] ; then 285 | EXPORT_PLIST="${APP_CURRENT_BUILD_PATH}/Export.plist" 286 | echo "Creating export plist:" 287 | createExportPlist "${EXPORT_PLIST}" 288 | cat "$EXPORT_PLIST" 289 | checkExit 290 | echo "Export plist was created in ${EXPORT_PLIST}\n" 291 | fi 292 | 293 | checkExit 294 | echo "Creating .ipa for ${APP} ${APP_CURRENT_BUILD_PATH} ${SIGNING_IDENTITY} ${PROVISIONING_PROFILE}\n" 295 | 296 | 297 | xcodebuild \ 298 | -allowProvisioningUpdates \ 299 | -exportArchive \ 300 | -archivePath "${ARCHIVE_PATH}" \ 301 | -exportOptionsPlist "${EXPORT_PLIST}" \ 302 | -exportPath "${APP_CURRENT_BUILD_PATH}" 303 | 304 | checkExit 305 | echo "Created .ipa for ${PROJECT_NAME}\n" 306 | 307 | if [ "$GOOGLE_SERVICE_PLIST" != "" ] ; then 308 | uploadSymbolesToFirebase 309 | fi 310 | } 311 | 312 | uploadSymbolesToFirebase(){ 313 | echo "dSYMs files uploading to Firebase/Crashlytics" 314 | find "${APP_DIR}" -name "*.dSYM" | xargs -I \{\} echo \{\} 315 | find "${APP_DIR}" -name "*.dSYM" | xargs -I \{\} "${SRC_DIR}/Pods/FirebaseCrashlytics/upload-symbols" -gsp "${SRC_DIR}/${GOOGLE_SERVICE_PLIST}" -p ios \{\} 316 | checkExit 317 | echo "dSYMs uploaded to Firebase for ${PROJECT_NAME}\n" 318 | } 319 | 320 | tests(){ 321 | local TEST_ADDITION="" 322 | local RESULT_PATH="${APP_BUILD_PATH}/tests" 323 | if [ "$TEST_PLAN" == "" ]; then 324 | echo "We have not test plan" 325 | else 326 | echo "We have test plan "$TEST_PLAN"" 327 | TEST_ADDITION="-testPlan "$TEST_PLAN"" 328 | fi 329 | rm -rf "${RESULT_PATH}" 330 | echo "Strarting Tests with distination: ${TEST_DESTINATION}\n\n" 331 | local ACTION="clean build test ${TEST_ADDITION}" 332 | xcodebuild \ 333 | $ACTION \ 334 | $XCODE_PROJECT \ 335 | -scheme ${SCHEME_NAME} \ 336 | -destination "${TEST_DESTINATION}" \ 337 | -resultBundlePath "${RESULT_PATH}/${PROJECT_NAME}" 338 | 339 | checkExit 340 | echo "Tests finished for ${PROJECT_NAME}\n\n" 341 | } 342 | 343 | createIpaAndSave(){ 344 | local CONFIGURATION_NAME=$1 345 | local SCHEME_NAME=$2 346 | local EXPORT_PLIST=$3 347 | local PROVISIONING_PROFILE=$4 348 | 349 | createIPA "${CONFIGURATION_NAME}" "${SCHEME_NAME}" "${EXPORT_PLIST}" "${PROVISIONING_PROFILE}" 350 | 351 | RESULT_DIR=${APP_BUILD_PATH}/${CONFIGURATION_NAME} 352 | IPA_FILES=( ${APP_CURRENT_BUILD_PATH}/*.ipa ) 353 | IPA_FILE=${IPA_FILES[0]} 354 | echo "Found builded ipa file: ${IPA_FILE}" 355 | IPA_PATH="${RESULT_DIR}/${OUTPUT_NAME}.ipa" 356 | 357 | rm -f -d -r "${RESULT_DIR}" 358 | mkdir -p "${RESULT_DIR}" 359 | cp "${IPA_FILE}" "${IPA_PATH}" 360 | 361 | checkExit 362 | 363 | echo "IPA saved to ${IPA_PATH}" 364 | } 365 | 366 | tagCommit(){ 367 | git tag -f -a "${BUILD_VERSION_TAG_GROUP_NAME}/${CURRENT_PROJECT_VERSION}" -m build 368 | git push -f --tags 369 | checkExit 370 | echo "Tag addition complete" 371 | } 372 | 373 | createExportPlist(){ 374 | local EXPORT_PLIST=$1 375 | 376 | if $IS_APP_STORE ; then 377 | SIGNING_METHOD=app-store 378 | else 379 | SIGNING_METHOD=ad-hoc 380 | fi 381 | 382 | cat > $EXPORT_PLIST << EOL 383 | 384 | 385 | 386 | 387 | signingStyle 388 | automatic 389 | signingCertificate 390 | Apple Distribution 391 | method 392 | ${SIGNING_METHOD} 393 | teamID 394 | ${TEAM_ID} 395 | iCloudContainerEnvironment 396 | Production 397 | compileBitcode 398 | <${HAS_BITCODE}/> 399 | 400 | 401 | EOL 402 | } 403 | 404 | #reserved 405 | copyToDownload(){ 406 | # create plist for download 407 | local VERSION_NAME=`plutil -p "${PROJECT_PLIST}" | grep "CFBundleShortVersionString.*$ApplicationVersionNumber"` 408 | local VERSION=$(echo $VERSION_NAME | grep -o '"[[:digit:].]*"' | sed 's/"//g') 409 | 410 | APP_PLIST_PATH="${RESULT_DIR}/${OUTPUT_NAME}.plist" 411 | 412 | sed "s/CURRENT_PROJECT_VERSION/${VERSION}/" build_result.plist > "${APP_PLIST_PATH}" 413 | 414 | checkExit 415 | 416 | local SERVER_PATH="${HOME}/Projects/vapor" 417 | local SERVER_DOWNLOAD_PATH="${SERVER_PATH}/Public/download" 418 | 419 | cp -f "${APP_CURRENT_BUILD_PATH}/${PROJECT_NAME}.plist" "${SERVER_DOWNLOAD_PATH}/${PROJECT_NAME}.plist" 420 | cp -f "${APP_CURRENT_BUILD_PATH}/${PROJECT_NAME}.ipa" "${SERVER_DOWNLOAD_PATH}/${PROJECT_NAME}.ipa" 421 | } 422 | 423 | podSetup(){ 424 | if [ -f Podfile.lock ]; then 425 | rm -rf ~/Library/Caches/CocoaPods 426 | rm -rf Pods 427 | pod install --repo-update 428 | checkExit 429 | elif [ -f Podfile ]; then 430 | pod update 431 | checkExit 432 | fi 433 | } 434 | 435 | uploadToStoreUser(){ 436 | xcrun altool --upload-app -f "${IPA_PATH}" -t "iOS" -u $USERNAME -p $PASSWORD 437 | checkExit 438 | echo "Application uploading finished with success" 439 | } 440 | 441 | uploadToStoreKey(){ 442 | KEYS_DIR="${PWD}/private_keys" 443 | rm -rf "${KEYS_DIR}" 444 | mkdir -p "${KEYS_DIR}" 445 | cp -f "${AUTH_KEY}" "${KEYS_DIR}/AuthKey_${API_KEY}.p8" 446 | xcrun altool --upload-app -f "${IPA_PATH}" -t "iOS" --apiKey "${API_KEY}" --apiIssuer "${API_ISSUER}" 447 | checkExit 448 | echo "Application uploading finished with success" 449 | } 450 | 451 | if $IS_PODS_INIT ; then 452 | echo "Starting pod init:" 453 | podSetup 454 | checkExit 455 | fi 456 | 457 | # General part of building: 458 | 459 | echo "Starting build script with parameters:" 460 | echo "PROJECT_NAME = ${PROJECT_NAME}" 461 | echo "CONFIGURATION_NAME = ${CONFIGURATION_NAME}" 462 | echo "SCHEME_NAME = ${SCHEME_NAME}" 463 | echo "OUTPUT_NAME = ${OUTPUT_NAME}" 464 | cat "$APP_CONFIG_PATH" 465 | 466 | if $HAS_TESTING ; then 467 | tests 468 | fi 469 | 470 | if $HAS_IPA_BUILD ; then 471 | createIpaAndSave "${CONFIGURATION_NAME}" "${SCHEME_NAME}" "${EXPORT_PLIST}" "${PROVISIONING_PROFILE}" 472 | fi 473 | 474 | if [ "$USERNAME" != "" ] ; then 475 | echo "" 476 | echo "Starting upload to store:" 477 | echo "USERNAME = ${USERNAME}" 478 | 479 | uploadToStoreUser 480 | elif [ "$API_KEY" != "" ] ; then 481 | echo "" 482 | echo "Starting upload to store:" 483 | echo "API KEY = ${API_KEY}" 484 | 485 | uploadToStoreKey 486 | fi 487 | 488 | if $IS_TAG_VERSION ; then 489 | echo "Starting addition tag:" 490 | tagCommit 491 | fi 492 | 493 | clearCurrentBuild 494 | -------------------------------------------------------------------------------- /Sources/ImagelinterExec/ImageInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageInfo.swift 3 | // 4 | // 5 | // Created by Sergey Balalaev on 03.04.2024. 6 | // 7 | 8 | import Foundation 9 | import AppKit 10 | 11 | struct AssetContents: Decodable { 12 | let images: [Image] 13 | struct Image: Decodable { 14 | let filename: String? 15 | let scale: String? 16 | } 17 | } 18 | 19 | struct FolderContents: Decodable { 20 | let properties: Properties? 21 | struct Properties: Decodable { 22 | let isNamespace: Bool 23 | 24 | enum CodingKeys: String, CodingKey { 25 | case isNamespace = "provides-namespace" 26 | } 27 | } 28 | } 29 | 30 | func load(_ type: T.Type, dir: String, for folder: String) -> T? { 31 | let contentsPath = dir + folder + "/Contents.json" 32 | guard let contentsData = NSData(contentsOfFile: contentsPath) as? Data else { 33 | return nil 34 | } 35 | return try? JSONDecoder().decode(type, from: contentsData) 36 | } 37 | 38 | let imagesetExtension = ".imageset" 39 | let appIconExtension = ".appiconset" 40 | let assetExtension = ".xcassets" 41 | 42 | class ImageInfo { 43 | struct File { 44 | // needs concatinate with ImageInfo.dir 45 | let path: String 46 | // if nil that vector-universal 47 | let scale: Int? 48 | 49 | init(path: String, scale: Int?) { 50 | self.path = path 51 | self.scale = scale 52 | } 53 | } 54 | 55 | enum ImageType { 56 | case undefined 57 | case vector 58 | case rastor 59 | case mixed 60 | } 61 | 62 | let name: String 63 | // dir for current Asset 64 | let dir: String 65 | var files: [File] 66 | 67 | var hash: String = "" 68 | 69 | var type: ImageType = .undefined 70 | 71 | private func setAndCheckType(newType: ImageType, filePath: String){ 72 | if type != .undefined, newType != type { 73 | printError( 74 | filePath: filePath, 75 | message: "The image with name '\(name)' has different types of files: \(newType) and \(type)" 76 | ) 77 | type = .mixed 78 | } else { 79 | type = newType 80 | } 81 | } 82 | 83 | private init(name: String, dir: String, path: String, scale: Int?) { 84 | self.name = name 85 | self.dir = dir 86 | files = [File(path: path, scale: scale)] 87 | } 88 | 89 | static func processFound(dir: String, path: String) -> ImageInfo? { 90 | var isAsset = false 91 | var folderName = "" 92 | let components = path.split(separator: "/") 93 | for (index, component) in components.enumerated() { 94 | if component.hasSuffix(assetExtension) { 95 | isAsset = true 96 | } else { 97 | if isAsset == false { // only for asset 98 | continue 99 | } 100 | if component.hasSuffix(imagesetExtension) { // it is asset 101 | let name = (component as NSString).substring(to: component.count - imagesetExtension.count) 102 | if let contents = load(AssetContents.self, dir: dir, for: components[0.. ImageInfo { 136 | if let existImage = foundedImages[name] { 137 | existImage.files.append(File(path: path, scale: scale)) 138 | return existImage 139 | } else { 140 | let result = ImageInfo(name: name, dir: dir, path: path, scale: scale) 141 | foundedImages[name] = result 142 | if isSwiftGen { 143 | foundedSwiftGenMirrorImages[name.swiftGenKey()] = name 144 | } 145 | return result 146 | } 147 | } 148 | 149 | private static func nameOfImageFile(path: String) -> (path: String, scale: Int) { 150 | return pathOfImageFile(path: (path as NSString).lastPathComponent) 151 | } 152 | 153 | private static func pathOfImageFile(path: String) -> (path: String, scale: Int) { 154 | var name = (path as NSString).deletingPathExtension 155 | var scale = 1 156 | for imageScale in allImageScales { 157 | let scaleSuffix = "@\(imageScale)x" 158 | if name.hasSuffix(scaleSuffix) { 159 | name = String(name.dropLast(scaleSuffix.count)) 160 | scale = imageScale 161 | break 162 | } 163 | } 164 | return (name, scale) 165 | } 166 | 167 | private static func isTheSameImage(path1: String, path2: String) -> Bool { 168 | pathOfImageFile(path: path1).path == pathOfImageFile(path: path2).path 169 | } 170 | 171 | private var assetPath: String? { 172 | var result: String? 173 | for imageFile in files { 174 | let components = imageFile.path.split(separator: "/") 175 | if components.isEmpty { // it just image 176 | return nil 177 | } else { 178 | for component in components { 179 | if component.hasSuffix(imagesetExtension) { // it is asset 180 | var name = (imageFile.path as NSString).components(separatedBy: imagesetExtension).first ?? "" 181 | name = name + imagesetExtension 182 | if let result = result { 183 | if name != result { 184 | return nil 185 | } 186 | } else { 187 | result = name 188 | } 189 | } 190 | } 191 | } 192 | } 193 | return result 194 | } 195 | 196 | func error(with message: String) { 197 | for file in files { 198 | let imageFilePath = dir + file.path 199 | printError(filePath: imageFilePath, message: message) 200 | guard settings.isAllFilesErrorShowing else { 201 | break 202 | } 203 | } 204 | } 205 | 206 | func checkDuplicateByName() { 207 | guard files.count > 1 else { 208 | return 209 | } 210 | if assetPath == nil { 211 | var isDifferentImages = false 212 | for file in files { 213 | if !Self.isTheSameImage(path1: files.first?.path ?? "", path2: file.path) { 214 | isDifferentImages = true 215 | break 216 | } 217 | } 218 | if isDifferentImages { 219 | error(with: "Duplicated image with name: '\(name)'") 220 | } 221 | } 222 | } 223 | 224 | private static let svgSearchWidthHeightRegex = try! NSRegularExpression(pattern: #""#, options: []) 225 | private static let svgSearchHeightWidthRegex = try! NSRegularExpression(pattern: #""#, options: []) 226 | 227 | func checkImageSizeAndDetectType() { 228 | var scaledSize: (width: Int, height: Int)? 229 | for file in files { 230 | let imageFilePath = dir + file.path 231 | if let image = NSImage(contentsOfFile: imageFilePath) { 232 | let pixelSize = image.pixelSize ?? NSSize() 233 | let size = image.size 234 | if pixelSize.height == 0, pixelSize.width == 0 { 235 | if size.height != 0, size.width != 0 { 236 | setAndCheckType(newType: .vector, filePath: imageFilePath) 237 | // it's okey just vector image 238 | // but can problems 239 | if let scale = file.scale { 240 | printError( 241 | filePath: imageFilePath, 242 | message: "It is vector image. But it has scale = \(scale). Found for image '\(name)'", 243 | isWarning: true 244 | ) 245 | } 246 | if size.width > settings.maxVectorImageSize.width || size.height > settings.maxVectorImageSize.height { 247 | printError( 248 | filePath: imageFilePath, 249 | message: "The vector image has very biggest image size (\(size.width), \(size.height)). Max image size for vector is (\(settings.maxVectorImageSize.width), \(settings.maxVectorImageSize.height)). Found for image '\(name)'" 250 | ) 251 | } 252 | } else { 253 | printError(filePath: imageFilePath, message: "Image has zero size. Found for image '\(name)'", isWarning: true) 254 | } 255 | } else { 256 | if let scale = file.scale { 257 | setAndCheckType(newType: .rastor, filePath: imageFilePath) 258 | if Int(pixelSize.width) % scale != 0 || Int(pixelSize.height) % scale != 0 { 259 | let newScaledSize: (width: Double, height: Double) = (Double(pixelSize.width) / Double(scale), Double(pixelSize.height) / Double(scale)) 260 | printError( 261 | filePath: imageFilePath, 262 | message: "Image has floating size from scaled images. Real size is \(pixelSize) and scale = \(scale). Please check the file, it must have integer size after apply this scale. But you actually have \(newScaledSize). Found for image '\(name)'." 263 | ) 264 | } else { 265 | let newScaledSize: (width: Int, height: Int) = (Int(pixelSize.width) / scale, Int(pixelSize.height) / scale) 266 | if let scaledSize = scaledSize { 267 | if scaledSize != newScaledSize { 268 | printError( 269 | filePath: imageFilePath, 270 | message: "Image has different size for scaled group. Real size is \(pixelSize) with scale = \(scale) but expected \(NSSize(width: scaledSize.0 * scale, height: scaledSize.1 * scale)). Found for image '\(name)'" 271 | ) 272 | } 273 | } else { 274 | scaledSize = newScaledSize 275 | } 276 | if CGFloat(newScaledSize.width) > settings.maxRastorImageSize.width || CGFloat(newScaledSize.height) > settings.maxRastorImageSize.height{ 277 | printError( 278 | filePath: imageFilePath, 279 | message: "The rastor image has very biggest image size (\(newScaledSize.width), \(newScaledSize.height)). Max image size for rastor is (\(settings.maxRastorImageSize.width), \(settings.maxRastorImageSize.height)). Found for image '\(name)'" 280 | ) 281 | } 282 | } 283 | } 284 | } 285 | } else if imageFilePath.uppercased().hasSuffix("SVG") { // NSImage can not support SVG files. You can use only from Assets 286 | setAndCheckType(newType: .vector, filePath: imageFilePath) 287 | if let scale = file.scale { 288 | printError( 289 | filePath: imageFilePath, 290 | message: "It is vector image. But it has scale = \(scale). Found for image '\(name)'", 291 | isWarning: true 292 | ) 293 | } 294 | 295 | // Need parse SVG and extract width / height for checking 296 | // examples: 297 | // vector: 298 | // rastor: 299 | if settings.isCheckingImageSize { 300 | if let string = try? String(contentsOfFile: imageFilePath, encoding: .ascii) { 301 | let range = NSRange(location: 0, length: string.count) 302 | 303 | if let result = Self.svgSearchWidthHeightRegex.firstMatch(in: string, options: [], range: range) { 304 | print("VALUE!!!") 305 | let _ = (1...result.numberOfRanges - 1).map { index in 306 | let value = (string as NSString).substring(with: result.range(at: index)) 307 | print("VALUE=\(value)") 308 | } 309 | } 310 | } else { 311 | printError(filePath: imageFilePath, message: "Can not parse SVG file. Found for image '\(name)'") 312 | } 313 | } 314 | } else { 315 | printError(filePath: imageFilePath, message: "That is not image. Found for image '\(name)'", isWarning: true) 316 | } 317 | } 318 | if type == .vector, files.count > 1 { 319 | printError( 320 | filePath: files.first?.path ?? "", 321 | message: "The vector image with name '\(name)' has \(files.count) files", 322 | isWarning: true 323 | ) 324 | } else if type == .rastor { 325 | // Analysis scales with dependency on target platforms 326 | let currentScalesDictionary : Dictionary = files.reduce(Dictionary()) { result, file in 327 | if let scale = file.scale { 328 | var newResult = result 329 | newResult[scale] = file 330 | return newResult 331 | } else { 332 | printError( 333 | filePath: file.path, 334 | message: "The rastor image with name '\(name)' has undefined scale. May be it's vector?", 335 | isWarning: true 336 | ) 337 | } 338 | return result 339 | } 340 | let currentScales = Set(currentScalesDictionary.keys) 341 | let extraScales = currentScales.subtracting(targetScales) 342 | for extraScale in extraScales { 343 | printError( 344 | filePath: currentScalesDictionary[extraScale]?.path ?? "", 345 | message: "The rastor image with name '\(name)' has extra scale=\(extraScale) for current platforms target (\(settings.targetPlatforms)).", 346 | isWarning: true 347 | ) 348 | } 349 | let missingScales = targetScales.subtracting(currentScales) 350 | if missingScales.count > 0 { 351 | printError( 352 | filePath: files.first?.path ?? "", 353 | message: "The rastor image with name '\(name)' has missing scale=\(missingScales). You need add images with this scales for correct showing at selected target platforms = \(settings.targetPlatforms).", 354 | isWarning: true 355 | ) 356 | } 357 | } 358 | } 359 | 360 | func calculateData() -> Data? { 361 | var maxScale = 0 362 | var result: Data? 363 | for file in files { 364 | let imageFilePath = dir + file.path 365 | if let image = NSImage(contentsOfFile: imageFilePath), let pixelSize = image.pixelSize { 366 | let size = image.size 367 | if pixelSize.height == 0, pixelSize.width == 0 { 368 | if size.height != 0, size.width != 0 { 369 | // it's okey just vector image 370 | var imageRect = CGRect(x: 0, y: 0, width: size.width, height: size.height) 371 | let cgImage = image.cgImage(forProposedRect: &imageRect, context: nil, hints: nil) 372 | if let data = cgImage?.png { 373 | result = data 374 | maxScale = 1 375 | } 376 | } 377 | } else { 378 | if let scale = file.scale { 379 | // calculate hash 380 | if maxScale < scale { 381 | var imageRect = CGRect( 382 | x: 0, 383 | y: 0, 384 | width: Int(pixelSize.width) / scale, 385 | height: Int(pixelSize.height) / scale 386 | ) 387 | let cgImage = image.cgImage(forProposedRect: &imageRect, context: nil, hints: nil) 388 | if let data = cgImage?.png { 389 | result = data 390 | maxScale = scale 391 | } 392 | } 393 | } 394 | } 395 | } 396 | } 397 | return result 398 | } 399 | } 400 | --------------------------------------------------------------------------------