All skills

Code generator skills that produce production-ready Swift code for common app components. Use when user wants to add logging, analytics, onboarding, review prompts, networking, authentication, paywalls, settings, persistence, error monitoring, CI/CD pipelines, localization, push notifications, deep linking, testing, accessibility, widgets, feature flags, app icons, image caching, pagination, HTTP caching, share cards, social export, subscription lifecycle, referral systems, watermarks, streak tracking, milestone celebrations, what's new screens, lapsed user re-engagement, usage insights, variable rewards, consent flows, account deletion, permission priming, force updates, state restoration, debug menus, offline queues, feedback forms, announcement banners, quick win sessions, Spotlight indexing, App Clips, screenshot automation, background processing, app extensions, data export, or SwiftUI preview sample data and variant matrices.

Use this Skill: https://skilld.dev/gh/rshankras/claude-code-apple-skills/generators

This session only. Nothing lands on disk.

data-exporttemplates.md

≈7.5k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Data Export Code Templates

Production-ready Swift templates for data export and import infrastructure. All code targets iOS 16+ / macOS 13+ and uses modern Swift concurrency. No third-party dependencies.

DataExportable.swift

import Foundation

/// Protocol for models that support data export in multiple formats.
///
/// Conform your data models to this protocol to enable JSON, CSV,
/// and PDF export through `DataExportManager`.
///
/// Usage:
/// ```swift
/// struct Expense: Codable, DataExportable {
///     let id: UUID
///     let title: String
///     let amount: Double
///     let date: Date
///     let category: String
///
///     static var csvHeaders: [String] {
///         ["ID", "Title", "Amount", "Date", "Category"]
///     }
///
///     var csvRow: [String] {
///         [id.uuidString, title, String(format: "%.2f", amount),
///          ISO8601DateFormatter().string(from: date), category]
///     }
///
///     var pdfDescription: String {
///         "\(title) - $\(String(format: "%.2f", amount)) (\(category))"
///     }
/// }
/// ```
protocol DataExportable: Codable {
    /// Column headers for CSV export.
    static var csvHeaders: [String] { get }

    /// Values for a single CSV row, matching the order of `csvHeaders`.
    var csvRow: [String] { get }

    /// A human-readable description for PDF report line items.
    var pdfDescription: String { get }
}

DataExportManager.swift

import Foundation
import os

/// Central coordinator for exporting data in multiple formats.
///
/// Routes export requests to the appropriate format-specific exporter
/// and returns a file URL suitable for sharing via UIActivityViewController
/// or SwiftUI ShareLink.
///
/// Usage:
/// ```swift
/// let url = try await DataExportManager.shared.export(
///     expenses, format: .csv, filename: "expenses"
/// )
/// // Present url via share sheet
/// ```
final class DataExportManager: Sendable {
    static let shared = DataExportManager()

    private let logger = Logger(
        subsystem: Bundle.main.bundleIdentifier ?? "com.app",
        category: "DataExport"
    )

    /// Supported export formats.
    enum ExportFormat: String, CaseIterable, Identifiable {
        case json
        case csv
        case pdf

        var id: String { rawValue }

        /// File extension for this format.
        var fileExtension: String {
            switch self {
            case .json: return "json"
            case .csv: return "csv"
            case .pdf: return "pdf"
            }
        }

        /// MIME type for this format.
        var mimeType: String {
            switch self {
            case .json: return "application/json"
            case .csv: return "text/csv"
            case .pdf: return "application/pdf"
            }
        }

        /// Display name for UI.
        var displayName: String {
            switch self {
            case .json: return "JSON"
            case .csv: return "CSV"
            case .pdf: return "PDF"
            }
        }
    }

