Skip to content

Commit

Permalink
Refactors for nested manifest data structures (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
orchetect committed Dec 5, 2023
1 parent b90968f commit b7e926c
Show file tree
Hide file tree
Showing 12 changed files with 280 additions and 79 deletions.
95 changes: 95 additions & 0 deletions Sources/MarkersExtractor/Export/ExportFieldValue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// ExportFieldValue.swift
// MarkersExtractor • https://github.com/TheAcharya/MarkersExtractor
// Licensed under MIT License
//

import Foundation

public enum ExportFieldValue: Equatable, Hashable, Sendable {
case string(_ string: String)
case array(_ array: [ExportFieldValue])
case dictionary(_ dictionary: [String: ExportFieldValue])
}

// MARK: - Static Constructors

extension ExportFieldValue {
public static func array(_ array: [String]) -> Self {
.array(array.map { .string($0) })
}

public static func array(_ array: [[String]]) -> Self {
.array(array.map { .array($0) })
}

public static func array(_ array: [[String: String]]) -> Self {
.array(array.map { .dictionary($0) })
}
}

extension ExportFieldValue {
public static func dictionary(_ dictionary: [String: String]) -> Self {
let mapped: [String: ExportFieldValue] = dictionary
.mapValues { .string($0) }
return .dictionary(mapped)
}

public static func dictionary(_ dictionary: [String: [String]]) -> Self {
let mapped: [String: ExportFieldValue] = dictionary
.mapValues { .array($0) }
return .dictionary(mapped)
}

public static func dictionary(_ dictionary: [String: [String: String]]) -> Self {
let mapped: [String: ExportFieldValue] = dictionary
.mapValues { .dictionary($0) }
return .dictionary(mapped)
}
}

// MARK: - Encoding

extension ExportFieldValue: Codable {
public func encode(to encoder: Encoder) throws {
switch self {
case .string(let string):
try string.encode(to: encoder)
case .array(let array):
try array.encode(to: encoder)
case .dictionary(let dictionary):
try dictionary.encode(to: encoder)
}
}

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()

var lastError: Error?

do {
let string = try container.decode(String.self)
self = .string(string)
return
} catch { lastError = error }

do {
let array = try container.decode([ExportFieldValue].self)
self = .array(array)
return
} catch { lastError = error }

do {
let dictionary = try container.decode([String: ExportFieldValue].self)
self = .dictionary(dictionary)
return
} catch { lastError = error }

throw lastError ?? DecodingError.dataCorrupted(
DecodingError.Context(
codingPath: [],
debugDescription: "No valid data type could be decoded."
)
)
}
}
2 changes: 1 addition & 1 deletion Sources/MarkersExtractor/Export/ExportMedia.swift
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ extension ExportProfile {
preparedMarkers: [PreparedMarker]
) -> [String] {
preparedMarkers
.map { manifestFields(for: $0, noMedia: false) }
.map { tableManifestFields(for: $0, noMedia: false) }
.map { markerDict in
headers
.map {
Expand Down
4 changes: 2 additions & 2 deletions Sources/MarkersExtractor/Export/ExportProfile Export.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ extension ExportProfile {
progress.completedUnitCount += thumbnailsProgressUnitCount
}

// metadata manifest file
// metadata manifest file(s)

try writeManifest(preparedMarkers, payload: payload, noMedia: media == nil)
try writeManifests(preparedMarkers, payload: payload, noMedia: media == nil)

// result file
let exportResult = try generateResult(date: Date(), payload: payload, outputURL: outputURL)
Expand Down
29 changes: 25 additions & 4 deletions Sources/MarkersExtractor/Export/ExportProfile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ public protocol ExportProfile: AnyObject where Self: ProgressReporting {
mediaInfo: ExportMarkerMediaInfo?
) -> [PreparedMarker]

/// Encode and write metadata manifest file to disk. (Such as csv file)
func writeManifest(
/// Encode and write all applicable metadata manifest file(s) to disk. (Such as csv file)
func writeManifests(
_ preparedMarkers: [PreparedMarker],
payload: Payload,
noMedia: Bool
Expand All @@ -50,12 +50,21 @@ public protocol ExportProfile: AnyObject where Self: ProgressReporting {
/// Provides the profile-specific result file content.
func resultFileContent(payload: Payload) throws -> ExportResult.ResultDictionary

/// Provides the manifest fields to use.
func manifestFields(
/// Provides the manifest fields to use for table-based data structure (ie: for CSV, TSV, etc.).
/// These values are also used for thumbnail image labels.
func tableManifestFields(
for marker: PreparedMarker,
noMedia: Bool
) -> OrderedDictionary<ExportField, String>

/// Provides the manifest fields to use for nested-based data structure (ie: for JSON, XML,
/// PLIST, etc.).
/// Defaults to using ``tableManifestFields(for:noMedia:)`` if no implementation is provided.
func nestedManifestFields(
for marker: PreparedMarker,
noMedia: Bool
) -> OrderedDictionary<ExportField, ExportFieldValue>

/// Boolean describing whether the export format is capable of using media.
/// (ie: able to generate thumbnail image files, etc.)
static var isMediaCapable: Bool { get }
Expand All @@ -74,3 +83,15 @@ extension ExportProfile {
/// Arbitrary overall progress total for export profile.
static var defaultProgressTotalUnitCount: Int64 { 100 }
}

// MARK: - Default Implementation

extension ExportProfile {
public func nestedManifestFields(
for marker: PreparedMarker,
noMedia: Bool
) -> OrderedDictionary<ExportField, ExportFieldValue> {
tableManifestFields(for: marker, noMedia: noMedia)
.mapValues { .string($0) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ extension ExportProfile {
_ preparedMarkers: [PreparedMarker],
noMedia: Bool
) -> [[String]] {
let dicts = preparedMarkers.map { manifestFields(for: $0, noMedia: noMedia) }
let dicts = preparedMarkers.map {
tableManifestFields(for: $0, noMedia: noMedia)
}
guard !dicts.isEmpty else { return [] }

// header
Expand Down
16 changes: 11 additions & 5 deletions Sources/MarkersExtractor/Export/Manifest/JSON Export Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,29 @@ extension ExportProfile {
private func jsonDicts(
_ preparedMarkers: [PreparedMarker],
noMedia: Bool
) -> [OrderedDictionary<String, String>] {
) -> [OrderedDictionary<String, ExportFieldValue>] {
preparedMarkers.map {
manifestFields(for: $0, noMedia: noMedia)
.reduce(into: OrderedDictionary<String, String>()) {
nestedManifestFields(for: $0, noMedia: noMedia)
.reduce(into: OrderedDictionary<String, ExportFieldValue>()) {
$0[$1.key.name] = $1.value
}
}
}
}

func dictToJSON<V: Codable>(_ dict: [String: V]) throws -> Data {
func dictToJSON(_ dict: [String: String]) throws -> Data {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted]
return try encoder.encode(dict)
}

func dictsToJSON<V: Codable>(_ dict: [[String: V]]) throws -> Data {
func dictToJSON(_ dict: [String: ExportFieldValue]) throws -> Data {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted]
return try encoder.encode(dict)
}

func dictsToJSON(_ dict: [[String: ExportFieldValue]]) throws -> Data {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted]
return try encoder.encode(dict)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ extension AirtableExportProfile {
}
}

public func writeManifest(
public func writeManifests(
_ preparedMarkers: [PreparedMarker],
payload: Payload,
noMedia: Bool
Expand All @@ -55,32 +55,61 @@ extension AirtableExportProfile {
]
}

public func manifestFields(
public func tableManifestFields(
for marker: PreparedMarker,
noMedia: Bool
) -> OrderedDictionary<ExportField, String> {
var dict: OrderedDictionary<ExportField, String> = [
.id: marker.id,
.name: marker.name,
.type: marker.type,
.checked: marker.checked,
.status: marker.status,
.notes: marker.notes,
.position: marker.position,
.clipType: marker.clipType,
.clipName: marker.clipName,
.clipDuration: marker.clipDuration,
.videoRole: marker.videoRole,
.audioRole: marker.audioRole,
.eventName: marker.eventName,
.projectName: marker.projectName,
.libraryName: marker.libraryName
]
var dict: OrderedDictionary<ExportField, String> = [:]

dict[.id] = marker.id
dict[.name] = marker.name
dict[.type] = marker.type
dict[.checked] = marker.checked
dict[.status] = marker.status
dict[.notes] = marker.notes
dict[.position] = marker.position
dict[.clipType] = marker.clipType
dict[.clipName] = marker.clipName
dict[.clipDuration] = marker.clipDuration
dict[.videoRole] = marker.videoRole
dict[.audioRole] = marker.audioRole.flat
dict[.eventName] = marker.eventName
dict[.projectName] = marker.projectName
dict[.libraryName] = marker.libraryName

if !noMedia {
dict[.imageFileName] = marker.imageFileName
}

return dict
}

public func nestedManifestFields(
for marker: PreparedMarker,
noMedia: Bool
) -> OrderedDictionary<ExportField, ExportFieldValue> {
var dict: OrderedDictionary<ExportField, ExportFieldValue> = [:]

dict[.id] = .string(marker.id)
dict[.name] = .string(marker.name)
dict[.type] = .string(marker.type)
dict[.checked] = .string(marker.checked)
dict[.status] = .string(marker.status)
dict[.notes] = .string(marker.notes)
dict[.position] = .string(marker.position)
dict[.clipType] = .string(marker.clipType)
dict[.clipName] = .string(marker.clipName)
dict[.clipDuration] = .string(marker.clipDuration)
dict[.videoRole] = .string(marker.videoRole)
dict[.audioRole] = .array(marker.audioRole.array)
dict[.eventName] = .string(marker.eventName)
dict[.projectName] = .string(marker.projectName)
dict[.libraryName] = .string(marker.libraryName)

if !noMedia {
dict[.imageFileName] = .string(marker.imageFileName)
}

return dict
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,15 @@ extension MIDIFileExportProfile {
}
}

public func writeManifest(
public func writeManifests(
_ preparedMarkers: [PreparedMarker],
payload: Payload,
noMedia: Bool
) throws {
try writeManifest(preparedMarkers, payload: payload, noMedia: noMedia)
}

func writeManifest(
_ preparedMarkers: [PreparedMarker],
payload: Payload,
noMedia: Bool
Expand Down Expand Up @@ -56,14 +64,17 @@ extension MIDIFileExportProfile {
[.midiFilePath: .url(payload.midiFilePath)]
}

public func manifestFields(
public func tableManifestFields(
for marker: PreparedMarker,
noMedia: Bool
) -> OrderedDictionary<ExportField, String> {
var dict: OrderedDictionary<ExportField, String> = [
.position: marker.position,
.name: marker.name
]
// can ignore `structure` since MIDI File is proprietary
// and does not have multiple format variants

var dict: OrderedDictionary<ExportField, String> = [:]

dict[.position] = marker.position
dict[.name] = marker.name

if !noMedia {
dict[.imageFileName] = marker.imageFileName
Expand Down
Loading

0 comments on commit b7e926c

Please sign in to comment.