Skip to content

Vaida12345/DetailedDescription

Repository files navigation

A package to provide detailed description of any structure.

Overview

CustomDetailedStringConvertible serves as an alternative to Mirror. This protocol describes the details to types confirming to CustomDetailedStringConvertible without exposing implementation details of Swift Foundation structures that Mirror would.

To use this package, your structure needs to conform to the CustomDetailedStringConvertible protocol.

struct BasicModel: CustomDetailedStringConvertible {

    let name: String

    let age: Int

    func detailedDescription(using descriptor: DetailedDescription.Descriptor<BasicModel>) -> any DescriptionBlockProtocol {
        descriptor.container {
            descriptor.value(for: \.name)
            descriptor.value(for: \.age)
        }
    }
}

let model = BasicModel(name: "hello", age: 100)

detailedPrint(model)
// BasicModel
//  ├─name: "hello"
//  ╰─age: 100

Getting Started

DetailedDescription uses Swift Package Manager as its build tool. If you want to import in your own project, it's as simple as adding a dependencies clause to your Package.swift:

dependencies: [
    .package(url: "https://www.github.com/Vaida12345/DetailedDescription", from: "1.0.0")
]

and then adding the appropriate module to your target dependencies.

Using Xcode Package support

You can add this framework as a dependency to your Xcode project by clicking File -> Swift Packages -> Add Package Dependency. The package is located at:

https://www.github.com/Vaida12345/DetailedDescription

Documentation

Full Documentation in DocC. View on Github Pages

More Examples

Recursive

Arguably the best use case is when dealing with recursive structures.

Definition Result
struct SCNNodeDescriptor: CustomDetailedStringConvertible {
    
    let node: SCNNode
    
    func detailedDescription(
        using descriptor: DetailedDescription.Descriptor
    ) -> any DescriptionBlockProtocol {
        descriptor.container {
            descriptor.value("name", of: node.name)
            descriptor.sequence("children", 
                       of: node.childNodes.map(SCNNodeDescriptor.init)
            )
        }
    }
    
}
SCNNodeDescriptor
 ├─name: nil
 ╰─children: <1 element>
   ╰─[0]: SCNNodeDescriptor
          ├─name: "scene"
          ╰─children: <1 element>
            ╰─[0]: SCNNodeDescriptor
                   ├─name: "Meshes"
                   ╰─children: ...

With the above definition, you can now inspect a complex SCNNode by detailedPrint(SCNNodeDescriptor(node: node)).

DSL

Similar to SwiftUI, the detailedDescription function also supports builing conditional blocks, and the use of loops.

func detailedDescription(using descriptor: DetailedDescription.Descriptor<Component>) -> any DescriptionBlockProtocol {
    descriptor.container(showType: false) {
        if !content.isEmpty {
            descriptor.value(for: \.content)
        }
        descriptor.sequence(for: \.metadata, hideEmptySequence: true)
        descriptor.value(for: \.boundary)
    }
}

In the above example, the existence of content in its output is conditional, and appears only when it is not empty.


It also supports complex block-building. The following is a portion of code for exploring the PDF structure using PDFKit

descriptor.container("CGPDFArray") {
    descriptor.forEach(0..<count) { index in
        if let innerArray = source._arrayGetValue(using: CGPDFArrayGetArray, index: index) {
            descriptor.value("", of: CGPDFArrayWrapper(source: innerArray))
        } else if let name = source._arrayGetValue(using: CGPDFArrayGetName, index: index) {
            descriptor.value("", of: String(cString: name))
        } else if let stream = source._arrayGetValue(using: CGPDFArrayGetStream, index: index) {
            descriptor.value("", of: stream.dictionary)
        } else if let dictionary = source._arrayGetValue(using: CGPDFArrayGetDictionary, index: index) {
            descriptor.value("", of: dictionary)
        } else {
            descriptor.string("(unknown)")
        }
    }
 }

Showing Types

The container comes with ways to configure how you want to describe the children, including showType.

struct BasicModel: CustomDetailedStringConvertible {
    
    let name: String
    
    let age: Int
    
    func detailedDescription(using descriptor: DetailedDescription.Descriptor<BasicModel>) -> any DescriptionBlockProtocol {
        descriptor.container(showType: true) {
            descriptor.container("details", showType: false) {
                descriptor.value(for: \.name)
                descriptor.value(for: \.age)
            }
            
            descriptor.value(for: \.name)
        }
    }
}

Similar to SwiftUI environment values, values are effected by the innermost definition of showType, and child containers inherit parent configuration if not specified.

BasicModel
 ├─details
  ├─name: "dog"
  ╰─age: 11
 ╰─name: "dog" <String>