    /// Export an array of items in the specified format.
    ///
    /// - Parameters:
    ///   - items: The items to export.
    ///   - format: The output format (JSON, CSV, or PDF).
    ///   - filename: The base filename (without extension).
    /// - Returns: A URL to the exported file in the temporary directory.
    func export<T: DataExportable>(
        _ items: [T],
        format: ExportFormat,
        filename: String
    ) async throws -> URL {
        let data: Data

        switch format {
        case .json:
            data = try exportAsJSON(items)
        case .csv:
            let csvString = CSVExporter.generate(
                from: items,
                headers: T.csvHeaders,
                rowMapper: { $0.csvRow }
            )
            guard let csvData = csvString.data(using: .utf8) else {
                throw ExportError.encodingFailed
            }
            data = csvData
        case .pdf:
            data = PDFExporter.generateReport(
                title: filename.capitalized,
                items: items.map { $0.pdfDescription }
            )
        }

        let url = temporaryFileURL(filename: filename, format: format)
        try data.write(to: url, options: .atomic)

        logger.info("Exported \(items.count) items as \(format.rawValue) to \(url.lastPathComponent)")
        return url
    }

    /// Export raw Codable data as JSON (for GDPR full export).
    ///
    /// - Parameters:
    ///   - value: Any Encodable value to export.
    ///   - filename: The base filename.
    /// - Returns: A URL to the exported JSON file.
    func exportJSON<T: Encodable>(_ value: T, filename: String) throws -> URL {
        let encoder = JSONEncoder()
        encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
        encoder.dateEncodingStrategy = .iso8601
        let data = try encoder.encode(value)

        let url = temporaryFileURL(filename: filename, format: .json)
        try data.write(to: url, options: .atomic)

        return url
    }

    // MARK: - Private

    private func exportAsJSON<T: Encodable>(_ items: [T]) throws -> Data {
        let encoder = JSONEncoder()
        encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
        encoder.dateEncodingStrategy = .iso8601
        return try encoder.encode(items)
    }

    private func temporaryFileURL(filename: String, format: ExportFormat) -> URL {
        let sanitizedName = filename.replacingOccurrences(of: " ", with: "-")
        let timestamp = ISO8601DateFormatter().string(from: Date())
            .replacingOccurrences(of: ":", with: "-")
        return FileManager.default.temporaryDirectory
            .appendingPathComponent("\(sanitizedName)-\(timestamp).\(format.fileExtension)")
    }

    /// Export errors.
    enum ExportError: LocalizedError {
        case encodingFailed
        case renderingFailed
        case noData

        var errorDescription: String? {
            switch self {
            case .encodingFailed: return "Failed to encode data for export"
            case .renderingFailed: return "Failed to render PDF"
            case .noData: return "No data to export"
            }
        }
    }
}

CSVExporter.swift

import Foundation

/// CSV generation and parsing with proper field escaping.
///
/// Handles special characters in field values:
/// - Fields containing commas are wrapped in double quotes
/// - Fields containing double quotes have quotes doubled (RFC 4180)
/// - Fields containing newlines are wrapped in double quotes
///
/// Usage:
/// ```swift
/// let csv = CSVExporter.generate(
///     from: expenses,
///     headers: ["Title", "Amount", "Category"],
///     rowMapper: { [$0.title, String($0.amount), $0.category] }
/// )
/// ```
enum CSVExporter {

    /// Generate a CSV string from an array of items.
    ///
    /// - Parameters:
    ///   - items: The source items to export.
    ///   - headers: Column header names.
    ///   - rowMapper: Closure that converts each item to an array of string values.
    /// - Returns: A complete CSV string with headers and data rows.
    static func generate<T>(
        from items: [T],
        headers: [String],
        rowMapper: (T) -> [String]
    ) -> String {
        var lines: [String] = []

        // Header row
        lines.append(headers.map { escapeField($0) }.joined(separator: ","))

        // Data rows
        for item in items {
            let values = rowMapper(item)
            let escapedValues = values.map { escapeField($0) }
            lines.append(escapedValues.joined(separator: ","))
        }

        return lines.joined(separator: "\n")
    }

