From 8949ef993c4f9c0a08f47addab6d33a03413ac63 Mon Sep 17 00:00:00 2001 From: Ivan Mikhailovskii Date: Tue, 22 Jul 2025 18:11:48 +0700 Subject: [PATCH] no message --- .../contents.xcworkspacedata | 7 ++ .../UserInterfaceState.xcuserstate | Bin 0 -> 68903 bytes .../xcdebugger/Breakpoints_v2.xcbkptlist | 6 ++ .../xcschemes/xcschememanagement.plist | 27 ++++++ Package.resolved | 61 ++++++++++++++ Package.swift | 39 +++++++++ README.md | 23 +++++ Sources/AssetsValidator/Input/Params.swift | 40 +++++++++ .../AssetsValidator/Input/ParamsReader.swift | 36 ++++++++ .../Subcommands/DimensionsValidator.swift | 62 ++++++++++++++ .../Subcommands/IconValidator.swift | 79 ++++++++++++++++++ .../Subcommands/PalleteValidator.swift | 79 ++++++++++++++++++ .../Subcommands/TypographyValidator.swift | 72 ++++++++++++++++ Sources/AssetsValidator/main.swift | 28 +++++++ Sources/Utils/UtilsJson.swift | 30 +++++++ 15 files changed, 589 insertions(+) create mode 100644 .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata create mode 100644 .swiftpm/xcode/package.xcworkspace/xcuserdata/ivanmikhailovskii.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 .swiftpm/xcode/xcuserdata/ivanmikhailovskii.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist create mode 100644 .swiftpm/xcode/xcuserdata/ivanmikhailovskii.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 Package.resolved create mode 100644 Package.swift create mode 100644 Sources/AssetsValidator/Input/Params.swift create mode 100644 Sources/AssetsValidator/Input/ParamsReader.swift create mode 100644 Sources/AssetsValidator/Subcommands/DimensionsValidator.swift create mode 100644 Sources/AssetsValidator/Subcommands/IconValidator.swift create mode 100644 Sources/AssetsValidator/Subcommands/PalleteValidator.swift create mode 100644 Sources/AssetsValidator/Subcommands/TypographyValidator.swift create mode 100644 Sources/AssetsValidator/main.swift create mode 100644 Sources/Utils/UtilsJson.swift diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/ivanmikhailovskii.xcuserdatad/UserInterfaceState.xcuserstate b/.swiftpm/xcode/package.xcworkspace/xcuserdata/ivanmikhailovskii.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..fff09f328672255fdc3b4afe1a93f49ebe7e2b13 GIT binary patch literal 68903 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 ON>TBx^6&rabN>s5gVFc^ literal 0 HcmV?d00001 diff --git a/.swiftpm/xcode/xcuserdata/ivanmikhailovskii.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/.swiftpm/xcode/xcuserdata/ivanmikhailovskii.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..cbadbdc --- /dev/null +++ b/.swiftpm/xcode/xcuserdata/ivanmikhailovskii.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/.swiftpm/xcode/xcuserdata/ivanmikhailovskii.xcuserdatad/xcschemes/xcschememanagement.plist b/.swiftpm/xcode/xcuserdata/ivanmikhailovskii.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..505af2a --- /dev/null +++ b/.swiftpm/xcode/xcuserdata/ivanmikhailovskii.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,27 @@ + + + + + SchemeUserState + + SQAssetsValidator.xcscheme_^#shared#^_ + + orderHint + 0 + + Spectre (Playground).xcscheme + + orderHint + 1 + + + SuppressBuildableAutocreation + + SQAssetsValidatort + + primary + + + + + diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..c9bbd1f --- /dev/null +++ b/Package.resolved @@ -0,0 +1,61 @@ +{ + "object": { + "pins": [ + { + "package": "PathKit", + "repositoryURL": "https://github.com/kylef/PathKit.git", + "state": { + "branch": null, + "revision": "3bfd2737b700b9a36565a8c94f4ad2b050a5e574", + "version": "1.0.1" + } + }, + { + "package": "Spectre", + "repositoryURL": "https://github.com/kylef/Spectre.git", + "state": { + "branch": null, + "revision": "26cc5e9ae0947092c7139ef7ba612e34646086c7", + "version": "0.10.1" + } + }, + { + "package": "Stencil", + "repositoryURL": "https://github.com/stencilproject/Stencil.git", + "state": { + "branch": null, + "revision": "4f222ac85d673f35df29962fc4c36ccfdaf9da5b", + "version": "0.15.1" + } + }, + { + "package": "swift-argument-parser", + "repositoryURL": "https://github.com/apple/swift-argument-parser", + "state": { + "branch": null, + "revision": "6b2aa2748a7881eebb9f84fb10c01293e15b52ca", + "version": "0.5.0" + } + }, + { + "package": "swift-log", + "repositoryURL": "https://github.com/apple/swift-log.git", + "state": { + "branch": null, + "revision": "3d8596ed08bd13520157f0355e35caed215ffbfa", + "version": "1.6.3" + } + }, + { + "package": "Yams", + "repositoryURL": "https://github.com/jpsim/Yams.git", + "state": { + "branch": null, + "revision": "9ff1cc9327586db4e0c8f46f064b6a82ec1566fa", + "version": "4.0.6" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..6ca9287 --- /dev/null +++ b/Package.swift @@ -0,0 +1,39 @@ +// swift-tools-version:5.7 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "SQAssetsValidator", + platforms: [ + .macOS(.v13), + ], + products: [ + .executable(name: "SQAssetsValidatort", targets: ["SQAssetsValidator"]) + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-argument-parser", from: "0.3.0"), + .package(url: "https://github.com/jpsim/Yams.git", from: "4.0.0"), + .package(url: "https://github.com/apple/swift-log.git", from: "1.4.0"), + .package(url: "https://github.com/stencilproject/Stencil.git", from: "0.14.0"), + ], + targets: [ + // Main target + .target( + name: "SQAssetsValidator", + dependencies: [ + "Utils", + .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "Yams", package: "Yams"), + .product(name: "Logging", package: "swift-log") + ], + path: "./Sources/AssetsValidator" + ), + + // Shared target + .target( + name: "Utils", + path: "./Sources/Utils" + ), + ] +) diff --git a/README.md b/README.md index 6977727..4351fe6 100644 --- a/README.md +++ b/README.md @@ -1 +1,24 @@ # SQAssetsValidator + +validator-config.yaml + +``` +icon: + graphicsFilePath: ./SQCoreGraphics.swift + iconPath: ./Resources/Assets/Assets.xcassets/Icons + illustrationsPath: ./Resources/Assets/Assets.xcassets/Illustrations + ignore: + - imgHeaderLogo + - imgSplashLogoSmall + - icNotification + - icNotificationMini +palette: + folderFilePath: ./Palette + colorPath: ./Resources/Assets/Colors.xcassets/Colors +typography: + resourcesJSON: ./Resources/Core/core_typography.json + folderFilesPath: ./Styles +dimension: + resourcesJSON: ./Resources/Core/core_dimensions.json + dimensionsFilePath: ./SQCoreDimensions.swift +``` diff --git a/Sources/AssetsValidator/Input/Params.swift b/Sources/AssetsValidator/Input/Params.swift new file mode 100644 index 0000000..c2dc163 --- /dev/null +++ b/Sources/AssetsValidator/Input/Params.swift @@ -0,0 +1,40 @@ +// +// Params.swift +// SQAssetsValidator +// +// Created by Ivan Mikhailovskii on 22.07.2025. +// + +struct Params: Decodable { + + struct Palette: Decodable { + let folderFilePath: String + let ignore: [String]? + let colorPath: String + } + + struct Icon: Decodable { + let graphicsFilePath: String + let iconPath: String + let illustrationsPath: String + let ignore: [String]? + } + + struct Typography: Decodable { + let folderFilesPath: String + let resourcesJSON: String + let ignore: [String]? + } + + struct Dimension: Decodable { + let dimensionsFilePath: String + let resourcesJSON: String + let ignore: [String]? + } + + let sqCorePath: String? + let icon: Icon? + let palette: Palette? + let typography: Typography? + let dimension: Dimension? +} diff --git a/Sources/AssetsValidator/Input/ParamsReader.swift b/Sources/AssetsValidator/Input/ParamsReader.swift new file mode 100644 index 0000000..8362899 --- /dev/null +++ b/Sources/AssetsValidator/Input/ParamsReader.swift @@ -0,0 +1,36 @@ +// +// ParceParams.swift +// SQAssetsValidator +// +// Created by Ivan Mikhailovskii on 22.07.2025. +// + +import Foundation +import Yams + +final class ParamsReader { + + private let fileManager: FileManager + private let inputPath: String + + init( + inputPath: String = "./validator-config.yaml", + fileManager: FileManager = .default + ) { + self.inputPath = inputPath + self.fileManager = fileManager + } + + func read() throws -> Params { + return try readParams(filePath: inputPath) + } + + private func readParams( + filePath: String + ) throws -> Params { + let data = try Data(contentsOf: URL(fileURLWithPath: filePath)) + + let decoder = YAMLDecoder() + return try decoder.decode(Params.self, from: String(data: data, encoding: .utf8)!) + } +} diff --git a/Sources/AssetsValidator/Subcommands/DimensionsValidator.swift b/Sources/AssetsValidator/Subcommands/DimensionsValidator.swift new file mode 100644 index 0000000..ef110d4 --- /dev/null +++ b/Sources/AssetsValidator/Subcommands/DimensionsValidator.swift @@ -0,0 +1,62 @@ +// +// DimensionsValidator.swift +// SQAssetsValidator +// +// Created by Ivan Mikhailovskii on 22.07.2025. +// + +import Foundation +import ArgumentParser +import Logging +import Utils + +extension SQAssetsValidatorCommand { + + struct DimensionsValidator: ParsableCommand { + + static let configuration = CommandConfiguration( + commandName: "dimensions", + abstract: "Validate dimensions from project", + discussion: "Validate dimensions from project" + ) + + func run() throws { + let logger = Logger(label: "assets-validator") + let params = try ParamsReader().read() + + let url = URL(fileURLWithPath: params.dimension?.resourcesJSON ?? "") + let dictonary = try? UtilsJson.loadJSON(from: url) + + var named = [String]() + + dictonary?.keys.forEach { + named.append($0) + } + + let urlDimensions = URL(fileURLWithPath: params.dimension?.dimensionsFilePath ?? "") + + guard let data = try? Data(contentsOf: urlDimensions) + else { fatalError() } + + let fileContent = String(decoding: data, as: UTF8.self) + + var notFoundResources = "" + named.forEach { name in + if params.dimension?.ignore?.contains(name) ?? false { + return + } + + if !fileContent.contains(name) { + notFoundResources.append("not found: \(name)\n") + } + } + + if notFoundResources.isEmpty { + logger.info("Dimensions resources validate") + } else { + logger.error(Logger.Message(stringLiteral: notFoundResources)) + fatalError() + } + } + } +} diff --git a/Sources/AssetsValidator/Subcommands/IconValidator.swift b/Sources/AssetsValidator/Subcommands/IconValidator.swift new file mode 100644 index 0000000..712548b --- /dev/null +++ b/Sources/AssetsValidator/Subcommands/IconValidator.swift @@ -0,0 +1,79 @@ +// +// IconValidator.swift +// SQAssetsValidator +// +// Created by Ivan Mikhailovskii on 21.07.2025. +// + +import Foundation +import ArgumentParser +import Logging + +extension SQAssetsValidatorCommand { + + struct IconValidator: ParsableCommand { + + static let configuration = CommandConfiguration( + commandName: "icons", + abstract: "Validate icons from project", + discussion: "Validate icons from project" + ) + + func run() throws { + let logger = Logger(label: "assets-validator") + let params = try ParamsReader().read() + + let url = URL(fileURLWithPath: "\(params.icon?.graphicsFilePath ?? "")") + guard let data = try? Data(contentsOf: url) + else { return } + + let fileContent = String(decoding: data, as: UTF8.self) + + let namedRanges = fileContent.ranges(of: #/\s+named:\s+"(\w+)",/#) + + var named = [String]() + namedRanges.forEach { range in + let rawNamedEntry = String(fileContent[fileContent.index(after: range.lowerBound).. [String: Any] { + let data = try? Data(contentsOf: url) + + guard let data, + let json = try? JSONSerialization.jsonObject(with: data, options: []), + let dictionary = json as? [String: Any] else { + + throw JSONError.parsingFailed + } + + return dictionary + } +}