diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ae22d88 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "swift" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..ad06939 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,81 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: '16 9 * * 1' + +jobs: + analyze: + name: Analyze + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners + # Consider using larger runners for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'swift' ] + # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] + # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/syndikit.yml b/.github/workflows/syndikit.yml index 3f5fcec..21ccbc2 100644 --- a/.github/workflows/syndikit.yml +++ b/.github/workflows/syndikit.yml @@ -115,11 +115,17 @@ jobs: watchName: "Apple Watch Ultra (49mm)" iPhoneName: "iPhone 14 Pro Max" - runs-on: macos-13 - xcode: "/Applications/Xcode_15.0.app" - iOSVersion: "17.0" + xcode: "/Applications/Xcode_15.0.1.app" + iOSVersion: "17.0.1" watchOSVersion: "10.0" + watchName: "Apple Watch Series 9 (41mm)" + iPhoneName: "iPhone 15 Pro" + - runs-on: macos-13 + xcode: "/Applications/Xcode_15.1.app" + iOSVersion: "17.2" + watchOSVersion: "10.2" watchName: "Apple Watch Ultra (49mm)" - iPhoneName: "iPhone 14 Pro Max" + iPhoneName: "iPhone 15 Pro Max" steps: - uses: actions/checkout@v3 - name: Cache swift package modules diff --git a/.swiftformat b/.swiftformat index 7001c5e..2601bf7 100644 --- a/.swiftformat +++ b/.swiftformat @@ -2,6 +2,6 @@ --header strip --commas inline --disable wrapMultilineStatementBraces ---extensionacl on-extension +--extensionacl on-declarations --decimalgrouping 3,4 --exclude .build, DerivedData diff --git a/.swiftlint.yml b/.swiftlint.yml index db96f2b..141707c 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -110,6 +110,12 @@ function_parameter_count: 8 line_length: - 90 - 90 +type_name: + excluded: + - iTunesDuration + - iTunesEpisode + - iTunesOwner + - iTunesImage identifier_name: excluded: - id diff --git a/Data/XML/wait-wait-dont-tell-me.xml b/Data/XML/wait-wait-dont-tell-me.xml new file mode 100644 index 0000000..291e544 --- /dev/null +++ b/Data/XML/wait-wait-dont-tell-me.xml @@ -0,0 +1,4410 @@ + + + + Wait Wait... Don't Tell Me! + https://www.npr.org/podcasts/344098539/wait-wait-don-t-tell-me +

Hate free content? Try a subscription to Wait Wait... Don't Tell Me!+. Your subscription supports public radio and unlocks fun bonus episodes along with sponsor-free listening. Learn more at https://plus.npr.org/waitwait]]>
+ Copyright 2014-2021 NPR - For Personal Use Only + NPR Feed Publish Service v1.15.7 + en + NPR + no + + podcasts@npr.org + NPR + + + + + + + + episodic + + https://media.npr.org/assets/img/2022/09/23/waitwait-don-t-tell-me_tile_npr-network-01_sq-d51413832c7ccf5301741d7f1ee2e1853fed9597.jpg?s=1400&c=66&f=jpg + Wait Wait... Don't Tell Me! + https://www.npr.org/podcasts/344098539/wait-wait-don-t-tell-me + + Tue, 19 Dec 2023 16:55:11 +0000 + es fi fr gb nl no se us dk at ch be au nz ca de it ie lu li ad mc pl pt is mx ee lv lt my gr ar hu cz mt bg sk cy br cl co uy sv py hn pa ni pe ec do gt cr bo id jp th ro il za + + Wait Wait Wayback Machine: December 2003 + Wait Wait... Don't Tell Me listener with questions about news events from our actual show — but the catch is they're shows that aired 20 years ago!

The Wait Wait Wayback Machine is normally a bonus episode that only Wait Wait... Don't Tell Me+ supporters can hear and play. Today, we're making it available for everyone.

If you want a chance to play the Wait Wait Wayback Machine, sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org!]]>
+ Tue, 19 Dec 2023 00:08:23 +0000 + 28333718-ffb4-40ba-b94a-047f73be1b9f + https://www.npr.org/2026/01/01/1198920316/zz-wwdtm-bonusdraft + no + Wait Wait Wayback Machine: December 2003 + + 858 + no + bonus + Wait Wait... Don't Tell Me listener with questions about news events from our actual show — but the catch is they're shows that aired 20 years ago!

The Wait Wait Wayback Machine is normally a bonus episode that only Wait Wait... Don't Tell Me+ supporters can hear and play. Today, we're making it available for everyone.

If you want a chance to play the Wait Wait Wayback Machine, sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org!]]>
+ +
+ + WWDTM: Bethenny Frankel + + Sat, 16 Dec 2023 14:48:46 +0000 + 9256964c-9a74-4f96-9d1f-0c03d1e9a106 + https://www.npr.org/2023/12/16/1198908307/bethenny-frankel-talks-feuds-throwing-drinks-and-becoming-an-accidental-influenc + no + WWDTM: Bethenny Frankel + 1375 + + 2933 + no + full + + + + + Extended cut: Dakota Johnson on her new doc, childhood, and curating a sex museum + Wait Wait... Don't Tell Me+ supporters! It's an extended cut of our interview with actor, producer, and activist Dakota Johnson, whose new documentary is The Disappearance of Shere Hite.

That's right — an extended cut. Our celebrity interviews sometimes last much longer than what we're able to include in our regular show. Wait Wait... Don't Tell Me+ supporters can hear extended cuts in regular bonus episodes. They also get a chance to play a special news quiz game over zoom with Peter Sagal and Wait Wait staffer! To find out more, and to hear our regular show without sponsor messages, sign up for Wait Wait... Don't Tell Me+ at plus.npr.org]]>
+ Mon, 11 Dec 2023 21:19:00 +0000 + 7f0b2f90-db95-4db2-bfcd-85d7a989b90e + https://www.npr.org/2026/01/01/1198920314/zz-wwdtm-bonusdraft + no + Extended cut: Dakota Johnson on her new doc, childhood, and curating a sex museum + + 1432 + no + bonus + Wait Wait... Don't Tell Me+ supporters! It's an extended cut of our interview with actor, producer, and activist Dakota Johnson, whose new documentary is The Disappearance of Shere Hite.

That's right — an extended cut. Our celebrity interviews sometimes last much longer than what we're able to include in our regular show. Wait Wait... Don't Tell Me+ supporters can hear extended cuts in regular bonus episodes. They also get a chance to play a special news quiz game over zoom with Peter Sagal and Wait Wait staffer! To find out more, and to hear our regular show without sponsor messages, sign up for Wait Wait... Don't Tell Me+ at plus.npr.org]]>
+ +
+ + WWDTM: Fred Schneider + + Sat, 09 Dec 2023 10:37:57 +0000 + 4a45e191-6fc1-4d1c-9a0e-f9eb0f0c4036 + https://www.npr.org/2023/12/09/1198908286/wait-wait-dont-tell-me-draft-12-09-2023 + no + WWDTM: Fred Schneider + 1374 + + 2938 + no + full + + + + + WWDTM: Dakota Johnson + The Disappearance of Shere Hite, joins panelists Adam Felber, Joyelle Nicole Johnson, and Alonzo Bodden to talk famous families, Fifty Shades of Grey, and more.]]> + Sat, 02 Dec 2023 14:13:46 +0000 + d015fdf2-15f8-46ba-b3b9-45944aed302c + https://www.npr.org/2023/12/02/1198908271/wait-wait-dont-tell-me-draft-12-02-2023 + no + WWDTM: Dakota Johnson + 1373 + + 2943 + no + full + The Disappearance of Shere Hite, joins panelists Adam Felber, Joyelle Nicole Johnson, and Alonzo Bodden to talk famous families, Fifty Shades of Grey, and more.]]> + + + + WWDTM: 25th Year Spectacular Part VIII! + + Sat, 25 Nov 2023 08:00:59 +0000 + 29282f8a-797f-4a39-8116-419cd0e96a56 + https://www.npr.org/2023/11/25/1198908242/happy-thanksgiving-with-adam-savage-jane-curtin-and-more + no + WWDTM: 25th Year Spectacular Part VIII! + 1372 + + 2852 + no + full + + + + + WWDTM: Stephen Smith + + Sat, 18 Nov 2023 17:00:29 +0000 + c0410a5a-a27a-4ba5-9d75-ac4c651860bf + https://www.npr.org/2023/11/18/1198908222/l-l-bean-ceo-stephen-smith-answers-questions-about-jelly-beans + no + WWDTM: Stephen Smith + 1371 + + 2877 + no + full + + + + + WWDTM: John Stamos + + Sat, 11 Nov 2023 08:00:59 +0000 + 796361ef-e59f-4fa2-ae75-bd4930bf07ee + https://www.npr.org/2023/11/11/1198908209/john-stamos-talks-being-ridiculously-handsome + no + WWDTM: John Stamos + 1370 + + 2879 + no + full + + + + + WWDTM: Dr. Rae Wynn-Grant + Mutual of Omaha's Wild Kingdom. She joins panelists Adam Burke, Maeve Higgins, and Tom Papa to talk bear attacks, gummy bears, and the strange joy of being squeezed by a boa constrictor.]]> + Sat, 04 Nov 2023 12:47:27 +0000 + 20fad301-24ff-41af-a94f-32ba87522f87 + https://www.npr.org/2023/11/04/1198908185/dr-rae-wynn-grant-talks-bear-attacks-and-gummy-bears + no + WWDTM: Dr. Rae Wynn-Grant + 1369 + + 2868 + no + full + Mutual of Omaha's Wild Kingdom. She joins panelists Adam Burke, Maeve Higgins, and Tom Papa to talk bear attacks, gummy bears, and the strange joy of being squeezed by a boa constrictor.]]> + + + + WWDTM: Bernie Taupin + Scattershot]]> + Sat, 28 Oct 2023 12:31:22 +0000 + ca95f8b5-bd49-4477-84f1-d4df1b913522 + https://www.npr.org/2023/10/28/1198908160/bernie-taupin-talks-his-new-memoir + no + WWDTM: Bernie Taupin + 1368 + + 2869 + no + full + Scattershot]]> + + + + WWDTM: James Patterson + + Sat, 21 Oct 2023 16:23:13 +0000 + 52062e52-f137-44d3-b325-a9be0e17d099 + https://www.npr.org/2023/10/21/1198908118/wait-wait-dont-tell-me-draft-10-21-2023 + no + WWDTM: James Patterson + 1367 + + 2872 + no + full + + + + + WWDTM: 25th Year Spectacular Part VII! + + Sat, 14 Oct 2023 12:02:50 +0000 + 6b527c16-6b90-4345-9215-dd55d607cec5 + https://www.npr.org/2023/10/14/1198908103/our-25th-anniversary-spectacular-continues + no + WWDTM: 25th Year Spectacular Part VII! + 1366 + + 2927 + no + full + + + + + WWDTM: Solicitor General Elizabeth Prelogar + + Sat, 07 Oct 2023 14:45:14 +0000 + e65f99d7-5065-4bec-b4c5-d4ba374e18d0 + https://www.npr.org/2023/10/07/1198908040/solicitor-general-elizabeth-prelogar-on-the-supreme-court-and-being-miss-idaho + no + WWDTM: Solicitor General Elizabeth Prelogar + 1365 + + 2845 + no + full + + + + + WWDTM: Bob and Erin Odenkirk + + Sat, 30 Sep 2023 16:03:57 +0000 + ecc899f0-4b76-41a1-be61-5dc476176866 + https://www.npr.org/2023/09/30/1198908024/bob-and-erin-odenkirk-debate-on-the-funniest-member-of-the-family + no + WWDTM: Bob and Erin Odenkirk + 1364 + + 2879 + no + full + + + + + WWDTM: John Wilson + How To With John Wilson, and joins us to talk about being paid in Wite-Out and the best place in New York to meet a referee.]]> + Sat, 23 Sep 2023 16:00:59 +0000 + 5bf1768e-b954-4c99-ae2c-5add8fa360f3 + https://www.npr.org/2023/09/23/1198907957/wait-wait-dont-tell-me-draft-09-23-2023 + no + WWDTM: John Wilson + 1363 + + 2842 + no + full + How To With John Wilson, and joins us to talk about being paid in Wite-Out and the best place in New York to meet a referee.]]> + + + + WWDTM: Secretary Clinton + + Sat, 16 Sep 2023 16:00:18 +0000 + 5985578b-8213-41ae-8c9b-dee1b6602d96 + https://www.npr.org/2023/09/16/1198748508/wait-wait-dont-tell-me-draft-09-16-2023 + no + WWDTM: Secretary Clinton + 1362 + + 2889 + no + full + + + + + WWDTM: Martinus Evans + + Sat, 09 Sep 2023 15:50:47 +0000 + c909f608-0bbf-41f2-b3b1-dafe6a8fbf9b + https://www.npr.org/2023/09/08/1198502011/slow-af-run-clubs-martinus-evans-talks-falling-off-a-treadmill-running-for-reven + no + WWDTM: Martinus Evans + 1361 + + 2876 + no + full + + + + + WWDTM: Bob Seger + + Sat, 02 Sep 2023 16:00:37 +0000 + bdcd43b6-e5a1-471e-b4c3-d1c39d1ed4c0 + https://www.npr.org/2023/08/29/1196586872/bob-seger-talks-about-partying-in-a-farmers-field-and-gives-us-hair-care-tips + no + WWDTM: Bob Seger + 1360 + 2875 + no + full + + + + + WWDTM: Mark Ronson + Barbie. He joins guest host Negin Farsad and panelists Shantira Jackson, Alzo Slade, and Luke Burbank.]]> + Sat, 26 Aug 2023 16:00:53 +0000 + 731366d4-3c67-44e2-8267-45294808d647 + https://www.npr.org/2023/08/25/1196087175/mark-ronson-on-how-rupaul-inspired-his-business-cards + no + WWDTM: Mark Ronson + 1359 + + 2840 + no + full + Barbie. He joins guest host Negin Farsad and panelists Shantira Jackson, Alzo Slade, and Luke Burbank.]]> + + + + WWDTM: 25th Year Spectacular Part VI! + + Sat, 19 Aug 2023 16:00:00 +0000 + d5b1f31c-9b50-4a39-9208-e0ff8d3c3830 + https://www.npr.org/2023/07/26/1190348223/wwdtm-25th-year-spectacular-part-vi + no + WWDTM: 25th Year Spectacular Part VI! + 1358 + + 2856 + no + full + + + + + WWDTM: 25th Year Spectacular Part V! + + Sat, 12 Aug 2023 16:00:46 +0000 + 18483f76-a201-4d35-9941-338fa2a99906 + https://www.npr.org/2023/07/26/1190348006/wwdtm-25th-year-spectacular-part-v + no + WWDTM: 25th Year Spectacular Part V! + 1357 + + 2850 + no + full + + + + + WWDTM: Maggie Smith + + Sat, 05 Aug 2023 16:00:11 +0000 + 38fe8ba2-afad-415e-a46d-9472c38df7d9 + https://www.npr.org/2023/08/01/1191395134/poet-maggie-smith-talks-going-viral-and-being-confused-with-that-other-maggie-sm + no + WWDTM: Maggie Smith + 1356 + + 2895 + no + full + + + + + WWDTM: Randall Park + Shortcomings. He joins guest host Karen Chee and panelists Zainab Johnson, Tom Bodett and Josh Gondelman.]]> + Sat, 29 Jul 2023 16:00:56 +0000 + f919be6a-cb9e-49c8-9c4f-6a7f26883e7a + https://www.npr.org/2023/07/26/1190348657/randall-park-the-person-gets-quizzed-on-randall-park-the-mall + no + WWDTM: Randall Park + 1355 + + 2836 + no + full + Shortcomings. He joins guest host Karen Chee and panelists Zainab Johnson, Tom Bodett and Josh Gondelman.]]> + + + + WWDTM: Damian Lillard + + Sat, 22 Jul 2023 16:00:11 +0000 + b13a2f35-3201-43fa-a03a-01895af2b3b5 + https://www.npr.org/2023/07/21/1189181410/damian-lillard-talks-rap-battling-shaq + no + WWDTM: Damian Lillard + 1354 + + 2845 + no + full + + + + + WWDTM: Patti LuPone + + Sat, 15 Jul 2023 16:00:04 +0000 + 722cfde2-e755-4df5-a878-01445a27670f + https://www.npr.org/2023/07/14/1187794395/patti-lupone-talks-quitting-broadway + no + WWDTM: Patti LuPone + 1353 + + 2822 + no + full + + + + + WWDTM: 25th Year Spectacular Part IV! + Barbie director Greta Gerwig!]]> + Sat, 08 Jul 2023 16:00:00 +0000 + 67043290-caeb-423b-8012-0fb1e6d73a5e + https://www.npr.org/2023/06/28/1184863768/25th-anniversary-spectacular-part-iv + no + WWDTM: 25th Year Spectacular Part IV! + 1352 + + 2873 + no + full + Barbie director Greta Gerwig!]]> + + + + WWDTM: Aleeza Ben Shalom + Jewish Matchmaking, and she joins panelists Adam Burke, Brian Babylon, and Roxanne Roberts to talk about how matchmaking is just like a live-action game of Where's Waldo]]> + Sat, 01 Jul 2023 16:00:55 +0000 + 1195cafe-b944-4073-8825-addcf804fd40 + https://www.npr.org/2023/06/28/1184863489/aleeza-ben-shalom-on-matchmaking + no + WWDTM: Aleeza Ben Shalom + 1351 + 2808 + no + full + Jewish Matchmaking, and she joins panelists Adam Burke, Brian Babylon, and Roxanne Roberts to talk about how matchmaking is just like a live-action game of Where's Waldo]]> + + + + WWDTM: Karen Allen + Indiana Jones movies. She joins Karen Chee, Roy Blount, Jr. and Negin Farsad to talk spitting in auditions, Tom Selleck, and working with snakes]]> + Sat, 24 Jun 2023 16:00:02 +0000 + d2656fdc-a44f-47d8-bb8d-67987114842c + https://www.npr.org/2023/06/23/1183949214/indiana-jones-karen-allen-on-working-with-6-000-snakes + no + WWDTM: Karen Allen + 1350 + + 2873 + no + full + Indiana Jones movies. She joins Karen Chee, Roy Blount, Jr. and Negin Farsad to talk spitting in auditions, Tom Selleck, and working with snakes]]> + + + + WWDTM: James Marsden + Jury Duty, playing yourself, and what it's like to be the Baxter in a romcom.]]> + Sat, 17 Jun 2023 16:00:32 +0000 + e7586bab-ca46-4610-82dc-addaf6a9584d + https://www.npr.org/2023/06/16/1182827705/james-marsden-on-little-white-lies-and-being-the-other-guy + no + WWDTM: James Marsden + 1349 + + 2884 + no + full + Jury Duty, playing yourself, and what it's like to be the Baxter in a romcom.]]> + + + + WWDTM: Radhika Jones + + Sat, 10 Jun 2023 16:00:45 +0000 + 3f8ae279-9e44-4c67-b4c6-81cb2f0fffdf + https://www.npr.org/2023/06/09/1181349799/vanity-fairs-radhika-jones-talks-little-house-on-the-prairie + no + WWDTM: Radhika Jones + 1348 + + 2825 + no + full + + + + + WWDTM: The First Quarter Century, pt. III +
Support NPR by signing up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ Sat, 03 Jun 2023 16:00:07 +0000 + c08ab0e3-ec55-4f45-95c2-50a6f16ac26e + https://www.npr.org/2023/05/23/1177691126/25th-anniversary-spectacular-part-three + no + WWDTM: The First Quarter Century, pt. III + 1348 + + 2839 + no + full +
Support NPR by signing up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ +
+ + WWDTM: John Goodman + The Big Lebowski, and being the voice of the St. Louis airport.

Support NPR by signing up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ Sat, 27 May 2023 16:00:04 +0000 + cc485059-f896-40cc-8fad-eb69093fac76 + https://www.npr.org/2023/05/23/1177690696/john-goodman-on-the-dark-secret-behind-all-his-lovable-characters + no + WWDTM: John Goodman + 1347 + + 2904 + no + full + The Big Lebowski, and being the voice of the St. Louis airport.

Support NPR by signing up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ +
+ + WWDTM: Golda Rosheuvel + Bridgerton's Queen Charlotte that Netflix made a show all about her. She joins Luke Burbank, Negin Farsad, and Adam Burke to talk wigs, neck braces, and bodice ripping]]> + Sat, 20 May 2023 16:00:08 +0000 + 69c4dc60-143d-4915-a439-4e49cdd18ef6 + https://www.npr.org/2023/05/19/1177100450/bridgertons-golda-rosheuvel-talks-giant-wigs-and-neck-braces + no + WWDTM: Golda Rosheuvel + 1346 + + 2832 + no + full + Bridgerton's Queen Charlotte that Netflix made a show all about her. She joins Luke Burbank, Negin Farsad, and Adam Burke to talk wigs, neck braces, and bodice ripping]]> + + + + WWDTM: Gabrielle Dennis + The Big Door Prize's Gabrielle Dennis joins panelists Paula Poundstone, Alonzo Bodden, and Adam Felber to talk about living to your full potential and the job at Six Flags that everyone else is jealous of.]]> + Sat, 13 May 2023 16:00:57 +0000 + a397029b-59f4-47ec-9e39-b8d2a4461c77 + https://www.npr.org/2023/05/10/1175307952/gabrielle-dennis-on-working-at-six-flags-and-giving-audiences-existential-crises + no + WWDTM: Gabrielle Dennis + 1345 + + 2900 + no + full + The Big Door Prize's Gabrielle Dennis joins panelists Paula Poundstone, Alonzo Bodden, and Adam Felber to talk about living to your full potential and the job at Six Flags that everyone else is jealous of.]]> + + + + WWDTM: Ray Romano +
Support NPR by signing up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ Sat, 06 May 2023 15:55:33 +0000 + a2d65027-6f4c-447a-b7f4-d300bbfeead1 + https://www.npr.org/2023/05/05/1174279082/ray-romano-on-the-real-secret-to-a-35-year-marriage + no + WWDTM: Ray Romano + 1344 + 2883 + no + full +
Support NPR by signing up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ +
+ + WWDTM: Brad Paisley + + Sat, 29 Apr 2023 16:04:28 +0000 + 90a30d89-0284-4702-b7a1-9be7e6acf065 + https://www.npr.org/2023/04/28/1172852428/brad-paisley-on-what-to-avoid-when-writing-songs-about-your-wife + no + WWDTM: Brad Paisley + 1343 + + 2864 + no + full + + + + + WWDTM: Weird Al Yankovic + + Sat, 22 Apr 2023 16:00:09 +0000 + 2e957727-c248-43c4-b346-825c36c551b9 + https://www.npr.org/2023/04/19/1170960666/weird-al-on-getting-turned-down-by-prince + no + WWDTM: Weird Al Yankovic + 1342 + + 2835 + no + full + + + + + WWDTM: Kaila Mullady +
Support NPR by signing up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ Sat, 15 Apr 2023 16:00:26 +0000 + 4f126ad8-260d-45f4-bbce-fb7e32fd32d9 + https://www.npr.org/2023/04/11/1169359677/beatbox-champion-kaila-mullady-on-boots-and-cats + no + WWDTM: Kaila Mullady + 1341 + + 2873 + no + full +
Support NPR by signing up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ +
+ + WWDTM: Everyone & Goodbyes + + Wed, 12 Apr 2023 14:00:16 +0000 + c6b8817b-d983-40a4-8efd-816baef4379b + https://www.npr.org/2023/04/11/1169326367/everyone-goodbyes + no + WWDTM: Everyone & Goodbyes + 1340 + + 357 + no + full + + + + + WWDTM: The First Quarter Century + + Sat, 08 Apr 2023 15:55:22 +0000 + 9f72fb3e-86f3-45a6-9360-e35f651decf1 + https://www.npr.org/2023/04/03/1167848677/the-wait-wait-25th-anniversary-spectacular-pt-ii + no + WWDTM: The First Quarter Century + 1359 + 2883 + no + full + + + + + WWDTM: Michelle Rodriguez + The Fast & The Furious, Dungeons & Dragons, and her personal state slogan for New Jersey.


Support NPR by signing up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ Sat, 01 Apr 2023 16:01:14 +0000 + b9108ee9-9e5c-490e-922f-d084dcfe0357 + https://www.npr.org/2023/03/29/1166938226/michelle-rodriguez-on-fast-cars-and-fiery-dragons + no + WWDTM: Michelle Rodriguez + 1358 + + 2900 + no + full + The Fast & The Furious, Dungeons & Dragons, and her personal state slogan for New Jersey.


Support NPR by signing up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ +
+ + WWDTM: David Axelrod +
Support NPR by signing up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ Sat, 25 Mar 2023 15:58:56 +0000 + 6cf547c3-a91f-4439-a4b6-c456a3aaed4a + https://www.npr.org/2023/03/24/1165881738/david-axelrod-on-president-paula-poundstone + no + WWDTM: David Axelrod + 1357 + + 2825 + no + full +
Support NPR by signing up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ +
+ + WWDTM: Sam Waterston + Law & Order, how to look good in a stovepipe hat, and where to see raccoons doing Shakespeare.

Support NPR by signing up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ Sat, 18 Mar 2023 16:00:26 +0000 + ec37b61d-4b54-4abc-ac68-3a8dcbed8f60 + https://www.npr.org/2023/03/17/1164375148/sam-waterston-on-being-the-most-recognizable-pretend-lawyer-in-new-york + no + WWDTM: Sam Waterston + 1356 + + 2899 + no + full + Law & Order, how to look good in a stovepipe hat, and where to see raccoons doing Shakespeare.

Support NPR by signing up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ +
+ + Nick Kroll + Big Mouth and History of the World, Part II. We Rick Roll Nick Kroll, that is, ask him questions about roles played by actors named Rick.

Support NPR by signing up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ Sat, 11 Mar 2023 16:55:06 +0000 + f15a2e22-5bc6-43ae-a1f5-01f541faa8ac + https://www.npr.org/2023/03/06/1161353805/nick-kroll-on-rejected-characters-and-getting-mel-brooks-to-laugh + no + Nick Kroll + 1355 + + 2832 + no + full + Big Mouth and History of the World, Part II. We Rick Roll Nick Kroll, that is, ask him questions about roles played by actors named Rick.

Support NPR by signing up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ +
+ + The Wait Wait Anthology: Reality TV Edition + The Wait Wait Anthology, we bring the drama and dive deep into the world of reality TV. From The Bachelor to The Kardashians, we leave no stone unturned and no suite un-fantasied.]]> + Wed, 08 Mar 2023 22:35:58 +0000 + 59c2e813-930d-4d34-b939-ca66abceed72 + https://www.npr.org/2023/03/01/1160422578/the-wait-wait-anthology-reality-tv-edition + no + The Wait Wait Anthology: Reality TV Edition + 1354 + 1002 + no + full + The Wait Wait Anthology, we bring the drama and dive deep into the world of reality TV. From The Bachelor to The Kardashians, we leave no stone unturned and no suite un-fantasied.]]> + + + + Malala Yousafzai on winning the Nobel Peace Prize while in chemistry class +
A message to listeners: a recent error with Apple Podcasts meant you might not have been able to hear our regular weekend show without signing up for Wait Wait... Don't Tell Me+. We have worked with Apple to correct the error. Regular episodes have not changed and will remain free and available. We've shared some information about managing your subscription via Apple on our Facebook page: https://bit.ly/3mamqm2

Support NPR by signing up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ Sat, 04 Mar 2023 17:00:40 +0000 + 5711c982-ea8d-4c66-9366-d54bc74b6730 + https://www.npr.org/2023/03/01/1160422137/malala-yousafzai-won-the-nobel-peace-prize-while-in-chemistry + no + Malala Yousafzai on winning the Nobel Peace Prize while in chemistry class + 1353 + + 2907 + no + full +
A message to listeners: a recent error with Apple Podcasts meant you might not have been able to hear our regular weekend show without signing up for Wait Wait... Don't Tell Me+. We have worked with Apple to correct the error. Regular episodes have not changed and will remain free and available. We've shared some information about managing your subscription via Apple on our Facebook page: https://bit.ly/3mamqm2

Support NPR by signing up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ +
+ + Best of the First 25 Years + + Sat, 25 Feb 2023 17:02:14 +0000 + 06cc54f1-d5af-4754-8739-1d6b2a83bd5f + https://www.npr.org/2023/02/22/1158897769/best-of-the-first-25-years + no + Best of the First 25 Years + 1352 + 2844 + no + full + + + + + Everyone & Spotify Stalking + + Wed, 22 Feb 2023 15:52:11 +0000 + 5a35c9d0-e5fb-4531-839b-c5c0f13d615f + https://www.npr.org/2023/02/15/1157244889/everyone-spotify-stalking + no + Everyone & Spotify Stalking + 1351 + + 913 + no + full + + + + + Rosie Perez + Do The Right Thing to White Men Can't Jump to her new role on Showtime's Your Honor. She's the best part of any project she's in, but can she answer our questions about advice columns?]]> + Sat, 18 Feb 2023 17:00:05 +0000 + dec38ff6-7281-47f7-9631-c589c2463164 + https://www.npr.org/2023/02/15/1157244228/rosie-perez-on-her-first-meeting-with-spike-lee + no + Rosie Perez + 1350 + + 2819 + no + full + Do The Right Thing to White Men Can't Jump to her new role on Showtime's Your Honor. She's the best part of any project she's in, but can she answer our questions about advice columns?]]> + + + + Geena Davis + Mission Unstoppable. But, can she answer our three questions about blue jeans?

Sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ Sat, 11 Feb 2023 17:00:06 +0000 + c3817e1b-8a5e-465f-a833-c173fe30d2e7 + https://www.npr.org/2023/02/08/1155478251/geena-davis-on-her-early-gig-as-a-mannequin + no + Geena Davis + 1348 + + 2859 + no + full + Mission Unstoppable. But, can she answer our three questions about blue jeans?

Sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ +
+ + The Wait Wait Anthology: Cats Edition + The Wait Wait Anthology, a deep dive into the Wait Wait archives hosted by Bill Kurtis]]> + Wed, 08 Feb 2023 20:33:14 +0000 + 6aeb0f22-f17b-476f-95aa-038de3270916 + https://www.npr.org/2023/02/08/1155477408/the-wait-wait-anthology-cats-edition + no + The Wait Wait Anthology: Cats Edition + 1347 + 746 + no + full + The Wait Wait Anthology, a deep dive into the Wait Wait archives hosted by Bill Kurtis]]> + + + + Billy Porter + 80 For Brady, but what does he know about the Brady Bunch?

Sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ Sat, 04 Feb 2023 17:00:22 +0000 + 6412028e-f52c-41f6-9350-8637d2d075b3 + https://www.npr.org/2023/01/30/1152554955/billy-porter-on-the-thin-line-between-fashion-and-pain + no + Billy Porter + 1346 + + 2819 + no + full + 80 For Brady, but what does he know about the Brady Bunch?

Sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ +
+ + Natasha Lyonne + Poker Face, so we ask her three questions about getting botox.

Sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ Sat, 28 Jan 2023 17:00:13 +0000 + 0a24549e-cfee-4d7b-8341-968d2ecfa0b5 + https://www.npr.org/2023/01/27/1152121831/natasha-lyonne-gets-kicked-out-of-boarding-school + no + Natasha Lyonne + 1345 + + 2846 + no + full + Poker Face, so we ask her three questions about getting botox.

Sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ +
+ + Everyone & Mistaken Identity + + Wed, 25 Jan 2023 20:47:02 +0000 + 2146f28a-685b-4207-8815-4bf17d36cd69 + https://www.npr.org/2023/01/10/1148231915/everyone-mistaken-identity + no + Everyone & Mistaken Identity + 1344 + + 663 + no + full + + + + + Secretary of State Antony Blinken +
Sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ Sat, 21 Jan 2023 17:07:28 +0000 + bb6527ee-51e2-431f-87ab-a09eeb3f4c30 + https://www.npr.org/2023/01/18/1149896025/secretary-of-state-antony-blinken-musical-alter-ego + no + Secretary of State Antony Blinken + 1343 + + 2846 + no + full +
Sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ +
+ + George Saunders +
Sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ Sat, 14 Jan 2023 16:50:21 +0000 + c979588b-d46c-4618-b61f-b1a368a1586a + https://www.npr.org/2023/01/10/1148230592/george-saunders-on-slaughterhouses-and-obscene-poetry + no + George Saunders + 1342 + + 2862 + no + full +
Sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ +
+ + Mariska Hargitay + Law and Order, SVU, answers three questions about Sweet Valley High, the location depicted in the teen book series. We also revisit our moments with Sean Hayes, Myles Stubblefield, and Mo Amer.]]> + Sat, 07 Jan 2023 16:59:10 +0000 + fbcda8b3-5e43-4505-9f47-8b3c2027d592 + https://www.npr.org/2022/12/21/1144810950/mariska-hargitay + no + Mariska Hargitay + 1341 + 2853 + no + full + Law and Order, SVU, answers three questions about Sweet Valley High, the location depicted in the teen book series. We also revisit our moments with Sean Hayes, Myles Stubblefield, and Mo Amer.]]> + + + + The Man Bill-hind the Voice! + + Wed, 04 Jan 2023 16:23:47 +0000 + 7744f58c-247c-4798-a163-b28f1ee10a8c + https://www.npr.org/2023/01/03/1146780258/the-man-bill-hind-the-voice + no + The Man Bill-hind the Voice! + 1340 + 850 + no + full + + + + + Best of Not My Job December 2022 + + Sat, 31 Dec 2022 17:00:40 +0000 + 647aba78-bbea-415f-b244-a3de697295ed + https://www.npr.org/2022/12/21/1144810585/best-of-not-my-job-december-2022 + no + Best of Not My Job December 2022 + 1339 + 2833 + no + full + + + + + Sarah Polley + + Sat, 24 Dec 2022 17:00:40 +0000 + 722c1847-8d83-4605-9cae-0b2cdf1bc4b5 + https://www.npr.org/2022/12/20/1144385047/sarah-polley-women-talking-book-club + no + Sarah Polley + 1338 + + 2802 + no + full + + + + + Everyone & Improv Nerds + + Wed, 21 Dec 2022 17:40:20 +0000 + 439de59d-7c49-4f34-8d90-ee2178b8bd2c + https://www.npr.org/2022/12/20/1144385003/peter-sagal-running-central-park + no + Everyone & Improv Nerds + 1337 + + 956 + no + full + + + + + Andrew Bird +

Sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ Sat, 17 Dec 2022 16:59:40 +0000 + 54652806-ba2e-43a7-ba9e-1e1b4101894e + https://www.npr.org/2022/12/14/1142825416/andrew-bird-inside-problems-mariah-carey + no + Andrew Bird + 1336 + + 2898 + no + full +

Sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ +
+ + Gayle King + CBS Mornings, Gayle King plays our game called, "Gayle King, meet the real Gale Kings" three questions about meteorologists. She is joined by panelists Negin Farsad, Peter Grosz, and Faith Salie.]]> + Sat, 10 Dec 2022 17:03:19 +0000 + 04ff4bb2-8342-4ce6-9c66-58226f51d5b4 + https://www.npr.org/2022/12/06/1141003550/gayle-king-cbs-mornings + no + Gayle King + 1335 + + 2822 + no + full + CBS Mornings, Gayle King plays our game called, "Gayle King, meet the real Gale Kings" three questions about meteorologists. She is joined by panelists Negin Farsad, Peter Grosz, and Faith Salie.]]> + + + + How to Become a Listener Contestant with Peter, Miles, and Sofie + + Wed, 07 Dec 2022 15:01:21 +0000 + fe2176b1-2cd5-4fab-a14c-95fb6fa133dd + https://www.npr.org/2022/12/06/1141003318/wait-wait-dont-tell-me-caller-contestant + no + How to Become a Listener Contestant with Peter, Miles, and Sofie + 1335 + 751 + no + full + + + + + Dana Carvey +
Sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ Sat, 03 Dec 2022 17:51:18 +0000 + 942fe26c-424d-4bc5-8385-94e93142c59f + https://www.npr.org/2022/11/15/1136884298/dana-carvey-snl-impressions + no + Dana Carvey + 1334 + + 2907 + no + full +
Sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ +
+ + WWDTM Best of NMJ Thanksgiving +

Sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ Sat, 26 Nov 2022 17:04:10 +0000 + 5abadc5c-f489-4ee3-b434-e2acdbe56308 + https://www.npr.org/2022/11/15/1136884107/wwdtm-best-of-nmj-thanksgiving + no + WWDTM Best of NMJ Thanksgiving + 1333 + 2842 + no + full +

Sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ +
+ + Everyone & Giant Puzzles + + Wed, 23 Nov 2022 14:59:00 +0000 + 281c1165-57af-43b1-8e94-5a8f53a387ef + https://www.npr.org/2022/11/22/1138692930/worlds-largest-puzzle-dowdle-costco + no + Everyone & Giant Puzzles + 1332 + + 811 + no + full + + + + + Freddie Johnson +

Sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ Sat, 19 Nov 2022 17:03:50 +0000 + fb02593b-fe26-48f3-aa84-9c95144fb169 + https://www.npr.org/2022/11/15/1136883659/freddie-johnson + no + Freddie Johnson + 1331 + 2865 + no + full +

Sign up for Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org.]]>
+ +
+ + Craig Robinson + Office and host of Peacock's Harlem Globetrotters: Play It Forward, plays our game about Craigslist. Joining him are panelists Tom Papa; Negin Farsad and Brian Babylon.]]> + Sat, 12 Nov 2022 17:15:45 +0000 + a59c695c-ce17-449b-9ffd-d5ac7df9a238 + https://www.npr.org/2022/11/09/1135596958/craig-robison-the-office-darryl-music-chicago + no + Craig Robinson + 1330 + + 2891 + no + full + Office and host of Peacock's Harlem Globetrotters: Play It Forward, plays our game about Craigslist. Joining him are panelists Tom Papa; Negin Farsad and Brian Babylon.]]> + + + + Behind the scenes with Peter and Lillian + + Wed, 09 Nov 2022 23:26:08 +0000 + bb735a89-8081-47ab-afa0-1f6d0bbfaec3 + https://www.npr.org/2022/11/09/1135596710/behind-the-scenes-with-peter-and-lillian + no + Behind the scenes with Peter and Lillian + 1329 + 838 + no + full + + + + + Amber Ruffin + The Amber Ruffin Show on Peacock and the purple M&M, plays our game called "Roughin' It" Three questions about camping. Joining in are panelists Karen Chee, Hari Kondabolu and Maz Jobrani.
Subscribe to Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org/waitwait.]]>
+ Sat, 05 Nov 2022 16:26:33 +0000 + 0ae327be-38ee-490f-afe6-b6539332762b + https://www.npr.org/2022/11/04/1134254076/amber-ruffin-peacock-purple-m-and-m + no + Amber Ruffin + 1328 + + 2914 + no + full + The Amber Ruffin Show on Peacock and the purple M&M, plays our game called "Roughin' It" Three questions about camping. Joining in are panelists Karen Chee, Hari Kondabolu and Maz Jobrani.
Subscribe to Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org/waitwait.]]>
+ +
+ + Hasan Minhaj + Patriot Act, plays our game about former Patriot Rob Gronkowski. Joining him are panelists Helen Hong, Mo Rocca, and Shane O'Neill.]]> + Sat, 29 Oct 2022 15:47:07 +0000 + f400235b-847a-4853-9db2-51df2591231c + https://www.npr.org/2022/10/25/1131475743/hasan-minhaj-netflix-kings-jester + no + Hasan Minhaj + 1327 + + 2878 + no + full + Patriot Act, plays our game about former Patriot Rob Gronkowski. Joining him are panelists Helen Hong, Mo Rocca, and Shane O'Neill.]]> + + + + Everyone & The Supernatural + + Wed, 26 Oct 2022 16:37:52 +0000 + a0a6ee55-032f-4cbc-9522-5e51100c9bb1 + https://www.npr.org/2022/10/25/1131475540/ghost-hunting-lighthouse-keepers + no + Everyone & The Supernatural + 1326 + + 1057 + no + full + + + + + Dr. Ibram X. Kendi, a real genius + + Sat, 22 Oct 2022 16:00:08 +0000 + 9f99e4eb-4f69-47fb-b973-95be5416bf55 + https://www.npr.org/2022/10/03/1126619472/dr-ibram-x-kendi-a-real-genius + no + Dr. Ibram X. Kendi, a real genius + 1325 + + 2953 + no + full + + + + + Vivek Murthy + + Sat, 15 Oct 2022 15:52:46 +0000 + 0f140b77-18c8-4c6f-b8de-595bbbe5bd75 + https://www.npr.org/2022/10/03/1126619233/vivek-murthy + no + Vivek Murthy + 1324 + 2879 + no + full + + + + + Behind the scenes with Peter Sagal and Emma Choi + + Wed, 12 Oct 2022 14:13:06 +0000 + 82c22bd1-5117-479b-9473-e89a76bf34e5 + https://www.npr.org/2022/10/07/1127600905/wait-wait-dont-tell-me-peter-sagal-emma-choi + no + Behind the scenes with Peter Sagal and Emma Choi + 1323 + + 1222 + no + full + + + + + Ralph Macchio + + Sat, 08 Oct 2022 16:22:39 +0000 + 337cbbce-49ad-475c-b666-68291130f434 + https://www.npr.org/2022/10/03/1126618998/ralph-macchio-cobra-kai-netflix + no + Ralph Macchio + 1322 + + 2905 + no + full + + + + + Julio Torres + Los Espookys, plays our game about The Addams Family called, "Los Ookys." Joining him are panelists Tom Papa, DulcÊ Sloan, and Hari Kondabolu.]]> + Sat, 01 Oct 2022 16:07:48 +0000 + 96b346b9-4fc4-4117-9768-619bdb55172e + https://www.npr.org/2022/09/27/1125421675/julio-torres-los-espookys-hbo + no + Julio Torres + 1321 + + 2831 + no + full + Los Espookys, plays our game about The Addams Family called, "Los Ookys." Joining him are panelists Tom Papa, DulcÊ Sloan, and Hari Kondabolu.]]> + + + + Everyone & Spoons + + Wed, 28 Sep 2022 14:00:15 +0000 + 1baa6fb7-ac8c-4c77-9531-b88fe04b3302 + https://www.npr.org/2022/09/27/1125421344/how-lalese-stamps-became-a-ceramics-celebrity + no + Everyone & Spoons + 1320 + + 786 + no + full + + + + + Michael Strahan + Good Morning America host, Michael plays our game called "Strahan? Meet Stray Hams" Three questions about wild hogs. He is joined by panelists Karen Chee, Negin Farsad, and Shane O'Neill.]]> + Sat, 24 Sep 2022 16:01:14 +0000 + 212a3095-86d8-448f-b50a-3a22d6df2425 + https://www.npr.org/2022/09/20/1124070886/michael-strahan-skincare-new-york-giants + no + Michael Strahan + 1319 + + 2894 + no + full + Good Morning America host, Michael plays our game called "Strahan? Meet Stray Hams" Three questions about wild hogs. He is joined by panelists Karen Chee, Negin Farsad, and Shane O'Neill.]]> + + + + Everyone & A Giant Worm + + Wed, 21 Sep 2022 14:00:14 +0000 + b351e290-80f0-41a5-920b-707c7eefbfde + https://www.npr.org/2022/09/20/1124073310/boy-found-big-worm-new-zealand + no + Everyone & A Giant Worm + 1318 + + 858 + no + full + + + + + Mo Amer + + Sat, 17 Sep 2022 16:00:10 +0000 + 0988865c-508b-424b-b3b0-9816c24ccc58 + https://www.npr.org/2022/09/13/1122767713/wait-wait-for-220917 + no + Mo Amer + 1317 + + 2859 + no + full + + + + + Everyone & Time Capsules + + Wed, 14 Sep 2022 17:31:56 +0000 + 9e6c328e-4ec6-4759-9340-d34dfe33950b + https://www.npr.org/2022/09/13/1122767012/andy-warhol-museum-pittsburgh-time-capsule + no + Everyone & Time Capsules + 1316 + + 946 + no + full + + + + + Abbi Jacobson +




Subscribe to Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org/waitwait.]]>
+ Sat, 10 Sep 2022 16:00:34 +0000 + fe3c7b5f-1d3c-46e9-b702-045b74c985bd + https://www.npr.org/2022/09/06/1121344648/abbi-jacobson-a-league-of-their-own + no + Abbi Jacobson + 1315 + + 2881 + no + full +