    /// Generate a CSV string using column definitions.
    ///
    /// A more declarative API where columns are defined with name and value extractor.
    ///
    /// Usage:
    /// ```swift
    /// let csv = CSVExporter.generate(from: expenses, columns: [
    ///     ("Title", { $0.title }),
    ///     ("Amount", { String(format: "%.2f", $0.amount) }),
    ///     ("Category", { $0.category }),
    /// ])
    /// ```
    static func generate<T>(
        from items: [T],
        columns: [(String, (T) -> String)]
    ) -> String {
        let headers = columns.map(\.0)
        return generate(from: items, headers: headers) { item in
            columns.map { $0.1(item) }
        }
    }

    // MARK: - Parsing

    /// Parse a CSV file into an array of dictionaries.
    ///
    /// Each dictionary maps column header names to row values.
    /// Handles quoted fields and escaped quotes.
    ///
    /// - Parameter url: The CSV file URL.
    /// - Returns: An array of dictionaries, one per data row.
    static func parse(from url: URL) throws -> [[String: String]] {
        let content = try String(contentsOf: url, encoding: .utf8)
        return parse(content: content)
    }

    /// Parse a CSV string into an array of dictionaries.
    static func parse(content: String) -> [[String: String]] {
        let lines = content.components(separatedBy: .newlines)
            .filter { !$0.trimmingCharacters(in: .whitespaces).isEmpty }

        guard let headerLine = lines.first else { return [] }
        let headers = parseRow(headerLine)

        return lines.dropFirst().map { line in
            let values = parseRow(line)
            var row: [String: String] = [:]
            for (index, header) in headers.enumerated() {
                if index < values.count {
                    row[header] = values[index]
                }
            }
            return row
        }
    }

    // MARK: - Field Escaping (RFC 4180)

    /// Escape a CSV field value according to RFC 4180.
    ///
    /// - If the field contains a comma, newline, or double quote, wrap in quotes.
    /// - Double quotes within the field are escaped as two double quotes.
    private static func escapeField(_ field: String) -> String {
        let needsQuoting = field.contains(",") ||
                           field.contains("\"") ||
                           field.contains("\n") ||
                           field.contains("\r")

        if needsQuoting {
            let escaped = field.replacingOccurrences(of: "\"", with: "\"\"")
            return "\"\(escaped)\""
        }

        return field
    }

    /// Parse a single CSV row into an array of field values.
    ///
    /// Handles quoted fields with embedded commas and escaped quotes.
    private static func parseRow(_ line: String) -> [String] {
        var fields: [String] = []
        var current = ""
        var inQuotes = false
        var iterator = line.makeIterator()

        while let char = iterator.next() {
            if inQuotes {
                if char == "\"" {
                    // Check for escaped quote (double-double quote)
                    // Peek at next character
                    if let next = iterator.next() {
                        if next == "\"" {
                            current.append("\"")
                        } else {
                            inQuotes = false
                            if next == "," {
                                fields.append(current)
                                current = ""
                            } else {
                                current.append(next)
                            }
                        }
                    } else {
                        inQuotes = false
                    }
                } else {
                    current.append(char)
                }
            } else {
                if char == "\"" {
                    inQuotes = true
                } else if char == "," {
                    fields.append(current)
                    current = ""
                } else {
                    current.append(char)
                }
            }
        }

        fields.append(current)
        return fields
    }
}

PDFExporter.swift

import UIKit
import os

/// PDF generation using UIGraphicsPDFRenderer.
///
/// Generates formatted PDF reports with title, metadata, and
/// line items. Uses standard US Letter page size (8.5" x 11").
///
/// Usage:
/// ```swift
/// let data = PDFExporter.generateReport(
///     title: "Expense Report",
///     items: expenses.map { "\($0.title) - $\($0.amount)" }
/// )
/// try data.write(to: url)
/// ```
enum PDFExporter {

    /// Standard US Letter page size in points (72 points per inch).
    private static let pageWidth: CGFloat = 612   // 8.5"
    private static let pageHeight: CGFloat = 792  // 11"
    private static let margin: CGFloat = 50
    private static let lineSpacing: CGFloat = 20

