Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse Other Podcast Elements for Phase 1 & Phase 2 #59

Merged
merged 24 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
48f287b
test: invalid expansionState
devahmedshendy Sep 19, 2023
d1ba1ea
add: new Podcast of Phase 1 & Phase 2
devahmedshendy Sep 21, 2023
3256999
update: integrate podcast types into RSSChannel & RSSItem
devahmedshendy Sep 22, 2023
1b01362
resolve: review threads
devahmedshendy Sep 22, 2023
e78fc9c
update: PodcastLocation value to name
devahmedshendy Sep 22, 2023
6ddd57a
update: PodcastPerson role as enum with supported values of Transisto…
devahmedshendy Sep 22, 2023
a6bce9a
resolve: review comments
devahmedshendy Sep 22, 2023
fcdf0ba
fix: all arrays should not be optional
devahmedshendy Sep 22, 2023
a0c23d0
update: make WordPressPost.role insensitive and add unknown
devahmedshendy Sep 22, 2023
aeda218
refactoring Role
leogdion Sep 23, 2023
406009c
Merge pull request #60 from brightdigit/parse-podcast-elementsperson
devahmedshendy Sep 23, 2023
f4b52a2
fix: Recursion happening Role.init?(rawValue: String) and Role.init(k…
devahmedshendy Sep 24, 2023
6771862
test: podcast elements of RSSChannel & RSSItem
devahmedshendy Sep 24, 2023
8974490
fix: some lint-issues
devahmedshendy Sep 24, 2023
ed5151e
test: UTF8EncodedURL, XMLStringInt
devahmedshendy Sep 25, 2023
ac18717
update: make geo,osm of PodcastLocation as custom types, update: swif…
devahmedshendy Sep 27, 2023
d17fe32
update: make all podcast elements as Equatable (except PodcastEpisode)
devahmedshendy Sep 27, 2023
13ca305
add: tests to podcast elements to increase testcov for previous commit
devahmedshendy Sep 27, 2023
ee0efbd
update: PodcastChapters & PodcastTranscript with custom decoding for …
devahmedshendy Sep 27, 2023
ec1f3c0
update: move custom decoding of geo & osm into their own types
devahmedshendy Sep 27, 2023
77e2bd2
update: osm to osmQuery, and remove all xml elements
devahmedshendy Sep 27, 2023
0d148a6
update: make osmQuery, geo as optional
devahmedshendy Sep 27, 2023
47c037b
resolve: review comments
devahmedshendy Sep 27, 2023
b913f81
fix: use default decoding for PodcastPerson+Role, PodcastTranscript+M…
devahmedshendy Sep 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Data/OPML/category_invalidExpansionState.opml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<opml version="2.0">
<head>
<title>Illustrating the category attribute</title>
<dateCreated>Mon, 31 Oct 2005 19:23:00 GMT</dateCreated>
<expansionState>one, two, three</expansionState>
</head>
<body>
<outline text="The Mets are the best team in baseball." category="/Philosophy/Baseball/Mets,/Tourism/New York" created="Mon, 31 Oct 2005 18:21:33 GMT"/>
</body>
</opml>
14 changes: 14 additions & 0 deletions Sources/SyndiKit/Character.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// File.swift
//
//
// Created by Ahmed Shendy on 27/09/2023.
//

import Foundation

extension Character {
func asOsmType() -> PodcastLocation.OsmQuery.OsmType? {
.init(rawValue: String(self))
}
}
7 changes: 7 additions & 0 deletions Sources/SyndiKit/Collection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

extension Collection {
subscript(safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
7 changes: 7 additions & 0 deletions Sources/SyndiKit/Formats/Feeds/RSS/RSSChannel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ public struct RSSChannel: Codable {
public let wpBaseSiteURL: URL?
public let wpBaseBlogURL: URL?

public let podcastLocked: PodcastLocked?
public let podcastFundings: [PodcastFunding]
public let podcastPeople: [PodcastPerson]

enum CodingKeys: String, CodingKey {
case title
case link
Expand All @@ -65,6 +69,9 @@ public struct RSSChannel: Codable {
case wpTags = "wp:tag"
case wpBaseSiteURL = "wp:baseSiteUrl"
case wpBaseBlogURL = "wp:baseBlogUrl"
case podcastLocked = "podcast:locked"
case podcastFundings = "podcast:funding"
case podcastPeople = "podcast:person"
}
}

Expand Down
51 changes: 42 additions & 9 deletions Sources/SyndiKit/Formats/Feeds/RSS/RSSItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ public struct RSSItem: Codable {
public let itunesExplicit: String?
public let itunesDuration: iTunesDuration?
public let itunesImage: iTunesImage?
public let podcastPerson: [PodcastPerson]?
public let podcastPeople: [PodcastPerson]
public let podcastTranscripts: [PodcastTranscript]
public let podcastChapters: PodcastChapters?
public let podcastSoundbites: [PodcastSoundbite]
public let podcastSeason: PodcastSeason?
public let enclosure: Enclosure?
public let creators: [String]
public let wpCommentStatus: CData?
Expand All @@ -35,7 +39,7 @@ public struct RSSItem: Codable {
public let wpModifiedDateGMT: Date?
public let wpPostName: CData?
public let wpPostType: CData?
public let wpPostMeta: [WordPressElements.PostMeta]?
public let wpPostMeta: [WordPressElements.PostMeta]
public let wpAttachmentURL: URL?
public let mediaContent: AtomMedia?
public let mediaThumbnail: AtomMedia?
Expand All @@ -58,7 +62,11 @@ public struct RSSItem: Codable {
itunesExplicit: String? = nil,
itunesDuration: TimeInterval? = nil,
itunesImage: iTunesImage? = nil,
podcastPerson: [PodcastPerson]? = nil,
podcastPeople: [PodcastPerson] = [],
podcastTranscripts: [PodcastTranscript] = [],
podcastChapters: PodcastChapters? = nil,
podcastSoundbites: [PodcastSoundbite] = [],
podcastSeason: PodcastSeason? = nil,
enclosure: Enclosure? = nil,
creators: [String] = [],
wpCommentStatus: String? = nil,
Expand All @@ -75,7 +83,7 @@ public struct RSSItem: Codable {
wpModifiedDateGMT: Date? = nil,
wpPostName: String? = nil,
wpPostType: String? = nil,
wpPostMeta: [WordPressElements.PostMeta]? = nil,
wpPostMeta: [WordPressElements.PostMeta] = [],
wpAttachmentURL: URL? = nil,
mediaContent: AtomMedia? = nil,
mediaThumbnail: AtomMedia? = nil
Expand All @@ -96,7 +104,11 @@ public struct RSSItem: Codable {
self.itunesExplicit = itunesExplicit
self.itunesDuration = itunesDuration.map(iTunesDuration.init)
self.itunesImage = itunesImage
self.podcastPerson = podcastPerson
self.podcastPeople = podcastPeople
self.podcastTranscripts = podcastTranscripts
self.podcastChapters = podcastChapters
self.podcastSoundbites = podcastSoundbites
self.podcastSeason = podcastSeason
self.enclosure = enclosure
self.creators = creators
self.wpCommentStatus = wpCommentStatus.map(CData.init)
Expand Down Expand Up @@ -143,9 +155,26 @@ public struct RSSItem: Codable {
)
itunesImage = try container.decodeIfPresent(iTunesImage.self, forKey: .itunesImage)

podcastPerson = try container.decodeIfPresent(
podcastPeople = try container.decodeIfPresent(
[PodcastPerson].self,
forKey: .podcastPerson
forKey: .podcastPeople
) ?? []
podcastTranscripts = try container.decodeIfPresent(
[PodcastTranscript].self,
forKey: .podcastTranscripts
) ?? []
podcastChapters = try container.decodeIfPresent(
PodcastChapters.self,
forKey: .podcastChapters
)
podcastSoundbites = try container.decodeIfPresent(
[PodcastSoundbite].self,
forKey: .podcastSoundbites
) ?? []

podcastSeason = try container.decodeIfPresent(
PodcastSeason.self,
forKey: .podcastSeason
)

enclosure = try container.decodeIfPresent(Enclosure.self, forKey: .enclosure)
Expand Down Expand Up @@ -203,7 +232,7 @@ public struct RSSItem: Codable {
wpPostMeta = try container.decodeIfPresent(
[WordPressElements.PostMeta].self,
forKey: .wpPostMeta
)
) ?? []
wpCommentStatus = try container.decodeIfPresent(CData.self, forKey: .wpCommentStatus)
wpPingStatus = try container.decodeIfPresent(CData.self, forKey: .wpPingStatus)
wpStatus = try container.decodeIfPresent(CData.self, forKey: .wpStatus)
Expand Down Expand Up @@ -231,7 +260,11 @@ public struct RSSItem: Codable {
case itunesSubtitle = "itunes:subtitle"
case itunesSummary = "itunes:summary"
case itunesExplicit = "itunes:explicit"
case podcastPerson = "podcast:person"
case podcastPeople = "podcast:person"
case podcastTranscripts = "podcast:transcript"
case podcastChapters = "podcast:chapters"
case podcastSoundbites = "podcast:soundbite"
case podcastSeason = "podcast:season"
case itunesDuration = "itunes:duration"
case itunesImage = "itunes:image"
case creators = "dc:creator"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Foundation

extension PodcastChapters {
private enum KnownMimeType: String, Codable {
case json = "application/json+chapters"

init?(caseInsensitive: String) {
self.init(rawValue: caseInsensitive)
}

init?(mimeType: MimeType) {
switch mimeType {
case .json: self = .json
case .unknown: return nil
}
}
}

public enum MimeType: Codable, Equatable, RawRepresentable {
case json
case unknown(String)

public var rawValue: String {
if let knownMimeType = KnownMimeType(mimeType: self) {
return knownMimeType.rawValue
} else if case let .unknown(string) = self {
return string
} else {
fatalError("Type attribute of <podcast:chapters> should either be `KnownMimeType`, or unknown!")

Check warning on line 29 in Sources/SyndiKit/Formats/Media/Podcast/PodcastChapters+MimeType.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SyndiKit/Formats/Media/Podcast/PodcastChapters+MimeType.swift#L29

Added line #L29 was not covered by tests
}
}

public init?(rawValue: String) {
if let knownMimeType = KnownMimeType(rawValue: rawValue) {
self = .init(knownMimeType: knownMimeType)
} else {
self = .unknown(rawValue)

Check warning on line 37 in Sources/SyndiKit/Formats/Media/Podcast/PodcastChapters+MimeType.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SyndiKit/Formats/Media/Podcast/PodcastChapters+MimeType.swift#L33-L37

Added lines #L33 - L37 were not covered by tests
}
}

public init(caseInsensitive: String) {
if let knownMimeType = KnownMimeType(caseInsensitive: caseInsensitive) {
self = .init(knownMimeType: knownMimeType)
} else {
self = .unknown(caseInsensitive)
}
}

private init(knownMimeType: KnownMimeType) {
switch knownMimeType {
case .json: self = .json
}
}
}
}
25 changes: 25 additions & 0 deletions Sources/SyndiKit/Formats/Media/Podcast/PodcastChapters.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Foundation

/// ```xml
/// <podcast:chapters
/// url="https://example.com/episode1/chapters.json"
/// type="application/json+chapters"
/// />
/// ```
public struct PodcastChapters: Codable, Equatable {
public let url: URL
public let type: MimeType

enum CodingKeys: String, CodingKey {
case url
case type
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.url = try container.decode(URL.self, forKey: .url)

let typeStr = try container.decode(String.self, forKey: .type)
self.type = MimeType(caseInsensitive: typeStr)
}
devahmedshendy marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import Foundation


public protocol PodcastEpisode {
var title: String? { get }
var episode: Int? { get }
Expand Down Expand Up @@ -37,6 +39,6 @@ struct PodcastEpisodeProperties: PodcastEpisode {
duration = rssItem.itunesDuration?.value
image = rssItem.itunesImage
self.enclosure = enclosure
people = rssItem.podcastPerson ?? []
people = rssItem.podcastPeople
}
}
18 changes: 18 additions & 0 deletions Sources/SyndiKit/Formats/Media/Podcast/PodcastFunding.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Foundation

/// ```xml
/// <podcast:funding
/// url="https://www.example.com/donations"
/// >
/// Support the show!
/// </podcast:funding>
/// ```
public struct PodcastFunding: Codable, Equatable {
public let url: URL
public let description: String?

enum CodingKeys: String, CodingKey {
case url
case description = ""
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import Foundation

extension PodcastLocation {
/// Examples:
/// - `geo:37.786971,-122.399677`, a simple latlon description.
/// - `geo:37.786971,-122.399677,250`, a latlon including a height of 250 meters above ground level.
/// - `geo:37.786971,-122.399677;u=350`, a latlon with an accuracy ('uncertainty') of 350 meters.
public struct GeoURI: Codable, Equatable {
let latitude: Double
let longitude: Double
let height: Int?
devahmedshendy marked this conversation as resolved.
Show resolved Hide resolved
let accuracy: Double?

public init(latitude: Double, longitude: Double, height: Int? = nil, accuracy: Double? = nil) {
self.latitude = latitude
self.longitude = longitude
self.height = height
self.accuracy = accuracy

Check warning on line 18 in Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+GeoURI.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+GeoURI.swift#L14-L18

Added lines #L14 - L18 were not covered by tests
}

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let geoStr = try container.decode(String.self)
devahmedshendy marked this conversation as resolved.
Show resolved Hide resolved

guard
let geoScheme = geoStr.split(separator: ":")[safe: 0],
geoScheme == "geo" else {
throw DecodingError.dataCorrupted(
.init(
codingPath: [PodcastLocation.CodingKeys.geo],
debugDescription: "Invalid prefix for geo attribute: \(geoStr)"
)
)
}
guard let geoPath = geoStr.split(separator: ":")[safe: 1] else {
throw DecodingError.dataCorrupted(
.init(
codingPath: [PodcastLocation.CodingKeys.geo],
debugDescription: "Invalid path for geo attribute: \(geoStr)"
)
)
}
guard
let geoCoords = geoPath.split(separator: ";")[safe: 0],
let latitude = geoCoords.split(separator: ",")[safe: 0]?.asDouble(),
let longitude = geoCoords.split(separator: ",")[safe: 1]?.asDouble()
devahmedshendy marked this conversation as resolved.
Show resolved Hide resolved
else {
throw DecodingError.dataCorrupted(
.init(
codingPath: [PodcastLocation.CodingKeys.geo],
debugDescription: "Invalid coordinates for geo attribute: \(geoStr)"
)
)
}
let height = geoCoords.split(separator: ",")[safe: 2]?.asExactInt()
let accuracy = geoPath.split(separator: ";")[safe: 1]?
.split(separator: "=")[safe: 1]?
.asDouble()

self.latitude = latitude
self.longitude = longitude
self.height = height
self.accuracy = accuracy
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Foundation

extension PodcastLocation {
/// Examples:
/// - The United States of America: `R148838`
/// - The Eiffel Tower in Paris: `W5013364`
/// - Paris, but - optionally - the revision made on 8 Jan 2021: `R7444#188`
public struct OsmQuery: Codable, Equatable {
enum OsmType: String, Codable, CaseIterable {
case node = "N"
case way = "W"
case relation = "R"

static func isValid(_ rawValue: String) -> Bool {
OsmType(rawValue: rawValue) != nil

Check warning on line 15 in Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+OsmQuery.swift

View check run for this annotation

Codecov / codecov/patch

Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+OsmQuery.swift#L14-L15

Added lines #L14 - L15 were not covered by tests
}
}

let id: Int
let type: OsmType
let revision: Int?

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

var osmStr = try container.decode(String.self)

guard let osmType = osmStr.removeFirst().asOsmType() else {
throw DecodingError.dataCorrupted(
.init(
codingPath: [PodcastLocation.CodingKeys.osm],
debugDescription: "Invalid type for osm attribute: \(osmStr)"
)
)
}
guard let osmID = osmStr.split(separator: "#")[safe: 0]?.asExactInt() else {
throw DecodingError.dataCorrupted(
.init(
codingPath: [PodcastLocation.CodingKeys.osm],
debugDescription: "Invalid id of type Int for osm attribute: \(osmStr)"
)
)
}
let osmRevision = osmStr.split(separator: "#")[safe: 1]?.asInt()

self.id = osmID
self.type = osmType
self.revision = osmRevision
}
devahmedshendy marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading
Loading