Subscribe to Wait Wait... Don't Tell Me+ via Apple Podcasts or at plus.npr.org/waitwait.]]>
+ +
+ + Everyone & Ostrich Escape + + Wed, 07 Sep 2022 13:59:17 +0000 + d33d9817-b1ca-4b36-9887-3478dffc00a5 + https://www.npr.org/2022/09/06/1121344199/comedian-josh-gondelman-joins-emma-to-outsmart-a-runaway-ostrich + no + Everyone & Ostrich Escape + 1314 + + 915 + no + full + + + + + Chris Estrada + This Fool, plays our game called, "This Fool, Meet April Fools!" He is joined by panelists Skyler Higley, Maeve Higgins and Mo Rocca.]]> + Sat, 03 Sep 2022 15:55:55 +0000 + 6b278567-8bbc-4de3-bc11-d4aa30afa2ac + https://www.npr.org/2022/08/02/1115198450/this-fool-hulu-chris-estrada-seinfeld + no + Chris Estrada + 1313 + + 2914 + no + full + This Fool, plays our game called, "This Fool, Meet April Fools!" He is joined by panelists Skyler Higley, Maeve Higgins and Mo Rocca.]]> + + + + Everyone & Names + + Wed, 31 Aug 2022 14:00:47 +0000 + ea51a3b2-f5bb-41a0-8240-b7f92e72f6db + https://www.npr.org/2022/08/02/1115199700/tattoo-design-tips-girl-knew-york + no + Everyone & Names + 1312 + + 907 + no + full + + + + + Congresswoman Eleanor Holmes Norton + + Sat, 27 Aug 2022 16:00:36 +0000 + 2fa15a53-bd77-47bd-be99-728b1755cf81 + https://www.npr.org/2022/08/02/1115198108/eleanor-holmes-norton-washington-dc-congress + no + Congresswoman Eleanor Holmes Norton + 1311 + + 2923 + no + full + + + + + Everyone & School + + Wed, 24 Aug 2022 14:00:39 +0000 + ed223e32-7223-4132-8924-255da8db6a7a + https://www.npr.org/2022/08/02/1115199545/best-back-to-school-tips-everyone-and-their-mom + no + Everyone & School + 1310 + + 1443 + no + full + + + + + More Best of Not My Job + + Sat, 20 Aug 2022 16:02:33 +0000 + 83d677d8-c808-4846-8e34-79812be6e981 + https://www.npr.org/2022/08/02/1115197883/wwdtm-220820 + no + More Best of Not My Job + 1309 + 2889 + no + full + + + + + It's an 'Everyone & Their Mom' family reunion! + + Wed, 17 Aug 2022 18:17:01 +0000 + 7adb4eb3-4a68-41e0-9013-d33af65aa82e + https://www.npr.org/2022/08/02/1115199406/everyone-and-their-mom-family-reunion + no + It's an 'Everyone & Their Mom' family reunion! + 1308 + + 949 + no + full + + + + + Best of Not My Job + + Sat, 13 Aug 2022 16:01:33 +0000 + d7ee3b59-4331-4728-80d4-f1ae3207b5e2 + https://www.npr.org/2022/08/02/1115197684/best-of-not-my-job + no + Best of Not My Job + 1307 + 2915 + no + full + + + + + Everyone & Asparagus + + Wed, 10 Aug 2022 14:00:58 +0000 + 25f57172-2731-4cd0-9974-2334bb93d3ef + https://www.npr.org/2022/08/02/1115199295/predict-the-future-asparagus-mystic-veg + no + Everyone & Asparagus + 1306 + + 943 + no + full + + + + + Robin Thede + A Black Lady Sketch Show, plays our game about Bob Ross, "A White Dude Painting Show," along with guest host Negin Farsad and panelists Adam Burke, Amy Dickinson and Hari Kondabolu.]]> + Sat, 06 Aug 2022 15:56:21 +0000 + 727943d3-8ad5-48aa-b515-020d3afbf26f + https://www.npr.org/2022/08/02/1115197498/robin-thede-a-black-lady-sketch-show + no + Robin Thede + 1305 + + 2835 + no + full + A Black Lady Sketch Show, plays our game about Bob Ross, "A White Dude Painting Show," along with guest host Negin Farsad and panelists Adam Burke, Amy Dickinson and Hari Kondabolu.]]> + + + + Everyone & Socks + + Wed, 03 Aug 2022 13:59:15 +0000 + e8f3076b-ccec-4400-b9eb-b1c8ed078f88 + https://www.npr.org/2022/08/02/1115198654/lifetime-guaratee-judge-marilyn-milian + no + Everyone & Socks + 1304 + + 716 + no + full + + + + + Jeremy Allen White + The Bear plays our game called "Please Look After This Bear" three questions about Paddington Bear. He is joined by guest host Tom Papa and panelists Helen Hong, Bobcat Goldthwait, and Faith Salie.]]> + Sat, 30 Jul 2022 17:23:02 +0000 + e6413e75-f6f5-499a-ad8d-b971e0d61b9e + https://www.npr.org/2022/07/25/1113450332/jeremy-allen-white-the-bear-hulu-chefs + no + Jeremy Allen White + 1303 + + 2859 + no + full + The Bear plays our game called "Please Look After This Bear" three questions about Paddington Bear. He is joined by guest host Tom Papa and panelists Helen Hong, Bobcat Goldthwait, and Faith Salie.]]> + + + + Everyone & Van Gogh + + Wed, 27 Jul 2022 14:00:41 +0000 + e6fb3b88-07bd-484d-a35e-f58da2929109 + https://www.npr.org/2022/07/25/1113449607/van-gogh-hidden-painting-tiktok-devin-halbal + no + Everyone & Van Gogh + 1302 + + 662 + no + full + + + + + Nathan Lane + + Sat, 23 Jul 2022 16:01:44 +0000 + 3af54edd-15c7-4f70-9500-d3aee8355409 + https://www.npr.org/2022/07/19/1112317705/nathan-lane-most-nominated-guest-actor-emmys + no + Nathan Lane + 1301 + + 2860 + no + full + + + + + Everyone & Minions + + Wed, 20 Jul 2022 14:02:50 +0000 + f518e734-8b2b-4616-bade-db65004cc1b7 + https://www.npr.org/2022/07/19/1112317555/jenny-slate-marcel-the-shell-minions + no + Everyone & Minions + 1300 + + 934 + no + full + + + + + Puja Patel + + Sat, 16 Jul 2022 15:53:57 +0000 + e15d0254-6ba5-42ad-a38f-6e59852ad4d1 + https://www.npr.org/2022/07/12/1111057119/puja-patel + no + Puja Patel + 1299 + + 2838 + no + full + + + + + Everyone & Games + + Wed, 13 Jul 2022 14:01:11 +0000 + 1f206077-b7b5-4ede-a62b-6e91b2341414 + https://www.npr.org/2022/07/12/1111056734/everyone-and-their-mom-wait-wait-secret-game-show + no + Everyone & Games + 1298 + + 673 + no + full + + + + + Del the Funky Homosapien + + Sat, 09 Jul 2022 16:01:26 +0000 + 94bb066d-5474-42ac-93ce-fe434454e4e7 + https://www.npr.org/2022/06/28/1108398040/del-the-funky-homosapien + no + Del the Funky Homosapien + 1297 + 2878 + no + full + + + + + Everyone & Cowboys + + Wed, 06 Jul 2022 14:00:48 +0000 + 09c005bf-8883-49fc-84a3-737ff0a985d2 + https://www.npr.org/2022/06/28/1108398301/cowboy-culture-orville-peck-the-compton-cowboys-randy-savvy + no + Everyone & Cowboys + 1296 + + 1609 + no + full + + + + + Darryl "Cornbread" McCray + + Sat, 02 Jul 2022 16:54:50 +0000 + 39e4d68a-14a7-4ed7-8e28-36ba16e29f70 + https://www.npr.org/2022/06/27/1107843977/darryl-cornbread-mccray + no + Darryl "Cornbread" McCray + 1295 + 2841 + no + full + + + + + Everyone & Lost and Found + + Wed, 29 Jun 2022 14:00:07 +0000 + f752a879-1643-47e5-9039-d0d40f3e4a80 + https://www.npr.org/2022/06/27/1107844771/what-we-lost-ubers-what-we-found + no + Everyone & Lost and Found + 1294 + + 1149 + no + full + + + + + Dan Perrault and Tony Yacenda + Players, on Paramount+ play our game about a C-sport: croquet. Joining them are panelists Peter Grosz, Adam Burke and Helen Hong.]]> + Sat, 25 Jun 2022 15:58:45 +0000 + 9b93bd02-dc08-438e-ae55-54281191bc34 + https://www.npr.org/2022/06/21/1106386916/dan-perrault-and-tony-yacenda + no + Dan Perrault and Tony Yacenda + 1293 + 2845 + no + full + Players, on Paramount+ play our game about a C-sport: croquet. Joining them are panelists Peter Grosz, Adam Burke and Helen Hong.]]> + + + + Everyone & Elvis + + Wed, 22 Jun 2022 14:00:23 +0000 + f0550ebb-3ca3-4b6e-ba9c-b20e912f7e09 + https://www.npr.org/2022/06/21/1106386782/no-more-elvis-presley-impersonator-wedding-las-vegas + no + Everyone & Elvis + 1292 + + 921 + no + full + + + + + Sean Hayes + + Sat, 18 Jun 2022 16:00:17 +0000 + a9d7d663-2cac-437e-8686-dbd3edfd1291 + https://www.npr.org/2022/06/14/1104971554/sean-hayes + no + Sean Hayes + 1291 + + 2909 + no + full + + + + + Everyone & Dolphins + + Wed, 15 Jun 2022 14:01:26 +0000 + e6b94802-fa41-43a1-9863-b8e318b44d96 + https://www.npr.org/2022/06/14/1104971354/dolphins-recognize-each-other-urine-study-stephen-f-austin + no + Everyone & Dolphins + 1290 + + 1000 + no + full + + + + + Kenan Thompson + + Sat, 11 Jun 2022 15:57:13 +0000 + aa94213b-6971-42c3-bc6d-8b1d39f6024d + https://www.npr.org/2022/06/07/1103508516/kenan-thompson + no + Kenan Thompson + 1289 + + 2895 + no + full + + + + + Everyone & Peter Sagal + Wait Wait... Don't Tell Me! host Peter Sagal pops by to share his favorites from the podcast so far. Turns out Peter loves Mountain Dew, Emma's Grandma's Kimchi, and unsolved ham mysteries.]]> + Wed, 08 Jun 2022 14:03:19 +0000 + 36f6d13c-57d4-4612-90b1-875b0193b436 + https://www.npr.org/2022/06/07/1103506745/best-of-peter-sagal-domee-shi-samin-nosrat + no + Everyone & Peter Sagal + 1288 + + 1266 + no + full + Wait Wait... Don't Tell Me! host Peter Sagal pops by to share his favorites from the podcast so far. Turns out Peter loves Mountain Dew, Emma's Grandma's Kimchi, and unsolved ham mysteries.]]> + + + + Best of NMJ June 2022 + + Sat, 04 Jun 2022 16:00:50 +0000 + a251c26f-3375-4ddb-8eff-e9c81b75f0c8 + https://www.npr.org/2022/05/31/1102174166/best-of-nmj-june-2022 + no + Best of NMJ June 2022 + 1287 + + 2825 + no + full + + + + + Everyone & Graduation + + Wed, 01 Jun 2022 14:11:26 +0000 + ec6275c1-6998-4a2b-b4e6-eebb9cfae5c0 + https://www.npr.org/2022/05/31/1102174465/2022-graduation-president-barack-obama-speechwriter + no + Everyone & Graduation + 1286 + + 1000 + no + full + + + + + Earlonne Woods and Nigel Poor + + Sat, 28 May 2022 15:50:57 +0000 + 84c8eb9c-e557-4909-b5ec-90613cc3bd34 + https://www.npr.org/2022/05/20/1100524480/earlonne-woods-and-nigel-poor + no + Earlonne Woods and Nigel Poor + 1285 + 2885 + no + full + + + + + Everyone & Cruise Ship Love + + Wed, 25 May 2022 13:12:22 +0000 + 6e92bc50-5ac0-4658-9bf0-8f22a24fa2f5 + https://www.npr.org/2022/05/20/1100524449/love-cruise-ships-guinness-world-record + no + Everyone & Cruise Ship Love + 1284 + + 914 + no + full + + + + + Mandy Moore + This Is Us, plays our game called This is Utz, three questions about the snack brand Utz. She is joined by panelists Alonzo Bodden, Maeve Higgins, and Tom Papa.]]> + Sat, 21 May 2022 15:58:10 +0000 + ccc9fa83-4c14-42dd-98e7-bca92949f95b + https://www.npr.org/2022/05/17/1099627383/mandy-moore + no + Mandy Moore + 1283 + + 2849 + no + full + This Is Us, plays our game called This is Utz, three questions about the snack brand Utz. She is joined by panelists Alonzo Bodden, Maeve Higgins, and Tom Papa.]]> + + + + Everyone & Skeletons + + Wed, 18 May 2022 12:00:32 +0000 + 75651d51-12bd-44df-bd15-0ed99fee929e + https://www.npr.org/2022/05/17/1099627268/medieval-times-knight-vegetarian-skeleton + no + Everyone & Skeletons + 1282 + + 965 + no + full + + + + + Hannah Einbinder + Hacks, Hannah Einbinder plays our game about life hacks. She is joined by panelists Peter Grosz, Faith Salie and Laci Mosley.]]> + Sat, 14 May 2022 16:00:41 +0000 + aa91fd70-cabf-469c-b46a-e94de9dd6b97 + https://www.npr.org/2022/05/10/1097962234/hannah-einbinder + no + Hannah Einbinder + 1281 + + 2871 + no + full + Hacks, Hannah Einbinder plays our game about life hacks. She is joined by panelists Peter Grosz, Faith Salie and Laci Mosley.]]> + + + + Everyone & Pay Phones + + Wed, 11 May 2022 14:31:51 +0000 + 8b6525fb-9d7c-4879-9b47-4b3949ab9c58 + https://www.npr.org/2022/05/10/1097965385/minnesota-pay-phone-samin-nosrat-salt-fat-acid-heat + no + Everyone & Pay Phones + 1280 + + 1008 + no + full + + + + + Adam Scott + Severance, plays our game about weddings and marriage proposals. He is joined by panelists Maz Jobrani, Emmy Blotnick and Paula Poundstone.]]> + Sat, 07 May 2022 16:01:43 +0000 + 5bb3320f-2c89-4875-86fc-983ca8df104f + https://www.npr.org/2022/05/03/1096128956/adam-scott + no + Adam Scott + 1279 + + 2820 + no + full + Severance, plays our game about weddings and marriage proposals. He is joined by panelists Maz Jobrani, Emmy Blotnick and Paula Poundstone.]]> + + + + Everyone & Corpse Flowers + + Wed, 04 May 2022 14:00:53 +0000 + 6fb020e0-411b-4d27-899e-dc678cfd6bfb + https://www.npr.org/2022/05/03/1096128413/corpse-flower-dating-smell-test + no + Everyone & Corpse Flowers + 1278 + + 710 + no + full + + + + + Myles Stubblefield + + Sat, 30 Apr 2022 16:00:41 +0000 + 39add13a-209c-4e16-bcae-4ae1e6efaa2f + https://www.npr.org/2022/04/26/1094895528/myles-stubblefield + no + Myles Stubblefield + 1277 + + 2827 + no + full + + + + + Everyone & Mice + + Wed, 27 Apr 2022 18:41:13 +0000 + 4e9a5fc8-5b1e-42f9-92c4-02ba2f2cf79b + https://www.npr.org/2022/04/26/1094895665/fda-mice-rat-kings-atsuko-okatsuka + no + Everyone & Mice + 1276 + + 1151 + no + full + + + + + Stephen Merchant + + Sat, 23 Apr 2022 15:56:06 +0000 + 81812e9d-f3ef-4031-ab6f-68f464b01568 + https://www.npr.org/2022/04/19/1093670892/stephen-merchant + no + Stephen Merchant + 1275 + + 2846 + no + full + + + + + Everyone & Secret Messages + + Wed, 20 Apr 2022 14:00:08 +0000 + 4563f817-9cbb-43ad-938e-233d2db1a277 + https://www.npr.org/2022/04/19/1093670338/darwin-lost-notebooks-domee-shi-turning-red + no + Everyone & Secret Messages + 1274 + + 1269 + no + full + + + + + Best of NMJ April 2022 + + Sat, 16 Apr 2022 16:00:25 +0000 + 13c1404e-295c-42a8-9ec3-718c2c9f3a1a + https://www.npr.org/2022/04/05/1091134655/best-of-nmj-april-2022 + no + Best of NMJ April 2022 + 1273 + + 2829 + no + full + + + + + Everyone & Robot Dogs + + Wed, 13 Apr 2022 14:09:54 +0000 + 57bca443-bd14-4a70-9af8-495d553008cd + https://www.npr.org/2022/04/11/1092162972/boston-dynamics-robot-dogs-pompeii + no + Everyone & Robot Dogs + 1272 + + 783 + no + full + + + + + Matt Walsh + + Sat, 09 Apr 2022 16:13:10 +0000 + 20cfe3ed-761b-4fae-a02e-981007d1302d + https://www.npr.org/2022/04/05/1091081502/matt-walsh + no + Matt Walsh + 1271 + + 2814 + no + full + + + + + Everyone & Boring People + + Wed, 06 Apr 2022 14:00:17 +0000 + 8a9f70ee-dfe2-493a-9548-b7b3153475eb + https://www.npr.org/2022/04/05/1091081488/boring-people + no + Everyone & Boring People + 1270 + + 1015 + no + full + + + + + Slash + sashes: beauty pageants. He is joined by panelists Tom Papa, Cristela Alonzo and Maeve Higgins.]]> + Sat, 02 Apr 2022 14:47:47 +0000 + 639261af-9509-4b97-9fbc-08a4d60b8dd5 + https://www.npr.org/2022/04/01/1090415984/slash + no + Slash + 1269 + + 2818 + no + full + sashes: beauty pageants. He is joined by panelists Tom Papa, Cristela Alonzo and Maeve Higgins.]]> + + + + Everyone & Fancy Ham + + Wed, 30 Mar 2022 14:14:35 +0000 + 490a4f55-1a2d-466c-9e10-b7e6b0b3864e + https://www.npr.org/2022/03/29/1089533105/everyone-fancy-ham + no + Everyone & Fancy Ham + 1268 + + 1193 + no + full + + + + + Dan Snow + + Sat, 26 Mar 2022 16:02:10 +0000 + e30f7336-50da-4434-aa9f-3ccc0b5d93a0 + https://www.npr.org/2022/03/25/1088901175/dan-snow + no + Dan Snow + 1267 + + 2830 + no + full + + + + + Everyone & Bears + + Wed, 23 Mar 2022 14:00:17 +0000 + f1ab00ac-ab14-4cb1-89a6-2b0a38da6926 + https://www.npr.org/2022/03/22/1088128286/bears-love-is-blind + no + Everyone & Bears + 1266 + + 1360 + no + full + + + + + Zazie Beetz + Atlanta, Zazie Beetz, plays our game called "Zazie Beetz, meet Sassy Beats!" Three stories about Rolling Stones drummer Charlie Watts. She is joined by panelists Roy Blount Jr, Helen Hong and Mo Rocca.]]> + Sat, 19 Mar 2022 16:21:42 +0000 + bc3ad9ba-47f5-49af-85c0-2216c0105a6f + https://www.npr.org/2022/03/18/1087659907/zazie-beetz + no + Zazie Beetz + 1265 + + 2862 + no + full + Atlanta, Zazie Beetz, plays our game called "Zazie Beetz, meet Sassy Beats!" Three stories about Rolling Stones drummer Charlie Watts. She is joined by panelists Roy Blount Jr, Helen Hong and Mo Rocca.]]> + + + + Everyone & Dinosaurs + + Wed, 16 Mar 2022 14:00:14 +0000 + dc6ed3de-db37-416a-8303-33b009baac1b + https://www.npr.org/2022/03/15/1086794313/everyone-dinosaurs + no + Everyone & Dinosaurs + 1264 + + 1212 + no + full + + + + + Elana Meyers Taylor + + Sat, 12 Mar 2022 17:27:16 +0000 + 76431c8b-c706-44ef-8616-f808957ce270 + https://www.npr.org/2022/03/11/1086215442/elana-meyers-taylor + no + Elana Meyers Taylor + 1263 + + 2818 + no + full + + + + + Everyone & The Dew + + Wed, 09 Mar 2022 15:00:19 +0000 + a0d85914-d53b-48d1-bf3b-d962fc4fb706 + https://www.npr.org/2022/03/07/1085016220/everyone-the-dew + no + Everyone & The Dew + 1262 + + 873 + no + full + + + + + Porsha Williams + + Sat, 05 Mar 2022 16:59:12 +0000 + bd0c19b0-9ece-4adf-92e7-4f00a5bce8b7 + https://www.npr.org/2022/03/04/1084712766/porsha-williams + no + Porsha Williams + 1261 + + 2869 + no + full + + + + + Everyone & Roy Choi + + Wed, 02 Mar 2022 15:00:14 +0000 + ab558ce0-8b5a-4c52-b0a4-94a295832074 + https://www.npr.org/2022/02/25/1083154152/kimchi-tips-roy-choi + no + Everyone & Roy Choi + 1260 + + 1069 + no + full + + + + + Best of Not My Job February 2022 + + Sat, 26 Feb 2022 17:17:16 +0000 + 95d8a253-9447-4346-aa52-61db928fe96a + https://www.npr.org/2022/02/25/1083125867/best-of-not-my-job-february-2022 + no + Best of Not My Job February 2022 + 1259 + + 2815 + no + full + + + + + Everyone & Monkey Business + + Wed, 23 Feb 2022 12:58:15 +0000 + b972db08-8523-4dfa-aa3f-3cc1d1d61047 + https://www.npr.org/2022/02/22/1082398220/everyone-and-monkey-business + no + Everyone & Monkey Business + 1258 + + 875 + no + full + + + + + Everyone & Their Mom + Everyone & Their Mom from Wait Wait...Don't Tell Me! Every week, there's an odd or funny story that everyone & their mom seems to be talking about. On this new show from your friends at Wait Wait... Don't Tell Me!, host Emma Choi takes that story and uses it as an excuse to talk to culture makers, Wait Wait panelists, and hilarious new comedians. New episodes every Wednesday, starting February 23rd. Tell your mom.]]> + Mon, 21 Feb 2022 13:00:08 +0000 + 9b94c40e-da04-414f-9990-bdb9025639f1 + https://www.npr.org/2022/02/18/1081659546/everyone-their-mom + no + Everyone & Their Mom + 1257 + + 99 + no + trailer + Everyone & Their Mom from Wait Wait...Don't Tell Me! Every week, there's an odd or funny story that everyone & their mom seems to be talking about. On this new show from your friends at Wait Wait... Don't Tell Me!, host Emma Choi takes that story and uses it as an excuse to talk to culture makers, Wait Wait panelists, and hilarious new comedians. New episodes every Wednesday, starting February 23rd. Tell your mom.]]> + + + + Marlon James + + Sat, 19 Feb 2022 17:18:32 +0000 + d8864c3f-b095-4801-837e-cf1bd4c3f669 + https://www.npr.org/2022/02/18/1081860469/marlon-james + no + Marlon James + 1256 + + 2857 + no + full + + + + + Patti Smith + + Sat, 12 Feb 2022 17:04:58 +0000 + 98e7ee36-56d0-41fd-9d83-e65de729c980 + https://www.npr.org/2022/02/11/1080303953/patti-smith + no + Patti Smith + 1255 + + 2850 + no + full + + + + + Shermann 'Dilla' Thomas + + Sat, 05 Feb 2022 17:05:50 +0000 + ba4cc4d1-b070-4790-b1f5-a7c1bf0b5fab + https://www.npr.org/2022/02/04/1078428743/shermann-dilla-thomas + no + Shermann 'Dilla' Thomas + 1254 + 2895 + no + full + + + + + Jeremy O. Harris + + Sat, 29 Jan 2022 17:00:36 +0000 + 3e25d9d6-7d88-4208-918e-ba81a171d002 + https://www.npr.org/2022/01/28/1076626735/jeremy-o-harris + no + Jeremy O. Harris + 1253 + 2907 + no + full + + + + + Brian Cox + Succession, answers three questions about suck sessions, or vacuum cleaning. He is joined by panelists Mo Rocca, Paula Poundstone and Cristela Alonzo.]]> + Sat, 22 Jan 2022 17:01:11 +0000 + 865cf43e-8288-48df-8302-54a01814a6e4 + https://www.npr.org/2022/01/21/1074998514/brian-cox + no + Brian Cox + 1252 + 2922 + no + full + Succession, answers three questions about suck sessions, or vacuum cleaning. He is joined by panelists Mo Rocca, Paula Poundstone and Cristela Alonzo.]]> + + + + Kacey Musgraves + + Sat, 15 Jan 2022 17:06:46 +0000 + 7f9fc568-6de2-4009-8384-e41ad96ae352 + https://www.npr.org/2022/01/14/1073293337/kacey-musgraves + no + Kacey Musgraves + 1251 + + 2880 + no + full + + + + + Woody Hoburg + + Sat, 08 Jan 2022 16:59:01 +0000 + 2fc55b5c-b430-4531-845e-56039388a5d1 + https://www.npr.org/2022/01/07/1071512040/woody-hoburg + no + Woody Hoburg + 1250 + 2944 + no + full + + + + + WWDTM Best of 2021 January + + Sat, 01 Jan 2022 05:10:45 +0000 + 2896ec62-a868-44d0-9486-3ac8f42fbde3 + https://www.npr.org/2021/12/29/1069000678/wwdtm-best-of-2021-january + no + WWDTM Best of 2021 January + 1249 + 2855 + no + full + + + + + WWDTM Best of 2021 Bonus Podcast + + Wed, 29 Dec 2021 17:30:55 +0000 + b4996ed8-97e8-4928-8d69-292b96f11cff + https://www.npr.org/2021/12/27/1068333165/wwdtm-best-of-2021-bonus-podcast + no + WWDTM Best of 2021 Bonus Podcast + 1248 + 1753 + no + full + + + + + Best of WWDTM December 2021 + + Sat, 25 Dec 2021 05:15:58 +0000 + 0db198b6-c671-4576-8394-d03ef7c1da5e + https://www.npr.org/2021/12/22/1067073106/best-of-wwdtm-december-2021 + no + Best of WWDTM December 2021 + 1247 + 2899 + no + full + + + + + Keke Palmer + Southern Belle Insults, plays our game about palm readers. She is joined by panelists Faith Salie, Mo Rocca, and Tom Bodett.]]> + Sat, 18 Dec 2021 17:27:27 +0000 + 72de1fa8-c7a1-4215-8b87-bd1d41c0bd9f + https://www.npr.org/2021/12/17/1065424189/keke-palmer + no + Keke Palmer + 1246 + 2810 + no + full + Southern Belle Insults, plays our game about palm readers. She is joined by panelists Faith Salie, Mo Rocca, and Tom Bodett.]]> + + + + Bashir and Sultan Salahuddin + South Side, play our game called "Welcome to the Real South Side!" Three questions about Antarctica. They are joined by panelists Cristela Alonzo, Luke Burbank and Maeve Higgins.]]> + Sat, 11 Dec 2021 17:00:54 +0000 + fd5d4097-87a8-48d9-a8f6-f2f6ac1f75ba + https://www.npr.org/2021/12/10/1063274801/bashir-and-sultan-salahuddin + no + Bashir and Sultan Salahuddin + 1245 + 2831 + no + full + South Side, play our game called "Welcome to the Real South Side!" Three questions about Antarctica. They are joined by panelists Cristela Alonzo, Luke Burbank and Maeve Higgins.]]> + + + + Audra McDonald + + Sat, 04 Dec 2021 17:00:56 +0000 + 7e4b3dd5-af3f-4062-8acf-0756720b09fa + https://www.npr.org/2021/12/03/1061428122/audra-mcdonald + no + Audra McDonald + 1244 + 2793 + no + full + + + + + Best of Thanksgiving 2021 + + Sat, 27 Nov 2021 17:00:42 +0000 + 5c0193f2-a40b-4b5b-884d-c722f01746bd + https://www.npr.org/2021/11/23/1058529654/best-of-thanksgiving-2021 + no + Best of Thanksgiving 2021 + 1243 + 2798 + no + full + + + + + Brook and Robin Lopez + + Sat, 20 Nov 2021 17:00:35 +0000 + 2028a76c-ee80-4757-951c-86e0de14fee9 + https://www.npr.org/2021/11/19/1057576360/brook-and-robin-lopez + no + Brook and Robin Lopez + 1242 + 2789 + no + full + + + + + Ed Begley Jr. + + Sat, 13 Nov 2021 17:00:46 +0000 + e73a837b-af31-4582-bac6-f4733b2b38b1 + https://www.npr.org/2021/11/12/1055465480/ed-begley-jr + no + Ed Begley Jr. + 1241 + 2863 + no + full + + + + + Chance The Rapper + + Sat, 06 Nov 2021 16:00:39 +0000 + fbdbdc87-4d56-4fe9-8067-ecf043ee62e8 + https://www.npr.org/2021/11/05/1053116356/chance-the-rapper + no + Chance The Rapper + 1240 + 2807 + no + full + + + + + P.K. Subban + + Sat, 30 Oct 2021 16:00:49 +0000 + 67b1a49f-a92e-4d0e-a09e-cf4ff9374daa + https://www.npr.org/2021/10/29/1050726499/p-k-subban + no + P.K. Subban + 1239 + 2828 + no + full + + + + + Ron and Clint Howard + The Boys, play our game called, "Mayberry, Maybe Not Berry." They are joined by panelists Maz Jobrani, Alonzo Bodden, and Karen Chee.]]> + Sat, 23 Oct 2021 16:00:16 +0000 + 155e83e9-467d-490b-9280-d65d95042e4a + https://www.npr.org/2021/10/23/1048615121/ron-and-clint-howard + no + Ron and Clint Howard + 1238 + 2943 + no + full + The Boys, play our game called, "Mayberry, Maybe Not Berry." They are joined by panelists Maz Jobrani, Alonzo Bodden, and Karen Chee.]]> + + + + Best of WWDTM October 2021 + + Sat, 16 Oct 2021 16:00:50 +0000 + 807c2d67-2d77-4706-8485-89a93a72ab6d + https://www.npr.org/2021/10/06/1043875977/best-of-wwdtm-october-2021 + no + Best of WWDTM October 2021 + 1237 + 2868 + no + full + + + + + Ilana Glazer + Broad City, plays our game about different kinds of glazers: donuts. She is joined by panelists Adam Burke, Helen Hong and Roxanne Roberts.]]> + Sat, 09 Oct 2021 16:00:53 +0000 + 08654a9b-1e3b-4a0e-b21e-a156d7a2bc12 + https://www.npr.org/2021/10/08/1044669800/ilana-glazer + no + Ilana Glazer + 1236 + 2868 + no + full + Broad City, plays our game about different kinds of glazers: donuts. She is joined by panelists Adam Burke, Helen Hong and Roxanne Roberts.]]> + + + + RZA + + Sat, 02 Oct 2021 16:00:58 +0000 + 890fd430-ca31-447f-9206-56ae952ef022 + https://www.npr.org/2021/10/01/1042621990/rza + no + RZA + 1235 + 2935 + no + full + + + + + Bowen Yang + Saturday Night Live cast member, plays our game called "Monday Through Friday Night Live" about local TV news. He is joined by panelists Hari Kondabolu, Roy Blount Jr and Faith Salie.]]> + Sat, 25 Sep 2021 16:00:52 +0000 + bbe2df6a-7390-4039-ad1c-1cb9a78072a2 + https://www.npr.org/2021/09/24/1040644216/bowen-yang + no + Bowen Yang + 2896 + no + full + Saturday Night Live cast member, plays our game called "Monday Through Friday Night Live" about local TV news. He is joined by panelists Hari Kondabolu, Roy Blount Jr and Faith Salie.]]> + + + + Yamiche Alcindor + + Sat, 18 Sep 2021 16:00:33 +0000 + bafd4c1b-96ef-4790-a7f5-061377556dc5 + https://www.npr.org/2021/09/17/1038488144/yamiche-alcindor + no + Yamiche Alcindor + 2919 + no + full + + + + + Antoni Porowski + + Sat, 11 Sep 2021 01:44:26 +0000 + b72817d6-0e95-453e-ac5d-1e894a816f75 + https://www.npr.org/2021/09/10/1036173872/antoni-porowski + no + Antoni Porowski + 2891 + no + full + + + + + Martin Short + Only Murders in the Building, plays our game about murders of crows. He is joined by panelists Peter Grosz, Helen Hong and Emmy Blotnick.]]> + Sat, 04 Sep 2021 16:00:18 +0000 + 9b50956b-2318-49bf-aac5-51fe80c2aac6 + https://www.npr.org/2021/09/03/1034248006/martin-short + no + Martin Short + 2946 + no + full + Only Murders in the Building, plays our game about murders of crows. He is joined by panelists Peter Grosz, Helen Hong and Emmy Blotnick.]]> + + + + Jane Kaczmarek + Malcolm In The Middle, plays our game called "Malcolm in the Middle, meet Finger in the Middle." She is joined by panelists Paula Poundstone, Josh Gondelman, and Maz Jobrani.]]> + Sat, 28 Aug 2021 16:00:24 +0000 + cab5e676-efe0-42e8-aa7c-5c685aa7f0f2 + https://www.npr.org/2021/08/27/1031926866/jane-kaczmarek + no + Jane Kaczmarek + 2853 + no + full + Malcolm In The Middle, plays our game called "Malcolm in the Middle, meet Finger in the Middle." She is joined by panelists Paula Poundstone, Josh Gondelman, and Maz Jobrani.]]> + + + + Best of WWDTM Summertime 2 + + Sat, 21 Aug 2021 16:00:47 +0000 + e60beb91-01fb-4b4a-92d5-06ae2eedf084 + https://www.npr.org/2021/08/20/1029908712/best-of-wwdtm-summertime-2 + no + Best of WWDTM Summertime 2 + 2860 + no + full + + + + + Best of WWDTM Summertime 1 + + Sat, 14 Aug 2021 16:00:46 +0000 + 3c5517d2-8b9d-44d9-ac6e-cbb3d8d0c694 + https://www.npr.org/2021/08/10/1026511507/best-of-wwdtm-summertime-1 + no + Best of WWDTM Summertime 1 + 2861 + no + full + + + + + Larry Krasner + + Sat, 07 Aug 2021 16:00:15 +0000 + 1485e407-7336-47f5-be39-fc30484ab8ea + https://www.npr.org/2021/08/06/1025708092/larry-krasner + no + Larry Krasner + 2861 + no + full + + + + + Stephen Fry + + Sat, 31 Jul 2021 16:00:27 +0000 + 16537372-310e-4664-8062-eef9f1cec688 + https://www.npr.org/2021/07/31/1023057288/stephen-fry + no + Stephen Fry + 2980 + no + full + + + + + Dr. Ellen Stofan + + Sat, 24 Jul 2021 17:16:18 +0000 + 589cdbe9-ea43-4f99-b1ec-5442e58a6d64 + https://www.npr.org/2021/07/23/1020024122/dr-ellen-stofan + no + Dr. Ellen Stofan + 2880 + no + full + + + + + Phillipa Soo + Hamilton, plays our game about a ton of ham. She is joined by panelists Gina Brillon, Helen Hong, and Mo Rocca.]]> + Sat, 17 Jul 2021 16:00:15 +0000 + ea35470e-5992-4570-922c-696295a97429 + https://www.npr.org/2021/07/16/1017177376/phillipa-soo + no + Phillipa Soo + 2899 + no + full + Hamilton, plays our game about a ton of ham. She is joined by panelists Gina Brillon, Helen Hong, and Mo Rocca.]]> + + + + Best of WWDTM July 2021 + + Sat, 10 Jul 2021 16:00:01 +0000 + 8b6b5f63-13a8-4f5b-b640-419bb847f17a + https://www.npr.org/2021/07/09/1014858059/best-of-wwdtm-july-2021 + no + Best of WWDTM July 2021 + 2882 + no + full + + + + + Roger Bennett + + Sat, 03 Jul 2021 16:00:41 +0000 + 4c268f3d-091a-47c3-9a96-f5543d893ef2 + https://www.npr.org/2021/07/02/1012804147/roger-bennett + no + Roger Bennett + 2836 + no + full + + + + + T-Pain + + Sat, 26 Jun 2021 16:00:54 +0000 + 4d184e09-a418-44f2-a4cd-47c2794f6e2b + https://www.npr.org/2021/06/25/1010490950/t-pain + no + T-Pain + 2865 + no + full + + + + + Anna Konkle + PEN15, plays our game about a drink that will give you a junior high: Red Bull. She is joined by panelists DulcÊ Sloan, Faith Salie, and Luke Burbank.]]> + Sat, 19 Jun 2021 16:00:06 +0000 + b385ff3b-28fb-477a-b3c3-8b80aec5194b + https://www.npr.org/2021/06/18/1008253058/anna-konkle + no + Anna Konkle + 2970 + no + full + PEN15, plays our game about a drink that will give you a junior high: Red Bull. She is joined by panelists DulcÊ Sloan, Faith Salie, and Luke Burbank.]]> + + + + Chris Bosh + + Sat, 12 Jun 2021 16:00:06 +0000 + df7dc04a-4172-40e8-bb65-0bcab162b143 + https://www.npr.org/2021/06/11/1005781698/chris-bosh + no + Chris Bosh + 2793 + no + full + + + + + WWDTM Best Of: Summer Fun Edition + + Sat, 05 Jun 2021 15:07:00 +0000 + 293cb68b-b0b8-4ee7-8fbe-0e05b5aa1d90 + https://www.npr.org/2021/06/03/1003120516/wwdtm-best-of-summer-fun-edition + no + WWDTM Best Of: Summer Fun Edition + 2926 + no + full + + + + + Joel McHale + Community, plays our game about community theater. He is joined by panelists Cristela Alonzo, Jessi Klein and Helen Hong.]]> + Sat, 29 May 2021 16:00:28 +0000 + affb45a6-20c7-4c9c-9285-4c79b6f4d59e + https://www.npr.org/2021/05/28/1001474796/joel-mchale + no + Joel McHale + 2880 + no + full + Community, plays our game about community theater. He is joined by panelists Cristela Alonzo, Jessi Klein and Helen Hong.]]> + + + + Jennifer Finney Boylan + + Sat, 22 May 2021 16:00:34 +0000 + 6d878f6d-9aa8-43f6-9111-c7fc2fded036 + https://www.npr.org/2021/05/21/999352237/jennifer-finney-boylan + no + Jennifer Finney Boylan + 2854 + no + full + + + + + Senator Elizabeth Warren + Persist and plays our game about "War and Peace." She is joined by panelists Karen Chee, Hari Kondabolu and Peter Grosz]]> + Sat, 15 May 2021 16:00:08 +0000 + b77c7d41-5474-48f6-a2db-5180fe7f63ad + https://www.npr.org/2021/05/14/997050412/senator-elizabeth-warren + no + Senator Elizabeth Warren + 2866 + no + full + Persist and plays our game about "War and Peace." She is joined by panelists Karen Chee, Hari Kondabolu and Peter Grosz]]> + + + + Symone + + Sat, 08 May 2021 16:00:20 +0000 + 005167c3-b401-48f4-ba12-7ac599f736ec + https://www.npr.org/2021/05/03/993164641/symone + no + Symone + 2855 + no + full + + + + + Tariq Trotter AKA Black Thought from The Roots + + Sat, 01 May 2021 16:00:28 +0000 + f6392b2b-009f-4977-829b-bd8822a4bb48 + https://www.npr.org/2021/04/30/992618948/tariq-trotter-aka-black-thought-from-the-roots + no + Tariq Trotter AKA Black Thought from The Roots + 2886 + no + full + + + + + AndrÊ De Shields + + Sat, 24 Apr 2021 16:00:33 +0000 + d990e491-fd5e-4598-8d86-0a6f9da6955a + https://www.npr.org/2021/04/23/990406071/andre-de-shields + no + AndrÊ De Shields + 2895 + no + full + + + + + Michelle Zauner + + Sat, 17 Apr 2021 16:00:21 +0000 + 57ca2fd2-58bf-4015-8828-520553c53eef + https://www.npr.org/2021/04/16/988279734/michelle-zauner + no + Michelle Zauner + 2877 + no + full + + + + + Ally Love + + Sat, 10 Apr 2021 16:00:23 +0000 + 125bd163-3d2b-42f5-9d19-0efa47dc2eb7 + https://www.npr.org/2021/04/09/985996542/ally-love + no + Ally Love + 2833 + no + full + + + + + Best of WWDTM April 2021 + + Sat, 03 Apr 2021 16:00:45 +0000 + 53995069-9687-4737-97dc-4781c1925a8b + https://www.npr.org/2021/04/02/984037792/best-of-wwdtm-april-2021 + no + Best of WWDTM April 2021 + 2863 + no + full + + + + + Kemp Powers + + Sat, 27 Mar 2021 16:00:38 +0000 + 4132b4d0-c8a7-45c3-9a36-cbecafc949d5 + https://www.npr.org/2021/03/26/981830888/kemp-powers + no + Kemp Powers + 2898 + no + full + + + + + Sam Sifton + + Sat, 20 Mar 2021 16:00:50 +0000 + f102f7c7-0e99-49cc-a3a3-4217e4c79ff5 + https://www.npr.org/2021/03/19/979444825/sam-sifton + no + Sam Sifton + 2838 + no + full + + + + + Desus Nice and The Kid Mero + Desus and Mero, play our Not My Job game. They join panelists Maz Jobrani, Karen Chee and Josh Gondelman, as well as Host Peter Sagal and Official Judge and Scorekeeper Bill Kurtis.]]> + Sat, 13 Mar 2021 17:00:23 +0000 + d01a1a1c-cb06-4cf3-9ed0-74fb0d016bff + https://www.npr.org/2021/03/12/976715877/desus-nice-and-the-kid-mero + no + Desus Nice and The Kid Mero + 2919 + no + full + Desus and Mero, play our Not My Job game. They join panelists Maz Jobrani, Karen Chee and Josh Gondelman, as well as Host Peter Sagal and Official Judge and Scorekeeper Bill Kurtis.]]> + + + + Jordan Jonas + + Sat, 06 Mar 2021 17:00:00 +0000 + 69f0d42a-010f-4b8d-9b81-689618238e72 + no + Jordan Jonas + 2816 + no + full + + + + + Dr. Swati Mohan + + Sat, 27 Feb 2021 17:00:00 +0000 + 426bcaf3-6b9f-4841-860a-0a7d2b0ee8d4 + no + Dr. Swati Mohan + 2905 + no + full + + + + + Best of WWDTM February 2020 + + Sat, 20 Feb 2021 17:00:00 +0000 + ddbfeb24-31b3-4549-a823-12a586c1d775 + no + Best of WWDTM February 2020 + 2906 + no + full + + + + + Abby Phillip + + Sat, 13 Feb 2021 17:00:00 +0000 + 9c867f9e-f3fe-467d-81a5-9017fc4b739c + no + Abby Phillip + 2938 + no + full + + + + + Owen Wilson + + Sat, 06 Feb 2021 17:00:00 +0000 + 414fbec1-f860-4e92-880a-d44fe0f1cf02 + no + Owen Wilson + 2911 + no + full + + + + + Jen Psaki + + Sat, 30 Jan 2021 17:00:00 +0000 + f1e9d12f-779c-4ca6-8235-0b0bbe403280 + no + Jen Psaki + 2952 + no + full + + + + + Mandy Patinkin and Kathryn Grody + + Sat, 23 Jan 2021 17:00:00 +0000 + 24a0a1af-cf0b-4b50-ba6c-04ad13dd8df1 + no + Mandy Patinkin and Kathryn Grody + 2952 + no + full + + + + + Phoebe Bridgers + + Sat, 16 Jan 2021 17:00:00 +0000 + a5fa7a2b-4704-4258-b274-fb29ea0fd76b + no + Phoebe Bridgers + 3003 + no + full + + + + + Jane Krakowski + + Sat, 09 Jan 2021 17:00:00 +0000 + 14bf9a12-9d63-40db-b0cc-d3419ab931c3 + no + Jane Krakowski + 2947 + no + full + + + + + WWDTM New Year 2021 + + Sat, 02 Jan 2021 17:00:00 +0000 + 30e5de7e-9a8c-4870-963c-3be2e26eb94a + no + WWDTM New Year 2021 + 2860 + no + full + + + + + WWDTM Christmas 2020 + + Sat, 26 Dec 2020 17:00:00 +0000 + 5956d260-83fc-4ba3-856e-40426235f3b5 + no + WWDTM Christmas 2020 + 2939 + no + full + + + + + Este and Alana Haim + + Sat, 19 Dec 2020 17:00:00 +0000 + a292086d-2e8c-4635-8b32-c902455a9fbd + no + Este and Alana Haim + 3010 + no + full + + + + + Wait Wait's Letter from the Editors VI + + Wed, 16 Dec 2020 18:57:00 +0000 + 74071ecd-a6c8-4b54-b8e0-6a6ec5450c54 + no + Wait Wait's Letter from the Editors VI + 398 + no + full + + + + + Robert Reich + + Sat, 12 Dec 2020 17:00:00 +0000 + e780106d-1759-458a-909f-5e675d1c47f1 + no + Robert Reich + 2998 + no + full + + + + + Wait Wait's Letter from the Editors V + + Wed, 09 Dec 2020 19:16:00 +0000 + 7380c75c-d45a-4a7e-a955-ad92143dead5 + no + Wait Wait's Letter from the Editors V + 474 + yes + full + + + + + Lindsey Vonn + + Sat, 05 Dec 2020 17:00:00 +0000 + d4253d0e-30c7-4e25-a7c3-99f555461bef + no + Lindsey Vonn + 2997 + no + full + + + + + Wait Wait's Letter from the Editors IV + + Wed, 02 Dec 2020 19:32:00 +0000 + 6d10c2e7-fe73-48f7-823d-144a90aca7de + no + Wait Wait's Letter from the Editors IV + 479 + no + full + + + + + WWDTM Thanksgiving 2020 + + Sat, 28 Nov 2020 17:00:00 +0000 + 3d3f161a-7519-4a9b-b90e-76f00811631f + no + WWDTM Thanksgiving 2020 + 3018 + no + full + + + + + Wait Wait's Letter from the Editors III + + Wed, 25 Nov 2020 19:07:00 +0000 + dcbdc5eb-30b4-4514-b3dd-ecc9c5122da9 + no + Wait Wait's Letter from the Editors III + 406 + no + full + + + + + Sarah Paulson + + Sat, 21 Nov 2020 17:00:00 +0000 + c5dc5c2b-75d8-41fd-b0ae-27cbf804dbc6 + no + Sarah Paulson + 2975 + no + full + + + + + Wait Wait's Letter from the Editors II + + Wed, 18 Nov 2020 17:59:00 +0000 + d38cc113-3480-4ea1-a4a8-3c73e562d3a0 + no + Wait Wait's Letter from the Editors II + 323 + no + full + + + + + Chelsea Peretti + + Sat, 14 Nov 2020 17:00:00 +0000 + 6d6d8a3b-00d6-4a30-b61c-8362675933ad + no + Chelsea Peretti + 2934 + no + full + + + + + Wait Wait's Letter from the Editors + + Wed, 11 Nov 2020 16:46:00 +0000 + 25f35f96-002d-4ae9-a526-2ca440eed8f5 + no + Wait Wait's Letter from the Editors + 376 + no + full + + + + + A'ja Wilson + + Sat, 07 Nov 2020 17:00:00 +0000 + a50ecb76-929f-4cb4-8a4a-34ae3c0a58fe + no + A'ja Wilson + 2907 + no + full + + + + + Mike Murphy + + Sat, 31 Oct 2020 16:00:00 +0000 + f4630480-5bce-43e6-ac1f-354a70e5660f + no + Mike Murphy + 2952 + no + full + + + + + Actor Doug Jones + + Sat, 24 Oct 2020 16:00:00 +0000 + 9b339c42-179b-4743-b5e5-b9fb52dafeca + no + Actor Doug Jones + 2982 + no + full + + + + + Best of WWDTM October 2020 + + Sat, 17 Oct 2020 16:00:00 +0000 + 4e5f2434-3dbe-4d62-9b9a-1d894f62a4b4 + no + Best of WWDTM October 2020 + 2982 + no + full + + + + + Jason Ward + + Sat, 10 Oct 2020 16:00:00 +0000 + 283fa607-fac0-42c8-8973-486dcc7b84c4 + no + Jason Ward + 2997 + no + full + + + + + Dame Karen Pierce + + Sat, 03 Oct 2020 16:00:00 +0000 + a59f139b-8240-4321-b5fb-8c8d2921fef3 + no + Dame Karen Pierce + 3004 + no + full + + + + + Jonna Mendez + + Sat, 26 Sep 2020 16:00:00 +0000 + 71081c4c-58da-40a3-a818-909afe18863f + no + Jonna Mendez + 2972 + no + full + + + + + Tituss Burgess + + Sat, 19 Sep 2020 16:00:00 +0000 + d2c93293-1d4c-4b76-92ea-7919728792bd + no + Tituss Burgess + 2789 + no + full + + + + + Tyra Banks + + Sat, 12 Sep 2020 16:00:00 +0000 + a54c8101-be09-474b-9efb-d2cf2e439844 + no + Tyra Banks + 2883 + no + full + + + + + Kellee Edwards + + Sat, 05 Sep 2020 16:00:00 +0000 + 20e75d5d-168d-4dbc-aff8-3a4910bc7165 + no + Kellee Edwards + 2868 + no + full + + + + + Cecily Strong + + Sat, 29 Aug 2020 16:00:00 +0000 + 3501de41-f173-4ff5-bacf-56bb3b87a133 + no + Cecily Strong + 2807 + no + full + + + + + Best of WWDTM August 2020 + + Sat, 22 Aug 2020 16:00:00 +0000 + 1fee804d-79ad-46ec-a9f3-0737eb084e02 + no + Best of WWDTM August 2020 + 2823 + no + full + + + + + Best of WWDTM Mo Rocca Revealed + + Sat, 15 Aug 2020 16:00:00 +0000 + 28b4e8a9-bf74-4f5f-adb3-eb03da638512 + no + Best of WWDTM Mo Rocca Revealed + 2831 + no + full + + + + + Bryan Cranston + + Sat, 08 Aug 2020 16:00:00 +0000 + 623b04b0-439a-4c8d-b338-2f0ee6464e07 + no + Bryan Cranston + 2796 + no + full + + + + + Ramy Youssef + + Sat, 01 Aug 2020 16:00:00 +0000 + 1dab26dc-a02c-4352-8057-0380a2cce384 + no + Ramy Youssef + 2919 + no + full + + + + + Padma Lakshmi + + Sat, 25 Jul 2020 16:00:00 +0000 + bbc5ffed-0d93-4f22-9552-30bee68a5c4d + no + Padma Lakshmi + 2928 + no + full + + + + + Maria Konnikova + + Sat, 18 Jul 2020 16:00:00 +0000 + 578fd11b-0448-406a-a63d-99b536c43318 + no + Maria Konnikova + 2874 + no + full + + + + + Bonus Podcast: Tur-Bill Tax + + Tue, 14 Jul 2020 22:55:00 +0000 + 531223ef-031b-4efb-a020-20e4ecbded1c + no + Bonus Podcast: Tur-Bill Tax + 74 + no + full + + + + + Jameela Jamil + + Sat, 11 Jul 2020 16:00:00 +0000 + 41868a31-df3e-48e6-b572-2ff0b0e4ae29 + no + Jameela Jamil + 2828 + no + full + + + + + WWDTM Quarantine Edition + + Sat, 04 Jul 2020 16:00:00 +0000 + c9b64706-efc5-4a8b-93ee-1f4323c8bfca + no + WWDTM Quarantine Edition + 2839 + no + full + + + + + Don Cheadle + + Sat, 27 Jun 2020 16:00:00 +0000 + 4662e484-dac3-4692-8e7a-e2b5ff176979 + no + Don Cheadle + 2869 + no + full + + + + + Dan Riskin + + Sat, 20 Jun 2020 16:00:00 +0000 + bc6029ab-2bec-4fe5-aa4f-d570a29257b7 + no + Dan Riskin + 2843 + no + full + + + + + Ashima Shiraishi + + Sat, 13 Jun 2020 16:00:00 +0000 + 98aaa454-fd63-4edc-8236-c2499f37a5fc + no + Ashima Shiraishi + 2833 + no + full + + + + + Sarah Cooper + + Sat, 06 Jun 2020 16:00:00 +0000 + db687322-1327-4ad2-a1cb-251a988da5fd + no + Sarah Cooper + 2828 + no + full + + + + + Tony Hawk and Jeff Tweedy + + Sat, 30 May 2020 16:00:00 +0000 + 0e0c5ee0-3733-45bc-979f-20b627a95c09 + no + Tony Hawk and Jeff Tweedy + 2896 + no + full + + + + + Christina Koch + + Sat, 23 May 2020 16:00:00 +0000 + b13d7726-d82a-4e00-b23c-d09e3e0d383e + no + Christina Koch + 2938 + no + full + + + + + Adam Rippon + + Sat, 16 May 2020 16:00:00 +0000 + 709d41f4-e5be-45ed-83fe-543282c2051d + no + Adam Rippon + 2912 + no + full + + + + + Bonus Podcast: Bill Kurtis In the Wild + + Tue, 12 May 2020 22:50:00 +0000 + 3903c8b4-b735-45c6-bf09-2b237b6d5e15 + no + Bonus Podcast: Bill Kurtis In the Wild + 139 + no + full + + + + + Samantha Bee + + Sat, 09 May 2020 16:00:00 +0000 + 29550aa1-e223-4496-b394-bdd685b0c08f + no + Samantha Bee + 2908 + no + full + + + + + Christine Baranski + + Sat, 02 May 2020 16:00:00 +0000 + 1c9b2452-02c9-4897-9f32-01156fc0bf8a + no + Christine Baranski + 2962 + no + full + + + + + Allison Janney + + Sat, 25 Apr 2020 16:00:00 +0000 + 01528da1-fa6f-4177-a19d-9aafb1926a86 + no + Allison Janney + 2956 + no + full + + + + + Tom Hanks At Home + + Sat, 18 Apr 2020 16:00:00 +0000 + 61215c56-640e-4fc0-bef3-d88810aa48b6 + no + Tom Hanks At Home + 2946 + no + full + + + + + Samin Nosrat + + Sat, 11 Apr 2020 16:00:00 +0000 + be3fe3c6-153b-4f9a-8082-56eedf498adc + no + Samin Nosrat + 2896 + no + full + + + + + Kumail Nanjiani and Emily V. Gordon + + Sat, 04 Apr 2020 16:00:00 +0000 + aef8d01b-c353-475f-862a-5568f1d67ca1 + no + Kumail Nanjiani and Emily V. Gordon + 2881 + no + full + + + + + Tim Gunn + + Sat, 28 Mar 2020 16:00:00 +0000 + 44b1eadf-d573-47b1-84bf-ab34aed89925 + no + Tim Gunn + 2822 + no + full + + + + + Bonus: The Viral Load + + Mon, 23 Mar 2020 21:39:00 +0000 + c97e0f29-bd09-46d0-af2d-5ea382483be8 + no + Bonus: The Viral Load + 193 + no + full + + + + + Stephen Colbert At Home + + Sat, 21 Mar 2020 16:00:00 +0000 + cdb93c98-42d2-446c-ac7d-6a4c2ea6e701 + no + Stephen Colbert At Home + 2858 + no + full + + + + + Big Boi + + Sat, 14 Mar 2020 16:00:00 +0000 + f8a58ace-b16a-457c-b2c8-d65adfde2861 + no + Big Boi + 2824 + no + full + + + + + Karamo Brown + + Sat, 07 Mar 2020 17:00:00 +0000 + f7bdbe9e-0d82-47ef-bcbc-79c11910b00d + no + Karamo Brown + 2811 + no + full + + + + + Will Arnett + + Sat, 29 Feb 2020 17:00:00 +0000 + 88661a25-e274-4380-997a-4f37b3c78fa3 + no + Will Arnett + 2917 + no + full + + + + + Best of Not My Job Feb2020 + + Sat, 22 Feb 2020 17:00:00 +0000 + 70197692-35ff-4bec-8561-0e0c67330948 + no + Best of Not My Job Feb2020 + 2950 + no + full + + + + + Barry Sonnenfeld Calling + + Sat, 15 Feb 2020 17:00:00 +0000 + 034852be-c88e-415e-81e1-35cd27f426ce + no + Barry Sonnenfeld Calling + 2856 + no + full + + + + + Tracy Letts + + Sat, 08 Feb 2020 17:00:00 +0000 + a881f6b0-ec34-4269-8d1e-54d5213005e4 + no + Tracy Letts + 2902 + no + full + + + + + Isabella Rossellini + + Sat, 01 Feb 2020 17:00:00 +0000 + 257cba6a-a25f-4763-8489-a6da26295ab5 + no + Isabella Rossellini + 2845 + no + full + + + + + Olivia Nuzzi + + Sat, 25 Jan 2020 17:00:00 +0000 + 65a23237-ff28-40b9-afee-41fd7d7c13ec + no + Olivia Nuzzi + 2836 + no + full + + + + + Alison Roman + + Sat, 18 Jan 2020 17:00:00 +0000 + 9b44ee36-5a2c-4a8b-864a-37d44638a930 + no + Alison Roman + 2848 + no + full + + + + + Ronan Farrow + + Sat, 11 Jan 2020 17:00:00 +0000 + ae901aa4-632c-4adb-aa04-12ba92266535 + no + Ronan Farrow + 2854 + no + full + + + + + WWDTM Best of the Decade + + Sat, 04 Jan 2020 17:00:00 +0000 + 1f350ca9-106a-4c7f-953b-22a69115e4f0 + no + WWDTM Best of the Decade + 2844 + no + full + + + + + Best of Not My Job 2019 + + Sat, 28 Dec 2019 17:00:00 +0000 + 615b5675-50af-4bd8-b939-3f2fdb5bdb2c + no + Best of Not My Job 2019 + 2892 + no + full + + + + + Jennifer Lee + + Sat, 21 Dec 2019 17:00:00 +0000 + 89014046-2b7a-435b-9eea-88877e7428bb + no + Jennifer Lee + 2924 + no + full + + + + + Sean Doolittle + + Sat, 14 Dec 2019 17:00:00 +0000 + d0f13fbc-98e8-435c-b3cc-a143b40d3a3e + no + Sean Doolittle + 2880 + no + full + + + + + Ali Wong + + Sat, 07 Dec 2019 17:00:00 +0000 + 10d1f786-4304-4dcc-9a56-fe256af24c0e + no + Ali Wong + 2919 + no + full + + + + + Alex BoyÊ + + Sat, 30 Nov 2019 17:00:00 +0000 + 6683cfb4-3f09-4b86-8473-580ff77f826b + no + Alex BoyÊ + 2937 + no + full + + + + + Elaine Welteroth + + Sat, 23 Nov 2019 17:00:00 +0000 + 08aa61ad-612c-44f7-b0ab-5cca26f2b7c7 + no + Elaine Welteroth + 2840 + no + full + + + + + Bonus Wait Wait: This week's Trump Dump + + Mon, 18 Nov 2019 18:38:00 +0000 + fa737a68-c1cb-4ca5-a4fa-c34412a30f5f + no + Bonus Wait Wait: This week's Trump Dump + 198 + no + full + + + + + Senator Tim Kaine + + Sat, 16 Nov 2019 17:00:00 +0000 + 78de894e-3c32-40e4-b587-1b2e0b818f06 + no + Senator Tim Kaine + 2854 + no + full + + + + + Leslie Odom, Jr. + + Sat, 09 Nov 2019 17:00:00 +0000 + 3864a441-93b7-49a2-9836-01da8188382b + no + Leslie Odom, Jr. + 2859 + no + full + + + + + Gloria Steinem + + Sat, 02 Nov 2019 16:00:00 +0000 + eab0419b-b9d2-40de-9f81-2a98428f9b10 + no + Gloria Steinem + 2888 + no + full + + + + + Nalini Nadkarni + + Sat, 26 Oct 2019 16:00:00 +0000 + 68d4f6fe-ee77-4520-802d-6002d26994ba + no + Nalini Nadkarni + 2869 + no + full + + + + + RenÊe Fleming + + Sat, 19 Oct 2019 16:00:00 +0000 + 77427bb2-db1d-44e2-9d2f-78a822ce4132 + no + RenÊe Fleming + 2890 + no + full + + + + + Regina King + + Sat, 12 Oct 2019 16:00:00 +0000 + 2479cafe-ad3e-4ef5-994b-954d7f4bc8c7 + no + Regina King + 2886 + no + full + + + + + Danica Patrick + + Sat, 05 Oct 2019 16:00:00 +0000 + 992595ca-24f9-4f00-921d-247dcb58a4a6 + no + Danica Patrick + 2895 + no + full + + + + + Charlie Day + + Sat, 28 Sep 2019 16:00:00 +0000 + 492c4179-6777-4507-a694-55b1c981509a + no + Charlie Day + 2870 + no + full + + + + + Zach Galifinakis + + Sat, 21 Sep 2019 16:00:00 +0000 + 9b6da58e-3244-4e8a-81ef-9f952c380e00 + no + Zach Galifinakis + 2875 + no + full + + + + + Tina Charles + + Sat, 14 Sep 2019 16:00:00 +0000 + 6796da8f-0dda-4603-804d-dd7c7ef0066b + no + Tina Charles + 2870 + no + full + + + + + Mary Wilson + + Sat, 07 Sep 2019 16:00:00 +0000 + dd139e2d-730c-4721-a932-a678871e48aa + no + Mary Wilson + 2865 + no + full + + + + + JosÊ AndrÊs + + Sat, 31 Aug 2019 16:00:00 +0000 + d4c94427-9bc5-412b-a4ab-ba2afa3b678f + no + JosÊ AndrÊs + 2889 + no + full + + + + + Best of WWDTM + + Sat, 24 Aug 2019 16:00:00 +0000 + f5224623-672d-4030-9de8-ea0dec2362de + no + Best of WWDTM + 2898 + no + full + + + + + Best of WWDTM + + Sat, 17 Aug 2019 16:00:00 +0000 + 52225aa2-8424-4820-a092-8d60f70b10d8 + no + Best of WWDTM + 2852 + no + full + + + + + Henry Winkler + + Sat, 10 Aug 2019 16:00:00 +0000 + 7c60085d-9b52-42da-b145-9bfee1a896a5 + no + Henry Winkler + 2878 + no + full + + + + + Anthony Anderson + + Sat, 03 Aug 2019 16:00:00 +0000 + e9000e11-3a99-4b0d-9d7d-90882263c0ee + no + Anthony Anderson + 2858 + no + full + + + + + Marin Alsop + + Sat, 27 Jul 2019 16:00:00 +0000 + f42c705b-ee54-4718-805d-4738c8c72924 + no + Marin Alsop + 2884 + no + full + + + + + Piper Kerman + + Sat, 20 Jul 2019 16:00:00 +0000 + 5fe8dd92-170d-48e9-b57d-17d73caaa084 + no + Piper Kerman + 2920 + no + full + + + + + Tiera Fletcher + + Sat, 13 Jul 2019 16:00:00 +0000 + 45f0ca0c-6cd6-4b6c-94b9-849f27919f2e + no + Tiera Fletcher + 2932 + no + full + + + + + Best of WWDTM + + Sat, 06 Jul 2019 16:00:00 +0000 + 940b79a5-8d75-443d-bf15-ec8e81dfc3a9 + no + Best of WWDTM + 2877 + no + full + + + + + Jennifer Weiner + + Sat, 29 Jun 2019 16:00:00 +0000 + 2b111598-ee97-428e-a379-af01d1797c49 + no + Jennifer Weiner + 2858 + no + full + + + + + Valerie Jarrett + + Sat, 22 Jun 2019 16:00:00 +0000 + bf9355d7-2da4-44c6-a29e-3d820f8092c7 + no + Valerie Jarrett + 2865 + no + full + + + + + Kristine Lilly + + Sat, 15 Jun 2019 16:00:00 +0000 + 65a0d76d-a1de-4eda-99a8-ebae963fb445 + no + Kristine Lilly + 2886 + no + full + + + + + Olivia Wilde + + Sat, 08 Jun 2019 16:00:00 +0000 + e159cf49-4bb5-4f0f-bb8d-d86f3fbd8ac3 + no + Olivia Wilde + 2883 + no + full + + + + + WWDTM Super Heros + + Sat, 01 Jun 2019 16:00:00 +0000 + 90ae36be-0dce-4d80-8214-7962b0ef53a5 + no + WWDTM Super Heros + 2884 + no + full + + + + + Kate Mulgrew + + Sat, 25 May 2019 16:00:00 +0000 + ef762e61-c8c6-4eb5-9a58-6b42e934e21c + no + Kate Mulgrew + 2852 + no + full + + + + + Lance Reddick + + Sat, 18 May 2019 16:00:00 +0000 + 1cb758c9-ee8b-4bc5-bc9c-b2dc7cd51506 + no + Lance Reddick + 2898 + no + full + + + + + Ozzie Smith + + Sat, 11 May 2019 16:00:00 +0000 + 284728fa-d48d-483f-9aab-872a682a088b + no + Ozzie Smith + 2909 + no + full + + + + + Steve Ballmer + + Sat, 04 May 2019 16:00:00 +0000 + 81733296-cb29-4806-a77d-124e1d9cb521 + no + Steve Ballmer + 2984 + no + full + + + +
+
\ No newline at end of file diff --git a/Mintfile b/Mintfile index 4769ce7..93228eb 100644 --- a/Mintfile +++ b/Mintfile @@ -1,3 +1,3 @@ nicklockwood/SwiftFormat@0.47.0 realm/SwiftLint@0.41.0 -peripheryapp/periphery@2.12.3 +peripheryapp/periphery@2.18.0 diff --git a/Package.swift b/Package.swift index c708bf2..b1facd9 100644 --- a/Package.swift +++ b/Package.swift @@ -1,5 +1,5 @@ // swift-tools-version:5.5 -// swiftlint:disable explicit_top_level_acl +// swiftlint:disable explicit_top_level_acl explicit_acl import PackageDescription let package = Package( diff --git a/Sources/SyndiKit/Character.swift b/Sources/SyndiKit/Character.swift index 6bbf343..aff55a0 100644 --- a/Sources/SyndiKit/Character.swift +++ b/Sources/SyndiKit/Character.swift @@ -1,7 +1,7 @@ import Foundation extension Character { - func asOsmType() -> PodcastLocation.OsmQuery.OsmType? { + internal func asOsmType() -> PodcastLocation.OsmQuery.OsmType? { .init(rawValue: String(self)) } } diff --git a/Sources/SyndiKit/Collection.swift b/Sources/SyndiKit/Collection.swift index a0beab0..586ff09 100644 --- a/Sources/SyndiKit/Collection.swift +++ b/Sources/SyndiKit/Collection.swift @@ -1,7 +1,7 @@ import Foundation extension Collection { - subscript(safe index: Index) -> Element? { + internal subscript(safe index: Index) -> Element? { indices.contains(index) ? self[index] : nil } } diff --git a/Sources/SyndiKit/Common/Author.swift b/Sources/SyndiKit/Common/Author.swift index 6ecb039..8c2933e 100644 --- a/Sources/SyndiKit/Common/Author.swift +++ b/Sources/SyndiKit/Common/Author.swift @@ -2,12 +2,6 @@ import Foundation /// a person, corporation, or similar entity. public struct Author: Codable, Equatable { - init(name: String) { - self.name = name - email = nil - uri = nil - } - /// Conveys a human-readable name for the person. public let name: String @@ -16,4 +10,10 @@ public struct Author: Codable, Equatable { /// Contains a home page for the person. public let uri: URL? + + public init(name: String) { + self.name = name + email = nil + uri = nil + } } diff --git a/Sources/SyndiKit/Common/EntryCategory.swift b/Sources/SyndiKit/Common/EntryCategory.swift index 4e39016..1ca3717 100644 --- a/Sources/SyndiKit/Common/EntryCategory.swift +++ b/Sources/SyndiKit/Common/EntryCategory.swift @@ -1,4 +1,5 @@ /// Abstract category type. public protocol EntryCategory { + /// Term used for the category var term: String { get } } diff --git a/Sources/SyndiKit/Common/Entryable.swift b/Sources/SyndiKit/Common/Entryable.swift index 164dd02..b381ae7 100644 --- a/Sources/SyndiKit/Common/Entryable.swift +++ b/Sources/SyndiKit/Common/Entryable.swift @@ -5,7 +5,7 @@ public protocol Entryable { /// Unique Identifier of the Item. var id: EntryID { get } /// The URL of the item. - var url: URL { get } + var url: URL? { get } /// The title of the item. var title: String { get } /// HTML content of the item. diff --git a/Sources/SyndiKit/Common/Primitives/CData.swift b/Sources/SyndiKit/Common/Primitives/CData.swift index 3d7b0e8..8bc0149 100644 --- a/Sources/SyndiKit/Common/Primitives/CData.swift +++ b/Sources/SyndiKit/Common/Primitives/CData.swift @@ -1,9 +1,7 @@ /// #CDATA XML element. public struct CData: Codable, ExpressibleByStringLiteral, Equatable { - public typealias StringLiteralType = String - - public init(stringLiteral value: String) { - self.value = value + public enum CodingKeys: String, CodingKey { + case value = "#CDATA" } public var description: String { @@ -13,8 +11,8 @@ public struct CData: Codable, ExpressibleByStringLiteral, Equatable { /// String value of the #CDATA element. public let value: String - enum CodingKeys: String, CodingKey { - case value = "#CDATA" + public init(stringLiteral value: String) { + self.value = value } public init(from decoder: Decoder) throws { diff --git a/Sources/SyndiKit/Common/Primitives/XMLStringInt.swift b/Sources/SyndiKit/Common/Primitives/XMLStringInt.swift index b25c06f..d6fc68f 100644 --- a/Sources/SyndiKit/Common/Primitives/XMLStringInt.swift +++ b/Sources/SyndiKit/Common/Primitives/XMLStringInt.swift @@ -1,14 +1,14 @@ -/// XML Element which contains a `String` parsable into a `Integer`. +/// XML Element which contains a ``String`` parsable into a ``Integer``. public struct XMLStringInt: Codable, ExpressibleByIntegerLiteral { - public init(integerLiteral value: Int) { - self.value = value - } - public typealias IntegerLiteralType = Int - /// The underlying `Int` value. + /// The underlying ``Int`` value. public let value: Int + public init(integerLiteral value: Int) { + self.value = value + } + public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let stringValue = try container.decode(String.self) diff --git a/Sources/SyndiKit/Decoding/AnyDecoding.swift b/Sources/SyndiKit/Decoding/AnyDecoding.swift index cececec..7539f46 100644 --- a/Sources/SyndiKit/Decoding/AnyDecoding.swift +++ b/Sources/SyndiKit/Decoding/AnyDecoding.swift @@ -1,6 +1,6 @@ import Foundation -protocol AnyDecoding { +internal protocol AnyDecoding { static var label: String { get } func decodeFeed(data: Data) throws -> Feedable } diff --git a/Sources/SyndiKit/Decoding/CustomDecoderSetup.swift b/Sources/SyndiKit/Decoding/CustomDecoderSetup.swift index 5520520..fe82cac 100644 --- a/Sources/SyndiKit/Decoding/CustomDecoderSetup.swift +++ b/Sources/SyndiKit/Decoding/CustomDecoderSetup.swift @@ -1,5 +1,5 @@ import Foundation -protocol CustomDecoderSetup { +internal protocol CustomDecoderSetup { func setup(decoder: TypeDecoder) } diff --git a/Sources/SyndiKit/Decoding/DateFormatterDecoder.swift b/Sources/SyndiKit/Decoding/DateFormatterDecoder.swift index 204b4d4..56ffc21 100644 --- a/Sources/SyndiKit/Decoding/DateFormatterDecoder.swift +++ b/Sources/SyndiKit/Decoding/DateFormatterDecoder.swift @@ -1,26 +1,26 @@ import Foundation -struct DateFormatterDecoder { - let formatters: [DateFormatter] - - public enum RSS { - static let dateFormatStrings = [ +internal struct DateFormatterDecoder { + internal enum RSS { + private static let dateFormatStrings = [ "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX", "yyyy-MM-dd'T'HH:mm:ssXXXXX", "E, d MMM yyyy HH:mm:ss zzz", "yyyy-MM-dd HH:mm:ss" ] - public static let decoder = DateFormatterDecoder( + internal static let decoder = DateFormatterDecoder( basedOnFormats: Self.dateFormatStrings ) } - internal init(formatters: [DateFormatter]) { - self.formatters = formatters + private let formatters: [DateFormatter] + + internal init(basedOnFormats formats: [String]) { + formatters = formats.map(Self.isoPOSIX(withFormat:)) } - static func isoPOSIX(withFormat dateFormat: String) -> DateFormatter { + private static func isoPOSIX(withFormat dateFormat: String) -> DateFormatter { let formatter = DateFormatter() formatter.calendar = Calendar(identifier: .iso8601) formatter.locale = Locale(identifier: "en_US_POSIX") @@ -29,11 +29,7 @@ struct DateFormatterDecoder { return formatter } - init(basedOnFormats formats: [String]) { - formatters = formats.map(Self.isoPOSIX(withFormat:)) - } - - public func decodeString(_ dateStr: String) -> Date? { + internal func decodeString(_ dateStr: String) -> Date? { for formatter in formatters { if let date = formatter.date(from: dateStr) { return date @@ -42,7 +38,7 @@ struct DateFormatterDecoder { return nil } - func decode(from decoder: Decoder) throws -> Date { + internal func decode(from decoder: Decoder) throws -> Date { let container = try decoder.singleValueContainer() let dateStr = try container.decode(String.self) diff --git a/Sources/SyndiKit/Decoding/DecodableFeed.swift b/Sources/SyndiKit/Decoding/DecodableFeed.swift index 3f9ec36..4fdafff 100644 --- a/Sources/SyndiKit/Decoding/DecodableFeed.swift +++ b/Sources/SyndiKit/Decoding/DecodableFeed.swift @@ -1,16 +1,16 @@ import Foundation -protocol DecodableFeed: Decodable, Feedable { +internal protocol DecodableFeed: Decodable, Feedable { static var source: DecoderSetup { get } static var label: String { get } } extension DecodableFeed { - static func decoding(using decoder: TypeDecoder) -> Decoding { + internal static func decoding(using decoder: TypeDecoder) -> Decoding { Decoding(for: Self.self, using: decoder) } - static func anyDecoding(using decoder: TypeDecoder) -> AnyDecoding { + internal static func anyDecoding(using decoder: TypeDecoder) -> AnyDecoding { Self.decoding(using: decoder) } } diff --git a/Sources/SyndiKit/Decoding/DecoderSetup.swift b/Sources/SyndiKit/Decoding/DecoderSetup.swift index 4e5a904..cb4b24a 100644 --- a/Sources/SyndiKit/Decoding/DecoderSetup.swift +++ b/Sources/SyndiKit/Decoding/DecoderSetup.swift @@ -1,5 +1,5 @@ import Foundation -protocol DecoderSetup { +internal protocol DecoderSetup { var source: DecoderSource { get } } diff --git a/Sources/SyndiKit/Decoding/DecoderSource.swift b/Sources/SyndiKit/Decoding/DecoderSource.swift index dc756cf..335d3d4 100644 --- a/Sources/SyndiKit/Decoding/DecoderSource.swift +++ b/Sources/SyndiKit/Decoding/DecoderSource.swift @@ -1,10 +1,10 @@ import Foundation -enum DecoderSource: UInt8, DecoderSetup { +internal enum DecoderSource: UInt8, DecoderSetup { case json = 0x007B case xml = 0x003C - public var source: DecoderSource { + internal var source: DecoderSource { self } } diff --git a/Sources/SyndiKit/Decoding/Decoding.swift b/Sources/SyndiKit/Decoding/Decoding.swift index 16cf444..44f9410 100644 --- a/Sources/SyndiKit/Decoding/Decoding.swift +++ b/Sources/SyndiKit/Decoding/Decoding.swift @@ -1,21 +1,21 @@ import Foundation -struct Decoding: AnyDecoding { - func decodeFeed(data: Data) throws -> Feedable { - try decode(data: data) +internal struct Decoding: AnyDecoding { + internal static var label: String { + DecodingType.label } - let decoder: TypeDecoder + internal let decoder: TypeDecoder - init(for _: DecodingType.Type, using decoder: TypeDecoder) { + internal init(for _: DecodingType.Type, using decoder: TypeDecoder) { self.decoder = decoder } - func decode(data: Data) throws -> DecodingType { - try decoder.decode(DecodingType.self, from: data) + internal func decodeFeed(data: Data) throws -> Feedable { + try decode(data: data) } - static var label: String { - DecodingType.label + internal func decode(data: Data) throws -> DecodingType { + try decoder.decode(DecodingType.self, from: data) } } diff --git a/Sources/SyndiKit/Decoding/DecodingError.swift b/Sources/SyndiKit/Decoding/DecodingError.swift index d98f02d..081a0fa 100644 --- a/Sources/SyndiKit/Decoding/DecodingError.swift +++ b/Sources/SyndiKit/Decoding/DecodingError.swift @@ -1,7 +1,7 @@ import Foundation extension DecodingError { - struct Dictionary: Error { + internal struct Dictionary: Error { internal init?(errors: [String: DecodingError]) { guard errors.count > 1 else { return nil @@ -9,10 +9,10 @@ extension DecodingError { self.errors = errors } - let errors: [String: DecodingError] + internal let errors: [String: DecodingError] } - static func failedAttempts(_ errors: [String: DecodingError]) -> Self { + internal static func failedAttempts(_ errors: [String: DecodingError]) -> Self { let context = DecodingError.Context( codingPath: [], debugDescription: "Failed to decode data with several decoders.", @@ -20,4 +20,16 @@ extension DecodingError { ) return DecodingError.dataCorrupted(context) } + + internal static func dataCorrupted( + codingKey: CodingKey, + debugDescription: String + ) -> Self { + DecodingError.dataCorrupted( + .init( + codingPath: [codingKey], + debugDescription: debugDescription + ) + ) + } } diff --git a/Sources/SyndiKit/Decoding/SynDecoder.swift b/Sources/SyndiKit/Decoding/SynDecoder.swift index 720e6cc..1a02377 100644 --- a/Sources/SyndiKit/Decoding/SynDecoder.swift +++ b/Sources/SyndiKit/Decoding/SynDecoder.swift @@ -12,55 +12,30 @@ import XMLCoder /// /// - ``decode(_:)`` public class SynDecoder { - static func setupJSONDecoder(_ decoder: JSONDecoder) { - decoder.keyDecodingStrategy = .convertFromSnakeCase - decoder.dateDecodingStrategy = .custom(DateFormatterDecoder.RSS.decoder.decode(from:)) - } - - static func setupXMLDecoder(_ decoder: XMLDecoder) { - decoder.keyDecodingStrategy = .convertFromSnakeCase - decoder.dateDecodingStrategy = .custom(DateFormatterDecoder.RSS.decoder.decode(from:)) - decoder.trimValueWhitespaces = false - } - - init( - types: [DecodableFeed.Type]? = nil, - defaultJSONDecoderSetup: ((JSONDecoder) -> Void)? = nil, - defaultXMLDecoderSetup: ((XMLDecoder) -> Void)? = nil - ) { - self.types = types ?? Self.defaultTypes - self.defaultJSONDecoderSetup = defaultJSONDecoderSetup ?? Self.setupJSONDecoder(_:) - self.defaultXMLDecoderSetup = defaultXMLDecoderSetup ?? Self.setupXMLDecoder(_:) - } - - /// Creates an instance of `RSSDecoder` - public convenience init() { - self.init(types: nil, defaultJSONDecoderSetup: nil, defaultXMLDecoderSetup: nil) - } - - let defaultJSONDecoderSetup: (JSONDecoder) -> Void - let defaultXMLDecoderSetup: (XMLDecoder) -> Void - let types: [DecodableFeed.Type] - - static let defaultTypes: [DecodableFeed.Type] = [ + private static let defaultTypes: [DecodableFeed.Type] = [ RSSFeed.self, AtomFeed.self, JSONFeed.self ] - lazy var defaultXMLDecoder: XMLDecoder = { + private let defaultJSONDecoderSetup: (JSONDecoder) -> Void + private let defaultXMLDecoderSetup: (XMLDecoder) -> Void + private let types: [DecodableFeed.Type] + + private lazy var defaultXMLDecoder: XMLDecoder = { let decoder = XMLDecoder() self.defaultXMLDecoderSetup(decoder) return decoder }() - lazy var defaultJSONDecoder: JSONDecoder = { + private lazy var defaultJSONDecoder: JSONDecoder = { let decoder = JSONDecoder() self.defaultJSONDecoderSetup(decoder) return decoder }() - lazy var decodings: [DecoderSource: [String: AnyDecoding]] = { + // swiftlint:disable:next closure_body_length + private lazy var decodings: [DecoderSource: [String: AnyDecoding]] = { let decodings = types.map { type -> (DecoderSource, AnyDecoding) in let source = type.source let setup = type.source as? CustomDecoderSetup @@ -84,7 +59,7 @@ public class SynDecoder { return (source.source, type.anyDecoding(using: decoder)) } - return Dictionary(grouping: decodings, by: { $0.0 }) + return Dictionary(grouping: decodings) { $0.0 } .mapValues { $0 .map { $0.1 } .map { (type(of: $0).label, $0) } @@ -92,9 +67,35 @@ public class SynDecoder { .mapValues(Dictionary.init(uniqueKeysWithValues:)) }() - /// Returns a `Feedable` object of the type you specify, decoded from a JSON object. + internal init( + types: [DecodableFeed.Type]? = nil, + defaultJSONDecoderSetup: ((JSONDecoder) -> Void)? = nil, + defaultXMLDecoderSetup: ((XMLDecoder) -> Void)? = nil + ) { + self.types = types ?? Self.defaultTypes + self.defaultJSONDecoderSetup = defaultJSONDecoderSetup ?? Self.setupJSONDecoder(_:) + self.defaultXMLDecoderSetup = defaultXMLDecoderSetup ?? Self.setupXMLDecoder(_:) + } + + /// Creates an instance of ``RSSDecoder`` + public convenience init() { + self.init(types: nil, defaultJSONDecoderSetup: nil, defaultXMLDecoderSetup: nil) + } + + internal static func setupJSONDecoder(_ decoder: JSONDecoder) { + decoder.keyDecodingStrategy = .convertFromSnakeCase + decoder.dateDecodingStrategy = .custom(DateFormatterDecoder.RSS.decoder.decode(from:)) + } + + internal static func setupXMLDecoder(_ decoder: XMLDecoder) { + decoder.keyDecodingStrategy = .convertFromSnakeCase + decoder.dateDecodingStrategy = .custom(DateFormatterDecoder.RSS.decoder.decode(from:)) + decoder.trimValueWhitespaces = false + } + + /// Returns a ``Feedable`` object of the type you specify, decoded from a JSON object. /// - Parameter data: The JSON or XML object to decode. - /// - Returns: A `Feedable` object + /// - Returns: A ``Feedable`` object /// /// If the data is not valid RSS, this method throws the /// `DecodingError.dataCorrupted(_:)` error. diff --git a/Sources/SyndiKit/Decoding/TypeDecoder.swift b/Sources/SyndiKit/Decoding/TypeDecoder.swift index 63ad550..c58e077 100644 --- a/Sources/SyndiKit/Decoding/TypeDecoder.swift +++ b/Sources/SyndiKit/Decoding/TypeDecoder.swift @@ -1,7 +1,7 @@ import Foundation import XMLCoder -protocol TypeDecoder { +internal protocol TypeDecoder { func decode(_ type: T.Type, from data: Data) throws -> T where T: DecodableFeed } diff --git a/Sources/SyndiKit/Dictionary.swift b/Sources/SyndiKit/Dictionary.swift index 096f91b..db1f51d 100644 --- a/Sources/SyndiKit/Dictionary.swift +++ b/Sources/SyndiKit/Dictionary.swift @@ -1,5 +1,5 @@ extension Dictionary { - mutating func formUnion( + internal mutating func formUnion( _ collection: SequenceType, key: Key ) where Value == Set, SequenceType.Element == ElementType { diff --git a/Sources/SyndiKit/Formats/Blogs/CategoryDescriptor.swift b/Sources/SyndiKit/Formats/Blogs/CategoryDescriptor.swift index 55a8ca6..4d8a8c2 100644 --- a/Sources/SyndiKit/Formats/Blogs/CategoryDescriptor.swift +++ b/Sources/SyndiKit/Formats/Blogs/CategoryDescriptor.swift @@ -1,4 +1,16 @@ +/// A struct representing an Atom category. +/// A descriptor for a category. +/// +/// - Note: This struct is publicly accessible. +/// +/// - Important: The ``title`` and ``description`` properties are read-only. +/// +/// - SeeAlso: ``Category`` +/// - SeeAlso: ``EntryCategory`` public struct CategoryDescriptor { + /// The title of the category. public let title: String + + /// The description of the category. public let description: String } diff --git a/Sources/SyndiKit/Formats/Blogs/CategoryLanguage.swift b/Sources/SyndiKit/Formats/Blogs/CategoryLanguage.swift index 93350b4..8ccbace 100644 --- a/Sources/SyndiKit/Formats/Blogs/CategoryLanguage.swift +++ b/Sources/SyndiKit/Formats/Blogs/CategoryLanguage.swift @@ -1,4 +1,33 @@ +/// A struct representing an Atom category. +/// A struct representing a category in a specific language. +/// +/// - Parameters: +/// - languageCategory: The category in a specific language. +/// - language: The language of the category. +/// +/// - Note: This struct is used internally. +/// +/// - SeeAlso: ``SiteCategoryType`` +/// - SeeAlso: ``CategoryDescriptor`` +/// - SeeAlso: ``SiteLanguageType`` +/// - SeeAlso: ``EntryCategory`` public struct CategoryLanguage { + /// The type of the category. + public let type: SiteCategoryType + + /// The descriptor of the category. + public let descriptor: CategoryDescriptor + + /// The language of the category. + public let language: SiteLanguageType + + /// A struct representing an Atom category. + /// Initializes a ``CategoryLanguage`` instance. + /// + /// - Parameters: + /// - languageCategory: The category in a specific language. + /// - language: The language of the category. + /// - SeeAlso: ``EntryCategory`` internal init(languageCategory: SiteLanguageCategory, language: SiteLanguageType) { type = languageCategory.slug descriptor = CategoryDescriptor( @@ -7,8 +36,4 @@ public struct CategoryLanguage { ) self.language = language } - - public let type: SiteCategoryType - public let descriptor: CategoryDescriptor - public let language: SiteLanguageType } diff --git a/Sources/SyndiKit/Formats/Blogs/Site.swift b/Sources/SyndiKit/Formats/Blogs/Site.swift index 8549fd6..8924b44 100644 --- a/Sources/SyndiKit/Formats/Blogs/Site.swift +++ b/Sources/SyndiKit/Formats/Blogs/Site.swift @@ -1,6 +1,34 @@ import Foundation +/// A struct representing a website. public struct Site { + /// The title of the website. + public let title: String + + /// The author of the website. + public let author: String + + /// The URL of the website. + public let siteURL: URL + + /// The URL of the website's feed. + public let feedURL: URL + + /// The URL of the website's Twitter page, if available. + public let twitterURL: URL? + + /// The category of the website. + public let category: SiteCategoryType + + /// The language of the website. + public let language: SiteLanguageType + + /// Initializes a new ``Site`` instance. + /// + /// - Parameters: + /// - site: The `SiteLanguageCategory.Site` instance to use as a base. + /// - categoryType: The category type of the website. + /// - languageType: The language type of the website. internal init( site: SiteLanguageCategory.Site, categoryType: SiteCategoryType, @@ -14,12 +42,4 @@ public struct Site { category = categoryType language = languageType } - - public let title: String - public let author: String - public let siteURL: URL - public let feedURL: URL - public let twitterURL: URL? - public let category: SiteCategoryType - public let language: SiteLanguageType } diff --git a/Sources/SyndiKit/Formats/Blogs/SiteCategory.swift b/Sources/SyndiKit/Formats/Blogs/SiteCategory.swift index 4e31054..db98462 100644 --- a/Sources/SyndiKit/Formats/Blogs/SiteCategory.swift +++ b/Sources/SyndiKit/Formats/Blogs/SiteCategory.swift @@ -1,13 +1,41 @@ +/// A struct representing an Atom category. +/// A struct representing a site category. +/// +/// - Note: This struct is used to categorize sites based on their type and descriptors. +/// +/// - Parameters: +/// - type: The type of the site category. +/// - descriptors: A dictionary mapping site language types to category descriptors. +/// +/// - Important: This struct should not be used directly. +/// Instead, use the ``SiteCategoryBuilder`` to create instances of ``SiteCategory``. +/// +/// - SeeAlso: ``SiteCategoryType`` +/// - SeeAlso: ``CategoryDescriptor`` +/// - SeeAlso: ``CategoryLanguage`` +/// - SeeAlso: ``SiteCategoryBuilder`` +/// - SeeAlso: ``EntryCategory`` public struct SiteCategory { + /// The type of the site category. + public let type: SiteCategoryType + + /// A dictionary mapping site language types to category descriptors. + public let descriptors: [SiteLanguageType: CategoryDescriptor] + + /// A struct representing an Atom category. + /// Initializes a ``SiteCategory`` instance with the given languages. + /// + /// - Parameter languages: An array of ``CategoryLanguage`` instances. + /// + /// - Returns: A new ``SiteCategory`` instance + /// if at least one language is provided, ``nil`` otherwise. + /// - SeeAlso: ``EntryCategory`` internal init?(languages: [CategoryLanguage]) { guard let type = languages.first?.type else { return nil } self.type = type - descriptors = Dictionary(grouping: languages, by: { $0.language }) + descriptors = Dictionary(grouping: languages) { $0.language } .compactMapValues { $0.first?.descriptor } } - - public let type: SiteCategoryType - public let descriptors: [SiteLanguageType: CategoryDescriptor] } diff --git a/Sources/SyndiKit/Formats/Blogs/SiteCategoryType.swift b/Sources/SyndiKit/Formats/Blogs/SiteCategoryType.swift index dcaf6fc..311038b 100644 --- a/Sources/SyndiKit/Formats/Blogs/SiteCategoryType.swift +++ b/Sources/SyndiKit/Formats/Blogs/SiteCategoryType.swift @@ -1 +1,2 @@ +/// A type alias representing a site category. public typealias SiteCategoryType = String diff --git a/Sources/SyndiKit/Formats/Blogs/SiteCollection.swift b/Sources/SyndiKit/Formats/Blogs/SiteCollection.swift index c3c7980..3fd9523 100644 --- a/Sources/SyndiKit/Formats/Blogs/SiteCollection.swift +++ b/Sources/SyndiKit/Formats/Blogs/SiteCollection.swift @@ -1 +1,2 @@ +/// A collection of site language content. public typealias SiteCollection = [SiteLanguageContent] diff --git a/Sources/SyndiKit/Formats/Blogs/SiteDirectory.swift b/Sources/SyndiKit/Formats/Blogs/SiteDirectory.swift index 58bf139..2a01b2f 100644 --- a/Sources/SyndiKit/Formats/Blogs/SiteDirectory.swift +++ b/Sources/SyndiKit/Formats/Blogs/SiteDirectory.swift @@ -1,105 +1,58 @@ import Foundation -public protocol SiteDirectory { - associatedtype SiteSequence: Sequence - where SiteSequence.Element == Site - associatedtype LanguageSequence: Sequence - where LanguageSequence.Element == SiteLanguage - associatedtype CategorySequence: Sequence - where CategorySequence.Element == SiteCategory - - func sites( - withLanguage language: SiteLanguageType?, - withCategory category: SiteCategoryType? - ) -> SiteSequence - - var languages: LanguageSequence { get } - var categories: CategorySequence { get } -} - -public extension SiteDirectory { - func sites( - withLanguage language: SiteLanguageType? = nil, - withCategory category: SiteCategoryType? = nil - ) -> SiteSequence { - sites(withLanguage: language, withCategory: category) - } -} - +/// A directory of site collections. public struct SiteCollectionDirectory: SiteDirectory { + /// A sequence of sites. public typealias SiteSequence = [Site] - public typealias LanguageSequence = - Dictionary.Values - - public typealias CategorySequence = - Dictionary.Values - - let instance: Instance - - public var languages: Dictionary< - SiteLanguageType, SiteLanguage - >.Values { - instance.languageDictionary.values - } - - public var categories: Dictionary< - SiteCategoryType, SiteCategory - >.Values { - instance.categoryDictionary.values - } - - public func sites( - withLanguage language: SiteLanguageType?, - withCategory category: SiteCategoryType? - ) -> [Site] { - instance.sites(withLanguage: language, withCategory: category) - } + /// A sequence of languages. + public typealias LanguageSequence = Dictionary.Values - init(blogs: SiteCollection) { - instance = .init(blogs: blogs) - } + /// A sequence of categories. + public typealias CategorySequence = Dictionary.Values - struct Instance { - let allSites: [Site] - let languageDictionary: [SiteLanguageType: SiteLanguage] - let categoryDictionary: [SiteCategoryType: SiteCategory] - let languageIndicies: [SiteLanguageType: Set] - let categoryIndicies: [SiteCategoryType: Set] + /// The internal structure of the site collection directory. + internal struct Instance { + internal let allSites: [Site] + internal let languageDictionary: [SiteLanguageType: SiteLanguage] + internal let categoryDictionary: [SiteCategoryType: SiteCategory] + internal let languageIndices: [SiteLanguageType: Set] + internal let categoryIndices: [SiteCategoryType: Set] - public func sites( + // swiftlint:disable:next function_body_length + internal func sites( withLanguage language: SiteLanguageType?, withCategory category: SiteCategoryType? ) -> [Site] { - let languageIndicies: Set? + let languageIndices: Set? if let language = language { - languageIndicies = self.languageIndicies[language] ?? .init() + languageIndices = self.languageIndices[language] ?? .init() } else { - languageIndicies = nil + languageIndices = nil } - let categoryIndicies: Set? + let categoryIndices: Set? if let category = category { - categoryIndicies = self.categoryIndicies[category] ?? .init() + categoryIndices = self.categoryIndices[category] ?? .init() } else { - categoryIndicies = nil + categoryIndices = nil } - var indicies: Set? + var indices: Set? - if let languageIndicies = languageIndicies { - indicies = languageIndicies + if let languageIndices = languageIndices { + indices = languageIndices } - if let categoryIndicies = categoryIndicies { - if let current = indicies { - indicies = current.intersection(categoryIndicies) + if let categoryIndices = categoryIndices { + if let current = indices { + indices = current.intersection(categoryIndices) } else { - indicies = categoryIndicies + indices = categoryIndices } } - if let current = indicies { + if let current = indices { return current.map { self.allSites[$0] } } else { return allSites @@ -107,18 +60,18 @@ public struct SiteCollectionDirectory: SiteDirectory { } // swiftlint:disable function_body_length - init(blogs: SiteCollection) { + internal init(blogs: SiteCollection) { var categories = [CategoryLanguage]() var languages = [SiteLanguage]() var sites = [Site]() - var languageIndicies = [SiteLanguageType: Set]() - var categoryIndicies = [SiteCategoryType: Set]() + var languageIndices = [SiteLanguageType: Set]() + var categoryIndices = [SiteCategoryType: Set]() for languageContent in blogs { let language = SiteLanguage(content: languageContent) - var thisLanguageIndicies = [Int]() + var thisLanguageIndices = [Int]() for languageCategory in languageContent.categories { - var thisCategoryIndicies = [Int]() + var thisCategoryIndices = [Int]() let category = CategoryLanguage( languageCategory: languageCategory, language: language.type @@ -131,26 +84,98 @@ public struct SiteCollectionDirectory: SiteDirectory { languageType: language.type ) sites.append(site) - thisCategoryIndicies.append(index) - thisLanguageIndicies.append(index) + thisCategoryIndices.append(index) + thisLanguageIndices.append(index) } - categoryIndicies.formUnion(thisCategoryIndicies, key: category.type) + categoryIndices.formUnion(thisCategoryIndices, key: category.type) categories.append(category) } - languageIndicies.formUnion(thisLanguageIndicies, key: language.type) + languageIndices.formUnion(thisLanguageIndices, key: language.type) languages.append(language) } - categoryDictionary = Dictionary( - grouping: categories, - by: { $0.type } - ).compactMapValues(SiteCategory.init) + categoryDictionary = Dictionary(grouping: categories) { $0.type } + .compactMapValues(SiteCategory.init) languageDictionary = Dictionary( uniqueKeysWithValues: languages.map { ($0.type, $0) } ) - self.languageIndicies = languageIndicies - self.categoryIndicies = categoryIndicies + self.languageIndices = languageIndices + self.categoryIndices = categoryIndices allSites = sites } } + + private let instance: Instance + + /// A sequence of languages in the site collection directory. + public var languages: Dictionary.Values { + instance.languageDictionary.values + } + + /// A sequence of categories in the site collection directory. + public var categories: Dictionary.Values { + instance.categoryDictionary.values + } + + /// Initializes a new instance of the ``SiteCollectionDirectory`` struct. + /// + /// - Parameter blogs: The site collection to use. + internal init(blogs: SiteCollection) { + instance = .init(blogs: blogs) + } + + /// Retrieves a list of sites based on the specified language and category. + /// + /// - Parameters: + /// - language: The language of the sites to retrieve. + /// - category: The category of the sites to retrieve. + /// - Returns: A list of sites matching the specified language and category. + public func sites( + withLanguage language: SiteLanguageType?, + withCategory category: SiteCategoryType? + ) -> [Site] { + instance.sites(withLanguage: language, withCategory: category) + } +} + +/// A protocol for site directories. +public protocol SiteDirectory { + /// List of Sites + associatedtype SiteSequence: Sequence where SiteSequence.Element == Site + /// List of Languages + associatedtype LanguageSequence: Sequence where LanguageSequence.Element == SiteLanguage + /// List of Categories + associatedtype CategorySequence: Sequence where CategorySequence.Element == SiteCategory + + /// A sequence of languages in the site directory. + var languages: LanguageSequence { get } + + /// A sequence of categories in the site directory. + var categories: CategorySequence { get } + + /// Retrieves a list of sites based on the specified language and category. + /// + /// - Parameters: + /// - language: The language of the sites to retrieve. + /// - category: The category of the sites to retrieve. + /// - Returns: A list of sites matching the specified language and category. + func sites( + withLanguage language: SiteLanguageType?, + withCategory category: SiteCategoryType? + ) -> SiteSequence +} + +extension SiteDirectory { + /// Retrieves a list of sites based on the specified language and category. + /// + /// - Parameters: + /// - language: The language of the sites to retrieve. + /// - category: The category of the sites to retrieve. + /// - Returns: A list of sites matching the specified language and category. + public func sites( + withLanguage language: SiteLanguageType? = nil, + withCategory category: SiteCategoryType? = nil + ) -> SiteSequence { + sites(withLanguage: language, withCategory: category) + } } diff --git a/Sources/SyndiKit/Formats/Blogs/SiteDirectoryBuilder.swift b/Sources/SyndiKit/Formats/Blogs/SiteDirectoryBuilder.swift index 51963eb..7f6c53f 100644 --- a/Sources/SyndiKit/Formats/Blogs/SiteDirectoryBuilder.swift +++ b/Sources/SyndiKit/Formats/Blogs/SiteDirectoryBuilder.swift @@ -1,13 +1,33 @@ import Foundation -public protocol SiteDirectoryBuilder { - associatedtype SiteDirectoryType: SiteDirectory - func directory(fromCollection blogs: SiteCollection) -> SiteDirectoryType -} - +/// A builder for creating a site collection directory. public struct SiteCollectionDirectoryBuilder: SiteDirectoryBuilder { + /// Initializes a new instance of ``SiteCollectionDirectoryBuilder``. public init() {} + + /// A struct representing an Atom category. + /// Creates a site collection directory from a site collection. + /// + /// - Parameter blogs: The site collection to build the directory from. + /// + /// - Returns: A new instance of ``SiteCollectionDirectory``. + /// - SeeAlso: ``EntryCategory`` public func directory(fromCollection blogs: SiteCollection) -> SiteCollectionDirectory { SiteCollectionDirectory(blogs: blogs) } } + +/// A protocol for building site directories. +public protocol SiteDirectoryBuilder { + /// The type of site directory to build. + associatedtype SiteDirectoryType: SiteDirectory + + /// A struct representing an Atom category. + /// Creates a site directory from a site collection. + /// + /// - Parameter blogs: The site collection to build the directory from. + /// + /// - Returns: A new instance of ``SiteDirectoryType``. + /// - SeeAlso: ``EntryCategory`` + func directory(fromCollection blogs: SiteCollection) -> SiteDirectoryType +} diff --git a/Sources/SyndiKit/Formats/Blogs/SiteLanguage.swift b/Sources/SyndiKit/Formats/Blogs/SiteLanguage.swift index da16869..23c684f 100644 --- a/Sources/SyndiKit/Formats/Blogs/SiteLanguage.swift +++ b/Sources/SyndiKit/Formats/Blogs/SiteLanguage.swift @@ -1,9 +1,34 @@ +/// A struct representing an Atom category. +/// A struct representing a site language. +/// +/// Use this struct to define the type and title of a site language. +/// +/// - Note: This struct is used internally and should not be directly instantiated. +/// +/// - Parameters: +/// - content: The content of the site language. +/// +/// - SeeAlso: ``SiteLanguageType`` +/// +/// - Author: Your Name +/// - SeeAlso: ``EntryCategory`` public struct SiteLanguage { - init(content: SiteLanguageContent) { + /// The type of the site language. + public let type: SiteLanguageType + + /// The title of the site language. + public let title: String + + /// A struct representing an Atom category. + /// Initializes a new ``SiteLanguage`` instance. + /// + /// - Parameters: + /// - content: The content of the site language. + /// + /// - Returns: A new ``SiteLanguage`` instance. + /// - SeeAlso: ``EntryCategory`` + internal init(content: SiteLanguageContent) { type = content.language title = content.title } - - public let type: SiteLanguageType - public let title: String } diff --git a/Sources/SyndiKit/Formats/Blogs/SiteLanguageCategory+Site.swift b/Sources/SyndiKit/Formats/Blogs/SiteLanguageCategory+Site.swift new file mode 100644 index 0000000..b5a16b9 --- /dev/null +++ b/Sources/SyndiKit/Formats/Blogs/SiteLanguageCategory+Site.swift @@ -0,0 +1,34 @@ +import Foundation + +// swiftlint:disable nesting +extension SiteLanguageCategory { + /// A ``struct`` representing a site. + public struct Site: Codable { + /// The title of the site. + public let title: String + + /// The author of the site. + public let author: String + + /// The URL of the site. + public let siteURL: URL + + /// The URL of the site's feed. + public let feedURL: URL + + /// The URL of the site's Twitter page. + public let twitterURL: URL? + + /// Coding keys to map properties to JSON keys. + internal enum CodingKeys: String, CodingKey { + case title + case author + case siteURL = "site_url" + case feedURL = "feed_url" + case twitterURL = "twitter_url" + } + } +} + +/// A type alias for `SiteLanguageCategory.Site`. +public typealias SiteStub = SiteLanguageCategory.Site diff --git a/Sources/SyndiKit/Formats/Blogs/SiteLanguageCategory.Site.swift b/Sources/SyndiKit/Formats/Blogs/SiteLanguageCategory.Site.swift deleted file mode 100644 index c76bdab..0000000 --- a/Sources/SyndiKit/Formats/Blogs/SiteLanguageCategory.Site.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation -public extension SiteLanguageCategory { - struct Site: Codable { - public let title: String - public let author: String - public let siteURL: URL - public let feedURL: URL - public let twitterURL: URL? - - enum CodingKeys: String, CodingKey { - case title - case author - case siteURL = "site_url" - case feedURL = "feed_url" - case twitterURL = "twitter_url" - } - } -} - -public typealias SiteStub = SiteLanguageCategory.Site diff --git a/Sources/SyndiKit/Formats/Blogs/SiteLanguageCategory.swift b/Sources/SyndiKit/Formats/Blogs/SiteLanguageCategory.swift index d485780..d7e9290 100644 --- a/Sources/SyndiKit/Formats/Blogs/SiteLanguageCategory.swift +++ b/Sources/SyndiKit/Formats/Blogs/SiteLanguageCategory.swift @@ -1,6 +1,22 @@ +/// A struct representing an Atom category. +/// A struct representing a category of site languages. +/// +/// - Note: This struct conforms to the ``Codable`` protocol. +/// +/// - Important: All properties of this struct are read-only. +/// +/// - SeeAlso: ``Site`` +/// - SeeAlso: ``EntryCategory`` public struct SiteLanguageCategory: Codable { + /// The title of the category. public let title: String + + /// The slug of the category. public let slug: String + + /// A description of the category. public let description: String + + /// An array of sites belonging to this category. public let sites: [Site] } diff --git a/Sources/SyndiKit/Formats/Blogs/SiteLanguageContent.swift b/Sources/SyndiKit/Formats/Blogs/SiteLanguageContent.swift index 4141712..4f9c7ab 100644 --- a/Sources/SyndiKit/Formats/Blogs/SiteLanguageContent.swift +++ b/Sources/SyndiKit/Formats/Blogs/SiteLanguageContent.swift @@ -1,5 +1,23 @@ +/// A struct representing an Atom category. +/// A struct representing the content of a site in a specific language. +/// +/// - Note: This struct conforms to the ``Codable`` protocol. +/// +/// - Important: All properties of this struct are read-only. +/// +/// - SeeAlso: ``SiteLanguageCategory`` +/// +/// - Author: Your Name +/// +/// - Version: 1.0 +/// - SeeAlso: ``EntryCategory`` public struct SiteLanguageContent: Codable { + /// The language of the site content. public let language: String + + /// The title of the site. public let title: String + + /// The categories of the site. public let categories: [SiteLanguageCategory] } diff --git a/Sources/SyndiKit/Formats/Blogs/SiteLanguageType.swift b/Sources/SyndiKit/Formats/Blogs/SiteLanguageType.swift index 289a526..28fa6e6 100644 --- a/Sources/SyndiKit/Formats/Blogs/SiteLanguageType.swift +++ b/Sources/SyndiKit/Formats/Blogs/SiteLanguageType.swift @@ -1 +1,2 @@ +/// A type representing the language of a website. public typealias SiteLanguageType = String diff --git a/Sources/SyndiKit/Formats/Feeds/Atom/AtomCategory.swift b/Sources/SyndiKit/Formats/Feeds/Atom/AtomCategory.swift index a5df7d6..e17c502 100644 --- a/Sources/SyndiKit/Formats/Feeds/Atom/AtomCategory.swift +++ b/Sources/SyndiKit/Formats/Feeds/Atom/AtomCategory.swift @@ -1,3 +1,11 @@ +/// A struct representing an Atom category. +/// A struct representing an Atom category. +/// +/// - Note: This struct conforms to the ``Codable`` and ``EntryCategory`` protocols. +/// +/// - SeeAlso: ``EntryCategory`` +/// - SeeAlso: ``EntryCategory`` public struct AtomCategory: Codable, EntryCategory { + /// The term of the category. public let term: String } diff --git a/Sources/SyndiKit/Formats/Feeds/Atom/AtomEntry.swift b/Sources/SyndiKit/Formats/Feeds/Atom/AtomEntry.swift index 0dc2288..c6cd3b7 100644 --- a/Sources/SyndiKit/Formats/Feeds/Atom/AtomEntry.swift +++ b/Sources/SyndiKit/Formats/Feeds/Atom/AtomEntry.swift @@ -1,82 +1,87 @@ import Foundation +/// A struct representing an entry in an Atom feed. public struct AtomEntry: Codable { - public static let defaultURL = URL(string: "/")! + /// The coding keys used for encoding and decoding. + public enum CodingKeys: String, CodingKey { + case id + case title + case published + case content + case updated + case links = "link" + case authors = "author" + case atomCategories = "category" + case youtubeVideoID = "yt:videoId" + case youtubeChannelID = "yt:channelId" + case creators = "dc:creator" + case mediaGroup = "media:group" + } /// A permanent, universally unique identifier for an entry. public let id: EntryID - /// a Text construct that conveys a human-readable title + /// A human-readable title for the entry. public let title: String - /// The most recent instant in time when the entry was published + /// The most recent instant in time when the entry was published. public let published: Date? - /// Content of the trny. + /// The content of the entry. public let content: String? - /// The most recent instant in time when the entry was modified in a way - /// the publisher considers significant. + /// The most recent instant in time when the entry was modified. public let updated: Date - /// Cateogires of the entry. + /// The categories associated with the entry. public let atomCategories: [AtomCategory] - /// a reference to a Web resource. + /// The links associated with the entry. public let links: [Link] - /// The author of the entry. - public let authors: [Author] - /// YouTube channel ID, if from a YouTube channel. - public let youtubeChannelID: String? + /// The authors of the entry. + public let authors: [Author] - /// YouTube video ID, if from a YouTube channel. + /// The YouTube video ID, if the entry is from a YouTube channel. public let youtubeVideoID: String? - /// the person or entity who wrote an item + /// The YouTube channel ID, if the entry is from a YouTube channel. + public let youtubeChannelID: String? + + /// The creators of the entry. public let creators: [String] - /// Grouping of elements that are effectively the same content, - /// yet different representations. + /// The media group associated with the entry. public let mediaGroup: AtomMediaGroup? - - enum CodingKeys: String, CodingKey { - case id - case title - case published - case content - case updated - case links = "link" - case authors = "author" - case atomCategories = "category" - case youtubeVideoID = "yt:videoId" - case youtubeChannelID = "yt:channelId" - case creators = "dc:creator" - case mediaGroup = "media:group" - } } extension AtomEntry: Entryable { + /// The categories associated with the entry. public var categories: [EntryCategory] { atomCategories } - public var url: URL { - links.first?.href ?? Self.defaultURL + /// The URL of the entry. + public var url: URL? { + links.first?.href } + /// The HTML content of the entry. public var contentHtml: String? { content?.trimmingCharacters(in: .whitespacesAndNewlines) } + /// The summary of the entry. public var summary: String? { nil } + /// The media content of the entry. public var media: MediaContent? { YouTubeIDProperties(entry: self).map(Video.youtube).map(MediaContent.video) } + /// The URL of the entry's image. public var imageURL: URL? { mediaGroup?.thumbnails.first?.url } diff --git a/Sources/SyndiKit/Formats/Feeds/Atom/AtomFeed.swift b/Sources/SyndiKit/Formats/Feeds/Atom/AtomFeed.swift index 7fbebed..ecb522d 100644 --- a/Sources/SyndiKit/Formats/Feeds/Atom/AtomFeed.swift +++ b/Sources/SyndiKit/Formats/Feeds/Atom/AtomFeed.swift @@ -1,10 +1,25 @@ import Foundation +/// A struct representing an Atom category. /// An XML-based Web content and metadata syndication format. /// /// Based on the /// [specifications here](https://datatracker.ietf.org/doc/html/rfc4287#section-4.1.2). +/// - SeeAlso: ``EntryCategory`` public struct AtomFeed { + public enum CodingKeys: String, CodingKey { + case id + case title + case description + case subtitle + case published + case pubDate + case links = "link" + case entries = "entry" + case authors = "author" + case youtubeChannelID = "yt:channelId" + } + /// Identifies the feed using a universally unique and permanent URI. /// If you have a long-term, renewable lease on your Internet domain name, /// then you can feel free to use your website's address. @@ -14,10 +29,10 @@ public struct AtomFeed { /// Often the same as the title of the associated website. public let title: String - /// Contains a human-readable description or subtitle for the feed + /// Contains a human-readable description or subtitle for the feed. public let description: String? - /// Contains a human-readable description or subtitle for the feed + /// Contains a human-readable description or subtitle for the feed. public let subtitle: String? /// The publication date for the content in the channel. @@ -26,61 +41,58 @@ public struct AtomFeed { /// The publication date for the content in the channel. public let pubDate: Date? - /// a reference from an entry or feed to a Web resource. + /// A reference from an entry or feed to a Web resource. public let links: [Link] - /// An individual entry, - /// acting as a container for metadata and data associated with the entry + /// Individual entries of the feed. public let entries: [AtomEntry] + /// The author of the feed. public let authors: [Author] /// YouTube channel ID, if from a YouTube channel. public let youtubeChannelID: String? - - enum CodingKeys: String, CodingKey { - case id - case title - case description - case subtitle - case published - case pubDate - case links = "link" - case entries = "entry" - case authors = "author" - case youtubeChannelID = "yt:channelId" - } } extension AtomFeed: DecodableFeed { + /// The source of the decoder for AtomFeed. + internal static let source: DecoderSetup = DecoderSource.xml + + /// The label for AtomFeed. + internal static let label: String = "Atom" + + /// The summary of the AtomFeed. public var summary: String? { description ?? subtitle } + /// The children of the AtomFeed. public var children: [Entryable] { entries } + /// The site URL of the AtomFeed. public var siteURL: URL? { links.first { $0.rel != "self" }?.href } + /// The updated date of the AtomFeed. public var updated: Date? { pubDate ?? published } + /// The copyright of the AtomFeed. public var copyright: String? { nil } + /// The image URL of the AtomFeed. public var image: URL? { nil } + /// The syndication update of the AtomFeed. public var syndication: SyndicationUpdate? { nil } - - static let source: DecoderSetup = DecoderSource.xml - static var label: String = "Atom" } diff --git a/Sources/SyndiKit/Formats/Feeds/Atom/AtomMedia.swift b/Sources/SyndiKit/Formats/Feeds/Atom/AtomMedia.swift index 1cbf53e..0c6c54d 100644 --- a/Sources/SyndiKit/Formats/Feeds/Atom/AtomMedia.swift +++ b/Sources/SyndiKit/Formats/Feeds/Atom/AtomMedia.swift @@ -1,18 +1,21 @@ import Foundation -/// Media structure which enables -/// content publishers and bloggers to syndicate multimedia content -/// such as TV and video clips, movies, images and audio. +/// A struct representing an Atom category. +/// Media structure which enables content publishers and bloggers +/// to syndicate multimedia content such as TV and video clips, movies, images and audio. /// -/// Fore more detils check out +/// For more details, check out /// [the Media RSS Specification](https://www.rssboard.org/media-rss). +/// - SeeAlso: ``EntryCategory`` public struct AtomMedia: Codable { - /// The type of object. + /// A struct representing an Atom category. + /// The type of object. /// - /// While this attribute can at times seem redundant if type is supplied, - /// it is included because it simplifies decision making on the reader side, - /// as well as flushes out any ambiguities between MIME type and object type. - /// It is an optional attribute. + /// While this attribute can at times seem redundant if type is supplied, + /// it is included because it simplifies decision making on the reader side, + /// as well as flushes out any ambiguities between MIME type and object type. + /// It is an optional attribute. + /// - SeeAlso: ``EntryCategory`` public let url: URL /// The direct URL to the media object. diff --git a/Sources/SyndiKit/Formats/Feeds/Atom/AtomMediaGroup.swift b/Sources/SyndiKit/Formats/Feeds/Atom/AtomMediaGroup.swift index 34b29fa..bae80db 100644 --- a/Sources/SyndiKit/Formats/Feeds/Atom/AtomMediaGroup.swift +++ b/Sources/SyndiKit/Formats/Feeds/Atom/AtomMediaGroup.swift @@ -1,15 +1,24 @@ import Foundation +/// A group of media elements in an Atom feed. public struct AtomMediaGroup: Codable { - public let title: String? - public let contents: [AtomMedia] - public let thumbnails: [AtomMedia] - public let descriptions: [String] - - enum CodingKeys: String, CodingKey { + /// Coding keys for encoding and decoding. + public enum CodingKeys: String, CodingKey { case title = "media:title" case descriptions = "media:description" case contents = "media:content" case thumbnails = "media:thumbnail" } + + /// The title of the media group. + public let title: String? + + /// The media elements within the group. + public let contents: [AtomMedia] + + /// The thumbnail images associated with the media group. + public let thumbnails: [AtomMedia] + + /// The descriptions of the media group. + public let descriptions: [String] } diff --git a/Sources/SyndiKit/Formats/Feeds/JSONFeed/JSONFeed.swift b/Sources/SyndiKit/Formats/Feeds/JSONFeed/JSONFeed.swift index c04796f..633930f 100644 --- a/Sources/SyndiKit/Formats/Feeds/JSONFeed/JSONFeed.swift +++ b/Sources/SyndiKit/Formats/Feeds/JSONFeed/JSONFeed.swift @@ -1,56 +1,84 @@ import Foundation +/// A struct representing an Atom category. +/// A struct representing a JSON feed. +/// +/// - Note: This struct conforms to the ``DecodableFeed`` protocol. +/// +/// - SeeAlso: ``DecodableFeed`` +/// - SeeAlso: ``EntryCategory`` public struct JSONFeed { + /// The version of the JSON feed. public let version: URL + + /// The title of the JSON feed. public let title: String + + /// The URL of the home page associated with the feed. public let homePageUrl: URL + + /// A description of the JSON feed. public let description: String? /// The author of the feed. public let author: Author? + + /// The items in the JSON feed. public let items: [JSONItem] } extension JSONFeed: DecodableFeed { + /// The source of the decoder for JSON feed. + internal static let source: DecoderSetup = DecoderSource.json + + /// The label for the JSON feed. + internal static let label: String = "JSON" + + /// The YouTube channel ID associated with the feed. public var youtubeChannelID: String? { nil } + /// The children of the JSON feed. public var children: [Entryable] { items } + /// The summary of the JSON feed. public var summary: String? { description } + /// The site URL associated with the feed. public var siteURL: URL? { homePageUrl } + /// The last updated date of the JSON feed. public var updated: Date? { nil } + /// The copyright information of the JSON feed. public var copyright: String? { nil } + /// The image URL associated with the feed. public var image: URL? { nil } + /// The syndication update information of the JSON feed. public var syndication: SyndicationUpdate? { nil } + /// The authors of the JSON feed. public var authors: [Author] { guard let author = author else { return [] } return [author] } - - static let source: DecoderSetup = DecoderSource.json - static var label: String = "JSON" } diff --git a/Sources/SyndiKit/Formats/Feeds/JSONFeed/JSONItem.swift b/Sources/SyndiKit/Formats/Feeds/JSONFeed/JSONItem.swift index dee2e4f..74703bb 100644 --- a/Sources/SyndiKit/Formats/Feeds/JSONFeed/JSONItem.swift +++ b/Sources/SyndiKit/Formats/Feeds/JSONFeed/JSONItem.swift @@ -1,11 +1,25 @@ import Foundation +/// A struct representing an Atom category. +/// A struct representing an item in JSON format. +/// - SeeAlso: ``EntryCategory`` public struct JSONItem: Codable { + /// The unique identifier of the item. public let guid: EntryID - public let url: URL + + /// The URL associated with the item. + public let url: URL? + + /// The title of the item. public let title: String + + /// The HTML content of the item. public let contentHtml: String? + + /// A summary of the item. public let summary: String? + + /// The date the item was published. public let datePublished: Date? /// The author of the item. @@ -13,6 +27,9 @@ public struct JSONItem: Codable { } extension JSONItem: Entryable { + /// A struct representing an Atom category. + /// Returns an array of authors for the item. + /// - SeeAlso: ``EntryCategory`` public var authors: [Author] { guard let author = author else { return [] @@ -20,26 +37,36 @@ extension JSONItem: Entryable { return [author] } + /// The URL of the item's image. public var imageURL: URL? { nil } + /// A struct representing an Atom category. + /// An array of creators associated with the item. + /// - SeeAlso: ``EntryCategory`` public var creators: [String] { [] } + /// The date the item was published. public var published: Date? { datePublished } + /// The unique identifier of the item. public var id: EntryID { guid } + /// A struct representing an Atom category. + /// An array of categories associated with the item. + /// - SeeAlso: ``EntryCategory`` public var categories: [EntryCategory] { [] } + /// The media content associated with the item. public var media: MediaContent? { nil } diff --git a/Sources/SyndiKit/Formats/Feeds/RSS/Enclosure.swift b/Sources/SyndiKit/Formats/Feeds/RSS/Enclosure.swift index 5aa0655..3df9ccf 100644 --- a/Sources/SyndiKit/Formats/Feeds/RSS/Enclosure.swift +++ b/Sources/SyndiKit/Formats/Feeds/RSS/Enclosure.swift @@ -1,35 +1,56 @@ import Foundation +/// A struct representing an enclosure for a resource. public struct Enclosure: Codable { - public let url: URL - public let type: String - public let length: Int? - - enum CodingKeys: String, CodingKey { + internal enum CodingKeys: String, CodingKey { case url case type case length } + /// The URL of the enclosure. + public let url: URL + + /// The type of the enclosure. + public let type: String + + /// The length of the enclosure, if available. + public let length: Int? + + /// Initializes a new ``Enclosure`` instance from a decoder. + /// + /// - Parameter decoder: The decoder to read data from. + /// - Throws: An error if the decoding process fails. public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Self.CodingKeys.self) + let container = try decoder.container(keyedBy: CodingKeys.self) url = try container.decode(UTF8EncodedURL.self, forKey: .url).value type = try container.decode(String.self, forKey: .type) + length = try Self.decodeLength(from: container) + } + + /// Decodes the length of the enclosure from the given container. + /// + /// - Parameter container: The container to decode from. + /// - Returns: The length of the enclosure, or ``nil`` if not available. + /// - Throws: An error if the decoding process fails. + private static func decodeLength( + from container: KeyedDecodingContainer + ) throws -> Int? { if container.contains(.length) { do { - length = try container.decode(Int.self, forKey: .length) + return try container.decode(Int.self, forKey: .length) } catch { let lengthString = try container.decode(String.self, forKey: .length) if lengthString.isEmpty { - length = nil + return nil } else if let length = Int(lengthString) { - self.length = length + return length } else { throw error } } } else { - length = nil + return nil } } } diff --git a/Sources/SyndiKit/Formats/Feeds/RSS/EntryID.swift b/Sources/SyndiKit/Formats/Feeds/RSS/EntryID.swift index 6e710e7..ed2d004 100644 --- a/Sources/SyndiKit/Formats/Feeds/RSS/EntryID.swift +++ b/Sources/SyndiKit/Formats/Feeds/RSS/EntryID.swift @@ -1,53 +1,59 @@ import Foundation -/// Entry identifier based on the RSS guid. -/// ## Topics +/// An identifier for an entry based on the RSS guid. /// -/// ### Enumeration Cases -/// -/// - ``url(_:)`` -/// - ``uuid(_:)`` -/// - ``path(_:separatedBy:)`` -/// - ``string(_:)`` -/// -/// ### String Conversion -/// -/// - ``init(string:)`` -/// - ``description`` -/// - ``init(_:)`` -/// -/// ### Codable Overrides -/// -/// - ``init(from:)`` -/// - ``encode(to:)`` +/// - Note: This enum conforms to +/// ``Codable``, ``Equatable``, and ``LosslessStringConvertible``. public enum EntryID: Codable, Equatable, LosslessStringConvertible { - /// URL format. + /// An identifier in URL format. case url(URL) - /// UUID format. + /// An identifier in UUID format. case uuid(UUID) - /// String path separated by a character string. + /// An identifier in string path format. /// - /// This is generally used by YouTube's RSS feed. in the format of: + /// This format is commonly used by YouTube's RSS feed, in the format of: /// ``` /// yt:video:(YouTube Video ID) /// ``` case path([String], separatedBy: String) - /// Plain un-parsable String. + /// An identifier in plain un-parsable string format. case string(String) - /// Implementation of ``LosslessStringConvertible`` initializer. - /// This will never return a nil instance. - /// Therefore you should use ``init(string:)``to avoid the `Optional` result. + /// A string representation of the entry identifier. + public var description: String { + let string: String + switch self { + case let .url(url): + string = url.absoluteString + + case let .uuid(uuid): + string = uuid.uuidString.lowercased() + + case let .path(components, separatedBy: separator): + string = components.joined(separator: separator) + + case let .string(value): + string = value + } + return string + } + + /// Initializes an ``EntryID`` from a string. + /// + /// - Parameter description: The string representation of the entry identifier. + /// - Note: This initializer will never return a ``nil`` instance. + /// To avoid the ``Optional`` result, use `init(string:)` instead. public init?(_ description: String) { self.init(string: description) } - /// Parses the String into a ``EntryID`` - /// - Parameter string: The String to parse. - /// You should use this rather than ``init(_:)`` to avoid the `Optional` result. + /// Initializes an ``EntryID`` from a string. + /// + /// - Parameter string: The string representation of the entry identifier. + /// - Note: Use this initializer instead of `init(_:)` to avoid the ``Optional`` result. public init(string: String) { if let url = URL(strict: string) { self = .url(url) @@ -68,30 +74,20 @@ public enum EntryID: Codable, Equatable, LosslessStringConvertible { } } - public var description: String { - let string: String - switch self { - case let .url(url): - string = url.absoluteString - - case let .uuid(uuid): - string = uuid.uuidString.lowercased() - - case let .path(components, separatedBy: separator): - string = components.joined(separator: separator) - - case let .string(value): - string = value - } - return string - } - + /// Initializes an ``EntryID`` from a decoder. + /// + /// - Parameter decoder: The decoder to read data from. + /// - Throws: An error if the decoding process fails. public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let string = try container.decode(String.self) self.init(string: string) } + /// Encodes the ``EntryID`` into the given encoder. + /// + /// - Parameter encoder: The encoder to write data to. + /// - Throws: An error if the encoding process fails. public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(description) diff --git a/Sources/SyndiKit/Formats/Feeds/RSS/RSSChannel.swift b/Sources/SyndiKit/Formats/Feeds/RSS/RSSChannel.swift index 26a4327..c90d527 100644 --- a/Sources/SyndiKit/Formats/Feeds/RSS/RSSChannel.swift +++ b/Sources/SyndiKit/Formats/Feeds/RSS/RSSChannel.swift @@ -1,7 +1,48 @@ import Foundation -/// information about the channel (metadata) and its contents. +/// A struct representing an Atom category. +/// A struct representing information about the channel (metadata) and its contents. +/// +/// - Note: This struct conforms to the ``Codable`` protocol. +/// +/// - Remark: The ``CodingKeys`` enum is used to specify the coding keys for the struct. +/// +/// - SeeAlso: ``RSSItem`` +/// - SeeAlso: ``RSSImage`` +/// - SeeAlso: ``Author`` +/// - SeeAlso: ``WordPressElements.Category`` +/// - SeeAlso: ``WordPressElements.Tag`` +/// - SeeAlso: ``iTunesOwner`` +/// - SeeAlso: ``PodcastLocked`` +/// - SeeAlso: ``PodcastFunding`` +/// - SeeAlso: ``PodcastPerson`` +/// - SeeAlso: ``EntryCategory`` public struct RSSChannel: Codable { + internal enum CodingKeys: String, CodingKey { + case title + case link + case description + case lastBuildDate + case pubDate + case ttl + case syUpdatePeriod = "sy:updatePeriod" + case syUpdateFrequency = "sy:updateFrequency" + case items = "item" + case itunesAuthor = "itunes:author" + case itunesImage = "itunes:image" + case itunesOwner = "itunes:owner" + case copyright + case image + case author + case wpCategories = "wp:category" + case wpTags = "wp:tag" + case wpBaseSiteURL = "wp:baseSiteUrl" + case wpBaseBlogURL = "wp:baseBlogUrl" + case podcastLocked = "podcast:locked" + case podcastFundings = "podcast:funding" + case podcastPeople = "podcast:person" + } + /// The name of the channel. public let title: String @@ -14,24 +55,31 @@ public struct RSSChannel: Codable { /// The last time the content of the channel changed. public let lastBuildDate: Date? - /// indicates the publication date and time of the feed's content + /// Indicates the publication date and time of the feed's content. public let pubDate: Date? - /// ttl stands for time to live. - /// It's a number of minutes - /// that indicates how long a channel can be cached - /// before refreshing from the source. + /// TTL stands for time to live. + /// It's a number of minutes that indicates + /// how long a channel can be cached before refreshing from the source. public let ttl: Int? + /// Describes the period over which the channel format is updated. public let syUpdatePeriod: SyndicationUpdatePeriod? - /// Used to describe the frequency of updates - /// in relation to the update period. - /// A positive integer indicates - /// how many times in that period the channel is updated. + + /// Used to describe the frequency of updates in relation to the update period. + /// A positive integer indicates how many times in that period the channel is updated. public let syUpdateFrequency: SyndicationUpdateFrequency? + + /// The items contained in the channel. public let items: [RSSItem] + + /// The author of the channel. public let itunesAuthor: String? + + /// The image associated with the channel. public let itunesImage: String? + + /// The owner of the channel. public let itunesOwner: iTunesOwner? /// Copyright notice for content in the channel. @@ -39,44 +87,40 @@ public struct RSSChannel: Codable { /// Specifies a GIF, JPEG or PNG image that can be displayed with the channel. public let image: RSSImage? + + /// The author of the channel. public let author: Author? + + /// The categories associated with the channel. public let wpCategories: [WordPressElements.Category] + + /// The tags associated with the channel. public let wpTags: [WordPressElements.Tag] + + /// The base site URL of the channel. public let wpBaseSiteURL: URL? + + /// The base blog URL of the channel. public let wpBaseBlogURL: URL? + /// Indicates whether the podcast is locked. public let podcastLocked: PodcastLocked? + + /// The fundings associated with the podcast. public let podcastFundings: [PodcastFunding] - public let podcastPeople: [PodcastPerson] - enum CodingKeys: String, CodingKey { - case title - case link - case description - case lastBuildDate - case pubDate - case ttl - case syUpdatePeriod = "sy:updatePeriod" - case syUpdateFrequency = "sy:updateFrequency" - case items = "item" - case itunesAuthor = "itunes:author" - case itunesImage = "itunes:image" - case itunesOwner = "itunes:owner" - case copyright - case image - case author - case wpCategories = "wp:category" - case wpTags = "wp:tag" - case wpBaseSiteURL = "wp:baseSiteUrl" - case wpBaseBlogURL = "wp:baseBlogUrl" - case podcastLocked = "podcast:locked" - case podcastFundings = "podcast:funding" - case podcastPeople = "podcast:person" - } + /// The people associated with the podcast. + public let podcastPeople: [PodcastPerson] } -public extension RSSChannel { - var syndication: SyndicationUpdate? { +extension RSSChannel { + /// A struct representing an Atom category. + /// A computed property that returns a ``SyndicationUpdate`` object + /// based on the ``syUpdatePeriod`` and ``syUpdateFrequency`` properties. + /// + /// - Returns: A ``SyndicationUpdate`` object. + /// - SeeAlso: ``EntryCategory`` + public var syndication: SyndicationUpdate? { SyndicationUpdate( period: syUpdatePeriod, frequency: syUpdateFrequency?.value diff --git a/Sources/SyndiKit/Formats/Feeds/RSS/RSSFeed.swift b/Sources/SyndiKit/Formats/Feeds/RSS/RSSFeed.swift index e7f4ac3..81f4800 100644 --- a/Sources/SyndiKit/Formats/Feeds/RSS/RSSFeed.swift +++ b/Sources/SyndiKit/Formats/Feeds/RSS/RSSFeed.swift @@ -1,5 +1,6 @@ import Foundation +/// A struct representing an Atom category. /// RSS is a Web content syndication format. /// /// Its name is an acronym for Really Simple Syndication. @@ -13,11 +14,16 @@ import Foundation /// the version attribute must be 2.0. /// For more details, check out the /// [W3 sepcifications.](https://validator.w3.org/feed/docs/rss2.html) +/// - SeeAlso: ``EntryCategory`` public struct RSSFeed { + /// Root Channel of hte RSS Feed public let channel: RSSChannel } extension RSSFeed: DecodableFeed { + internal static let source: DecoderSetup = DecoderSource.xml + public static let label: String = "RSS" + public var youtubeChannelID: String? { nil } @@ -60,7 +66,4 @@ extension RSSFeed: DecodableFeed { public var syndication: SyndicationUpdate? { channel.syndication } - - static let source: DecoderSetup = DecoderSource.xml - static var label: String = "RSS" } diff --git a/Sources/SyndiKit/Formats/Feeds/RSS/RSSImage.swift b/Sources/SyndiKit/Formats/Feeds/RSS/RSSImage.swift index 022d8b3..6c89510 100644 --- a/Sources/SyndiKit/Formats/Feeds/RSS/RSSImage.swift +++ b/Sources/SyndiKit/Formats/Feeds/RSS/RSSImage.swift @@ -1,21 +1,20 @@ import Foundation -/// Specifies a GIF, JPEG or PNG image. +/// Represents a GIF, JPEG, or PNG image. public struct RSSImage: Codable { - /// The URL of a GIF, JPEG or PNG image + /// The URL of the image. public let url: URL - /// Describes the image. + /// The title or description of the image. /// - /// It's used in the ALT attribute of the HTML tag + /// This is used in the ``alt`` attribute of the HTML `` tag /// when the channel is rendered in HTML. public let title: String - /// The URL of the site, when the channel is rendered, - /// the image is a link to the site. + /// The URL of the site that the image links to. /// - /// In practice the image and <link> should have - /// the same value as the channel's <title> and <link> + /// In practice, the image ``title`` and ``link`` should have + /// the same value as the channel's ``title`` and ``link``. public let link: URL /// The width of the image in pixels. @@ -24,7 +23,9 @@ public struct RSSImage: Codable { /// The height of the image in pixels. public let height: Int? - /// This contains text that is included in the TITLE attribute - /// of the link formed around the image in the HTML rendering. + /// Additional description of the image. + /// + /// This text is included in the ``title`` attribute of the link + /// formed around the image in the HTML rendering. public let description: String? } diff --git a/Sources/SyndiKit/Formats/Feeds/RSS/RSSItem+Decodings.swift b/Sources/SyndiKit/Formats/Feeds/RSS/RSSItem+Decodings.swift new file mode 100644 index 0000000..7d2cd94 --- /dev/null +++ b/Sources/SyndiKit/Formats/Feeds/RSS/RSSItem+Decodings.swift @@ -0,0 +1,126 @@ +import Foundation +import XMLCoder + +extension RSSItem { + // swiftlint:disable function_body_length + /// A struct representing an Atom category. + /// Initializes a new ``RSSItem`` by decoding data from a decoder. + /// + /// - Parameter decoder: The decoder to read data from. + /// + /// - Throws: An error if the decoding fails. + /// - SeeAlso: ``EntryCategory`` + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + title = try container.decode(String.self, forKey: .title) + link = try container.decodeIfPresent(URL.self, forKey: .link) + description = try container.decodeIfPresent(CData.self, forKey: .description) + guid = try container.decode(EntryID.self, forKey: .guid) + pubDate = try container.decodeDateIfPresentAndValid(forKey: .pubDate) + contentEncoded = try container.decodeIfPresent(CData.self, forKey: .contentEncoded) + categoryTerms = try container.decode([RSSItemCategory].self, forKey: .categoryTerms) + content = try container.decodeIfPresent(String.self, forKey: .content) + itunesTitle = try container.decodeIfPresent(String.self, forKey: .itunesTitle) + itunesEpisode = try container.decodeIfPresent( + iTunesEpisode.self, forKey: .itunesEpisode + ) + itunesAuthor = try container.decodeIfPresent(String.self, forKey: .itunesAuthor) + itunesSubtitle = try container.decodeIfPresent(String.self, forKey: .itunesSubtitle) + itunesSummary = try container.decodeIfPresent(CData.self, forKey: .itunesSummary) + itunesExplicit = try container.decodeIfPresent(String.self, forKey: .itunesExplicit) + itunesDuration = try container.decodeIfPresent( + iTunesDuration.self, forKey: .itunesDuration + ) + itunesImage = try container.decodeIfPresent(iTunesImage.self, forKey: .itunesImage) + + podcastPeople = try container.decodeIfPresent( + [PodcastPerson].self, + 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) + creators = try container.decode([String].self, forKey: .creators) + + mediaContent = + try container.decodeIfPresent(AtomMedia.self, forKey: .mediaContent) + mediaThumbnail = + try container.decodeIfPresent(AtomMedia.self, forKey: .mediaThumbnail) + + wpPostID = try container.decodeIfPresent(Int.self, forKey: .wpPostID) + wpPostDate = try container.decodeIfPresent(Date.self, forKey: .wpPostDate) + let wpPostDateGMT = try container.decodeIfPresent( + String.self, forKey: .wpPostDateGMT + ) + if let wpPostDateGMT = wpPostDateGMT { + if wpPostDateGMT == "0000-00-00 00:00:00" { + self.wpPostDateGMT = nil + } else { + self.wpPostDateGMT = try container.decode( + Date.self, forKey: .wpPostDateGMT + ) + } + } else { + self.wpPostDateGMT = nil + } + + wpModifiedDate = try container.decodeIfPresent( + Date.self, forKey: .wpModifiedDate + ) + + let wpModifiedDateGMT = try container.decodeIfPresent( + String.self, forKey: .wpModifiedDateGMT + ) + if let wpModifiedDateGMT = wpModifiedDateGMT { + if wpModifiedDateGMT == "0000-00-00 00:00:00" { + self.wpModifiedDateGMT = nil + } else { + self.wpModifiedDateGMT = try container.decode( + Date.self, forKey: .wpModifiedDateGMT + ) + } + } else { + self.wpModifiedDateGMT = nil + } + + let wpAttachmentURLCDData = try container.decodeIfPresent( + CData.self, + forKey: .wpAttachmentURL + ) + wpAttachmentURL = wpAttachmentURLCDData.map { $0.value }.flatMap(URL.init(string:)) + + wpPostName = try container.decodeIfPresent(CData.self, forKey: .wpPostName) + wpPostType = try container.decodeIfPresent(CData.self, forKey: .wpPostType) + 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) + wpPostParent = try container.decodeIfPresent(Int.self, forKey: .wpPostParent) + wpMenuOrder = try container.decodeIfPresent(Int.self, forKey: .wpMenuOrder) + wpIsSticky = try container.decodeIfPresent(Int.self, forKey: .wpIsSticky) + wpPostPassword = try container.decodeIfPresent( + CData.self, forKey: .wpPostPassword + ) + } + + // swiftlint:enable function_body_length +} diff --git a/Sources/SyndiKit/Formats/Feeds/RSS/RSSItem+Init.swift b/Sources/SyndiKit/Formats/Feeds/RSS/RSSItem+Init.swift new file mode 100644 index 0000000..a6671c0 --- /dev/null +++ b/Sources/SyndiKit/Formats/Feeds/RSS/RSSItem+Init.swift @@ -0,0 +1,139 @@ +import Foundation +import XMLCoder + +extension RSSItem { + // swiftlint:disable function_body_length + /// A struct representing an Atom category. + /// Initializes a new ``RSSItem`` instance. + /// + /// - Parameters: + /// - title: The title of the RSS item. + /// - link: The URL link of the RSS item. + /// - description: The description of the RSS item. + /// - guid: The globally unique identifier of the RSS item. + /// - pubDate: The publication date of the RSS item. + /// - contentEncoded: The encoded content of the RSS item. + /// - categoryTerms: The category terms of the RSS item. + /// - content: The content of the RSS item. + /// - itunesTitle: The iTunes title of the RSS item. + /// - itunesEpisode: The iTunes episode of the RSS item. + /// - itunesAuthor: The iTunes author of the RSS item. + /// - itunesSubtitle: The iTunes subtitle of the RSS item. + /// - itunesSummary: The iTunes summary of the RSS item. + /// - itunesExplicit: The iTunes explicit flag of the RSS item. + /// - itunesDuration: The iTunes duration of the RSS item. + /// - itunesImage: The iTunes image of the RSS item. + /// - podcastPeople: The podcast people of the RSS item. + /// - podcastTranscripts: The podcast transcripts of the RSS item. + /// - podcastChapters: The podcast chapters of the RSS item. + /// - podcastSoundbites: The podcast soundbites of the RSS item. + /// - podcastSeason: The podcast season of the RSS item. + /// - enclosure: The enclosure of the RSS item. + /// - creators: The creators of the RSS item. + /// - wpCommentStatus: The WordPress comment status of the RSS item. + /// - wpPingStatus: The WordPress ping status of the RSS item. + /// - wpStatus: The WordPress status of the RSS item. + /// - wpPostParent: The WordPress post parent of the RSS item. + /// - wpMenuOrder: The WordPress menu order of the RSS item. + /// - wpIsSticky: The WordPress sticky flag of the RSS item. + /// - wpPostPassword: The WordPress post password of the RSS item. + /// - wpPostID: The WordPress post ID of the RSS item. + /// - wpPostDate: The WordPress post date of the RSS item. + /// - wpPostDateGMT: The WordPress post date in GMT of the RSS item. + /// - wpModifiedDate: The WordPress modified date of the RSS item. + /// - wpModifiedDateGMT: The WordPress modified date in GMT of the RSS item. + /// - wpPostName: The WordPress post name of the RSS item. + /// - wpPostType: The WordPress post type of the RSS item. + /// - wpPostMeta: The WordPress post meta of the RSS item. + /// - wpAttachmentURL: The WordPress attachment URL of the RSS item. + /// - mediaContent: The media content of the RSS item. + /// - mediaThumbnail: The media thumbnail of the RSS item. + /// - SeeAlso: ``EntryCategory`` + public init( + title: String, + link: URL, + description: String?, + guid: EntryID, + pubDate: Date? = nil, + contentEncoded: String? = nil, + categoryTerms: [RSSItemCategory] = [], + content: String? = nil, + itunesTitle: String? = nil, + itunesEpisode: Int? = nil, + itunesAuthor: String? = nil, + itunesSubtitle: String? = nil, + itunesSummary: CData? = nil, + itunesExplicit: String? = nil, + itunesDuration: TimeInterval? = nil, + itunesImage: iTunesImage? = nil, + podcastPeople: [PodcastPerson] = [], + podcastTranscripts: [PodcastTranscript] = [], + podcastChapters: PodcastChapters? = nil, + podcastSoundbites: [PodcastSoundbite] = [], + podcastSeason: PodcastSeason? = nil, + enclosure: Enclosure? = nil, + creators: [String] = [], + wpCommentStatus: String? = nil, + wpPingStatus: String? = nil, + wpStatus: String? = nil, + wpPostParent: Int? = nil, + wpMenuOrder: Int? = nil, + wpIsSticky: Int? = nil, + wpPostPassword: String? = nil, + wpPostID: Int? = nil, + wpPostDate: Date? = nil, + wpPostDateGMT: Date? = nil, + wpModifiedDate: Date? = nil, + wpModifiedDateGMT: Date? = nil, + wpPostName: String? = nil, + wpPostType: String? = nil, + wpPostMeta: [WordPressElements.PostMeta] = [], + wpAttachmentURL: URL? = nil, + mediaContent: AtomMedia? = nil, + mediaThumbnail: AtomMedia? = nil + ) { + self.title = title + self.link = link + self.description = description.map(CData.init) + self.guid = guid + self.pubDate = pubDate + self.contentEncoded = contentEncoded.map(CData.init) + self.categoryTerms = categoryTerms + self.content = content + self.itunesTitle = itunesTitle + self.itunesEpisode = itunesEpisode.map(iTunesEpisode.init) + self.itunesAuthor = itunesAuthor + self.itunesSubtitle = itunesSubtitle + self.itunesSummary = itunesSummary + self.itunesExplicit = itunesExplicit + self.itunesDuration = itunesDuration.map(iTunesDuration.init) + self.itunesImage = itunesImage + 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) + self.wpPingStatus = wpPingStatus.map(CData.init) + self.wpStatus = wpStatus.map(CData.init) + self.wpPostParent = wpPostParent + self.wpMenuOrder = wpMenuOrder + self.wpIsSticky = wpIsSticky + self.wpPostPassword = wpPostPassword.map(CData.init) + self.wpPostID = wpPostID + self.wpPostDate = wpPostDate + self.wpPostDateGMT = wpPostDateGMT + self.wpModifiedDate = wpModifiedDate + self.wpModifiedDateGMT = wpModifiedDateGMT + self.wpPostName = wpPostName.map(CData.init) + self.wpPostType = wpPostType.map(CData.init) + self.wpPostMeta = wpPostMeta + self.wpAttachmentURL = wpAttachmentURL + self.mediaContent = mediaContent + self.mediaThumbnail = mediaThumbnail + } + + // swiftlint:enable function_body_length +} diff --git a/Sources/SyndiKit/Formats/Feeds/RSS/RSSItem.swift b/Sources/SyndiKit/Formats/Feeds/RSS/RSSItem.swift index 647bebb..ab32f67 100644 --- a/Sources/SyndiKit/Formats/Feeds/RSS/RSSItem.swift +++ b/Sources/SyndiKit/Formats/Feeds/RSS/RSSItem.swift @@ -2,249 +2,7 @@ import Foundation import XMLCoder public struct RSSItem: Codable { - public let title: String - public let link: URL - public let description: CData? - public let guid: EntryID - public let pubDate: Date? - public let contentEncoded: CData? - public let categoryTerms: [RSSItemCategory] - public let content: String? - public let itunesTitle: String? - public let itunesEpisode: iTunesEpisode? - public let itunesAuthor: String? - public let itunesSubtitle: String? - public let itunesSummary: CData? - public let itunesExplicit: String? - public let itunesDuration: iTunesDuration? - public let itunesImage: iTunesImage? - 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? - public let wpPingStatus: CData? - public let wpStatus: CData? - public let wpPostParent: Int? - public let wpMenuOrder: Int? - public let wpIsSticky: Int? - public let wpPostPassword: CData? - public let wpPostID: Int? - public let wpPostDate: Date? - public let wpPostDateGMT: Date? - public let wpModifiedDate: Date? - public let wpModifiedDateGMT: Date? - public let wpPostName: CData? - public let wpPostType: CData? - public let wpPostMeta: [WordPressElements.PostMeta] - public let wpAttachmentURL: URL? - public let mediaContent: AtomMedia? - public let mediaThumbnail: AtomMedia? - - // swiftlint:disable:next function_body_length - public init( - title: String, - link: URL, - description: String?, - guid: EntryID, - pubDate: Date? = nil, - contentEncoded: String? = nil, - categoryTerms: [RSSItemCategory] = [], - content: String? = nil, - itunesTitle: String? = nil, - itunesEpisode: Int? = nil, - itunesAuthor: String? = nil, - itunesSubtitle: String? = nil, - itunesSummary: CData? = nil, - itunesExplicit: String? = nil, - itunesDuration: TimeInterval? = nil, - itunesImage: iTunesImage? = nil, - podcastPeople: [PodcastPerson] = [], - podcastTranscripts: [PodcastTranscript] = [], - podcastChapters: PodcastChapters? = nil, - podcastSoundbites: [PodcastSoundbite] = [], - podcastSeason: PodcastSeason? = nil, - enclosure: Enclosure? = nil, - creators: [String] = [], - wpCommentStatus: String? = nil, - wpPingStatus: String? = nil, - wpStatus: String? = nil, - wpPostParent: Int? = nil, - wpMenuOrder: Int? = nil, - wpIsSticky: Int? = nil, - wpPostPassword: String? = nil, - wpPostID: Int? = nil, - wpPostDate: Date? = nil, - wpPostDateGMT: Date? = nil, - wpModifiedDate: Date? = nil, - wpModifiedDateGMT: Date? = nil, - wpPostName: String? = nil, - wpPostType: String? = nil, - wpPostMeta: [WordPressElements.PostMeta] = [], - wpAttachmentURL: URL? = nil, - mediaContent: AtomMedia? = nil, - mediaThumbnail: AtomMedia? = nil - ) { - self.title = title - self.link = link - self.description = description.map(CData.init) - self.guid = guid - self.pubDate = pubDate - self.contentEncoded = contentEncoded.map(CData.init) - self.categoryTerms = categoryTerms - self.content = content - self.itunesTitle = itunesTitle - self.itunesEpisode = itunesEpisode.map(iTunesEpisode.init) - self.itunesAuthor = itunesAuthor - self.itunesSubtitle = itunesSubtitle - self.itunesSummary = itunesSummary - self.itunesExplicit = itunesExplicit - self.itunesDuration = itunesDuration.map(iTunesDuration.init) - self.itunesImage = itunesImage - 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) - self.wpPingStatus = wpPingStatus.map(CData.init) - self.wpStatus = wpStatus.map(CData.init) - self.wpPostParent = wpPostParent - self.wpMenuOrder = wpMenuOrder - self.wpIsSticky = wpIsSticky - self.wpPostPassword = wpPostPassword.map(CData.init) - self.wpPostID = wpPostID - self.wpPostDate = wpPostDate - self.wpPostDateGMT = wpPostDateGMT - self.wpModifiedDate = wpModifiedDate - self.wpModifiedDateGMT = wpModifiedDateGMT - self.wpPostName = wpPostName.map(CData.init) - self.wpPostType = wpPostType.map(CData.init) - self.wpPostMeta = wpPostMeta - self.wpAttachmentURL = wpAttachmentURL - self.mediaContent = mediaContent - self.mediaThumbnail = mediaThumbnail - } - - // swiftlint:disable:next function_body_length - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - title = try container.decode(String.self, forKey: .title) - link = try container.decode(URL.self, forKey: .link) - description = try container.decodeIfPresent(CData.self, forKey: .description) - guid = try container.decode(EntryID.self, forKey: .guid) - pubDate = try container.decodeDateIfPresentAndValid(forKey: .pubDate) - contentEncoded = try container.decodeIfPresent(CData.self, forKey: .contentEncoded) - categoryTerms = try container.decode([RSSItemCategory].self, forKey: .categoryTerms) - content = try container.decodeIfPresent(String.self, forKey: .content) - itunesTitle = try container.decodeIfPresent(String.self, forKey: .itunesTitle) - itunesEpisode = try container.decodeIfPresent( - iTunesEpisode.self, forKey: .itunesEpisode - ) - itunesAuthor = try container.decodeIfPresent(String.self, forKey: .itunesAuthor) - itunesSubtitle = try container.decodeIfPresent(String.self, forKey: .itunesSubtitle) - itunesSummary = try container.decodeIfPresent(CData.self, forKey: .itunesSummary) - itunesExplicit = try container.decodeIfPresent(String.self, forKey: .itunesExplicit) - itunesDuration = try container.decodeIfPresent( - iTunesDuration.self, forKey: .itunesDuration - ) - itunesImage = try container.decodeIfPresent(iTunesImage.self, forKey: .itunesImage) - - podcastPeople = try container.decodeIfPresent( - [PodcastPerson].self, - 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) - creators = try container.decode([String].self, forKey: .creators) - - mediaContent = - try container.decodeIfPresent(AtomMedia.self, forKey: .mediaContent) - mediaThumbnail = - try container.decodeIfPresent(AtomMedia.self, forKey: .mediaThumbnail) - - wpPostID = try container.decodeIfPresent(Int.self, forKey: .wpPostID) - wpPostDate = try container.decodeIfPresent(Date.self, forKey: .wpPostDate) - let wpPostDateGMT = try container.decodeIfPresent( - String.self, forKey: .wpPostDateGMT - ) - if let wpPostDateGMT = wpPostDateGMT { - if wpPostDateGMT == "0000-00-00 00:00:00" { - self.wpPostDateGMT = nil - } else { - self.wpPostDateGMT = try container.decode( - Date.self, forKey: .wpPostDateGMT - ) - } - } else { - self.wpPostDateGMT = nil - } - - wpModifiedDate = try container.decodeIfPresent( - Date.self, forKey: .wpModifiedDate - ) - - let wpModifiedDateGMT = try container.decodeIfPresent( - String.self, forKey: .wpModifiedDateGMT - ) - if let wpModifiedDateGMT = wpModifiedDateGMT { - if wpModifiedDateGMT == "0000-00-00 00:00:00" { - self.wpModifiedDateGMT = nil - } else { - self.wpModifiedDateGMT = try container.decode( - Date.self, forKey: .wpModifiedDateGMT - ) - } - } else { - self.wpModifiedDateGMT = nil - } - - let wpAttachmentURLCDData = try container.decodeIfPresent( - CData.self, - forKey: .wpAttachmentURL - ) - wpAttachmentURL = wpAttachmentURLCDData.map { $0.value }.flatMap(URL.init(string:)) - - wpPostName = try container.decodeIfPresent(CData.self, forKey: .wpPostName) - wpPostType = try container.decodeIfPresent(CData.self, forKey: .wpPostType) - 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) - wpPostParent = try container.decodeIfPresent(Int.self, forKey: .wpPostParent) - wpMenuOrder = try container.decodeIfPresent(Int.self, forKey: .wpMenuOrder) - wpIsSticky = try container.decodeIfPresent(Int.self, forKey: .wpIsSticky) - wpPostPassword = try container.decodeIfPresent( - CData.self, forKey: .wpPostPassword - ) - } - - enum CodingKeys: String, CodingKey { + public enum CodingKeys: String, CodingKey { case title case link case description @@ -290,6 +48,48 @@ public struct RSSItem: Codable { case mediaContent = "media:content" case mediaThumbnail = "media:thumbnail" } + + public let title: String + public let link: URL? + public let description: CData? + public let guid: EntryID + public let pubDate: Date? + public let contentEncoded: CData? + public let categoryTerms: [RSSItemCategory] + public let content: String? + public let itunesTitle: String? + public let itunesEpisode: iTunesEpisode? + public let itunesAuthor: String? + public let itunesSubtitle: String? + public let itunesSummary: CData? + public let itunesExplicit: String? + public let itunesDuration: iTunesDuration? + public let itunesImage: iTunesImage? + 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? + public let wpPingStatus: CData? + public let wpStatus: CData? + public let wpPostParent: Int? + public let wpMenuOrder: Int? + public let wpIsSticky: Int? + public let wpPostPassword: CData? + public let wpPostID: Int? + public let wpPostDate: Date? + public let wpPostDateGMT: Date? + public let wpModifiedDate: Date? + public let wpModifiedDateGMT: Date? + public let wpPostName: CData? + public let wpPostType: CData? + public let wpPostMeta: [WordPressElements.PostMeta] + public let wpAttachmentURL: URL? + public let mediaContent: AtomMedia? + public let mediaThumbnail: AtomMedia? } extension RSSItem: Entryable { @@ -297,7 +97,7 @@ extension RSSItem: Entryable { categoryTerms } - public var url: URL { + public var url: URL? { link } diff --git a/Sources/SyndiKit/Formats/Feeds/RSS/RSSItemCategory.swift b/Sources/SyndiKit/Formats/Feeds/RSS/RSSItemCategory.swift index 2f8bb18..676d797 100644 --- a/Sources/SyndiKit/Formats/Feeds/RSS/RSSItemCategory.swift +++ b/Sources/SyndiKit/Formats/Feeds/RSS/RSSItemCategory.swift @@ -1,24 +1,65 @@ +/// A struct representing an Atom category. +/// A struct representing a category for an RSS item. +/// +/// This struct conforms to the ``Codable`` and ``EntryCategory`` protocols. +/// +/// - Note: The ``CodingKeys`` enum is used to specify the coding keys for the struct. +/// +/// - SeeAlso: ``EntryCategory`` +/// +/// - Remark: This struct is ``public`` to allow access from other modules. +/// +/// - Warning: Do not modify the ``CodingKeys`` enum. +/// +/// - Version: 1.0 +/// - SeeAlso: ``EntryCategory`` public struct RSSItemCategory: Codable, EntryCategory { + /// The coding keys for the struct. + internal enum CodingKeys: String, CodingKey { + case value = "#CDATA" + case domain + case nicename + } + + /// The term of the category. public var term: String { value } + /// The value of the category. public let value: String + + /// The domain of the category. public let domain: String? - public let nicename: String? - enum CodingKeys: String, CodingKey { - case value = "#CDATA" - case domain - case nicename - } + /// The nicename of the category. + public let nicename: String? + /// A struct representing an Atom category. + /// Initializes a new instance of ``RSSItemCategory``. + /// + /// - Parameters: + /// - value: The value of the category. + /// - domain: The domain of the category. Default value is ``nil``. + /// - nicename: The nicename of the category. Default value is ``nil``. + /// + /// - Returns: A new instance of ``RSSItemCategory``. + /// - SeeAlso: ``EntryCategory`` public init(value: String, domain: String? = nil, nicename: String? = nil) { self.value = value self.domain = domain self.nicename = nicename } + /// A struct representing an Atom category. + /// Initializes a new instance of ``RSSItemCategory`` from a decoder. + /// + /// - Parameter decoder: The decoder to use for decoding. + /// + /// - Throws: An error if the decoding fails. + /// + /// - Returns: A new instance of ``RSSItemCategory``. + /// - SeeAlso: ``EntryCategory`` public init(from decoder: Decoder) throws { let value: String let container: KeyedDecodingContainer<CodingKeys>? @@ -38,6 +79,15 @@ public struct RSSItemCategory: Codable, EntryCategory { } extension RSSItemCategory: Equatable { + /// A struct representing an Atom category. + /// Checks if two ``RSSItemCategory`` instances are equal. + /// + /// - Parameters: + /// - lhs: The left-hand side ``RSSItemCategory`` instance. + /// - rhs: The right-hand side ``RSSItemCategory`` instance. + /// + /// - Returns: ``true`` if the instances are equal, otherwise ``false``. + /// - SeeAlso: ``EntryCategory`` public static func == (lhs: RSSItemCategory, rhs: RSSItemCategory) -> Bool { lhs.value == rhs.value && lhs.domain == rhs.domain diff --git a/Sources/SyndiKit/Formats/Media/MediaContent.swift b/Sources/SyndiKit/Formats/Media/MediaContent.swift index 59593f1..fe76944 100644 --- a/Sources/SyndiKit/Formats/Media/MediaContent.swift +++ b/Sources/SyndiKit/Formats/Media/MediaContent.swift @@ -1,4 +1,13 @@ +/// A struct representing an Atom category. +/// Represents different types of media content. +/// +/// - podcast: A podcast episode. +/// - video: A video. +/// - SeeAlso: ``EntryCategory`` public enum MediaContent { + /// A podcast episode. case podcast(PodcastEpisode) + + /// A video. case video(Video) } diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastChapters+MimeType.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastChapters+MimeType.swift index 4b57a7e..e8f19a1 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastChapters+MimeType.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastChapters+MimeType.swift @@ -1,25 +1,33 @@ import Foundation extension PodcastChapters { + /// A private enum representing known MIME types for podcast chapters. private enum KnownMimeType: String, Codable { case json = "application/json+chapters" + /// Initializes a ``KnownMimeType`` from a case-insensitive string. init?(caseInsensitive: String) { self.init(rawValue: caseInsensitive) } + /// Initializes a ``KnownMimeType`` from a ``MimeType``. init?(mimeType: MimeType) { switch mimeType { - case .json: self = .json - case .unknown: return nil + case .json: + self = .json + + case .unknown: + return nil } } } + /// An enum representing the MIME type of podcast chapters. public enum MimeType: Codable, Equatable, RawRepresentable { case json case unknown(String) + /// The raw value of the MIME type. public var rawValue: String { if let knownMimeType = KnownMimeType(mimeType: self) { return knownMimeType.rawValue @@ -28,15 +36,17 @@ extension PodcastChapters { } else { fatalError( // swiftlint:disable:next line_length - "Type attribute of <podcast:chapters> with value: \(self) should either be `KnownMimeType`, or unknown!" + "Type attribute of <podcast:chapters> with value: \(self) should either be ``KnownMimeType``, or unknown!" ) } } + /// Initializes a ``MimeType`` from a raw value. public init?(rawValue: String) { self.init(caseInsensitive: rawValue) } + /// Initializes a ``MimeType`` from a case-insensitive string. public init(caseInsensitive: String) { if let knownMimeType = KnownMimeType(caseInsensitive: caseInsensitive) { self = .init(knownMimeType: knownMimeType) @@ -45,9 +55,11 @@ extension PodcastChapters { } } + /// Initializes a ``MimeType`` from a ``KnownMimeType``. private init(knownMimeType: KnownMimeType) { switch knownMimeType { - case .json: self = .json + case .json: + self = .json } } } diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastChapters.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastChapters.swift index 561012e..a3ef7e5 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastChapters.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastChapters.swift @@ -1,11 +1,16 @@ import Foundation +/// A struct representing chapters of a podcast. public struct PodcastChapters: Codable, Equatable { - public let url: URL - public let type: MimeType - - enum CodingKeys: String, CodingKey { + /// The coding keys for encoding and decoding. + public enum CodingKeys: String, CodingKey { case url case type } + + /// The URL of the chapter file. + public let url: URL + + /// The MIME type of the chapter file. + public let type: MimeType } diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastEpisode.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastEpisode.swift index d5b3488..7ccaa71 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastEpisode.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastEpisode.swift @@ -1,31 +1,46 @@ import Foundation -public protocol PodcastEpisode { - var title: String? { get } - var episode: Int? { get } - var author: String? { get } - var subtitle: String? { get } - var summary: String? { get } - var explicit: String? { get } - var duration: TimeInterval? { get } - var image: iTunesImage? { get } - var enclosure: Enclosure { get } - var people: [PodcastPerson] { get } -} +/// A struct representing properties of a podcast episode. +internal struct PodcastEpisodeProperties: PodcastEpisode { + /// The title of the episode. + internal let title: String? + + /// The episode number. + internal let episode: Int? + + /// The author of the episode. + internal let author: String? + + /// The subtitle of the episode. + internal let subtitle: String? + + /// A summary of the episode. + internal let summary: String? + + /// Indicates if the episode contains explicit content. + internal let explicit: String? -struct PodcastEpisodeProperties: PodcastEpisode { - public let title: String? - public let episode: Int? - public let author: String? - public let subtitle: String? - public let summary: String? - public let explicit: String? - public let duration: TimeInterval? - public let image: iTunesImage? - public let enclosure: Enclosure - public let people: [PodcastPerson] - - init?(rssItem: RSSItem) { + /// The duration of the episode. + internal let duration: TimeInterval? + + /// The image associated with the episode. + internal let image: iTunesImage? + + /// The enclosure of the episode. + internal let enclosure: Enclosure + + /// The people involved in the episode. + internal let people: [PodcastPerson] + + /// A struct representing an Atom category. + /// Initializes a ``PodcastEpisodeProperties`` instance from an ``RSSItem``. + /// + /// - Parameter rssItem: The ``RSSItem`` to extract the properties from. + /// + /// - Returns: An initialized ``PodcastEpisodeProperties`` instance, + /// or ``nil`` if the ``enclosure`` property is missing. + /// - SeeAlso: ``EntryCategory`` + internal init?(rssItem: RSSItem) { guard let enclosure = rssItem.enclosure else { return nil } @@ -41,3 +56,36 @@ struct PodcastEpisodeProperties: PodcastEpisode { people = rssItem.podcastPeople } } + +/// A protocol representing a podcast episode. +public protocol PodcastEpisode { + /// The title of the episode. + var title: String? { get } + + /// The episode number. + var episode: Int? { get } + + /// The author of the episode. + var author: String? { get } + + /// The subtitle of the episode. + var subtitle: String? { get } + + /// A summary of the episode. + var summary: String? { get } + + /// Indicates if the episode contains explicit content. + var explicit: String? { get } + + /// The duration of the episode. + var duration: TimeInterval? { get } + + /// The image associated with the episode. + var image: iTunesImage? { get } + + /// The enclosure of the episode. + var enclosure: Enclosure { get } + + /// The people involved in the episode. + var people: [PodcastPerson] { get } +} diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastFunding.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastFunding.swift index 227ccb4..ab8d23a 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastFunding.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastFunding.swift @@ -1,11 +1,16 @@ import Foundation +/// A struct representing funding information for a podcast. public struct PodcastFunding: Codable, Equatable { - public let url: URL - public let description: String? - - enum CodingKeys: String, CodingKey { + /// The coding keys used for encoding and decoding. + public enum CodingKeys: String, CodingKey { case url case description = "" } + + /// The URL for the funding source. + public let url: URL + + /// A description of the funding source. + public let description: String? } diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+GeoURI.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+GeoURI.swift index a5bbe92..6acfe6d 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+GeoURI.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+GeoURI.swift @@ -1,12 +1,21 @@ import Foundation -public extension PodcastLocation { - struct GeoURI: Codable, Equatable, LosslessStringConvertible { +extension PodcastLocation { + /// A ``struct`` representing a geographic URI for a podcast location. + public struct GeoURI: Codable, Equatable, LosslessStringConvertible { + /// The latitude coordinate. public let latitude: Double + + /// The longitude coordinate. public let longitude: Double + + /// The altitude coordinate, if available. public let altitude: Double? + + /// The accuracy of the coordinates, if available. public let accuracy: Double? + /// A string representation of the geographic URI. public var description: String { var description = "geo:\(latitude),\(longitude)" @@ -21,6 +30,13 @@ public extension PodcastLocation { return description } + /// Initializes a ``GeoURI`` instance with the specified coordinates. + /// + /// - Parameters: + /// - latitude: The latitude coordinate. + /// - longitude: The longitude coordinate. + /// - altitude: The altitude coordinate, if available. + /// - accuracy: The accuracy of the coordinates, if available. public init( latitude: Double, longitude: Double, @@ -33,10 +49,18 @@ public extension PodcastLocation { self.accuracy = accuracy } + /// Initializes a ``GeoURI`` instance from a string representation. + /// + /// - Parameter description: The string representation of the geographic URI. public init?(_ description: String) { try? self.init(singleValue: description) } + // swiftlint:disable function_body_length + /// Initializes a ``GeoURI`` instance from a single value string. + /// + /// - Parameter singleValue: The single value string representing the geographic URI. + /// - Throws: A ``DecodingError`` if the single value string is invalid. public init(singleValue: String) throws { let pathComponents = try Self.pathComponents(from: singleValue) @@ -46,10 +70,8 @@ public extension PodcastLocation { let longitude = geoCoords[safe: 1]?.asDouble() else { throw DecodingError.dataCorrupted( - .init( - codingPath: [PodcastLocation.CodingKeys.geo], - debugDescription: "Invalid coordinates for geo attribute: \(singleValue)" - ) + codingKey: PodcastLocation.CodingKeys.geo, + debugDescription: "Invalid coordinates for geo attribute: \(singleValue)" ) } @@ -67,6 +89,12 @@ public extension PodcastLocation { ) } + // swiftlint:enable function_body_length + + /// Initializes a ``GeoURI`` instance from a decoder. + /// + /// - Parameter decoder: The decoder to read data from. + /// - Throws: A ``DecodingError`` if the decoding process fails. public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let singleValue = try container.decode(String.self) @@ -80,24 +108,24 @@ public extension PodcastLocation { guard components[safe: 0] == "geo" else { throw DecodingError.dataCorrupted( - .init( - codingPath: [PodcastLocation.CodingKeys.geo], - debugDescription: "Invalid prefix for geo attribute: \(string)" - ) + codingKey: PodcastLocation.CodingKeys.geo, + debugDescription: "Invalid prefix for geo attribute: \(string)" ) } guard let geoPath = components[safe: 1] else { throw DecodingError.dataCorrupted( - .init( - codingPath: [PodcastLocation.CodingKeys.geo], - debugDescription: "Invalid path for geo attribute: \(string)" - ) + codingKey: PodcastLocation.CodingKeys.geo, + debugDescription: "Invalid path for geo attribute: \(string)" ) } return geoPath.split(separator: ";") } + /// Encodes the ``GeoURI`` instance into the given encoder. + /// + /// - Parameter encoder: The encoder to write data to. + /// - Throws: An error if the encoding process fails. public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(description) diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+OsmQuery.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+OsmQuery.swift index 6eaa0de..4bb03e9 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+OsmQuery.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation+OsmQuery.swift @@ -1,21 +1,31 @@ import Foundation -public extension PodcastLocation { - struct OsmQuery: Codable, Equatable { - enum OsmType: String, Codable, CaseIterable { +extension PodcastLocation { + /// Represents a query for OpenStreetMap (OSM) data. + public struct OsmQuery: Codable, Equatable { + // swiftlint:disable nesting + /// The type of OSM element. + public enum OsmType: String, Codable, CaseIterable { case node = "N" case way = "W" case relation = "R" - - static func isValid(_ rawValue: String) -> Bool { - OsmType(rawValue: rawValue) != nil - } } - let id: Int - let type: OsmType - let revision: Int? + // swiftlint:enable nesting + + /// The ID of the OSM element. + public let id: Int + + /// The type of the OSM element. + public let type: OsmType + + /// The revision number of the OSM element. + public let revision: Int? + /// Initializes an ``OsmQuery`` instance from a decoder. + /// + /// - Parameter decoder: The decoder to read data from. + /// - Throws: `DecodingError.dataCorrupted` if the data is invalid. public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() @@ -23,18 +33,14 @@ public extension PodcastLocation { guard let osmType = osmStr.removeFirst().asOsmType() else { throw DecodingError.dataCorrupted( - .init( - codingPath: [PodcastLocation.CodingKeys.osmQuery], - debugDescription: "Invalid type for osm attribute: \(osmStr)" - ) + codingKey: 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)" - ) + codingKey: PodcastLocation.CodingKeys.osmQuery, + debugDescription: "Invalid id of type Int for osm attribute: \(osmStr)" ) } let osmRevision = osmStr.split(separator: "#")[safe: 1]?.asInt() diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation.swift index df8c1c1..1513654 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocation.swift @@ -1,15 +1,21 @@ import Foundation +/// A struct representing the location of a podcast. public struct PodcastLocation: Codable, Equatable { - public let geo: GeoURI? - public let osmQuery: OsmQuery? - - public let name: String - - enum CodingKeys: String, CodingKey { + /// The geographic coordinates of the location. + internal enum CodingKeys: String, CodingKey { case geo case osmQuery = "osm" case name = "" } + + /// The geographic coordinates of the location. + public let geo: GeoURI? + + /// The OpenStreetMap query for the location. + public let osmQuery: OsmQuery? + + /// The name of the location. + public let name: String } diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocked.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocked.swift index b35c891..6a80f65 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocked.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastLocked.swift @@ -1,14 +1,23 @@ import Foundation +/// A struct representing a locked podcast. public struct PodcastLocked: Codable, Equatable { - public let owner: String? - public let isLocked: Bool - - enum CodingKeys: String, CodingKey { + /// Coding keys for encoding and decoding. + public enum CodingKeys: String, CodingKey { case owner case isLocked = "" } + /// The owner of the podcast. + public let owner: String? + + /// Indicates whether the podcast is locked. + public let isLocked: Bool + + /// Initializes a new instance of ``PodcastLocked`` from a decoder. + /// + /// - Parameter decoder: The decoder to read data from. + /// - Throws: An error if the decoding process fails. public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) owner = try container.decodeIfPresent(String.self, forKey: .owner) diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastPerson+Role.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastPerson+Role.swift index d3359d1..29cb86e 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastPerson+Role.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastPerson+Role.swift @@ -1,6 +1,7 @@ import Foundation extension PodcastPerson { + /// A private enum representing known roles for a podcast person. private enum KnownRole: String { case guest case host @@ -10,24 +11,45 @@ extension PodcastPerson { case composer case producer + /// Initializes a ``KnownRole`` with a case-insensitive string. init?(caseInsensitive: String) { self.init(rawValue: caseInsensitive.lowercased()) } + // swiftlint:disable function_body_length cyclomatic_complexity + /// Initializes a ``KnownRole`` with a ``Role`` value. init?(role: Role) { switch role { - case .guest: self = .guest - case .host: self = .host - case .editor: self = .editor - case .writer: self = .writer - case .designer: self = .designer - case .composer: self = .composer - case .producer: self = .producer - case .unknown: return nil + case .guest: + self = .guest + + case .host: + self = .host + + case .editor: + self = .editor + + case .writer: + self = .writer + + case .designer: + self = .designer + + case .composer: + self = .composer + + case .producer: + self = .producer + + case .unknown: + return nil } } } + // swiftlint:enable function_body_length cyclomatic_complexity + + /// An enum representing the role of a podcast person. public enum Role: Codable, Equatable, RawRepresentable { case guest case host @@ -38,6 +60,7 @@ extension PodcastPerson { case producer case unknown(String) + /// The raw value of the role. public var rawValue: String { if let knownRole = KnownRole(role: self) { return knownRole.rawValue @@ -46,15 +69,17 @@ extension PodcastPerson { } else { fatalError( // swiftlint:disable:next line_length - "Role attribute of <podcast:person> with value: \(self) should either be a `KnownRole`, or unknown!" + "Role attribute of <podcast:person> with value: \(self) should either be a ``KnownRole``, or unknown!" ) } } + /// Initializes a ``Role`` with a raw value. public init?(rawValue: String) { self.init(caseInsensitive: rawValue) } + /// Initializes a ``Role`` with a case-insensitive string. public init(caseInsensitive: String) { if let knownRole = KnownRole(caseInsensitive: caseInsensitive) { self = .init(knownRole: knownRole) @@ -63,15 +88,29 @@ extension PodcastPerson { } } + // swiftlint:disable:next cyclomatic_complexity private init(knownRole: KnownRole) { switch knownRole { - case .guest: self = .guest - case .host: self = .host - case .editor: self = .editor - case .writer: self = .writer - case .designer: self = .designer - case .composer: self = .composer - case .producer: self = .producer + case .guest: + self = .guest + + case .host: + self = .host + + case .editor: + self = .editor + + case .writer: + self = .writer + + case .designer: + self = .designer + + case .composer: + self = .composer + + case .producer: + self = .producer } } } diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastPerson.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastPerson.swift index 8ee3322..9b1c795 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastPerson.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastPerson.swift @@ -1,14 +1,9 @@ import Foundation +/// A struct representing a person associated with a podcast. public struct PodcastPerson: Codable, Equatable { - public let role: Role? - public let group: String? - public let href: URL? - public let img: URL? - - public let fullname: String - - enum CodingKeys: String, CodingKey { + /// The role of the person. + public enum CodingKeys: String, CodingKey { case role case group case href @@ -16,6 +11,26 @@ public struct PodcastPerson: Codable, Equatable { case fullname = "" } + /// The role of the person. + public let role: Role? + + /// The group the person belongs to. + public let group: String? + + /// The URL associated with the person. + public let href: URL? + + /// The URL of the person's image. + public let img: URL? + + /// The full name of the person. + public let fullname: String + + /// Initializes a new instance of ``PodcastPerson`` + /// by decoding data from the given decoder. + /// + /// - Parameter decoder: The decoder to read data from. + /// - Throws: An error if the decoding process fails. public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) role = try container.decodeIfPresent(Role.self, forKey: .role) diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastSeason.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastSeason.swift index e18ba77..962ac4f 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastSeason.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastSeason.swift @@ -1,11 +1,16 @@ import Foundation +/// A struct representing a season of a podcast. public struct PodcastSeason: Codable, Equatable { - public let name: String? - public let number: Int - - enum CodingKeys: String, CodingKey { + /// The coding keys for the ``PodcastSeason`` struct. + public enum CodingKeys: String, CodingKey { case name case number = "" } + + /// The name of the season. + public let name: String? + + /// The number of the season. + public let number: Int } diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastSoundbite.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastSoundbite.swift index 3119f6a..14740d3 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastSoundbite.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastSoundbite.swift @@ -1,15 +1,21 @@ import Foundation +/// A struct representing a soundbite from a podcast. public struct PodcastSoundbite: Codable, Equatable { - public let startTime: TimeInterval - public let duration: TimeInterval - - public let title: String? - - enum CodingKeys: String, CodingKey { + /// The coding keys used for encoding and decoding. + public enum CodingKeys: String, CodingKey { case startTime case duration case title = "" } + + /// The start time of the soundbite. + public let startTime: TimeInterval + + /// The duration of the soundbite. + public let duration: TimeInterval + + /// The title of the soundbite. + public let title: String? } diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastTranscript+MimeType.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastTranscript+MimeType.swift index 654c9d6..0cfb4bd 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastTranscript+MimeType.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastTranscript+MimeType.swift @@ -1,6 +1,7 @@ import Foundation extension PodcastTranscript { + /// A private enum representing known MIME types for the transcript. private enum KnownMimeType: String, Codable { case plain = "text/plain" case html = "text/html" @@ -9,23 +10,42 @@ extension PodcastTranscript { case json = "application/json" case subrip = "application/x-subrip" + /// Initializes a ``KnownMimeType`` with a case-insensitive string. init?(caseInsensitive: String) { self.init(rawValue: caseInsensitive) } + // swiftlint:disable cyclomatic_complexity + /// Initializes a ``KnownMimeType`` with a ``MimeType``. init?(mimeType: MimeType) { switch mimeType { - case .plain: self = .plain - case .html: self = .html - case .srt: self = .srt - case .vtt: self = .vtt - case .json: self = .json - case .subrip: self = .subrip - case .unknown: return nil + case .plain: + self = .plain + + case .html: + self = .html + + case .srt: + self = .srt + + case .vtt: + self = .vtt + + case .json: + self = .json + + case .subrip: + self = .subrip + + case .unknown: + return nil } } + + // swiftlint:enable cyclomatic_complexity } + /// An enum representing the MIME type of the transcript. public enum MimeType: Codable, Equatable, RawRepresentable { case plain case html @@ -35,6 +55,7 @@ extension PodcastTranscript { case subrip case unknown(String) + /// The raw value of the MIME type. public var rawValue: String { if let knownMimeType = KnownMimeType(mimeType: self) { return knownMimeType.rawValue @@ -43,15 +64,17 @@ extension PodcastTranscript { } else { fatalError( // swiftlint:disable:next line_length - "Type attribute of <podcast:transcript> with value: \(self) should either be a `KnownMimeType`, or unknown!" + "Type attribute of <podcast:transcript> with value: \(self) should either be a ``KnownMimeType``, or unknown!" ) } } + /// Initializes a ``MimeType`` with a raw value. public init?(rawValue: String) { self.init(caseInsensitive: rawValue) } + /// Initializes a ``MimeType`` with a case-insensitive string. public init(caseInsensitive: String) { if let knownMimeType = KnownMimeType(caseInsensitive: caseInsensitive) { self = .init(knownMimeType: knownMimeType) @@ -60,14 +83,26 @@ extension PodcastTranscript { } } + /// Initializes a ``MimeType`` with a ``KnownMimeType``. private init(knownMimeType: KnownMimeType) { switch knownMimeType { - case .plain: self = .plain - case .html: self = .html - case .srt: self = .srt - case .vtt: self = .vtt - case .json: self = .json - case .subrip: self = .subrip + case .plain: + self = .plain + + case .html: + self = .html + + case .srt: + self = .srt + + case .vtt: + self = .vtt + + case .json: + self = .json + + case .subrip: + self = .subrip } } } diff --git a/Sources/SyndiKit/Formats/Media/Podcast/PodcastTranscript.swift b/Sources/SyndiKit/Formats/Media/Podcast/PodcastTranscript.swift index bde41ac..89bf654 100644 --- a/Sources/SyndiKit/Formats/Media/Podcast/PodcastTranscript.swift +++ b/Sources/SyndiKit/Formats/Media/Podcast/PodcastTranscript.swift @@ -1,19 +1,29 @@ import Foundation +/// A struct representing a podcast transcript. public struct PodcastTranscript: Codable, Equatable { + /// The coding keys for the podcast transcript. + public enum CodingKeys: String, CodingKey { + case url + case type + case language + case rel + } + + /// The relationship between the podcast transcript and the podcast. public enum Relationship: String, Codable { case captions } + /// The URL of the podcast transcript. public let url: URL + + /// The MIME type of the podcast transcript. public let type: MimeType + + /// The language of the podcast transcript. public let language: String? - public let rel: Relationship? - enum CodingKeys: String, CodingKey { - case url - case type - case language - case rel - } + /// The relationship of the podcast transcript. + public let rel: Relationship? } diff --git a/Sources/SyndiKit/Formats/Media/Video.swift b/Sources/SyndiKit/Formats/Media/Video.swift index 5882919..120bc6d 100644 --- a/Sources/SyndiKit/Formats/Media/Video.swift +++ b/Sources/SyndiKit/Formats/Media/Video.swift @@ -1,3 +1,9 @@ +/// A struct representing an Atom category. +/// An enumeration representing different types of videos. +/// - SeeAlso: ``EntryCategory`` public enum Video { + /// A video from YouTube. + /// - Parameters: + /// - id: The ID of the YouTube video. case youtube(YouTubeID) } diff --git a/Sources/SyndiKit/Formats/Media/Wordpress/WPCategory.swift b/Sources/SyndiKit/Formats/Media/Wordpress/WPCategory.swift index 3c395a8..54078e1 100644 --- a/Sources/SyndiKit/Formats/Media/Wordpress/WPCategory.swift +++ b/Sources/SyndiKit/Formats/Media/Wordpress/WPCategory.swift @@ -1,19 +1,41 @@ import Foundation -public extension WordPressElements { - struct Category: Codable { - public let termID: Int - public let niceName: CData - public let parent: CData - public let name: String +/// A typealias for the `WordPressElements.Category` type. +public typealias WPCategory = WordPressElements.Category - enum CodingKeys: String, CodingKey { +// swiftlint:disable nesting +extension WordPressElements { + /// A struct representing a category in WordPress. + public struct Category: Codable { + /// The coding keys for the ``Category`` struct. + internal enum CodingKeys: String, CodingKey { case termID = "wp:termId" case niceName = "wp:categoryNicename" case parent = "wp:categoryParent" case name = "wp:catName" } + /// The unique identifier of the category. + public let termID: Int + + /// The nice name of the category. + public let niceName: CData + + /// The parent category of the category. + public let parent: CData + + /// The name of the category. + public let name: String + + /// A struct representing an Atom category. + /// Initializes a new ``Category`` instance. + /// + /// - Parameters: + /// - termID: The unique identifier of the category. + /// - niceName: The nice name of the category. + /// - parent: The parent category of the category. + /// - name: The name of the category. + /// - SeeAlso: ``EntryCategory`` public init(termID: Int, niceName: CData, parent: CData, name: String) { self.termID = termID self.niceName = niceName @@ -24,6 +46,7 @@ public extension WordPressElements { } extension WordPressElements.Category: Equatable { + /// Checks if two ``Category`` instances are equal. public static func == ( lhs: WordPressElements.Category, rhs: WordPressElements.Category diff --git a/Sources/SyndiKit/Formats/Media/Wordpress/WPPostMeta.swift b/Sources/SyndiKit/Formats/Media/Wordpress/WPPostMeta.swift index fd56724..9d87606 100644 --- a/Sources/SyndiKit/Formats/Media/Wordpress/WPPostMeta.swift +++ b/Sources/SyndiKit/Formats/Media/Wordpress/WPPostMeta.swift @@ -1,15 +1,31 @@ import Foundation -public extension WordPressElements { - struct PostMeta: Codable { - public let key: CData - public let value: CData +/// A typealias for `WordPressElements.Category`. +public typealias WPPostMeta = WordPressElements.Category - enum CodingKeys: String, CodingKey { +// swiftlint:disable nesting +extension WordPressElements { + /// A struct representing metadata for a WordPress post. + public struct PostMeta: Codable { + /// The coding keys for encoding and decoding. + internal enum CodingKeys: String, CodingKey { case key = "wp:metaKey" case value = "wp:metaValue" } + /// The key of the metadata. + public let key: CData + + /// The value of the metadata. + public let value: CData + + /// A struct representing an Atom category. + /// Initializes a new ``PostMeta`` instance. + /// + /// - Parameters: + /// - key: The key of the metadata. + /// - value: The value of the metadata. + /// - SeeAlso: ``EntryCategory`` public init(key: String, value: String) { self.key = .init(stringLiteral: key) self.value = .init(stringLiteral: value) @@ -18,6 +34,15 @@ public extension WordPressElements { } extension WordPressElements.PostMeta: Equatable { + /// A struct representing an Atom category. + /// Checks if two ``PostMeta`` instances are equal. + /// + /// - Parameters: + /// - lhs: The left-hand side ``PostMeta`` instance. + /// - rhs: The right-hand side ``PostMeta`` instance. + /// + /// - Returns: ``true`` if the two instances are equal, ``false`` otherwise. + /// - SeeAlso: ``EntryCategory`` public static func == ( lhs: WordPressElements.PostMeta, rhs: WordPressElements.PostMeta diff --git a/Sources/SyndiKit/Formats/Media/Wordpress/WPTag.swift b/Sources/SyndiKit/Formats/Media/Wordpress/WPTag.swift index 85f574e..51122cb 100644 --- a/Sources/SyndiKit/Formats/Media/Wordpress/WPTag.swift +++ b/Sources/SyndiKit/Formats/Media/Wordpress/WPTag.swift @@ -1,17 +1,36 @@ import Foundation -public extension WordPressElements { - struct Tag: Codable { - public let termID: Int - public let slug: CData - public let name: CData +/// A typealias for `WordPressElements.Tag` +public typealias WPTag = WordPressElements.Tag - enum CodingKeys: String, CodingKey { +// swiftlint:disable nesting +extension WordPressElements { + /// A struct representing a tag in WordPress. + public struct Tag: Codable { + /// The term ID of the tag. + internal enum CodingKeys: String, CodingKey { case termID = "wp:termId" case slug = "wp:tagSlug" case name = "wp:tagName" } + /// The term ID of the tag. + public let termID: Int + + /// The slug of the tag. + public let slug: CData + + /// The name of the tag. + public let name: CData + + /// A struct representing an Atom category. + /// Initializes a new ``Tag`` instance. + /// + /// - Parameters: + /// - termID: The term ID of the tag. + /// - slug: The slug of the tag. + /// - name: The name of the tag. + /// - SeeAlso: ``EntryCategory`` public init(termID: Int, slug: CData, name: CData) { self.termID = termID self.slug = slug @@ -21,6 +40,7 @@ public extension WordPressElements { } extension WordPressElements.Tag: Equatable { + /// Checks if two ``Tag`` instances are equal. public static func == (lhs: WordPressElements.Tag, rhs: WordPressElements.Tag) -> Bool { lhs.termID == rhs.termID && lhs.slug == rhs.slug diff --git a/Sources/SyndiKit/Formats/Media/Wordpress/WordPressPost+RSSItem.swift b/Sources/SyndiKit/Formats/Media/Wordpress/WordPressPost+RSSItem.swift new file mode 100644 index 0000000..8409dd8 --- /dev/null +++ b/Sources/SyndiKit/Formats/Media/Wordpress/WordPressPost+RSSItem.swift @@ -0,0 +1,95 @@ +import Foundation + +extension WordPressPost { + // swiftlint:disable cyclomatic_complexity function_body_length + /// A struct representing an Atom category. + /// Initializes a ``WordPressPost`` instance from an ``RSSItem``. + /// + /// - Parameter item: The ``RSSItem`` to initialize from. + /// + /// - Throws: `WordPressError.missingField` if any required field is missing. + /// + /// - Note: This initializer is marked as ``public`` to allow external usage. + /// + /// - SeeAlso: ``EntryCategory`` + public init(item: RSSItem) throws { + guard let name = item.wpPostName else { + throw WordPressError.missingField(.name) + } + guard let type = item.wpPostType else { + throw WordPressError.missingField(.type) + } + guard let creator = item.creators.first else { + throw WordPressError.missingField(.creator) + } + guard let body = item.contentEncoded else { + throw WordPressError.missingField(.body) + } + guard let status = item.wpStatus else { + throw WordPressError.missingField(.status) + } + guard let commentStatus = item.wpCommentStatus else { + throw WordPressError.missingField(.commentStatus) + } + guard let pingStatus = item.wpPingStatus else { + throw WordPressError.missingField(.pingStatus) + } + guard let parentID = item.wpPostParent else { + throw WordPressError.missingField(.parentID) + } + guard let menuOrder = item.wpMenuOrder else { + throw WordPressError.missingField(.menuOrder) + } + guard let id = item.wpPostID else { + throw WordPressError.missingField(.id) + } + guard let isSticky = item.wpIsSticky else { + throw WordPressError.missingField(.isSticky) + } + guard let postDate = item.wpPostDate else { + throw WordPressError.missingField(.postDate) + } + guard let modifiedDate = item.wpModifiedDate else { + throw WordPressError.missingField(.modifiedDate) + } + guard let link = item.link else { + throw WordPressError.missingField(.link) + } + + let title = item.title + let categoryTerms = item.categoryTerms + let meta = item.wpPostMeta + let pubDate = item.pubDate + + let categoryDictionary = Dictionary( + grouping: categoryTerms) { + $0.domain + } + + modifiedDateGMT = item.wpModifiedDateGMT + self.name = name.value + self.title = title + self.type = type.value + self.link = link + self.pubDate = pubDate + self.creator = creator + self.body = body.value + tags = categoryDictionary["post_tag", default: []].map { $0.value } + categories = categoryDictionary["category", default: []].map { $0.value } + self.meta = Dictionary(grouping: meta) { $0.key.value } + .compactMapValues { $0.last?.value.value } + self.status = status.value + self.commentStatus = commentStatus.value + self.pingStatus = pingStatus.value + self.parentID = parentID + self.menuOrder = menuOrder + self.id = id + self.isSticky = (isSticky != 0) + self.postDate = postDate + postDateGMT = item.wpPostDateGMT + self.modifiedDate = modifiedDate + attachmentURL = item.wpAttachmentURL + } + + // swiftlint:enable cyclomatic_complexity function_body_length +} diff --git a/Sources/SyndiKit/Formats/Media/Wordpress/WordPressPost.swift b/Sources/SyndiKit/Formats/Media/Wordpress/WordPressPost.swift index 10daded..830ed86 100644 --- a/Sources/SyndiKit/Formats/Media/Wordpress/WordPressPost.swift +++ b/Sources/SyndiKit/Formats/Media/Wordpress/WordPressPost.swift @@ -1,84 +1,29 @@ import Foundation +/// A namespace for WordPress related elements. public enum WordPressElements {} +/// An error type representing a missing field in a WordPress post. +public enum WordPressError: Error, Equatable { + case missingField(WordPressPost.Field) +} + +/// A struct representing a WordPress post. public struct WordPressPost { + /// The type of the post. public typealias PostType = String + + /// The comment status of the post. public typealias CommentStatus = String + + /// The ping status of the post. public typealias PingStatus = String - public typealias Status = String - init( - name: String, - title: String, - type: PostType, - link: URL, - pubDate: Date?, - creator: String, - body: String, - tags: [String], - categories: [String], - meta: [String: String], - status: Status, - commentStatus: CommentStatus, - pingStatus: PingStatus, - parentID: Int?, - menuOrder: Int?, - ID: Int, - isSticky: Bool, - postDate: Date, - postDateGMT: Date?, - modifiedDate: Date, - modifiedDateGMT: Date?, - attachmentURL: URL? - ) { - self.name = name - self.title = title - self.type = type - self.link = link - self.pubDate = pubDate - self.creator = creator - self.body = body - self.tags = tags - self.categories = categories - self.meta = meta - self.status = status - self.commentStatus = commentStatus - self.pingStatus = pingStatus - self.parentID = parentID - self.menuOrder = menuOrder - self.ID = ID - self.isSticky = isSticky - self.postDate = postDate - self.postDateGMT = postDateGMT - self.modifiedDate = modifiedDate - self.modifiedDateGMT = modifiedDateGMT - self.attachmentURL = attachmentURL - } - public let name: String - public let title: String - public let type: PostType - public let link: URL - public let pubDate: Date? - public let creator: String - public let body: String - public let tags: [String] - public let categories: [String] - public let meta: [String: String] - public let status: Status - public let commentStatus: CommentStatus - public let pingStatus: PingStatus - public let parentID: Int? - public let menuOrder: Int? - public let ID: Int - public let isSticky: Bool - public let postDate: Date - public let postDateGMT: Date? - public let modifiedDate: Date - public let modifiedDateGMT: Date? - public let attachmentURL: URL? + /// The status of the post. + public typealias Status = String - enum Field: Equatable { + /// An enum representing the fields of a WordPress post. + public enum Field: Equatable { case name case title case type @@ -94,113 +39,94 @@ public struct WordPressPost { case pingStatus case parentID case menuOrder - case ID + case id case isSticky case postDate case postDateGMT case modifiedDate case modifiedDateGMT } -} -enum WordPressError: Error, Equatable { - case missingField(WordPressPost.Field) -} + /// The name of the post. + public let name: String -public extension WordPressPost { - // swiftlint:disable:next cyclomatic_complexity function_body_length - init(item: RSSItem) throws { - guard let name = item.wpPostName else { - throw WordPressError.missingField(.name) - } - guard let type = item.wpPostType else { - throw WordPressError.missingField(.type) - } - guard let creator = item.creators.first else { - throw WordPressError.missingField(.creator) - } - guard let body = item.contentEncoded else { - throw WordPressError.missingField(.body) - } - guard let status = item.wpStatus else { - throw WordPressError.missingField(.status) - } - guard let commentStatus = item.wpCommentStatus else { - throw WordPressError.missingField(.commentStatus) - } - guard let pingStatus = item.wpPingStatus else { - throw WordPressError.missingField(.pingStatus) - } - guard let parentID = item.wpPostParent else { - throw WordPressError.missingField(.parentID) - } - guard let menuOrder = item.wpMenuOrder else { - throw WordPressError.missingField(.menuOrder) - } - guard let ID = item.wpPostID else { - throw WordPressError.missingField(.ID) - } - guard let isSticky = item.wpIsSticky else { - throw WordPressError.missingField(.isSticky) - } - guard let postDate = item.wpPostDate else { - throw WordPressError.missingField(.postDate) - } - guard let modifiedDate = item.wpModifiedDate else { - throw WordPressError.missingField(.modifiedDate) - } + /// The title of the post. + public let title: String - let title = item.title - let link = item.link - let categoryTerms = item.categoryTerms - let meta = item.wpPostMeta - let pubDate = item.pubDate - - let categoryDictionary = Dictionary(grouping: categoryTerms, by: { - $0.domain - }) - - modifiedDateGMT = item.wpModifiedDateGMT - self.name = name.value - self.title = title - self.type = type.value - self.link = link - self.pubDate = pubDate - self.creator = creator - self.body = body.value - tags = categoryDictionary["post_tag", default: []].map { $0.value } - categories = categoryDictionary["category", default: []].map { $0.value } - self.meta = Dictionary(grouping: meta, by: { - $0.key.value - }).compactMapValues { - $0.last?.value.value - } - self.status = status.value - self.commentStatus = commentStatus.value - self.pingStatus = pingStatus.value - self.parentID = parentID - self.menuOrder = menuOrder - self.ID = ID - self.isSticky = (isSticky != 0) - self.postDate = postDate - postDateGMT = item.wpPostDateGMT - self.modifiedDate = modifiedDate - attachmentURL = item.wpAttachmentURL - } + /// The type of the post. + public let type: PostType + + /// The link of the post. + public let link: URL + + /// The publication date of the post. + public let pubDate: Date? + + /// The creator of the post. + public let creator: String + + /// The body of the post. + public let body: String + + /// The tags of the post. + public let tags: [String] + + /// The categories of the post. + public let categories: [String] + + /// The meta data of the post. + public let meta: [String: String] + + /// The status of the post. + public let status: Status + + /// The comment status of the post. + public let commentStatus: CommentStatus + + /// The ping status of the post. + public let pingStatus: PingStatus + + /// The parent ID of the post. + public let parentID: Int? + + /// The menu order of the post. + public let menuOrder: Int? + + /// The ID of the post. + public let id: Int + + /// A boolean indicating if the post is sticky. + public let isSticky: Bool + + /// The post date of the post. + public let postDate: Date + + /// The post date in GMT of the post. + public let postDateGMT: Date? + + /// The modified date of the post. + public let modifiedDate: Date + + /// The modified date in GMT of the post. + public let modifiedDateGMT: Date? + + /// The attachment URL of the post. + public let attachmentURL: URL? } extension WordPressPost: Hashable { public static func == (lhs: WordPressPost, rhs: WordPressPost) -> Bool { - lhs.ID == rhs.ID + lhs.id == rhs.id } public func hash(into hasher: inout Hasher) { - hasher.combine(ID) + hasher.combine(id) } } -public extension Entryable { - var wpPost: WordPressPost? { +extension Entryable { + /// Returns a WordPress post if the entry is an RSS item. + public var wpPost: WordPressPost? { guard let rssItem = self as? RSSItem else { return nil } diff --git a/Sources/SyndiKit/Formats/Media/YouTube/YouTubeID.swift b/Sources/SyndiKit/Formats/Media/YouTube/YouTubeID.swift index 59f54a0..43d65e0 100644 --- a/Sources/SyndiKit/Formats/Media/YouTube/YouTubeID.swift +++ b/Sources/SyndiKit/Formats/Media/YouTube/YouTubeID.swift @@ -1,27 +1,55 @@ -/// Specific type abstracting the id properties a YouTube RSS Feed. -/// ```xml -/// <yt:videoId>3hccNoPE59U</yt:videoId> -/// <yt:channelId>UCv75sKQFFIenWHrprnrR9aA</yt:channelId> -/// ``` -public protocol YouTubeID { - /// YouTube video ID. - var videoID: String { get } - - /// YouTube channel ID. - var channelID: String { get } -} - -struct YouTubeIDProperties: YouTubeID { - public let videoID: String - - public let channelID: String +/// A struct representing an Atom category. +/// A struct representing the properties of a YouTube ID. +/// +/// - Note: This struct conforms to the ``YouTubeID`` protocol. +/// +/// - SeeAlso: ``YouTubeID`` +/// +/// - Important: This struct is internal. +/// +/// - Parameters: +/// - videoID: The YouTube video ID. +/// - channelID: The YouTube channel ID. +/// - SeeAlso: ``EntryCategory`` +internal struct YouTubeIDProperties: YouTubeID { + internal let videoID: String + internal let channelID: String - init?(entry: AtomEntry) { - guard let channelID = entry.youtubeChannelID, - let videoID = entry.youtubeVideoID else { + /// A struct representing an Atom category. + /// Initializes a ``YouTubeIDProperties`` instance with the given AtomEntry. + /// + /// - Parameters: + /// - entry: The AtomEntry containing the YouTube ID properties. + /// + /// - Returns: A new ``YouTubeIDProperties`` instance, + /// or ``nil`` if the required properties are missing. + /// - SeeAlso: ``EntryCategory`` + internal init?(entry: AtomEntry) { + guard + let channelID = entry.youtubeChannelID, + let videoID = entry.youtubeVideoID else { return nil } self.channelID = channelID self.videoID = videoID } } + +/// A struct representing an Atom category. +/// A protocol abstracting the ID properties of a YouTube RSS Feed. +/// +/// - Note: This protocol is public. +/// +/// - SeeAlso: ``YouTubeIDProperties`` +/// +/// - Important: This protocol is specific to YouTube. +/// +/// - Requires: Conforming types must provide a ``videoID`` and a ``channelID``. +/// - SeeAlso: ``EntryCategory`` +public protocol YouTubeID { + /// The YouTube video ID. + var videoID: String { get } + + /// The YouTube channel ID. + var channelID: String { get } +} diff --git a/Sources/SyndiKit/Formats/Media/iTunes/iTunesDuration.swift b/Sources/SyndiKit/Formats/Media/iTunes/iTunesDuration.swift index 2fe71af..47aa2f6 100644 --- a/Sources/SyndiKit/Formats/Media/iTunes/iTunesDuration.swift +++ b/Sources/SyndiKit/Formats/Media/iTunes/iTunesDuration.swift @@ -1,23 +1,25 @@ import Foundation -// swiftlint:disable:next type_name -public struct iTunesDuration: Codable, ExpressibleByFloatLiteral { - public init(floatLiteral value: TimeInterval) { - self.value = value - } +/// A struct representing the duration of an iTunes track. +public struct iTunesDuration: Codable, ExpressibleByFloatLiteral { + /// The type used to represent a floating-point literal. public typealias FloatLiteralType = TimeInterval - static func timeInterval(_ timeString: String) -> TimeInterval? { - let timeStrings = timeString.components(separatedBy: ":").prefix(3) - let doubles = timeStrings.compactMap(Double.init) - guard doubles.count == timeStrings.count else { - return nil - } - return doubles.reduce(0) { partialResult, value in - partialResult * 60.0 + value - } + /// The value of the duration in seconds. + public let value: TimeInterval + + /// Creates a new instance with the specified floating-point literal value. + /// + /// - Parameter value: The value of the duration in seconds. + public init(floatLiteral value: TimeInterval) { + self.value = value } + /// Creates a new instance by decoding from the given decoder. + /// + /// - Parameter decoder: The decoder to read data from. + /// - Throws: An error if reading from the decoder fails, + /// or if the data is corrupted or invalid. public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let stringValue = try container.decode(String.self) @@ -32,6 +34,10 @@ public struct iTunesDuration: Codable, ExpressibleByFloatLiteral { self.value = value } + /// Creates a new instance from the given description string. + /// + /// - Parameter description: The description string representing the duration. + /// - Returns: A new instance if the description is valid, otherwise ``nil``. public init?(_ description: String) { guard let value = Self.timeInterval(description) else { return nil @@ -39,5 +45,19 @@ public struct iTunesDuration: Codable, ExpressibleByFloatLiteral { self.value = value } - public let value: TimeInterval + /// Converts a time string to a ``TimeInterval`` value. + /// + /// - Parameter timeString: The time string to convert. + /// - Returns: The ``TimeInterval`` value representing the time string, + /// or ``nil`` if the string is invalid. + internal static func timeInterval(_ timeString: String) -> TimeInterval? { + let timeStrings = timeString.components(separatedBy: ":").prefix(3) + let doubles = timeStrings.compactMap(Double.init) + guard doubles.count == timeStrings.count else { + return nil + } + return doubles.reduce(0) { partialResult, value in + partialResult * 60.0 + value + } + } } diff --git a/Sources/SyndiKit/Formats/Media/iTunes/iTunesEpisode.swift b/Sources/SyndiKit/Formats/Media/iTunes/iTunesEpisode.swift index 3e54ed2..ac8ff5e 100644 --- a/Sources/SyndiKit/Formats/Media/iTunes/iTunesEpisode.swift +++ b/Sources/SyndiKit/Formats/Media/iTunes/iTunesEpisode.swift @@ -1,2 +1,8 @@ -// swiftlint:disable:next type_name +/// A struct representing an Atom category. +/// A type alias for an iTunes episode. +/// +/// - Note: This type is an alias for ``XMLStringInt``. +/// +/// - SeeAlso: ``XMLStringInt`` +/// - SeeAlso: ``EntryCategory`` public typealias iTunesEpisode = XMLStringInt diff --git a/Sources/SyndiKit/Formats/Media/iTunes/iTunesImage.swift b/Sources/SyndiKit/Formats/Media/iTunes/iTunesImage.swift index a4fac9f..0f34f66 100644 --- a/Sources/SyndiKit/Formats/Media/iTunes/iTunesImage.swift +++ b/Sources/SyndiKit/Formats/Media/iTunes/iTunesImage.swift @@ -1,3 +1,4 @@ import Foundation -// swiftlint:disable:next type_name + +/// A type alias for iTunes image links. public typealias iTunesImage = Link diff --git a/Sources/SyndiKit/Formats/Media/iTunes/iTunesOwner.swift b/Sources/SyndiKit/Formats/Media/iTunes/iTunesOwner.swift index fa26208..3cfa2f3 100644 --- a/Sources/SyndiKit/Formats/Media/iTunes/iTunesOwner.swift +++ b/Sources/SyndiKit/Formats/Media/iTunes/iTunesOwner.swift @@ -1,10 +1,28 @@ -// swiftlint:disable:next type_name +/// A struct representing an Atom category. +/// A struct representing the owner of an iTunes account. +/// +/// - Note: This struct conforms to the ``Codable`` protocol. +/// +/// - Warning: Do not modify the ``CodingKeys`` enum. +/// +/// - SeeAlso: ``CodingKeys`` +/// +/// - Remark: The ``email`` property is optional. +/// +/// - Important: The ``name`` property is required. +/// +/// - Version: 1.0 +/// - SeeAlso: ``EntryCategory`` public struct iTunesOwner: Codable { - public let name: String - public let email: String? - - enum CodingKeys: String, CodingKey { + /// The coding keys used to encode and decode the struct. + internal enum CodingKeys: String, CodingKey { case name = "itunes:name" case email = "itunes:email" } + + /// The name of the iTunes owner. + public let name: String + + /// The email address of the iTunes owner. + public let email: String? } diff --git a/Sources/SyndiKit/Formats/OPML/OPML+Body.swift b/Sources/SyndiKit/Formats/OPML/OPML+Body.swift new file mode 100644 index 0000000..830d6d8 --- /dev/null +++ b/Sources/SyndiKit/Formats/OPML/OPML+Body.swift @@ -0,0 +1,12 @@ +import Foundation + +extension OPML { + public struct Body: Codable, Equatable { + // swiftlint:disable:next nesting + internal enum CodingKeys: String, CodingKey { + case outlines = "outline" + } + + public let outlines: [Outline] + } +} diff --git a/Sources/SyndiKit/Formats/OPML/OPMLHead.swift b/Sources/SyndiKit/Formats/OPML/OPML+Head.swift similarity index 84% rename from Sources/SyndiKit/Formats/OPML/OPMLHead.swift rename to Sources/SyndiKit/Formats/OPML/OPML+Head.swift index 7d62c8f..a2912b5 100644 --- a/Sources/SyndiKit/Formats/OPML/OPMLHead.swift +++ b/Sources/SyndiKit/Formats/OPML/OPML+Head.swift @@ -1,7 +1,7 @@ import Foundation -public extension OPML { - struct Head: Codable, Equatable { +extension OPML { + public struct Head: Codable, Equatable { public let title: String? public let dateCreated: String? public let dateModified: String? @@ -16,7 +16,8 @@ public extension OPML { public let windowBottom: Int? public let windowRight: Int? - enum CodingKeys: String, CodingKey { + // swiftlint:disable:next nesting + internal enum CodingKeys: String, CodingKey { case title case dateCreated case dateModified diff --git a/Sources/SyndiKit/Formats/OPML/OPMLOutline.swift b/Sources/SyndiKit/Formats/OPML/OPML+Outline.swift similarity index 82% rename from Sources/SyndiKit/Formats/OPML/OPMLOutline.swift rename to Sources/SyndiKit/Formats/OPML/OPML+Outline.swift index ec098d5..33f5434 100644 --- a/Sources/SyndiKit/Formats/OPML/OPMLOutline.swift +++ b/Sources/SyndiKit/Formats/OPML/OPML+Outline.swift @@ -1,24 +1,10 @@ import Foundation -public extension OPML { - struct Outline: Codable, Equatable { - public let text: String - public let title: String? - public let description: String? - public let type: OutlineType? - public let url: URL? - public let htmlUrl: URL? - public let xmlUrl: URL? - public let language: String? - public let created: String? - public let categories: ListString<String>? - public let isComment: Bool? - public let isBreakpoint: Bool? - public let version: String? - - public let outlines: [Outline]? +// swiftlint:disable nesting discouraged_optional_boolean - enum CodingKeys: String, CodingKey { +extension OPML { + public struct Outline: Codable, Equatable { + public enum CodingKeys: String, CodingKey { case text case title case description @@ -35,5 +21,21 @@ public extension OPML { case outlines = "outline" } + + public let text: String + public let title: String? + public let description: String? + public let type: OutlineType? + public let url: URL? + public let htmlUrl: URL? + public let xmlUrl: URL? + public let language: String? + public let created: String? + public let categories: ListString<String>? + public let isComment: Bool? + public let isBreakpoint: Bool? + public let version: String? + + public let outlines: [Outline]? } } diff --git a/Sources/SyndiKit/Formats/OPML/OPML.swift b/Sources/SyndiKit/Formats/OPML/OPML.swift index debc827..18403fa 100644 --- a/Sources/SyndiKit/Formats/OPML/OPML.swift +++ b/Sources/SyndiKit/Formats/OPML/OPML.swift @@ -1,14 +1,14 @@ import Foundation public struct OPML: Codable, Equatable { - public let version: String - - public let head: Head - public let body: Body - - enum CodingKeys: String, CodingKey { + internal enum CodingKeys: String, CodingKey { case version case head case body } + + public let version: String + + public let head: Head + public let body: Body } diff --git a/Sources/SyndiKit/Formats/OPML/OPMLBody.swift b/Sources/SyndiKit/Formats/OPML/OPMLBody.swift deleted file mode 100644 index 1e405e0..0000000 --- a/Sources/SyndiKit/Formats/OPML/OPMLBody.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -public extension OPML { - struct Body: Codable, Equatable { - public let outlines: [Outline] - - enum CodingKeys: String, CodingKey { - case outlines = "outline" - } - } -} diff --git a/Sources/SyndiKit/Formats/OPML/OPMLOutlineType.swift b/Sources/SyndiKit/Formats/OPML/OutlineType.swift similarity index 100% rename from Sources/SyndiKit/Formats/OPML/OPMLOutlineType.swift rename to Sources/SyndiKit/Formats/OPML/OutlineType.swift diff --git a/Sources/SyndiKit/Formats/SyndicationUpdate/SyndicationUpdate.swift b/Sources/SyndiKit/Formats/SyndicationUpdate/SyndicationUpdate.swift index 8df788f..6d80f45 100644 --- a/Sources/SyndiKit/Formats/SyndicationUpdate/SyndicationUpdate.swift +++ b/Sources/SyndiKit/Formats/SyndicationUpdate/SyndicationUpdate.swift @@ -26,7 +26,7 @@ public struct SyndicationUpdate: Codable, Equatable { /// to calculate the publishing schedule. public let base: Date? - init?( + internal init?( period: SyndicationUpdatePeriod? = nil, frequency: Int? = nil, base: Date? = nil diff --git a/Sources/SyndiKit/KeyedDecodingContainerProtocol.swift b/Sources/SyndiKit/KeyedDecodingContainerProtocol.swift index 20582b6..afaf1d2 100644 --- a/Sources/SyndiKit/KeyedDecodingContainerProtocol.swift +++ b/Sources/SyndiKit/KeyedDecodingContainerProtocol.swift @@ -1,7 +1,7 @@ import Foundation extension KeyedDecodingContainerProtocol { - func decodeDateIfPresentAndValid(forKey key: Key) throws -> Date? { + internal func decodeDateIfPresentAndValid(forKey key: Key) throws -> Date? { if let pubDateString = try decodeIfPresent(String.self, forKey: key), !pubDateString.isEmpty { diff --git a/Sources/SyndiKit/Substring.SubSequence.swift b/Sources/SyndiKit/Substring.SubSequence.swift index 38eafa7..dec93ef 100644 --- a/Sources/SyndiKit/Substring.SubSequence.swift +++ b/Sources/SyndiKit/Substring.SubSequence.swift @@ -1,18 +1,22 @@ import Foundation extension Substring.SubSequence { - func asDouble() -> Double? { + internal func asDouble() -> Double? { Double(self) } - func asInt() -> Int? { - guard let double = Double(self) else { return nil } + internal func asInt() -> Int? { + guard let double = Double(self) else { + return nil + } return Int(double) } - func asExactInt() -> Int? { - guard let double = Double(self) else { return nil } + internal func asExactInt() -> Int? { + guard let double = Double(self) else { + return nil + } return Int(exactly: double) } diff --git a/Sources/SyndiKit/SyndiKit.docc/SyndiKit.md b/Sources/SyndiKit/SyndiKit.docc/SyndiKit.md index e2c3281..8db5229 100644 --- a/Sources/SyndiKit/SyndiKit.docc/SyndiKit.md +++ b/Sources/SyndiKit/SyndiKit.docc/SyndiKit.md @@ -6,7 +6,7 @@ Swift Package for Decoding RSS Feeds. ![SyndiKit Logo](logo.png) -Built on top of [XMLCoder](https://github.com/CoreOffice/XMLCoder) and , **SyndiKit** provides models and utilities for decoding RSS feeds of various formats and extensions. +Built on top of [XMLCoder](https://github.com/CoreOffice/XMLCoder), **SyndiKit** provides models and utilities for decoding RSS feeds of various formats and extensions. ### Features @@ -251,6 +251,10 @@ Specific extension properties provided by WordPress. - ``WordPressElements`` - ``WordPressPost`` +- ``WPTag`` +- ``WPCategory`` +- ``WPPostMeta`` +- ``WordPressError`` ### YouTube Extensions diff --git a/Sources/SyndiKit/URL.swift b/Sources/SyndiKit/URL.swift index e19039e..fb278d9 100644 --- a/Sources/SyndiKit/URL.swift +++ b/Sources/SyndiKit/URL.swift @@ -1,7 +1,7 @@ import Foundation extension URL { - init?(strict string: String) { + internal init?(strict string: String) { guard string.starts(with: "http") else { return nil } diff --git a/Tests/SyndiKitTests/Content.Directories.swift b/Tests/SyndiKitTests/Content.Directories.swift index 24fd7eb..f40ea3c 100644 --- a/Tests/SyndiKitTests/Content.Directories.swift +++ b/Tests/SyndiKitTests/Content.Directories.swift @@ -1,8 +1,8 @@ import Foundation @testable import SyndiKit -internal extension Content { - enum Directories { +extension Content { + internal enum Directories { static let data = URL(fileURLWithPath: #file) .deletingLastPathComponent() .deletingLastPathComponent() diff --git a/Tests/SyndiKitTests/Extensions/FileManager.swift b/Tests/SyndiKitTests/Extensions/FileManager.swift index 7e4951a..9e5a351 100644 --- a/Tests/SyndiKitTests/Extensions/FileManager.swift +++ b/Tests/SyndiKitTests/Extensions/FileManager.swift @@ -1,7 +1,7 @@ import Foundation -internal extension FileManager { - func dataFromDirectory(at sourceURL: URL) throws -> [(String, Result<Data, Error>)] { +extension FileManager { + internal func dataFromDirectory(at sourceURL: URL) throws -> [(String, Result<Data, Error>)] { let urls = try contentsOfDirectory( at: sourceURL, includingPropertiesForKeys: nil, diff --git a/Tests/SyndiKitTests/Extensions/JSONFeed.swift b/Tests/SyndiKitTests/Extensions/JSONFeed.swift index cf44cda..e675421 100644 --- a/Tests/SyndiKitTests/Extensions/JSONFeed.swift +++ b/Tests/SyndiKitTests/Extensions/JSONFeed.swift @@ -1,8 +1,8 @@ import Foundation import SyndiKit -internal extension JSONFeed { - var homePageURLHttp: URL? { +extension JSONFeed { + internal var homePageURLHttp: URL? { var components = URLComponents(url: homePageUrl, resolvingAgainstBaseURL: false) components?.scheme = "http" return components?.url diff --git a/Tests/SyndiKitTests/Extensions/Sequence.swift b/Tests/SyndiKitTests/Extensions/Sequence.swift index 85950ba..c01ea43 100644 --- a/Tests/SyndiKitTests/Extensions/Sequence.swift +++ b/Tests/SyndiKitTests/Extensions/Sequence.swift @@ -1,5 +1,5 @@ -internal extension Sequence { - func mapPairResult<Success>( +extension Sequence { + internal func mapPairResult<Success>( _ transform: @escaping (Element) throws -> Success ) -> [(Element, Result<Success, Error>)] { map { element in @@ -7,7 +7,7 @@ internal extension Sequence { } } - func mapResult<Success>( + internal func mapResult<Success>( _ transform: @escaping (Element) throws -> Success ) -> [Result<Success, Error>] { map { element in @@ -15,7 +15,7 @@ internal extension Sequence { } } - func flatResultMapValue<SuccessKey, SuccessValue, NewSuccess>( + internal func flatResultMapValue<SuccessKey, SuccessValue, NewSuccess>( _ transform: @escaping (SuccessValue) throws -> NewSuccess ) -> [(SuccessKey, Result<NewSuccess, Error>)] where Element == (SuccessKey, Result<SuccessValue, Error>) { @@ -27,7 +27,7 @@ internal extension Sequence { } } - func flatResultMap<Success, NewSuccess>( + internal func flatResultMap<Success, NewSuccess>( _ transform: @escaping (Success) throws -> NewSuccess ) -> [Result<NewSuccess, Error>] where Element == Result<Success, Error> { diff --git a/Tests/SyndiKitTests/Extensions/SiteCollection.swift b/Tests/SyndiKitTests/Extensions/SiteCollection.swift index 2fde8e3..dee0918 100644 --- a/Tests/SyndiKitTests/Extensions/SiteCollection.swift +++ b/Tests/SyndiKitTests/Extensions/SiteCollection.swift @@ -1,8 +1,8 @@ import Foundation @testable import SyndiKit -internal extension SiteCollection { - init(contentsOf url: URL, using decoder: JSONDecoder = .init()) throws { +extension SiteCollection { + internal init(contentsOf url: URL, using decoder: JSONDecoder = .init()) throws { let data = try Data(contentsOf: url) self = try decoder.decode(SiteCollection.self, from: data) } diff --git a/Tests/SyndiKitTests/Extensions/String.swift b/Tests/SyndiKitTests/Extensions/String.swift index 6049af4..1391a9c 100644 --- a/Tests/SyndiKitTests/Extensions/String.swift +++ b/Tests/SyndiKitTests/Extensions/String.swift @@ -1,5 +1,5 @@ -internal extension String { - func trimAndNilIfEmpty() -> String? { +extension String { + internal func trimAndNilIfEmpty() -> String? { let text = trimmingCharacters(in: .whitespacesAndNewlines) return text.isEmpty ? nil : text } diff --git a/Tests/SyndiKitTests/RSSCoded.Durations.swift b/Tests/SyndiKitTests/RSSCoded.Durations.swift index 5dd1f0e..b4e99b3 100644 --- a/Tests/SyndiKitTests/RSSCoded.Durations.swift +++ b/Tests/SyndiKitTests/RSSCoded.Durations.swift @@ -1,7 +1,7 @@ import Foundation -internal extension SyndiKitTests { - static let durationSets: [String: [TimeInterval]] = [ +extension SyndiKitTests { + internal static let durationSets: [String: [TimeInterval]] = [ "empowerapps-show": [ 2_746, 3_500, diff --git a/Tests/SyndiKitTests/RSSCodedTests.swift b/Tests/SyndiKitTests/RSSCodedTests.swift index 56f9dcc..384a3e5 100644 --- a/Tests/SyndiKitTests/RSSCodedTests.swift +++ b/Tests/SyndiKitTests/RSSCodedTests.swift @@ -406,6 +406,27 @@ public final class SyndiKitTests: XCTestCase { try assertInvalidGeoData(from: invalidCoords) } + func testPodcastMissingLink() throws { + guard let feed = try? Content.xmlFeeds["wait-wait-dont-tell-me"]?.get() else { + XCTFail("Missing Podcast \(name)") + return + } + + guard let rss = feed as? RSSFeed else { + XCTFail("Wrong Type \(name)") + return + } + + guard rss.channel.items.count > 193 else { + XCTFail("Missing Item \(name)") + return + } + + let item = rss.channel.items[193] + + XCTAssertNil(item.link) + } + private func assertInvalidGeoData(from xmlStr: String) throws { guard let data = xmlStr.data(using: .utf8) else { XCTFail("Expected data out of \(xmlStr)") diff --git a/Tests/SyndiKitTests/WordpressTests.swift b/Tests/SyndiKitTests/WordpressTests.swift index 3682da0..7fe364f 100644 --- a/Tests/SyndiKitTests/WordpressTests.swift +++ b/Tests/SyndiKitTests/WordpressTests.swift @@ -143,7 +143,7 @@ final class WordpressTests: XCTestCase { XCTAssertEqual(post.parentID, wpPostParent) XCTAssertEqual(post.menuOrder, wpMenuOrder) XCTAssertEqual(post.isSticky, wpIsSticky != 0) - XCTAssertEqual(post.ID, wpPostID) + XCTAssertEqual(post.id, wpPostID) XCTAssertEqual(post.postDate, wpPostDate) XCTAssertEqual(post.modifiedDate, wpModifiedDate) XCTAssertEqual(post.name, wpPostName) @@ -229,7 +229,7 @@ final class WordpressTests: XCTestCase { XCTAssertEqual(post.parentID, wpPostParent) XCTAssertEqual(post.menuOrder, wpMenuOrder) XCTAssertEqual(post.isSticky, wpIsSticky != 0) - XCTAssertEqual(post.ID, wpPostID) + XCTAssertEqual(post.id, wpPostID) XCTAssertEqual(post.postDate, wpPostDate) XCTAssertEqual(post.modifiedDate, wpModifiedDate) XCTAssertEqual(post.name, wpPostName) @@ -312,7 +312,7 @@ final class WordpressTests: XCTestCase { XCTAssertEqual(post.parentID, wpPostParent) XCTAssertEqual(post.menuOrder, wpMenuOrder) XCTAssertEqual(post.isSticky, wpIsSticky != 0) - XCTAssertEqual(post.ID, wpPostID) + XCTAssertEqual(post.id, wpPostID) XCTAssertEqual(post.postDate, wpPostDate) XCTAssertEqual(post.modifiedDate, wpModifiedDate) XCTAssertEqual(post.name, wpPostName)