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 all 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>
7 changes: 7 additions & 0 deletions Sources/SyndiKit/Character.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
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,51 @@
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) {
self.init(caseInsensitive: rawValue)
}

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
}
}
}
}
11 changes: 11 additions & 0 deletions Sources/SyndiKit/Formats/Media/Podcast/PodcastChapters.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

public struct PodcastChapters: Codable, Equatable {
public let url: URL
public let type: MimeType

enum CodingKeys: String, CodingKey {
case url
case type
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation

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

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,62 @@
import Foundation

extension PodcastLocation {
public struct GeoURI: Codable, Equatable {
let latitude: Double
let longitude: Double
let altitude: Double?
let accuracy: Double?

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

Check warning on line 14 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#L10-L14

Added lines #L10 - L14 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 altitude = geoCoords.split(separator: ",")[safe: 2]?.asDouble()
let accuracy = geoPath.split(separator: ";")[safe: 1]?
.split(separator: "=")[safe: 1]?
.asDouble()

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

extension PodcastLocation {
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 11 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#L10-L11

Added lines #L10 - L11 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.osmQuery],
debugDescription: "Invalid type for osm attribute: \(osmStr)"
)
)
}
guard let osmID = osmStr.split(separator: "#")[safe: 0]?.asExactInt() else {
throw DecodingError.dataCorrupted(
.init(
codingPath: [PodcastLocation.CodingKeys.osmQuery],
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
}
}
}
15 changes: 15 additions & 0 deletions Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

public struct PodcastLocation: Codable, Equatable {
public let geo: GeoURI?
public let osmQuery: OsmQuery?

public let name: String

enum CodingKeys: String, CodingKey {
case geo
case osmQuery = "osm"

case name = ""
}
}
17 changes: 17 additions & 0 deletions Sources/SyndiKit/Formats/Media/Podcast/PodcastLocked.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Foundation

public struct PodcastLocked: Codable, Equatable {
public let owner: String?
public let isLocked: Bool

enum CodingKeys: String, CodingKey {
case owner
case isLocked = ""
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
owner = try container.decodeIfPresent(String.self, forKey: .owner)
isLocked = try container.decode(String.self, forKey: .isLocked).lowercased() == "yes"
}
}
Loading
Loading