    /// Generate a PDF report with a title and line items.
    ///
    /// - Parameters:
    ///   - title: The report title displayed at the top.
    ///   - subtitle: Optional subtitle (e.g., date range).
    ///   - items: Array of string descriptions, one per line.
    /// - Returns: The rendered PDF as Data.
    static func generateReport(
        title: String,
        subtitle: String? = nil,
        items: [String]
    ) -> Data {
        let pageRect = CGRect(x: 0, y: 0, width: pageWidth, height: pageHeight)
        let renderer = UIGraphicsPDFRenderer(bounds: pageRect)

        return renderer.pdfData { context in
            var currentY: CGFloat = 0
            var isFirstPage = true

            func beginNewPage() {
                context.beginPage()
                currentY = margin

                if isFirstPage {
                    currentY = drawHeader(title: title, subtitle: subtitle, at: currentY)
                    isFirstPage = false
                } else {
                    // Continuation header
                    currentY = drawContinuationHeader(title: title, at: currentY)
                }
            }

            beginNewPage()

            // Draw each line item
            for (index, item) in items.enumerated() {
                let itemHeight = heightForText(item, maxWidth: pageWidth - (margin * 2))

                // Check if we need a new page
                if currentY + itemHeight + lineSpacing > pageHeight - margin {
                    // Draw page number on current page
                    drawPageNumber(context: context, pageRect: pageRect)
                    beginNewPage()
                }

                // Alternate row background
                if index % 2 == 0 {
                    let rowRect = CGRect(
                        x: margin - 4,
                        y: currentY - 2,
                        width: pageWidth - (margin * 2) + 8,
                        height: itemHeight + 4
                    )
                    UIColor.systemGray6.setFill()
                    UIBezierPath(roundedRect: rowRect, cornerRadius: 4).fill()
                }

                // Draw item text
                let textRect = CGRect(
                    x: margin,
                    y: currentY,
                    width: pageWidth - (margin * 2),
                    height: itemHeight
                )

                let paragraphStyle = NSMutableParagraphStyle()
                paragraphStyle.lineBreakMode = .byWordWrapping

                let attributes: [NSAttributedString.Key: Any] = [
                    .font: UIFont.systemFont(ofSize: 11),
                    .foregroundColor: UIColor.label,
                    .paragraphStyle: paragraphStyle,
                ]

                item.draw(in: textRect, withAttributes: attributes)
                currentY += itemHeight + lineSpacing
            }

            // Draw footer on last page
            drawPageNumber(context: context, pageRect: pageRect)
            drawFooter(pageRect: pageRect)
        }
    }

