Category: Swift

  • iTunes TV Show Seasons Release Finder Swift Script

    This Swift script lets you search for a TV show by title from the macOS Terminal, displaying its initial release date and a chronological list of seasons with their release dates, all pulled from the iTunes Store API. Perfect for TV enthusiasts wanting quick access to show timelines!

    Click to view script…
    #!/usr/bin/env swift
    
    import Foundation
    
    // Define structs to match the iTunes Search API response for TV seasons
    struct SearchResponse: Codable {
        let resultCount: Int
        let results: [TVSeason]
    }
    
    struct TVSeason: Codable {
        let artistName: String      // The TV show's name
        let collectionName: String  // The season's name
        let releaseDate: Date       // The season's release date
    }
    
    // Check for command-line arguments
    if CommandLine.arguments.count < 2 {
        print("Usage: \(CommandLine.arguments[0]) <tv show title>")
        exit(1)
    }
    
    // Combine all arguments after the script name into a single TV show title
    let tvShowTitle = CommandLine.arguments[1...].joined(separator: " ")
    
    // Construct the API URL for searching TV seasons
    let baseURL = "https://itunes.apple.com/search"
    let queryItems = [
        URLQueryItem(name: "term", value: tvShowTitle),
        URLQueryItem(name: "media", value: "tvShow"),
        URLQueryItem(name: "entity", value: "tvSeason"),
        URLQueryItem(name: "country", value: "us")
    ]
    var urlComponents = URLComponents(string: baseURL)!
    urlComponents.queryItems = queryItems
    guard let url = urlComponents.url else {
        print("Invalid URL.")
        exit(1)
    }
    
    // Set up semaphore to handle asynchronous URLSession task
    let semaphore = DispatchSemaphore(value: 0)
    var exitCode: Int32 = 0  // Exit code for the script
    
    // Perform the network request
    let session = URLSession.shared
    let task = session.dataTask(with: url) { data, response, error in
        if let error = error {
            print("Error: \(error.localizedDescription)")
            exitCode = 1
        } else if let data = data {
            let decoder = JSONDecoder()
            decoder.dateDecodingStrategy = .iso8601 // Handle ISO 8601 date format
            do {
                let searchResponse = try decoder.decode(SearchResponse.self, from: data)
                if searchResponse.resultCount > 0 {
                    let seasons = searchResponse.results
                    let showName = seasons[0].artistName
                    // Sort seasons by release date
                    let sortedSeasons = seasons.sorted { $0.releaseDate < $1.releaseDate }
                    // Display show info and initial release date
                    if let firstReleaseDate = sortedSeasons.first?.releaseDate {
                        let dateFormatter = DateFormatter()
                        dateFormatter.dateStyle = .long
                        let formattedDate = dateFormatter.string(from: firstReleaseDate)
                        print("TV Show: \(showName)")
                        print("Initial Release Date: \(formattedDate)")
                    } else {
                        print("TV Show: \(showName)")
                        print("Release date not available.")
                    }
                    // List all seasons
                    print("Seasons:")
                    for season in sortedSeasons {
                        let seasonDateFormatter = DateFormatter()
                        seasonDateFormatter.dateStyle = .long
                        let seasonFormattedDate = seasonDateFormatter.string(from: season.releaseDate)
                        print("- \(season.collectionName): \(seasonFormattedDate)")
                    }
                    exitCode = 0
                } else {
                    print("No TV show found for \"\(tvShowTitle)\".")
                    exitCode = 1
                }
            } catch {
                print("Error decoding JSON: \(error)")
                exitCode = 1
            }
        } else {
            print("No data received.")
            exitCode = 1
        }
        semaphore.signal() // Signal completion
    }
    
    // Start the task and wait for it to complete
    task.resume()
    semaphore.wait()
    exit(exitCode)

  • iTunes Movie Release Lookup Swift Script

    This Swift script searches the iTunes Store for a movie by title, provided as a command-line argument, and prints the release date of the first matching result. It uses the iTunes Search API and handles errors gracefully with appropriate exit codes

    Click to view script…
    #!/usr/bin/env swift
    
    import Foundation
    
    // Define structs to match the iTunes Search API response
    struct SearchResponse: Codable {
        let resultCount: Int
        let results: [Movie]
    }
    
    struct Movie: Codable {
        let trackName: String
        let releaseDate: Date
    }
    
    // Check for command-line arguments
    if CommandLine.arguments.count < 2 {
        print("Usage: \(CommandLine.arguments[0]) <movie title>")
        exit(1)
    }
    
    // Combine all arguments after the script name into a single movie title
    let movieTitle = CommandLine.arguments[1...].joined(separator: " ")
    
    // Construct the API URL
    let baseURL = "https://itunes.apple.com/search"
    let queryItems = [
        URLQueryItem(name: "term", value: movieTitle),
        URLQueryItem(name: "media", value: "movie"),
        URLQueryItem(name: "entity", value: "movie")
    ]
    var urlComponents = URLComponents(string: baseURL)!
    urlComponents.queryItems = queryItems
    guard let url = urlComponents.url else {
        print("Invalid URL.")
        exit(1)
    }
    
    // Set up semaphore to handle asynchronous URLSession task
    let semaphore = DispatchSemaphore(value: 0)
    var exitCode: Int32 = 0  // Explicitly typed as Int32 to match exit() requirement
    
    // Perform the network request
    let session = URLSession.shared
    let task = session.dataTask(with: url) { data, response, error in
        if let error = error {
            print("Error: \(error.localizedDescription)")
            exitCode = 1
        } else if let data = data {
            let decoder = JSONDecoder()
            decoder.dateDecodingStrategy = .iso8601 // Handle ISO 8601 date format from API
            do {
                let searchResponse = try decoder.decode(SearchResponse.self, from: data)
                if searchResponse.resultCount > 0 {
                    let movie = searchResponse.results[0] // Take the first result
                    let dateFormatter = DateFormatter()
                    dateFormatter.dateStyle = .long
                    let formattedDate = dateFormatter.string(from: movie.releaseDate)
                    print("Release date of \(movie.trackName): \(formattedDate)")
                    exitCode = 0
                } else {
                    print("No results found for \"\(movieTitle)\".")
                    exitCode = 1
                }
            } catch {
                print("Error decoding JSON: \(error)")
                exitCode = 1
            }
        } else {
            print("No data received.")
            exitCode = 1
        }
        semaphore.signal() // Signal completion of the network task
    }
    
    // Start the task and wait for it to complete
    task.resume()
    semaphore.wait()
    exit(exitCode)
  • Interactive Image Converter Swift Script

    This Swift script guides users through selecting an input image from common directories using numbered lists and then converting it to a desired format (PNG, JPEG, or TIFF) while also letting them choose an output directory and filename. It simplifies the image conversion process on macOS by providing an interactive, user-friendly interface.

    Click to view script…
    #!/usr/bin/env swift
    import Foundation
    import AppKit
    
    // MARK: - Helper Functions
    
    /// Prompts the user for input with a message.
    func prompt(_ message: String) -> String {
        print(message, terminator: " ")
        return readLine() ?? ""
    }
    
    /// Presents a numbered list of directory options to the user and returns the selected directory URL.
    func selectDirectory(options: [(name: String, url: URL)], promptMessage: String) -> URL {
        for (index, option) in options.enumerated() {
            print("\(index + 1): \(option.name) -> \(option.url.path)")
        }
        let choiceStr = prompt(promptMessage)
        if let choiceNum = Int(choiceStr), choiceNum > 0, choiceNum <= options.count {
            let selected = options[choiceNum - 1]
            if selected.name.hasPrefix("Other") {
                let customPath = prompt("Enter custom directory path:")
                return URL(fileURLWithPath: customPath, isDirectory: true)
            } else {
                return selected.url
            }
        } else {
            let manualPath = prompt("Invalid selection. Enter directory path manually:")
            return URL(fileURLWithPath: manualPath, isDirectory: true)
        }
    }
    
    /// Presents a numbered list of files for the user to choose from.
    func selectFile(from files: [URL], in directory: URL) -> URL {
        for (index, file) in files.enumerated() {
            print("\(index + 1): \(file.lastPathComponent)")
        }
        let choiceStr = prompt("Enter the number of your choice:")
        if let choiceNum = Int(choiceStr), choiceNum > 0, choiceNum <= files.count {
            return files[choiceNum - 1]
        } else {
            let manualFile = prompt("Invalid selection. Enter the file name (in \(directory.path)):")
            return directory.appendingPathComponent(manualFile)
        }
    }
    
    /// Converts a file size (in bytes) to a human-readable string.
    func humanReadableSize(_ size: UInt64) -> String {
        let formatter = ByteCountFormatter()
        formatter.countStyle = .file
        formatter.allowedUnits = [.useKB, .useMB, .useGB, .useTB]
        return formatter.string(fromByteCount: Int64(size))
    }
    
    // MARK: - Candidate Directories Setup
    
    let fileManager = FileManager.default
    var candidateDirs: [(name: String, url: URL)] = []
    
    if let desktop = fileManager.urls(for: .desktopDirectory, in: .userDomainMask).first {
        candidateDirs.append(("Desktop", desktop))
    }
    if let documents = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
        candidateDirs.append(("Documents", documents))
    }
    if let downloads = fileManager.urls(for: .downloadsDirectory, in: .userDomainMask).first {
        candidateDirs.append(("Downloads", downloads))
    }
    candidateDirs.append(("Other (enter path manually)", URL(fileURLWithPath: fileManager.currentDirectoryPath)))
    
    // MARK: - Input Directory and File Selection
    
    print("Select the input directory:")
    let inputDirectory = selectDirectory(options: candidateDirs, promptMessage: "Enter the number of your choice:")
    
    // Verify the input directory exists.
    var isDir: ObjCBool = false
    guard fileManager.fileExists(atPath: inputDirectory.path, isDirectory: &isDir), isDir.boolValue else {
        print("Directory \(inputDirectory.path) does not exist or is not a directory.")
        exit(1)
    }
    
    // Define supported image extensions.
    let validImageExtensions: Set<String> = ["png", "jpg", "jpeg", "tiff", "gif"]
    
    // Get and filter files in the chosen directory.
    guard let filesInDirectory = try? fileManager.contentsOfDirectory(at: inputDirectory,
                                                                       includingPropertiesForKeys: nil,
                                                                       options: [.skipsHiddenFiles]) else {
        print("Unable to read contents of directory \(inputDirectory.path)")
        exit(1)
    }
    let imageFiles = filesInDirectory.filter { validImageExtensions.contains($0.pathExtension.lowercased()) }
    
    if imageFiles.isEmpty {
        print("No image files found in \(inputDirectory.path)")
        exit(1)
    }
    
    print("\nSelect the image file to convert:")
    let inputFileURL = selectFile(from: imageFiles, in: inputDirectory)
    
    // Optionally, display file size.
    if let attrs = try? fileManager.attributesOfItem(atPath: inputFileURL.path),
       let fileSize = attrs[.size] as? UInt64 {
        print("Selected file: \(inputFileURL.lastPathComponent) (\(humanReadableSize(fileSize)))")
    }
    
    // MARK: - Output Format Selection
    
    print("\nSelect the desired output image format:")
    print("1: PNG")
    print("2: JPEG")
    print("3: TIFF")
    let formatChoiceStr = prompt("Enter the number of your choice:")
    var outputFileType: NSBitmapImageRep.FileType
    var outputExtension: String
    
    switch formatChoiceStr {
    case "1":
        outputFileType = .png
        outputExtension = "png"
    case "2":
        outputFileType = .jpeg
        outputExtension = "jpg"
    case "3":
        outputFileType = .tiff
        outputExtension = "tiff"
    default:
        print("Invalid selection.")
        exit(1)
    }
    
    // MARK: - Output Directory and File Name Selection
    
    print("\nSelect the output directory:")
    let outputDirectory = selectDirectory(options: candidateDirs, promptMessage: "Enter the number of your choice:")
    
    isDir = false
    guard fileManager.fileExists(atPath: outputDirectory.path, isDirectory: &isDir), isDir.boolValue else {
        print("Output directory \(outputDirectory.path) does not exist or is not a directory.")
        exit(1)
    }
    
    let defaultOutputName = inputFileURL.deletingPathExtension().lastPathComponent + "_converted." + outputExtension
    let outputFileName = prompt("Enter output file name (default: \(defaultOutputName)):")
    let finalOutputFileName = outputFileName.isEmpty ? defaultOutputName : outputFileName
    let outputFileURL = outputDirectory.appendingPathComponent(finalOutputFileName)
    
    // MARK: - Image Conversion
    
    print("\nConverting image...")
    
    guard let inputImage = NSImage(contentsOf: inputFileURL) else {
        print("Failed to load image from \(inputFileURL.path)")
        exit(1)
    }
    guard let tiffData = inputImage.tiffRepresentation else {
        print("Failed to get TIFF representation of the image.")
        exit(1)
    }
    guard let bitmapRep = NSBitmapImageRep(data: tiffData) else {
        print("Failed to create bitmap representation.")
        exit(1)
    }
    guard let outputImageData = bitmapRep.representation(using: outputFileType, properties: [:]) else {
        print("Failed to convert image to selected format.")
        exit(1)
    }
    
    do {
        try outputImageData.write(to: outputFileURL)
        print("Image successfully converted and saved to \(outputFileURL.path)")
    } catch {
        print("Error saving converted image: \(error)")
        exit(1)
    }
  • macOS File Finder Swift Script

    This Swift script allows users to search for files of a specific type in a chosen directory on macOS. It interactively prompts for a directory (from common options or a custom path) and a file extension, then recursively locates matching files and displays their names along with human-readable file sizes.

    Click to view script…
    #!/usr/bin/env swift
    
    import Foundation
    
    // MARK: - Helper: Format File Size in Human-Readable Format
    func humanReadableSize(_ size: UInt64) -> String {
        let formatter = ByteCountFormatter()
        formatter.countStyle = .file
        // Allow units from bytes up to TB
        formatter.allowedUnits = [.useKB, .useMB, .useGB, .useTB]
        return formatter.string(fromByteCount: Int64(size))
    }
    
    // MARK: - Helper: Prompt for User Input
    func prompt(_ message: String) -> String {
        print(message, terminator: " ")
        return readLine() ?? ""
    }
    
    // MARK: - Prepare Candidate Directories
    let fileManager = FileManager.default
    var candidateDirectories: [String: URL] = [:]
    
    if let desktopURL = fileManager.urls(for: .desktopDirectory, in: .userDomainMask).first {
        candidateDirectories["Desktop"] = desktopURL
    }
    if let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
        candidateDirectories["Documents"] = documentsURL
    }
    if let downloadsURL = fileManager.urls(for: .downloadsDirectory, in: .userDomainMask).first {
        candidateDirectories["Downloads"] = downloadsURL
    }
    // Provide an option for a custom directory.
    candidateDirectories["Other"] = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
    
    // MARK: - Display Directory Options
    print("Select a directory to search in:")
    let directoryKeys = Array(candidateDirectories.keys).sorted() // sorted for consistent ordering
    for (index, key) in directoryKeys.enumerated() {
        if key == "Other" {
            print("\(index + 1): Enter a custom directory path")
        } else if let url = candidateDirectories[key] {
            print("\(index + 1): \(key) -> \(url.path)")
        }
    }
    
    guard let dirChoiceStr = readLine(), let choiceNum = Int(dirChoiceStr),
          choiceNum > 0, choiceNum <= directoryKeys.count else {
        print("Invalid directory choice. Exiting.")
        exit(1)
    }
    
    let chosenKey = directoryKeys[choiceNum - 1]
    var searchDirectory: URL
    
    if chosenKey == "Other" {
        let customPath = prompt("Enter full directory path:")
        searchDirectory = URL(fileURLWithPath: customPath, isDirectory: true)
    } else {
        searchDirectory = candidateDirectories[chosenKey]!
    }
    
    // Verify the directory exists
    var isDir: ObjCBool = false
    guard fileManager.fileExists(atPath: searchDirectory.path, isDirectory: &isDir), isDir.boolValue else {
        print("The selected path does not exist or is not a directory. Exiting.")
        exit(1)
    }
    
    // MARK: - Get File Extension to Search For
    let fileExtensionInput = prompt("Enter file extension to search for (e.g., txt, pdf, jpg):")
    let fileExtension = fileExtensionInput.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
    
    if fileExtension.isEmpty {
        print("No file extension provided. Exiting.")
        exit(1)
    }
    
    // Validate file extension (alphanumeric only)
    let regex = try! NSRegularExpression(pattern: "^[a-zA-Z0-9]+$")
    let range = NSRange(location: 0, length: fileExtension.utf16.count)
    if regex.firstMatch(in: fileExtension, options: [], range: range) == nil {
        print("Invalid file extension provided. Exiting.")
        exit(1)
    }
    
    // MARK: - Search for Files
    print("\nSearching for .\(fileExtension) files in \(searchDirectory.path)...")
    
    guard let enumerator = fileManager.enumerator(at: searchDirectory,
                                                    includingPropertiesForKeys: [.fileSizeKey],
                                                    options: [.skipsHiddenFiles],
                                                    errorHandler: { (url, error) -> Bool in
                                                        print("Error accessing \(url.path): \(error.localizedDescription)")
                                                        return true
                                                    }) else {
        print("Failed to create directory enumerator. Exiting.")
        exit(1)
    }
    
    var foundFiles: [(url: URL, size: UInt64)] = []
    
    while let fileURL = enumerator.nextObject() as? URL {
        if fileURL.pathExtension.lowercased() == fileExtension {
            do {
                let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey])
                if let fileSize = resourceValues.fileSize {
                    foundFiles.append((fileURL, UInt64(fileSize)))
                }
            } catch {
                print("Error getting size for \(fileURL.path): \(error.localizedDescription)")
            }
        }
    }
    
    // MARK: - Display Results
    if foundFiles.isEmpty {
        print("No files with .\(fileExtension) extension were found in \(searchDirectory.path).")
    } else {
        print("\nFound \(foundFiles.count) file(s):")
        for (file, size) in foundFiles {
            print("\(file.lastPathComponent) - \(humanReadableSize(size))")
        }
    }
  • Animated Algorithm Playground Swift Script

    This Swift script visually demonstrates the bubble sort algorithm by animating each step in the terminal as it sorts a randomly generated array of numbers from unsorted to sorted order.

    Click to view script…
    #!/usr/bin/env swift
    
    import Foundation
    
    // Function to clear the terminal screen using ANSI escape codes.
    func clearScreen() {
        print("\u{001B}[2J", terminator: "")
        print("\u{001B}[H", terminator: "")
    }
    
    // Function to display the array as bars with their values.
    func displayArray(_ arr: [Int]) {
        for value in arr {
            let bar = String(repeating: "▇", count: value)
            print("\(bar) (\(value))")
        }
    }
    
    // Generate an array of 20 random integers between 1 and 20.
    var numbers = (0..<20).map { _ in Int.random(in: 1...20) }
    
    // Show the initial unsorted array.
    clearScreen()
    print("Initial Array:")
    displayArray(numbers)
    print("\nPress Enter to start the animated bubble sort...")
    _ = readLine()
    
    // Bubble sort with animation.
    let n = numbers.count
    
    for i in 0..<n {
        var didSwap = false
        for j in 0..<n - i - 1 {
            if numbers[j] > numbers[j + 1] {
                numbers.swapAt(j, j + 1)
                didSwap = true
    
                // Clear screen and show the current state of the array.
                clearScreen()
                print("Bubble Sort Animation - Pass \(i + 1), Swap at index \(j) and \(j + 1):")
                displayArray(numbers)
                usleep(200000) // Pause for 0.2 seconds
            }
        }
        // If no swaps occurred, the array is sorted.
        if !didSwap {
            break
        }
    }
    
    // Final sorted array.
    clearScreen()
    print("Sorted Array:")
    displayArray(numbers)
    print("\nSorting complete!")
  • Hello Swift Script

    This simple Swift script prints a greeting message (“Hello, Swift scripting!”) to the console when executed.

    Click to view script…
    #!/usr/bin/env swift
    print("Hello, Swift scripting!")