Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
uuid = "F5F310FE-0BEF-47E0-8330-8291E799CB26"
type = "1"
version = "2.0">
</Bucket>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>SQAssetsValidator.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>Spectre (Playground).xcscheme</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>SQAssetsValidatort</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>
61 changes: 61 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -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"
),
]
)
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
```
40 changes: 40 additions & 0 deletions Sources/AssetsValidator/Input/Params.swift
Original file line number Diff line number Diff line change
@@ -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?
}
36 changes: 36 additions & 0 deletions Sources/AssetsValidator/Input/ParamsReader.swift
Original file line number Diff line number Diff line change
@@ -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)!)
}
}
62 changes: 62 additions & 0 deletions Sources/AssetsValidator/Subcommands/DimensionsValidator.swift
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
}
79 changes: 79 additions & 0 deletions Sources/AssetsValidator/Subcommands/IconValidator.swift
Original file line number Diff line number Diff line change
@@ -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)..<range.upperBound])
.trimmingCharacters(in: .whitespaces)

let components = rawNamedEntry.split(separator: ":")
let cleanedName = components.last?
.replacingOccurrences(of: "\"", with: "")
.replacingOccurrences(of: ",", with: "")
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""

named.append(cleanedName)
}

var notFoundResources = ""
named.forEach { name in
let urlIcon = URL(
fileURLWithPath: "\(params.icon?.iconPath ?? "")/\(name).imageset",
isDirectory: true
)

let urlIllustration = URL(
fileURLWithPath: "\(params.icon?.illustrationsPath ?? "")/\(name).imageset",
isDirectory: true
)

if params.icon?.ignore?.contains(name) ?? false {
return
}

if !FileManager().fileExists(atPath: urlIcon.path()) &&
!FileManager().fileExists(atPath: urlIllustration.path()) {

notFoundResources.append("not found: \(name)\n")
}
}

if notFoundResources.isEmpty {
logger.info("Icon resources validate")
} else {
logger.error(Logger.Message(stringLiteral: notFoundResources))
fatalError()
}
}
}
}
Loading