    /// Generate a PDF with tabular data (rows and columns).
    ///
    /// - Parameters:
    ///   - title: The report title.
    ///   - headers: Column header names.
    ///   - rows: Array of row arrays matching header count.
    /// - Returns: The rendered PDF as Data.
    static func generateTable(
        title: String,
        headers: [String],
        rows: [[String]]
    ) -> Data {
        let pageRect = CGRect(x: 0, y: 0, width: pageWidth, height: pageHeight)
        let renderer = UIGraphicsPDFRenderer(bounds: pageRect)
        let contentWidth = pageWidth - (margin * 2)
        let columnWidth = contentWidth / CGFloat(headers.count)

        return renderer.pdfData { context in
            context.beginPage()
            var currentY = margin

            // Title
            currentY = drawHeader(title: title, subtitle: nil, at: currentY)

            // Table headers
            let headerFont = UIFont.boldSystemFont(ofSize: 10)
            let headerAttributes: [NSAttributedString.Key: Any] = [
                .font: headerFont,
                .foregroundColor: UIColor.white,
            ]

            // Header background
            let headerRect = CGRect(
                x: margin, y: currentY,
                width: contentWidth, height: 24
            )
            UIColor.systemBlue.setFill()
            UIBezierPath(roundedRect: headerRect, cornerRadius: 4).fill()

            for (colIndex, header) in headers.enumerated() {
                let cellRect = CGRect(
                    x: margin + CGFloat(colIndex) * columnWidth + 4,
                    y: currentY + 4,
                    width: columnWidth - 8,
                    height: 16
                )
                header.draw(in: cellRect, withAttributes: headerAttributes)
            }
            currentY += 28

            // Table rows
            let cellFont = UIFont.systemFont(ofSize: 10)
            let cellAttributes: [NSAttributedString.Key: Any] = [
                .font: cellFont,
                .foregroundColor: UIColor.label,
            ]

            for (rowIndex, row) in rows.enumerated() {
                if currentY + 24 > pageHeight - margin {
                    drawPageNumber(context: context, pageRect: pageRect)
                    context.beginPage()
                    currentY = margin
                }

                // Alternating row color
                if rowIndex % 2 == 0 {
                    let rowBgRect = CGRect(
                        x: margin, y: currentY,
                        width: contentWidth, height: 20
                    )
                    UIColor.systemGray6.setFill()
                    UIBezierPath(rect: rowBgRect).fill()
                }

                for (colIndex, value) in row.enumerated() {
                    guard colIndex < headers.count else { break }
                    let cellRect = CGRect(
                        x: margin + CGFloat(colIndex) * columnWidth + 4,
                        y: currentY + 3,
                        width: columnWidth - 8,
                        height: 14
                    )
                    value.draw(in: cellRect, withAttributes: cellAttributes)
                }
                currentY += 20
            }

            drawPageNumber(context: context, pageRect: pageRect)
            drawFooter(pageRect: pageRect)
        }
    }

    // MARK: - Drawing Helpers

    private static func drawHeader(
        title: String,
        subtitle: String?,
        at startY: CGFloat
    ) -> CGFloat {
        var y = startY

        // Title
        let titleAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.boldSystemFont(ofSize: 22),
            .foregroundColor: UIColor.label,
        ]
        let titleRect = CGRect(
            x: margin, y: y,
            width: pageWidth - (margin * 2), height: 30
        )
        title.draw(in: titleRect, withAttributes: titleAttributes)
        y += 34

        // Subtitle
        if let subtitle {
            let subtitleAttributes: [NSAttributedString.Key: Any] = [
                .font: UIFont.systemFont(ofSize: 12),
                .foregroundColor: UIColor.secondaryLabel,
            ]
            let subtitleRect = CGRect(
                x: margin, y: y,
                width: pageWidth - (margin * 2), height: 18
            )
            subtitle.draw(in: subtitleRect, withAttributes: subtitleAttributes)
            y += 22
        }

        // Date
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .long
        let dateString = "Generated: \(dateFormatter.string(from: Date()))"
        let dateAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 10),
            .foregroundColor: UIColor.tertiaryLabel,
        ]
        let dateRect = CGRect(
            x: margin, y: y,
            width: pageWidth - (margin * 2), height: 14
        )
        dateString.draw(in: dateRect, withAttributes: dateAttributes)
        y += 20

        // Separator line
        let separatorPath = UIBezierPath()
        separatorPath.move(to: CGPoint(x: margin, y: y))
        separatorPath.addLine(to: CGPoint(x: pageWidth - margin, y: y))
        UIColor.separator.setStroke()
        separatorPath.lineWidth = 0.5
        separatorPath.stroke()
        y += 16

        return y
    }

    private static func drawContinuationHeader(title: String, at startY: CGFloat) -> CGFloat {
        let attributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 10, weight: .medium),
            .foregroundColor: UIColor.secondaryLabel,
        ]
        let rect = CGRect(x: margin, y: startY, width: pageWidth - (margin * 2), height: 14)
        "\(title) (continued)".draw(in: rect, withAttributes: attributes)
        return startY + 24
    }

    private static func drawPageNumber(context: UIGraphicsPDFRendererContext, pageRect: CGRect) {
        let pageNumber = context.pdfContextBounds
        let text = "Page"
        let attributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 9),
            .foregroundColor: UIColor.tertiaryLabel,
        ]
        let textSize = text.size(withAttributes: attributes)
        let textRect = CGRect(
            x: pageRect.width - margin - textSize.width,
            y: pageRect.height - margin + 10,
            width: textSize.width,
            height: textSize.height
        )
        _ = pageNumber
        text.draw(in: textRect, withAttributes: attributes)
    }

    private static func drawFooter(pageRect: CGRect) {
        let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String ?? "App"
        let text = "Exported from \(appName)"
        let attributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 9),
            .foregroundColor: UIColor.tertiaryLabel,
        ]
        let rect = CGRect(
            x: margin,
            y: pageRect.height - margin + 10,
            width: 200,
            height: 14
        )
        text.draw(in: rect, withAttributes: attributes)
    }

    private static func heightForText(_ text: String, maxWidth: CGFloat) -> CGFloat {
        let attributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 11),
        ]
        let boundingRect = (text as NSString).boundingRect(
            with: CGSize(width: maxWidth, height: .greatestFiniteMagnitude),
            options: [.usesLineFragmentOrigin, .usesFontLeading],
            attributes: attributes,
            context: nil
        )
        return ceil(boundingRect.height)
    }
}

