Recursively summarize storage sizes of file types in folder

Here is the Swift source and build instructions for a command line tool to produce the following type of output: The source is best suited for opening in a proper programmer’s editor, and the only dependency is that one needs the current command-line tools for Xcode, or Xcode itself installed.

This was tested with command line tools for Xcode 26.4 (Swift 6.3) on macOS Tahoe 26.4.1. Your mileage may vary if you use older tools. It will use NSOpenPanel when no folder argument is provided on the command line.

Screenshot 2026-04-18 at 9.45.32 AM

#!/usr/bin/env swift

// Compile: xcrun --sdk macosx swiftc -Osize -o extSumx extSumx.swift
// Tested:  2026-04-18, Swift v6.3, macOS Tahoe 26.4.1
// Usage:   extSumx
//          extSumx ~/Downloads

import Foundation
import AppKit

func formatBytes(nbr: Int64) -> String {
    // automatically uses appropriate SI storage abbreviation for locale
    let autoLocale = Locale.autoupdatingCurrent.identifier
    let style = ByteCountFormatStyle(style: .file,
                                 allowedUnits: [.all],
                                 spellsOutZero: true,
                                 includesActualByteCount: false,
                                 locale: Locale(identifier: autoLocale))
    return style.format(nbr)
}

func fileChooser() -> String {
    let openPanel = NSOpenPanel()
    openPanel.isFloatingPanel = true
    openPanel.setFrame(CGRect(x: 0, y: 0, width: 600, height: 525), display: true)
    openPanel.allowsMultipleSelection = false
    openPanel.canChooseDirectories = true
    openPanel.canCreateDirectories = false
    openPanel.canChooseFiles = false
    openPanel.allowedContentTypes = [.folder]

	guard openPanel.runModal() == .OK else {
		exit(1)
	}
    return openPanel.url!.path
}

var urlFolder: URL
let inputArgs: [URL]
var extDict: [String: Int64] = [:]
let headingOne = "Extension"
let headingSpaces = String(repeating: " ", count: 15)
let headingTwo = "Size"

var mutString = NSMutableString(format: "%-16@%@%12@\n",
                                headingOne, headingSpaces, headingTwo)

inputArgs = CommandLine.arguments.dropFirst().map { URL(fileURLWithPath: $0).standardizedFileURL }

if inputArgs.isEmpty {
    urlFolder = NSURL(fileURLWithPath: fileChooser() ) as URL
} else {
	urlFolder = inputArgs[0]
}

let fm = FileManager.default
let propKeys: [URLResourceKey] = [.fileSizeKey]
let enumOptions: FileManager.DirectoryEnumerationOptions = [.skipsHiddenFiles,
                                                           .skipsPackageDescendants]
// perform a recursive dive into specified directory
let enumerator = fm.enumerator(at: urlFolder, includingPropertiesForKeys: propKeys,
                                              options: enumOptions,
                                              errorHandler: nil)

let filePaths = enumerator?.allObjects as? [URL]
for afile in filePaths! {
    let anExt = afile.pathExtension.localizedLowercase
    guard !anExt.isEmpty else {
        continue
    }
    let attributes = try fm.attributesOfItem(atPath: afile.path)
    let fileSize = (attributes[FileAttributeKey.size] as? NSNumber)!.int64Value

    // dynamically add a new extension, or update value for extension if multiples found
    if extDict[anExt] != nil {
        // add the new same extension file size to existing value
        let itsValue = fileSize + extDict[anExt]!
        extDict.updateValue(itsValue, forKey: anExt)
    } else {
        extDict[anExt] = fileSize
    }
}

// Descending sort on size
let extDictSorted = extDict.sorted(by: { $0.value > $1.value })

for (k, v) in extDictSorted {
    guard !k.isEmpty else {
        continue
    }
    mutString.appendFormat("%-16s    %12s\n", (k as NSString).utf8String!,
                            (formatBytes(nbr: v) as NSString).utf8String!)
}
print(mutString)
exit(0)

and how does one go about compiling this with the command-line tools?