DataImporter.swift

import Foundation
import UniformTypeIdentifiers
import os

/// File import with UTType-based file picker and format-specific parsing.
///
/// Supports importing JSON and CSV files selected via `.fileImporter`
/// or drag-and-drop. Handles security-scoped URL access automatically.
///
/// Usage:
/// ```swift
/// // In a SwiftUI view with .fileImporter:
/// .fileImporter(
///     isPresented: $showPicker,
///     allowedContentTypes: DataImporter.supportedTypes
/// ) { result in
///     Task {
///         let url = try result.get()
///         let items: [Expense] = try await DataImporter.importFile(
///             from: url, as: Expense.self
///         )
///     }
/// }
/// ```
enum DataImporter {

    /// Content types supported for import.
    static let supportedTypes: [UTType] = [.json, .commaSeparatedText]

    private static let logger = Logger(
        subsystem: Bundle.main.bundleIdentifier ?? "com.app",
        category: "DataImport"
    )

    /// Import a file and decode its contents to the specified type.
    ///
    /// Automatically detects the format from the file extension
    /// and parses accordingly. Handles security-scoped URL access.
    ///
    /// - Parameters:
    ///   - url: The file URL (may be security-scoped from file picker).
    ///   - type: The Decodable type to parse into.
    /// - Returns: An array of decoded items.
    static func importFile<T: Decodable>(
        from url: URL,
        as type: T.Type
    ) async throws -> [T] {
        // Access security-scoped resource
        let didStartAccess = url.startAccessingSecurityScopedResource()
        defer {
            if didStartAccess {
                url.stopAccessingSecurityScopedResource()
            }
        }

        let data = try Data(contentsOf: url)

        // Detect format from file extension
        switch url.pathExtension.lowercased() {
        case "json":
            return try importJSON(data: data, as: type)
        case "csv":
            logger.warning("CSV import requires DataExportable conformance for column mapping")
            throw ImportError.unsupportedFormat("CSV import requires custom mapping")
        default:
            throw ImportError.unsupportedFormat(url.pathExtension)
        }
    }

    /// Import JSON data as an array of items.
    ///
    /// Handles both array `[{...}, {...}]` and single object `{...}` formats.
    static func importJSON<T: Decodable>(
        data: Data,
        as type: T.Type
    ) throws -> [T] {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601

        // Try array first
        if let items = try? decoder.decode([T].self, from: data) {
            logger.info("Imported \(items.count) items from JSON array")
            return items
        }

        // Try single object
        if let item = try? decoder.decode(T.self, from: data) {
            logger.info("Imported single item from JSON object")
            return [item]
        }

        throw ImportError.decodingFailed("Could not decode JSON as \(T.self) or [\(T.self)]")
    }

    /// Import CSV data using a custom row mapper.
    ///
    /// Parses the CSV into dictionaries (header -> value) and applies
    /// the provided mapper to convert each row into the target type.
    ///
    /// Usage:
    /// ```swift
    /// let expenses = try DataImporter.importCSV(from: url) { row in
    ///     Expense(
    ///         id: UUID(uuidString: row["ID"] ?? "") ?? UUID(),
    ///         title: row["Title"] ?? "",
    ///         amount: Double(row["Amount"] ?? "0") ?? 0,
    ///         date: ISO8601DateFormatter().date(from: row["Date"] ?? "") ?? Date(),
    ///         category: row["Category"] ?? ""
    ///     )
    /// }
    /// ```
    static func importCSV<T>(
        from url: URL,
        rowMapper: ([String: String]) throws -> T
    ) throws -> [T] {
        let didStartAccess = url.startAccessingSecurityScopedResource()
        defer {
            if didStartAccess {
                url.stopAccessingSecurityScopedResource()
            }
        }

        let rows = try CSVExporter.parse(from: url)
        logger.info("Parsed \(rows.count) rows from CSV")

        return try rows.map { row in
            try rowMapper(row)
        }
    }

    /// Validate that a file can be imported before attempting.
    ///
    /// Checks file existence, size, and format support.
    static func validate(url: URL) -> ImportValidation {
        let didStartAccess = url.startAccessingSecurityScopedResource()
        defer {
            if didStartAccess {
                url.stopAccessingSecurityScopedResource()
            }
        }

        guard FileManager.default.fileExists(atPath: url.path) else {
            return .invalid("File not found")
        }

        guard let attributes = try? FileManager.default.attributesOfItem(atPath: url.path),
              let fileSize = attributes[.size] as? Int64 else {
            return .invalid("Cannot read file attributes")
        }

        // Warn for large files (> 50 MB)
        let maxSize: Int64 = 50 * 1024 * 1024
        if fileSize > maxSize {
            return .warning("File is \(fileSize / 1024 / 1024) MB. Import may be slow.")
        }

        let ext = url.pathExtension.lowercased()
        guard ext == "json" || ext == "csv" else {
            return .invalid("Unsupported format: .\(ext)")
        }

        return .valid
    }

    // MARK: - Types

    /// Result of file validation before import.
    enum ImportValidation {
        case valid
        case warning(String)
        case invalid(String)

        var isUsable: Bool {
            switch self {
            case .valid, .warning: return true
            case .invalid: return false
            }
        }
    }

    /// Import errors.
    enum ImportError: LocalizedError {
        case unsupportedFormat(String)
        case decodingFailed(String)
        case fileTooLarge(Int64)

        var errorDescription: String? {
            switch self {
            case .unsupportedFormat(let format):
                return "Unsupported import format: \(format)"
            case .decodingFailed(let detail):
                return "Failed to decode imported data: \(detail)"
            case .fileTooLarge(let size):
                return "File too large (\(size / 1024 / 1024) MB)"
            }
        }
    }
}

Source: SKILL.md on GitHub

1 alert16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The 'generators' skill provides a robust set of templates and workflows for generating Swift code for iOS and macOS applications. Security analysis identified a surface for indirect prompt injection because the skill reads local project files to adapt its generation logic. It also utilizes dynamic execution by generating and running local Swift scripts to create application icons and uses shell commands to process images and audit project code. All external references are to well-known, established services.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: MEDIUM · 1 issue

  • Runlayer7mo

    59/179 files flagged

  • ZeroLeaks5mo

    Scan incomplete

Signed by skilld at 30e898f. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 2 months ago
What it can do
Reads files Edits files Runs commands
last_verified
2026-07-16
review_by
2027-06-22
os_version
iOS 27 / macOS 27
All 7 allowed tools
ReadWriteEditGlobGrepBashAskUserQuestion

README badge

README badge for rshankras/claude-code-apple-skills/generators