diff --git a/.gitignore b/.gitignore
index 660a296..3eb0e70 100644
--- a/.gitignore
+++ b/.gitignore
@@ -63,6 +63,4 @@ Carthage/Build
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
-fastlane/test_output
-
-Pods
+fastlane/test_output
\ No newline at end of file
diff --git a/Doughnut/AppDelegate.swift b/Doughnut/AppDelegate.swift
index 1bb09e2..da231af 100644
--- a/Doughnut/AppDelegate.swift
+++ b/Doughnut/AppDelegate.swift
@@ -1,10 +1,20 @@
-//
-// AppDelegate.swift
-// Doughnut
-//
-// Created by Chris Dyer on 22/09/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
import MASPreferences
diff --git a/Doughnut/DoughnutApp.swift b/Doughnut/DoughnutApp.swift
index 9f634b6..54a1655 100644
--- a/Doughnut/DoughnutApp.swift
+++ b/Doughnut/DoughnutApp.swift
@@ -1,10 +1,20 @@
-//
-// DoughnutApp.swift
-// Doughnut
-//
-// Created by Chris Dyer on 19/10/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
diff --git a/Doughnut/Library/DownloadManager.swift b/Doughnut/Library/DownloadManager.swift
index b2a86ab..4b84a8a 100644
--- a/Doughnut/Library/DownloadManager.swift
+++ b/Doughnut/Library/DownloadManager.swift
@@ -1,10 +1,20 @@
-//
-// Download.swift
-// Doughnut
-//
-// Created by Chris Dyer on 10/10/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Foundation
import AVFoundation
diff --git a/Doughnut/Library/Episode.swift b/Doughnut/Library/Episode.swift
index 9812520..5e0d2d0 100644
--- a/Doughnut/Library/Episode.swift
+++ b/Doughnut/Library/Episode.swift
@@ -1,10 +1,20 @@
-//
-// Episode.swift
-// Doughnut
-//
-// Created by Chris Dyer on 29/09/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Foundation
import AVFoundation
diff --git a/Doughnut/Library/Library.swift b/Doughnut/Library/Library.swift
index 49fd210..c6f4cca 100644
--- a/Doughnut/Library/Library.swift
+++ b/Doughnut/Library/Library.swift
@@ -1,10 +1,20 @@
-//
-// Library.swift
-// Doughnut
-//
-// Created by Chris Dyer on 27/09/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Foundation
import FeedKit
diff --git a/Doughnut/Library/MarkupGenerator.swift b/Doughnut/Library/MarkupGenerator.swift
index fde55cd..ccbfb0e 100644
--- a/Doughnut/Library/MarkupGenerator.swift
+++ b/Doughnut/Library/MarkupGenerator.swift
@@ -1,11 +1,21 @@
-//
-// MarkupGenerator.swift
-// Doughnut
-//
-// Created by Chris Dyer on 16/10/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
-
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
import Foundation
class MarkupGenerator {
diff --git a/Doughnut/Library/Migrations.swift b/Doughnut/Library/Migrations.swift
index 2b79e27..d76eecd 100644
--- a/Doughnut/Library/Migrations.swift
+++ b/Doughnut/Library/Migrations.swift
@@ -1,10 +1,20 @@
-//
-// Migrations.swift
-// Doughnut
-//
-// Created by Chris Dyer on 27/09/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Foundation
import GRDB
diff --git a/Doughnut/Library/Podcast.swift b/Doughnut/Library/Podcast.swift
index 445a32c..1fb3058 100644
--- a/Doughnut/Library/Podcast.swift
+++ b/Doughnut/Library/Podcast.swift
@@ -1,10 +1,20 @@
-//
-// Podcast.swift
-// Doughnut
-//
-// Created by Chris Dyer on 28/09/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Foundation
import GRDB
diff --git a/Doughnut/Library/Utils.swift b/Doughnut/Library/Utils.swift
index 60e184f..1be9949 100644
--- a/Doughnut/Library/Utils.swift
+++ b/Doughnut/Library/Utils.swift
@@ -1,10 +1,20 @@
-//
-// Utils.swift
-// Doughnut
-//
-// Created by Chris Dyer on 15/10/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Foundation
diff --git a/Doughnut/Player.swift b/Doughnut/Player.swift
index 208018d..0f04099 100644
--- a/Doughnut/Player.swift
+++ b/Doughnut/Player.swift
@@ -1,10 +1,20 @@
-//
-// Player.swift
-// Doughnut
-//
-// Created by Chris Dyer on 01/10/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
import AVFoundation
diff --git a/Doughnut/Preference/PrefLibraryViewController.swift b/Doughnut/Preference/PrefLibraryViewController.swift
index 193b346..1561b4d 100644
--- a/Doughnut/Preference/PrefLibraryViewController.swift
+++ b/Doughnut/Preference/PrefLibraryViewController.swift
@@ -1,10 +1,20 @@
-//
-// PrefGeneralViewController.swift
-// Doughnut
-//
-// Created by Chris Dyer on 16/10/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
import MASPreferences
diff --git a/Doughnut/Preference/Preference.swift b/Doughnut/Preference/Preference.swift
index 504db33..3495200 100644
--- a/Doughnut/Preference/Preference.swift
+++ b/Doughnut/Preference/Preference.swift
@@ -1,10 +1,20 @@
-//
-// Preference.swift
-// Doughnut
-//
-// Created by Chris Dyer on 27/09/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
diff --git a/Doughnut/View Controllers/DetailViewController.swift b/Doughnut/View Controllers/DetailViewController.swift
index 2a7409e..189bb4d 100644
--- a/Doughnut/View Controllers/DetailViewController.swift
+++ b/Doughnut/View Controllers/DetailViewController.swift
@@ -1,10 +1,20 @@
-//
-// DetailViewController.swift
-// Doughnut
-//
-// Created by Chris Dyer on 23/09/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
import WebKit
diff --git a/Doughnut/View Controllers/DownloadsViewController.swift b/Doughnut/View Controllers/DownloadsViewController.swift
index b479ae6..32cd30e 100644
--- a/Doughnut/View Controllers/DownloadsViewController.swift
+++ b/Doughnut/View Controllers/DownloadsViewController.swift
@@ -1,10 +1,20 @@
-//
-// DownloadsViewController.swift
-// Doughnut
-//
-// Created by Chris Dyer on 11/10/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
diff --git a/Doughnut/View Controllers/EditPodcastViewController.swift b/Doughnut/View Controllers/EditPodcastViewController.swift
index bc0edb5..f702beb 100644
--- a/Doughnut/View Controllers/EditPodcastViewController.swift
+++ b/Doughnut/View Controllers/EditPodcastViewController.swift
@@ -1,10 +1,20 @@
-//
-// EditPodcastViewController.swift
-// Doughnut
-//
-// Created by Chris Dyer on 30/11/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
diff --git a/Doughnut/View Controllers/EpisodeFilterViewController.swift b/Doughnut/View Controllers/EpisodeFilterViewController.swift
index ee9db70..67b7490 100644
--- a/Doughnut/View Controllers/EpisodeFilterViewController.swift
+++ b/Doughnut/View Controllers/EpisodeFilterViewController.swift
@@ -1,10 +1,20 @@
-//
-// EpisodeFilterViewController.swift
-// Doughnut
-//
-// Created by Chris Dyer on 30/09/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
diff --git a/Doughnut/View Controllers/EpisodeViewController.swift b/Doughnut/View Controllers/EpisodeViewController.swift
index 8148462..392d7d2 100644
--- a/Doughnut/View Controllers/EpisodeViewController.swift
+++ b/Doughnut/View Controllers/EpisodeViewController.swift
@@ -1,10 +1,20 @@
-//
-// EpisodeViewController.swift
-// Doughnut
-//
-// Created by Chris Dyer on 23/09/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
diff --git a/Doughnut/View Controllers/PodcastViewController.swift b/Doughnut/View Controllers/PodcastViewController.swift
index 15b2c1a..9ed0f53 100644
--- a/Doughnut/View Controllers/PodcastViewController.swift
+++ b/Doughnut/View Controllers/PodcastViewController.swift
@@ -1,10 +1,20 @@
-//
-// PodcastViewController.swift
-// Doughnut
-//
-// Created by Chris Dyer on 23/09/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
diff --git a/Doughnut/View Controllers/SubscribeViewController.swift b/Doughnut/View Controllers/SubscribeViewController.swift
index aaf6b6a..3b1796c 100644
--- a/Doughnut/View Controllers/SubscribeViewController.swift
+++ b/Doughnut/View Controllers/SubscribeViewController.swift
@@ -1,10 +1,20 @@
-//
-// SubscribeViewController.swift
-// Doughnut
-//
-// Created by Chris Dyer on 15/10/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
diff --git a/Doughnut/View Controllers/ViewController.swift b/Doughnut/View Controllers/ViewController.swift
index bc1962a..0d306bd 100644
--- a/Doughnut/View Controllers/ViewController.swift
+++ b/Doughnut/View Controllers/ViewController.swift
@@ -1,10 +1,20 @@
-//
-// ViewController.swift
-// Doughnut
-//
-// Created by Chris Dyer on 22/09/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
diff --git a/Doughnut/Views/DetailWebView.swift b/Doughnut/Views/DetailWebView.swift
index 4663a5f..99f6add 100644
--- a/Doughnut/Views/DetailWebView.swift
+++ b/Doughnut/Views/DetailWebView.swift
@@ -1,10 +1,20 @@
-//
-// DetailWebView.swift
-// Doughnut
-//
-// Created by Chris Dyer on 16/10/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
import WebKit
diff --git a/Doughnut/Views/DownloadCellView.swift b/Doughnut/Views/DownloadCellView.swift
index 8ad223d..d043e68 100644
--- a/Doughnut/Views/DownloadCellView.swift
+++ b/Doughnut/Views/DownloadCellView.swift
@@ -1,10 +1,20 @@
-//
-// DownloadCellView.swift
-// Doughnut
-//
-// Created by Chris Dyer on 11/10/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
diff --git a/Doughnut/Views/EpisodeCellView.swift b/Doughnut/Views/EpisodeCellView.swift
index 87e588c..8b7f11c 100644
--- a/Doughnut/Views/EpisodeCellView.swift
+++ b/Doughnut/Views/EpisodeCellView.swift
@@ -1,10 +1,20 @@
-//
-// EpisodeCellView.swift
-// Doughnut
-//
-// Created by Chris Dyer on 27/09/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
diff --git a/Doughnut/Views/PlayerView.swift b/Doughnut/Views/PlayerView.swift
index 1eb7383..c09d72f 100644
--- a/Doughnut/Views/PlayerView.swift
+++ b/Doughnut/Views/PlayerView.swift
@@ -1,10 +1,20 @@
-//
-// PlayerView.swift
-// Doughnut
-//
-// Created by Chris Dyer on 01/10/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
diff --git a/Doughnut/Views/PodcastCellView.swift b/Doughnut/Views/PodcastCellView.swift
index a99d028..4592070 100644
--- a/Doughnut/Views/PodcastCellView.swift
+++ b/Doughnut/Views/PodcastCellView.swift
@@ -1,10 +1,20 @@
-//
-// PodcastRowView.swift
-// Doughnut
-//
-// Created by Chris Dyer on 27/09/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
diff --git a/Doughnut/Views/SeekSlider.swift b/Doughnut/Views/SeekSlider.swift
index e3a0fef..e2d68fa 100644
--- a/Doughnut/Views/SeekSlider.swift
+++ b/Doughnut/Views/SeekSlider.swift
@@ -1,10 +1,20 @@
-//
-// SeekSlider.swift
-// Doughnut
-//
-// Created by Chris Dyer on 01/10/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
diff --git a/Doughnut/WindowController.swift b/Doughnut/WindowController.swift
index 1081ad4..4a97b7c 100644
--- a/Doughnut/WindowController.swift
+++ b/Doughnut/WindowController.swift
@@ -1,10 +1,20 @@
-//
-// WindowController.swift
-// Doughnut
-//
-// Created by Chris Dyer on 23/09/2017.
-// Copyright © 2017 Chris Dyer. All rights reserved.
-//
+/*
+ * Doughnut Podcast Client
+ * Copyright (C) 2017 Chris Dyer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import Cocoa
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..18c6e1b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {one line to give the program's name and a brief idea of what it does.}
+ Copyright (C) {year} {name of author}
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ {project} Copyright (C) {year} {fullname}
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
\ No newline at end of file
diff --git a/Pods/FeedKit/LICENSE b/Pods/FeedKit/LICENSE
new file mode 100644
index 0000000..365f1c2
--- /dev/null
+++ b/Pods/FeedKit/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2017 Nuno Manuel Dias
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Pods/FeedKit/README.md b/Pods/FeedKit/README.md
new file mode 100755
index 0000000..4fb4b52
--- /dev/null
+++ b/Pods/FeedKit/README.md
@@ -0,0 +1,233 @@
+
+
+[](https://travis-ci.org/nmdias/FeedKit)
+[](https://cocoapods.org/pods/FeedKit)
+[](https://github.com/Carthage/Carthage)
+[](https://swift.org)
+[](https://github.com/nmdias/DefaultsKit/releases)
+
+## Features
+
+- [x] [Atom](https://tools.ietf.org/html/rfc4287)
+- [x] RSS [0.90](http://www.rssboard.org/rss-0-9-0), [0.91](http://www.rssboard.org/rss-0-9-1), [1.00](http://web.resource.org/rss/1.0/spec), [2.00](http://cyber.law.harvard.edu/rss/rss.html)
+- [x] [JSON](https://jsonfeed.org/version/1)
+- [x] Namespaces
+ - [x] [Dublin Core](http://web.resource.org/rss/1.0/modules/dc/)
+ - [x] [Syndication](http://web.resource.org/rss/1.0/modules/syndication/)
+ - [x] [Content](http://web.resource.org/rss/1.0/modules/content/)
+ - [x] [Media RSS](http://www.rssboard.org/media-rss)
+ - [x] [iTunes Podcasting Tags](https://help.apple.com/itc/podcasts_connect/#/itcb54353390)
+- [x] [Documentation](http://cocoadocs.org/docsets/FeedKit)
+- [x] Unit Test Coverage
+
+## Requirements
+
+
+
+
+
+
+
+
+Installation >> [`instructions`](https://github.com/nmdias/FeedKit/blob/master/INSTALL.md) <<
+
+## Usage
+
+Build a URL pointing to an RSS, Atom or JSON Feed.
+```swift
+let feedURL = URL(string: "http://images.apple.com/main/rss/hotnews/hotnews.rss")!
+```
+
+Get an instance of `FeedParser`
+```swift
+let parser = FeedParser(URL: feedURL) // or FeedParser(data: data)
+```
+
+Then call `parse` or `parseAsync` to start parsing the feed...
+
+> A **common scenario** in UI environments would be parsing a feed **asynchronously** from a user initiated action, such as the touch of a button. e.g.
+
+```swift
+// Parse asynchronously, not to block the UI.
+parser.parseAsync(queue: DispatchQueue.global(qos: .userInitiated)) { (result) in
+ // Do your thing, then back to the Main thread
+ DispatchQueue.main.async {
+ // ..and update the UI
+ }
+}
+```
+
+Remember, you are responsible to manually bring the result closure to whichever queue is apropriate. Usually to the Main thread, for UI apps, by calling `DispatchQueue.main.async` .
+
+Alternatively, you can also parse synchronously.
+
+```swift
+let result = parser.parse()
+```
+
+## Parse Result
+
+Whichever the case, if parsing succeeds you should now have a `Strongly Typed Model` of an `RSS`, `Atom` or `JSON Feed`.
+```swift
+switch result {
+case let .atom(feed): // Atom Syndication Format Feed Model
+case let .rss(feed): // Really Simple Syndication Feed Model
+case let .json(feed): // JSON Feed Model
+case let .failure(error):
+}
+```
+
+
+#### Parse Success
+You can check if a Feed was `successfully` parsed or not.
+```swift
+result.isSuccess // If parsing was a success
+result.isFailure // If parsing failed
+result.error // An error, if any
+```
+
+## Model Preview
+Safely bind a feed of your choosing:
+> You may find the example bellow useful, if you're dealing with only a single type of feed.
+```swift
+guard let feed = result.rssFeed, result.isSuccess else {
+ print(result.error)
+ return
+}
+```
+Then go through it's properties:
+
+> The RSS and Atom feed Models are rather extensive throughout the supported namespaces. These are just a preview of what's available.
+
+#### RSS
+
+```swift
+feed.title
+feed.link
+feed.description
+feed.language
+feed.copyright
+feed.managingEditor
+feed.webMaster
+feed.pubDate
+feed.lastBuildDate
+feed.categories
+feed.generator
+feed.docs
+feed.cloud
+feed.rating
+feed.ttl
+feed.image
+feed.textInput
+feed.skipHours
+feed.skipDays
+//...
+feed.dublinCore
+feed.syndication
+feed.iTunes
+// ...
+
+let item = feed.items?.first
+
+item?.title
+item?.link
+item?.description
+item?.author
+item?.categories
+item?.comments
+item?.enclosure
+item?.guid
+item?.pubDate
+item?.source
+//...
+item?.dublinCore
+item?.content
+item?.iTunes
+item?.media
+// ...
+```
+
+> Refer to the [`documentation`](http://cocoadocs.org/docsets/FeedKit) for the complete model properties and descriptions
+
+#### Atom
+
+```swift
+feed.title
+feed.subtitle
+feed.links
+feed.updated
+feed.authors
+feed.contributors
+feed.id
+feed.generator
+feed.icon
+feed.logo
+feed.rights
+// ...
+
+let entry = feed.entries?.first
+
+entry?.title
+entry?.summary
+entry?.authors
+entry?.contributors
+entry?.links
+entry?.updated
+entry?.categories
+entry?.id
+entry?.content
+entry?.published
+entry?.source
+entry?.rights
+// ...
+```
+
+> Refer to the [`documentation`](http://cocoadocs.org/docsets/FeedKit) for the complete model properties and descriptions
+
+#### JSON
+
+```swift
+feed.version
+feed.title
+feed.homePageURL
+feed.feedUrl
+feed.description
+feed.userComment
+feed.nextUrl
+feed.icon
+feed.favicon
+feed.author
+feed.expired
+feed.hubs
+feed.extensions
+// ...
+
+let item = feed.items?.first
+
+item?.id
+item?.url
+item?.externalUrl
+item?.title
+item?.contentText
+item?.contentHtml
+item?.summary
+item?.image
+item?.bannerImage
+item?.datePublished
+item?.dateModified
+item?.author
+item?.url
+item?.tags
+item?.attachments
+item?.extensions
+// ...
+```
+
+> Refer to the [`documentation`](http://cocoadocs.org/docsets/FeedKit) for the complete model properties and descriptions
+
+## License
+
+FeedKit is released under the MIT license. See [LICENSE](https://github.com/nmdias/FeedKit/blob/master/LICENSE) for details.
+
+
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Dates/DateSpec.swift b/Pods/FeedKit/Sources/FeedKit/Dates/DateSpec.swift
new file mode 100644
index 0000000..1213585
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Dates/DateSpec.swift
@@ -0,0 +1,39 @@
+//
+// DateSpec.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Date specifications
+///
+/// - rfc822: The `Standard for the format of arpa internet text messages`.
+/// See https://www.ietf.org/rfc/rfc0822.txt
+/// - rfc3999: The `Date and Time on the Internet: Timestamps`.
+/// See https://www.ietf.org/rfc/rfc3339.txt
+/// - iso8601: The `W3CDTF` date time format specification
+/// See http://www.w3.org/TR/NOTE-datetime
+enum DateSpec {
+ case rfc822
+ case rfc3999
+ case iso8601
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Dates/ISO8601DateFormatter.swift b/Pods/FeedKit/Sources/FeedKit/Dates/ISO8601DateFormatter.swift
new file mode 100644
index 0000000..546a9bd
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Dates/ISO8601DateFormatter.swift
@@ -0,0 +1,58 @@
+//
+// ISO8601DateFormatter.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Converts date and time textual representations within the ISO8601
+/// date specification into `Date` objects
+class ISO8601DateFormatter: DateFormatter {
+
+ let dateFormats = [
+ "yyyy-mm-dd'T'hh:mm",
+ "yyyy-MM-dd'T'HH:mm:ssZZZZZ",
+ "yyyy-MM-dd'T'HH:mm:ss.SSZZZZZ",
+ "yyyy-MM-dd'T'HH:mmSSZZZZZ"
+ ]
+
+ override init() {
+ super.init()
+ self.timeZone = TimeZone(secondsFromGMT: 0)
+ self.locale = Locale(identifier: "en_US_POSIX")
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) not supported")
+ }
+
+ override func date(from string: String) -> Date? {
+ for dateFormat in self.dateFormats {
+ self.dateFormat = dateFormat
+ if let date = super.date(from: string) {
+ return date
+ }
+ }
+ return nil
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Dates/RFC3339DateFormatter.swift b/Pods/FeedKit/Sources/FeedKit/Dates/RFC3339DateFormatter.swift
new file mode 100644
index 0000000..d1cac9c
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Dates/RFC3339DateFormatter.swift
@@ -0,0 +1,57 @@
+//
+// RFC3339DateFormatter.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Converts date and time textual representations within the RFC3339
+/// date specification into `Date` objects
+class RFC3339DateFormatter: DateFormatter {
+
+ let dateFormats = [
+ "yyyy-MM-dd'T'HH:mm:ssZZZZZ",
+ "yyyy-MM-dd'T'HH:mm:ss.SSZZZZZ",
+ "yyyy-MM-dd'T'HH:mm:ss-SS:ZZ"
+ ]
+
+ override init() {
+ super.init()
+ self.timeZone = TimeZone(secondsFromGMT: 0)
+ self.locale = Locale(identifier: "en_US_POSIX")
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) not supported")
+ }
+
+ override func date(from string: String) -> Date? {
+ for dateFormat in self.dateFormats {
+ self.dateFormat = dateFormat
+ if let date = super.date(from: string) {
+ return date
+ }
+ }
+ return nil
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Dates/RFC822DateFormatter.swift b/Pods/FeedKit/Sources/FeedKit/Dates/RFC822DateFormatter.swift
new file mode 100644
index 0000000..77e0f06
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Dates/RFC822DateFormatter.swift
@@ -0,0 +1,57 @@
+//
+// RFC822DateFormatter.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Converts date and time textual representations within the RFC822
+/// date specification into `Date` objects
+class RFC822DateFormatter: DateFormatter {
+
+ let dateFormats = [
+ "EEE, d MMM yyyy HH:mm:ss zzz",
+ "EEE, d MMM yyyy HH:mm zzz",
+ "d MMM yyyy HH:mm:ss Z"
+ ]
+
+ override init() {
+ super.init()
+ self.timeZone = TimeZone(secondsFromGMT: 0)
+ self.locale = Locale(identifier: "en_US_POSIX")
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) not supported")
+ }
+
+ override func date(from string: String) -> Date? {
+ for dateFormat in self.dateFormats {
+ self.dateFormat = dateFormat
+ if let date = super.date(from: string) {
+ return date
+ }
+ }
+ return nil
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Extensions/Array + Equatable.swift b/Pods/FeedKit/Sources/FeedKit/Extensions/Array + Equatable.swift
new file mode 100644
index 0000000..38e1396
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Extensions/Array + Equatable.swift
@@ -0,0 +1,42 @@
+//
+// Array + Equatable.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Optional arrays equatable
+///
+/// - Parameters:
+/// - lhs: The left-hand side
+/// - rhs: The right-hand side
+/// - Returns: A boolean value
+public func ==(lhs: [T]?, rhs: [T]?) -> Bool {
+ switch (lhs,rhs) {
+ case (.some(let lhs), .some(let rhs)):
+ return lhs == rhs
+ case (.none, .none):
+ return true
+ default:
+ return false
+ }
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Extensions/Data + toUtf8.swift b/Pods/FeedKit/Sources/FeedKit/Extensions/Data + toUtf8.swift
new file mode 100644
index 0000000..a0662c0
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Extensions/Data + toUtf8.swift
@@ -0,0 +1,38 @@
+//
+// String + toUtf8.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+extension Data {
+
+ /// Detect encoding and convert data to UTF-8
+ func toUtf8() -> Data? {
+ var convertedString: NSString?
+ let encoding = NSString.stringEncoding(for: self, encodingOptions: nil, convertedString: &convertedString, usedLossyConversion: nil)
+
+ guard let str = NSString(data: self, encoding: encoding) as String? else { return nil }
+ return str.data(using: .utf8)
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Extensions/String + toBool.swift b/Pods/FeedKit/Sources/FeedKit/Extensions/String + toBool.swift
new file mode 100644
index 0000000..13b0dce
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Extensions/String + toBool.swift
@@ -0,0 +1,39 @@
+//
+// String + toBool.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+extension String {
+
+ /// Convert a string representation of a logical value to it's `Bool`.
+ /// equivalent
+ func toBool() -> Bool? {
+ switch self {
+ case "True", "true", "Yes", "yes", "1": return true
+ case "False", "false", "No", "no", "0": return false
+ default: return nil
+ }
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Extensions/String + toDate.swift b/Pods/FeedKit/Sources/FeedKit/Extensions/String + toDate.swift
new file mode 100644
index 0000000..1270da6
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Extensions/String + toDate.swift
@@ -0,0 +1,42 @@
+//
+// String + toDate.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+extension String {
+
+ /// Attempts to convert the textual representation of a date with
+ /// the specified `DateSpec` to an `Date` object.
+ ///
+ /// - Parameter spec: The `DateSpec` to interpert the string.
+ /// - Returns: A `Date` object, or nil if the conversion failed.
+ func toDate(from spec: DateSpec) -> Date? {
+ switch spec {
+ case .rfc822: return RFC822DateFormatter().date(from: self)
+ case .rfc3999: return RFC3339DateFormatter().date(from: self)
+ case .iso8601: return ISO8601DateFormatter().date(from: self)
+ }
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Extensions/String + toDuration.swift b/Pods/FeedKit/Sources/FeedKit/Extensions/String + toDuration.swift
new file mode 100644
index 0000000..b22bbc9
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Extensions/String + toDuration.swift
@@ -0,0 +1,54 @@
+//
+// String + toDuration.swift
+//
+// Copyright (c) 2017 Ben Murphy
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+extension String {
+
+ /// Convert the string representation of a time duration to a Time Interval.
+ ///
+ /// - Returns: A TimeInterval.
+ func toDuration() -> TimeInterval? {
+ let comps = self.components(separatedBy: ":")
+
+ guard
+ !comps.contains(where: { Int($0) == nil }),
+ !comps.contains(where: { Int($0)! < 0 })
+ else { return nil }
+
+ return comps
+ .reversed()
+ .enumerated()
+ .map { i, e in
+ (Double(e) ?? 0)
+ *
+ pow(Double(60), Double(i))
+ }
+ .reduce(0, +)
+
+ }
+
+}
+
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeed + mapAttributes.swift b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeed + mapAttributes.swift
new file mode 100644
index 0000000..3b5bec6
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeed + mapAttributes.swift
@@ -0,0 +1,354 @@
+//
+// AtomFeed + mapAttributes.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+extension AtomFeed {
+
+ /// Maps the attributes of the specified dictionary for a given `AtomPath`
+ /// to the `AtomFeed` model
+ ///
+ /// - Parameters:
+ /// - attributes: The attribute dictionary to map to the model.
+ /// - path: The path of feed's element.
+ func map(_ attributes: [String : String], for path: AtomPath) {
+
+ switch path {
+
+ case .feedSubtitle:
+
+ if self.subtitle == nil {
+ self.subtitle = AtomFeedSubtitle(attributes: attributes)
+ }
+
+ case .feedLink:
+
+ if self.links == nil {
+ self.links = []
+ }
+
+ self.links?.append(AtomFeedLink(attributes: attributes))
+
+ case .feedCategory:
+
+ if self.categories == nil {
+ self.categories = []
+ }
+
+ self.categories?.append(AtomFeedCategory(attributes: attributes))
+
+ case .feedAuthor:
+
+ if self.authors == nil {
+ self.authors = []
+ }
+
+ self.authors?.append(AtomFeedAuthor())
+
+ case .feedContributor:
+
+ if self.contributors == nil {
+ self.contributors = []
+ }
+
+ self.contributors?.append(AtomFeedContributor())
+
+ case .feedGenerator:
+
+ if self.generator == nil {
+ self.generator = AtomFeedGenerator(attributes: attributes)
+ }
+
+ case .feedEntry:
+
+ if self.entries == nil {
+ self.entries = []
+ }
+
+ self.entries?.append(AtomFeedEntry())
+
+ case .feedEntrySummary:
+
+ if self.entries?.last?.summary == nil {
+ self.entries?.last?.summary = AtomFeedEntrySummary(attributes: attributes)
+ }
+
+ case .feedEntryAuthor:
+
+ if self.entries?.last?.authors == nil {
+ self.entries?.last?.authors = []
+ }
+
+ self.entries?.last?.authors?.append(AtomFeedEntryAuthor())
+
+ case .feedEntryContributor:
+
+ if self.entries?.last?.contributors == nil {
+ self.entries?.last?.contributors = []
+ }
+
+ self.entries?.last?.contributors?.append(AtomFeedEntryContributor())
+
+ case .feedEntryLink:
+
+ if self.entries?.last?.links == nil {
+ self.entries?.last?.links = []
+ }
+
+ self.entries?.last?.links?.append(AtomFeedEntryLink(attributes: attributes))
+
+ case .feedEntryCategory:
+
+ if self.entries?.last?.categories == nil {
+ self.entries?.last?.categories = []
+ }
+
+ self.entries?.last?.categories?.append(AtomFeedEntryCategory(attributes: attributes))
+
+ case .feedEntryContent:
+
+ if self.entries?.last?.content == nil {
+ self.entries?.last?.content = AtomFeedEntryContent(attributes: attributes)
+ }
+
+ case .feedEntrySource:
+
+ if self.entries?.last?.source == nil {
+ self.entries?.last?.source = AtomFeedEntrySource()
+ }
+
+ // MARK: Media
+
+ case
+ .feedEntryMediaThumbnail,
+ .feedEntryMediaContent,
+ .feedEntryMediaCommunity,
+ .feedEntryMediaCommunityMediaStarRating,
+ .feedEntryMediaCommunityMediaStatistics,
+ .feedEntryMediaCommunityMediaTags,
+ .feedEntryMediaComments,
+ .feedEntryMediaCommentsMediaComment,
+ .feedEntryMediaEmbed,
+ .feedEntryMediaEmbedMediaParam,
+ .feedEntryMediaResponses,
+ .feedEntryMediaResponsesMediaResponse,
+ .feedEntryMediaBackLinks,
+ .feedEntryMediaBackLinksBackLink,
+ .feedEntryMediaStatus,
+ .feedEntryMediaPrice,
+ .feedEntryMediaLicense,
+ .feedEntryMediaSubTitle,
+ .feedEntryMediaPeerLink,
+ .feedEntryMediaLocation,
+ .feedEntryMediaLocationPosition,
+ .feedEntryMediaRestriction,
+ .feedEntryMediaScenes,
+ .feedEntryMediaScenesMediaScene,
+ .feedEntryMediaGroup,
+ .feedEntryMediaGroupMediaCategory,
+ .feedEntryMediaGroupMediaCredit,
+ .feedEntryMediaGroupMediaRating,
+ .feedEntryMediaGroupMediaContent:
+
+ if self.entries?.last?.media == nil {
+ self.entries?.last?.media = MediaNamespace()
+ }
+
+ switch path {
+
+ case .feedEntryMediaThumbnail:
+
+ if self.entries?.last?.media?.mediaThumbnails == nil {
+ self.entries?.last?.media?.mediaThumbnails = []
+ }
+
+ self.entries?.last?.media?.mediaThumbnails?.append(MediaThumbnail(attributes: attributes))
+
+ case .feedEntryMediaContent:
+
+ if self.entries?.last?.media?.mediaContents == nil {
+ self.entries?.last?.media?.mediaContents = []
+ }
+
+ self.entries?.last?.media?.mediaContents?.append(MediaContent(attributes: attributes))
+
+ case .feedEntryMediaCommunity:
+
+ if self.entries?.last?.media?.mediaCommunity == nil {
+ self.entries?.last?.media?.mediaCommunity = MediaCommunity()
+ }
+
+ case .feedEntryMediaCommunityMediaStarRating:
+
+ if self.entries?.last?.media?.mediaCommunity?.mediaStarRating == nil {
+ self.entries?.last?.media?.mediaCommunity?.mediaStarRating = MediaStarRating(attributes: attributes)
+ }
+
+ case .feedEntryMediaCommunityMediaStatistics:
+
+ if self.entries?.last?.media?.mediaCommunity?.mediaStatistics == nil {
+ self.entries?.last?.media?.mediaCommunity?.mediaStatistics = MediaStatistics(attributes: attributes)
+ }
+
+ case .feedEntryMediaCommunityMediaTags:
+
+ if self.entries?.last?.media?.mediaCommunity?.mediaTags == nil {
+ self.entries?.last?.media?.mediaCommunity?.mediaTags = []
+ }
+
+ case .feedEntryMediaComments:
+
+ if self.entries?.last?.media?.mediaComments == nil {
+ self.entries?.last?.media?.mediaComments = []
+ }
+
+ case .feedEntryMediaEmbed:
+
+ if self.entries?.last?.media?.mediaEmbed == nil {
+ self.entries?.last?.media?.mediaEmbed = MediaEmbed(attributes: attributes)
+ }
+
+ case .feedEntryMediaEmbedMediaParam:
+
+ if self.entries?.last?.media?.mediaEmbed?.mediaParams == nil {
+ self.entries?.last?.media?.mediaEmbed?.mediaParams = []
+ }
+
+ self.entries?.last?.media?.mediaEmbed?.mediaParams?.append(MediaParam(attributes: attributes))
+
+ case .feedEntryMediaResponses:
+
+ if self.entries?.last?.media?.mediaResponses == nil {
+ self.entries?.last?.media?.mediaResponses = []
+ }
+
+ case .feedEntryMediaBackLinks:
+
+ if self.entries?.last?.media?.mediaBackLinks == nil {
+ self.entries?.last?.media?.mediaBackLinks = []
+ }
+
+ case .feedEntryMediaStatus:
+
+ if self.entries?.last?.media?.mediaStatus == nil {
+ self.entries?.last?.media?.mediaStatus = MediaStatus(attributes: attributes)
+ }
+
+ case .feedEntryMediaPrice:
+
+ if self.entries?.last?.media?.mediaPrices == nil {
+ self.entries?.last?.media?.mediaPrices = []
+ }
+
+ self.entries?.last?.media?.mediaPrices?.append(MediaPrice(attributes: attributes))
+
+ case .feedEntryMediaLicense:
+
+ if self.entries?.last?.media?.mediaLicense == nil {
+ self.entries?.last?.media?.mediaLicense = MediaLicence(attributes: attributes)
+ }
+
+ case .feedEntryMediaSubTitle:
+
+ if self.entries?.last?.media?.mediaSubTitle == nil {
+ self.entries?.last?.media?.mediaSubTitle = MediaSubTitle(attributes: attributes)
+ }
+
+ case .feedEntryMediaPeerLink:
+
+ if self.entries?.last?.media?.mediaPeerLink == nil {
+ self.entries?.last?.media?.mediaPeerLink = MediaPeerLink(attributes: attributes)
+ }
+
+ case .feedEntryMediaLocation:
+
+ if self.entries?.last?.media?.mediaLocation == nil {
+ self.entries?.last?.media?.mediaLocation = MediaLocation(attributes: attributes)
+ }
+
+ case .feedEntryMediaRestriction:
+
+ if self.entries?.last?.media?.mediaRestriction == nil {
+ self.entries?.last?.media?.mediaRestriction = MediaRestriction(attributes: attributes)
+ }
+
+ case .feedEntryMediaScenes:
+
+ if self.entries?.last?.media?.mediaScenes == nil {
+ self.entries?.last?.media?.mediaScenes = []
+ }
+
+ case .feedEntryMediaScenesMediaScene:
+
+ if self.entries?.last?.media?.mediaScenes == nil {
+ self.entries?.last?.media?.mediaScenes = []
+ }
+
+ self.entries?.last?.media?.mediaScenes?.append(MediaScene())
+
+ case .feedEntryMediaGroup:
+
+ if self.entries?.last?.media?.mediaGroup == nil {
+ self.entries?.last?.media?.mediaGroup = MediaGroup()
+ }
+
+ case .feedEntryMediaGroupMediaCategory:
+
+ if self.entries?.last?.media?.mediaGroup?.mediaCategory == nil {
+ self.entries?.last?.media?.mediaGroup?.mediaCategory = MediaCategory(attributes: attributes)
+ }
+
+ case .feedEntryMediaGroupMediaCredit:
+
+ if self.entries?.last?.media?.mediaGroup?.mediaCredits == nil {
+ self.entries?.last?.media?.mediaGroup?.mediaCredits = []
+ }
+
+ self.entries?.last?.media?.mediaGroup?.mediaCredits?.append(MediaCredit(attributes: attributes))
+
+ case .feedEntryMediaGroupMediaRating:
+
+ if self.entries?.last?.media?.mediaGroup?.mediaRating == nil {
+ self.entries?.last?.media?.mediaGroup?.mediaRating = MediaRating(attributes: attributes)
+ }
+
+ case .feedEntryMediaGroupMediaContent:
+
+ if self.entries?.last?.media?.mediaGroup?.mediaContents == nil {
+ self.entries?.last?.media?.mediaGroup?.mediaContents = []
+ }
+
+ self.entries?.last?.media?.mediaGroup?.mediaContents?.append(MediaContent(attributes: attributes))
+
+ default: break
+
+ }
+
+ default: break
+
+ }
+
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeed + mapCharacters.swift b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeed + mapCharacters.swift
new file mode 100644
index 0000000..ec0e024
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeed + mapCharacters.swift
@@ -0,0 +1,86 @@
+//
+// AtomFeed + mapCharacters.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+extension AtomFeed {
+
+ /// Maps the characters in the specified string to the `AtomFeed` model.
+ ///
+ /// - Parameters:
+ /// - string: The string to map to the model.
+ /// - path: The path of feed's element.
+ func map(_ string: String, for path: AtomPath) {
+ switch path {
+ case .feedTitle: self.title = self.title?.appending(string) ?? string
+ case .feedSubtitle: self.subtitle?.value = self.subtitle?.value?.appending(string) ?? string
+ case .feedUpdated: self.updated = string.toDate(from: .rfc3999)
+ case .feedAuthorName: self.authors?.last?.name = self.authors?.last?.name?.appending(string) ?? string
+ case .feedAuthorEmail: self.authors?.last?.email = self.authors?.last?.email?.appending(string) ?? string
+ case .feedAuthorUri: self.authors?.last?.uri = self.authors?.last?.uri?.appending(string) ?? string
+ case .feedContributorName: self.contributors?.last?.name = self.contributors?.last?.name?.appending(string) ?? string
+ case .feedContributorEmail: self.contributors?.last?.email = self.contributors?.last?.email?.appending(string) ?? string
+ case .feedContributorUri: self.contributors?.last?.uri = self.contributors?.last?.uri?.appending(string) ?? string
+ case .feedID: self.id = self.id?.appending(string) ?? string
+ case .feedGenerator: self.generator?.value = self.generator?.value?.appending(string) ?? string
+ case .feedIcon: self.icon = self.icon?.appending(string) ?? string
+ case .feedLogo: self.logo = self.logo?.appending(string) ?? string
+ case .feedRights: self.rights = self.rights?.appending(string) ?? string
+ case .feedEntryTitle: self.entries?.last?.title = self.entries?.last?.title?.appending(string) ?? string
+ case .feedEntrySummary: self.entries?.last?.summary?.value = self.entries?.last?.summary?.value?.appending(string) ?? string
+ case .feedEntryUpdated: self.entries?.last?.updated = string.toDate(from: .rfc3999)
+ case .feedEntryID: self.entries?.last?.id = self.entries?.last?.id?.appending(string) ?? string
+ case .feedEntryContent: self.entries?.last?.content?.value = self.entries?.last?.content?.value?.appending(string) ?? string
+ case .feedEntryPublished: self.entries?.last?.published = string.toDate(from: .rfc3999)
+ case .feedEntrySourceID: self.entries?.last?.source?.id = self.entries?.last?.source?.id?.appending(string) ?? string
+ case .feedEntrySourceTitle: self.entries?.last?.source?.title = self.entries?.last?.source?.title?.appending(string) ?? string
+ case .feedEntrySourceUpdated: self.entries?.last?.source?.updated = string.toDate(from: .rfc3999)
+ case .feedEntryRights: self.entries?.last?.rights = self.entries?.last?.rights?.appending(string) ?? string
+ case .feedEntryAuthorName: self.entries?.last?.authors?.last?.name = self.entries?.last?.authors?.last?.name?.appending(string) ?? string
+ case .feedEntryAuthorEmail: self.entries?.last?.authors?.last?.email = self.entries?.last?.authors?.last?.email?.appending(string) ?? string
+ case .feedEntryAuthorUri: self.entries?.last?.authors?.last?.uri = self.entries?.last?.authors?.last?.uri?.appending(string) ?? string
+ case .feedEntryContributorName: self.entries?.last?.contributors?.last?.name = self.entries?.last?.contributors?.last?.name?.appending(string) ?? string
+ case .feedEntryContributorEmail: self.entries?.last?.contributors?.last?.email = self.entries?.last?.contributors?.last?.email?.appending(string) ?? string
+ case .feedEntryContributorUri: self.entries?.last?.contributors?.last?.uri = self.entries?.last?.contributors?.last?.uri?.appending(string) ?? string
+ case .feedEntryMediaThumbnail: self.entries?.last?.media?.mediaThumbnails?.last?.value = self.entries?.last?.media?.mediaThumbnails?.last?.value?.appending(string) ?? string
+ case .feedEntryMediaLicense: self.entries?.last?.media?.mediaLicense?.value = self.entries?.last?.media?.mediaLicense?.value?.appending(string) ?? string
+ case .feedEntryMediaRestriction: self.entries?.last?.media?.mediaRestriction?.value = self.entries?.last?.media?.mediaRestriction?.value?.appending(string) ?? string
+ case .feedEntryMediaCommunityMediaTags: self.entries?.last?.media?.mediaCommunity?.mediaTags = MediaTag.tagsFrom(string: string)
+ case .feedEntryMediaCommentsMediaComment: self.entries?.last?.media?.mediaComments?.append(string)
+ case .feedEntryMediaEmbedMediaParam: self.entries?.last?.media?.mediaEmbed?.mediaParams?.last?.value = self.entries?.last?.media?.mediaEmbed?.mediaParams?.last?.value?.appending(string) ?? string
+ case .feedEntryMediaGroupMediaCredit: self.entries?.last?.media?.mediaGroup?.mediaCredits?.last?.value = self.entries?.last?.media?.mediaGroup?.mediaCredits?.last?.value?.appending(string) ?? string
+ case .feedEntryMediaGroupMediaCategory: self.entries?.last?.media?.mediaGroup?.mediaCategory?.value = self.entries?.last?.media?.mediaGroup?.mediaCategory?.value?.appending(string) ?? string
+ case .feedEntryMediaGroupMediaRating: self.entries?.last?.media?.mediaGroup?.mediaRating?.value = self.entries?.last?.media?.mediaGroup?.mediaRating?.value?.appending(string) ?? string
+ case .feedEntryMediaResponsesMediaResponse: self.entries?.last?.media?.mediaResponses?.append(string)
+ case .feedEntryMediaBackLinksBackLink: self.entries?.last?.media?.mediaBackLinks?.append(string)
+ case .feedEntryMediaLocationPosition: self.entries?.last?.media?.mediaLocation?.mapFrom(latLng: string)
+ case .feedEntryMediaScenesMediaSceneSceneTitle: self.entries?.last?.media?.mediaScenes?.last?.sceneTitle = self.entries?.last?.media?.mediaScenes?.last?.sceneTitle?.appending(string) ?? string
+ case .feedEntryMediaScenesMediaSceneSceneDescription: self.entries?.last?.media?.mediaScenes?.last?.sceneDescription = self.entries?.last?.media?.mediaScenes?.last?.sceneDescription?.appending(string) ?? string
+ case .feedEntryMediaScenesMediaSceneSceneStartTime: self.entries?.last?.media?.mediaScenes?.last?.sceneStartTime = string.toDuration()
+ case .feedEntryMediaScenesMediaSceneSceneEndTime: self.entries?.last?.media?.mediaScenes?.last?.sceneEndTime = string.toDuration()
+ default: break
+ }
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeed.swift b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeed.swift
new file mode 100644
index 0000000..5eb1602
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeed.swift
@@ -0,0 +1,188 @@
+//
+// AtomFeed.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Data model for the XML DOM of the Atom Specification
+/// See https://tools.ietf.org/html/rfc4287
+///
+/// The "atom:feed" element is the document (i.e., top-level) element of
+/// an Atom Feed Document, acting as a container for metadata and data
+/// associated with the feed. Its element children consist of metadata
+/// elements followed by zero or more atom:entry child elements.
+open class AtomFeed {
+
+ /// The "atom:title" element is a Text construct that conveys a human-
+ /// readable title for an entry or feed.
+ public var title: String?
+
+ /// The "atom:subtitle" element is a Text construct that conveys a human-
+ /// readable description or subtitle for a feed.
+ public var subtitle: AtomFeedSubtitle?
+
+ /// The "atom:link" element defines a reference from an entry or feed to
+ /// a Web resource. This specification assigns no meaning to the content
+ /// (if any) of this element.
+ public var links: [AtomFeedLink]?
+
+ /// The "atom:updated" element is a Date construct indicating the most
+ /// recent instant in time when an entry or feed was modified in a way
+ /// the publisher considers significant. Therefore, not all
+ /// modifications necessarily result in a changed atom:updated value.
+ public var updated: Date?
+
+ /// The "atom:category" element conveys information about a category
+ /// associated with an entry or feed. This specification assigns no
+ /// meaning to the content (if any) of this element.
+ public var categories: [AtomFeedCategory]?
+
+ /// The "atom:author" element is a Person construct that indicates the
+ /// author of the entry or feed.
+ ///
+ /// If an atom:entry element does not contain atom:author elements, then
+ /// the atom:author elements of the contained atom:source element are
+ /// considered to apply. In an Atom Feed Document, the atom:author
+ /// elements of the containing atom:feed element are considered to apply
+ /// to the entry if there are no atom:author elements in the locations
+ /// described above.
+ public var authors: [AtomFeedAuthor]?
+
+ /// The "atom:contributor" element is a Person construct that indicates a
+ /// person or other entity who contributed to the entry or feed.
+ public var contributors: [AtomFeedContributor]?
+
+ /// The "atom:id" element conveys a permanent, universally unique
+ /// identifier for an entry or feed.
+ ///
+ /// Its content MUST be an IRI, as defined by [RFC3987]. Note that the
+ /// definition of "IRI" excludes relative references. Though the IRI
+ /// might use a dereferencable scheme, Atom Processors MUST NOT assume it
+ /// can be dereferenced.
+ ///
+ /// When an Atom Document is relocated, migrated, syndicated,
+ /// republished, exported, or imported, the content of its atom:id
+ /// element MUST NOT change. Put another way, an atom:id element
+ /// pertains to all instantiations of a particular Atom entry or feed;
+ /// revisions retain the same content in their atom:id elements. It is
+ /// suggested that the atom:id element be stored along with the
+ /// associated resource.
+ ///
+ /// The content of an atom:id element MUST be created in a way that
+ /// assures uniqueness.
+ ///
+ /// Because of the risk of confusion between IRIs that would be
+ /// equivalent if they were mapped to URIs and dereferenced, the
+ /// following normalization strategy SHOULD be applied when generating
+ /// atom:id elements:
+ ///
+ /// - Provide the scheme in lowercase characters.
+ /// - Provide the host, if any, in lowercase characters.
+ /// - Only perform percent-encoding where it is essential.
+ /// - Use uppercase A through F characters when percent-encoding.
+ /// - Prevent dot-segments from appearing in paths.
+ /// - For schemes that define a default authority, use an empty
+ /// authority if the default is desired.
+ /// - For schemes that define an empty path to be equivalent to a path
+ /// of "/", use "/".
+ /// - For schemes that define a port, use an empty port if the default
+ /// is desired.
+ /// - Preserve empty fragment identifiers and queries.
+ /// - Ensure that all components of the IRI are appropriately character
+ /// normalized, e.g., by using NFC or NFKC.
+ public var id: String?
+
+ /// The "atom:generator" element's content identifies the agent used to
+ /// generate a feed, for debugging and other purposes.
+ ///
+ /// The content of this element, when present, MUST be a string that is a
+ /// human-readable name for the generating agent. Entities such as
+ /// "&" and "<" represent their corresponding characters ("&" and
+ /// "<" respectively), not markup.
+ ///
+ /// The atom:generator element MAY have a "uri" attribute whose value
+ /// MUST be an IRI reference [RFC3987]. When dereferenced, the resulting
+ /// URI (mapped from an IRI, if necessary) SHOULD produce a
+ /// representation that is relevant to that agent.
+ ///
+ /// The atom:generator element MAY have a "version" attribute that
+ /// indicates the version of the generating agent.
+ public var generator: AtomFeedGenerator?
+
+ /// The "atom:icon" element's content is an IRI reference [RFC3987] that
+ /// identifies an image that provides iconic visual identification for a
+ /// feed.
+ ///
+ /// The image SHOULD have an aspect ratio of one (horizontal) to one
+ /// (vertical) and SHOULD be suitable for presentation at a small size.
+ public var icon: String?
+
+ /// The "atom:logo" element's content is an IRI reference [RFC3987] that
+ /// identifies an image that provides visual identification for a feed.
+ ///
+ /// The image SHOULD have an aspect ratio of 2 (horizontal) to 1
+ /// (vertical).
+ public var logo: String?
+
+ /// The "atom:rights" element is a Text construct that conveys
+ /// information about rights held in and over an entry or feed.
+ ///
+ /// The atom:rights element SHOULD NOT be used to convey machine-readable
+ /// licensing information.
+ ///
+ /// If an atom:entry element does not contain an atom:rights element,
+ /// then the atom:rights element of the containing atom:feed element, if
+ /// present, is considered to apply to the entry.
+ public var rights: String?
+
+ /// The "atom:entry" element represents an individual entry, acting as a
+ /// container for metadata and data associated with the entry. This
+ /// element can appear as a child of the atom:feed element, or it can
+ /// appear as the document (i.e., top-level) element of a stand-alone
+ /// Atom Entry Document.
+ public var entries: [AtomFeedEntry]?
+
+}
+
+// MARK: - Equatable
+
+extension AtomFeed: Equatable {
+
+ public static func ==(lhs: AtomFeed, rhs: AtomFeed) -> Bool {
+ return
+ lhs.title == rhs.title &&
+ lhs.subtitle == rhs.subtitle &&
+ lhs.links == rhs.links &&
+ lhs.updated == rhs.updated &&
+ lhs.categories == rhs.categories &&
+ lhs.authors == rhs.authors &&
+ lhs.contributors == rhs.contributors &&
+ lhs.id == rhs.id &&
+ lhs.generator == rhs.generator &&
+ lhs.icon == rhs.icon &&
+ lhs.logo == rhs.logo &&
+ lhs.rights == rhs.rights &&
+ lhs.entries == rhs.entries
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedAuthor.swift b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedAuthor.swift
new file mode 100644
index 0000000..f701193
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedAuthor.swift
@@ -0,0 +1,68 @@
+//
+// AtomFeedAuthor.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The "atom:author" element is a Person construct that indicates the
+/// author of the entry or feed.
+///
+/// If an atom:entry element does not contain atom:author elements, then
+/// the atom:author elements of the contained atom:source element are
+/// considered to apply. In an Atom Feed Document, the atom:author
+/// elements of the containing atom:feed element are considered to apply
+/// to the entry if there are no atom:author elements in the locations
+/// described above.
+public class AtomFeedAuthor {
+
+ /// The "atom:name" element's content conveys a human-readable name for
+ /// the person. The content of atom:name is Language-Sensitive. Person
+ /// constructs MUST contain exactly one "atom:name" element.
+ public var name: String?
+
+ /// The "atom:email" element's content conveys an e-mail address
+ /// associated with the person. Person constructs MAY contain an
+ /// atom:email element, but MUST NOT contain more than one. Its content
+ /// MUST conform to the "addr-spec" production in [RFC2822].
+ public var email: String?
+
+ /// The "atom:uri" element's content conveys an IRI associated with the
+ /// person. Person constructs MAY contain an atom:uri element, but MUST
+ /// NOT contain more than one. The content of atom:uri in a Person
+ /// construct MUST be an IRI reference [RFC3987].
+ public var uri: String?
+
+}
+
+// MARK: - Equatable
+
+extension AtomFeedAuthor: Equatable {
+
+ public static func ==(lhs: AtomFeedAuthor, rhs: AtomFeedAuthor) -> Bool {
+ return
+ lhs.name == rhs.name &&
+ lhs.email == rhs.email &&
+ lhs.uri == rhs.uri
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedCategory.swift b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedCategory.swift
new file mode 100644
index 0000000..b06a0cd
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedCategory.swift
@@ -0,0 +1,107 @@
+//
+// AtomFeedCategory.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The "atom:category" element conveys information about a category
+/// associated with an entry or feed. This specification assigns no
+/// meaning to the content (if any) of this element.
+public class AtomFeedCategory {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// The "term" attribute is a string that identifies the category to
+ /// which the entry or feed belongs. Category elements MUST have a
+ /// "term" attribute.
+ public var term: String?
+
+ /// The "scheme" attribute is an IRI that identifies a categorization
+ /// scheme. Category elements MAY have a "scheme" attribute.
+ public var scheme: String?
+
+ /// The "label" attribute provides a human-readable label for display in
+ /// end-user applications. The content of the "label" attribute is
+ /// Language-Sensitive. Entities such as "&" and "<" represent
+ /// their corresponding characters ("&" and "<", respectively), not
+ /// markup. Category elements MAY have a "label" attribute.
+ public var label: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+}
+
+// MARK: - Initializers
+
+extension AtomFeedCategory {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = AtomFeedCategory.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension AtomFeedCategory.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.term = attributeDict["term"]
+ self.scheme = attributeDict["scheme"]
+ self.label = attributeDict["label"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension AtomFeedCategory: Equatable {
+
+ public static func ==(lhs: AtomFeedCategory, rhs: AtomFeedCategory) -> Bool {
+ return lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension AtomFeedCategory.Attributes: Equatable {
+
+ public static func ==(lhs: AtomFeedCategory.Attributes, rhs: AtomFeedCategory.Attributes) -> Bool {
+ return
+ lhs.term == rhs.term &&
+ lhs.scheme == rhs.scheme &&
+ lhs.label == rhs.label
+ }
+
+}
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedContributor.swift b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedContributor.swift
new file mode 100644
index 0000000..a62e428
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedContributor.swift
@@ -0,0 +1,61 @@
+//
+// AtomFeedContributor.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The "atom:contributor" element is a Person construct that indicates a
+/// person or other entity who contributed to the entry or feed.
+public class AtomFeedContributor {
+
+ /// The "atom:name" element's content conveys a human-readable name for
+ /// the person. The content of atom:name is Language-Sensitive. Person
+ /// constructs MUST contain exactly one "atom:name" element.
+ public var name: String?
+
+ /// The "atom:email" element's content conveys an e-mail address
+ /// associated with the person. Person constructs MAY contain an
+ /// atom:email element, but MUST NOT contain more than one. Its content
+ /// MUST conform to the "addr-spec" production in [RFC2822].
+ public var email: String?
+
+ /// The "atom:uri" element's content conveys an IRI associated with the
+ /// person. Person constructs MAY contain an atom:uri element, but MUST
+ /// NOT contain more than one. The content of atom:uri in a Person
+ /// construct MUST be an IRI reference [RFC3987].
+ public var uri: String?
+
+}
+
+// MARK: - Equatable
+
+extension AtomFeedContributor: Equatable {
+
+ public static func ==(lhs: AtomFeedContributor, rhs: AtomFeedContributor) -> Bool {
+ return
+ lhs.name == rhs.name &&
+ lhs.email == rhs.email &&
+ lhs.uri == rhs.uri
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntry.swift b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntry.swift
new file mode 100644
index 0000000..991dbc2
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntry.swift
@@ -0,0 +1,189 @@
+//
+// AtomFeedEntry.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The "atom:entry" element represents an individual entry, acting as a
+/// container for metadata and data associated with the entry. This
+/// element can appear as a child of the atom:feed element, or it can
+/// appear as the document (i.e., top-level) element of a stand-alone
+/// Atom Entry Document.
+public class AtomFeedEntry {
+
+ /// The "atom:title" element is a Text construct that conveys a human-
+ /// readable title for an entry or feed.
+ public var title: String?
+
+ /// The "atom:summary" element is a Text construct that conveys a short
+ /// summary, abstract, or excerpt of an entry.
+ ///
+ /// atomSummary = element atom:summary { atomTextConstruct }
+ ///
+ /// It is not advisable for the atom:summary element to duplicate
+ /// atom:title or atom:content because Atom Processors might assume there
+ /// is a useful summary when there is none.
+ public var summary: AtomFeedEntrySummary?
+
+ /// The "atom:author" element is a Person construct that indicates the
+ /// author of the entry or feed.
+ ///
+ /// If an atom:entry element does not contain atom:author elements, then
+ /// the atom:author elements of the contained atom:source element are
+ /// considered to apply. In an Atom Feed Document, the atom:author
+ /// elements of the containing atom:feed element are considered to apply
+ /// to the entry if there are no atom:author elements in the locations
+ /// described above.
+ public var authors: [AtomFeedEntryAuthor]?
+
+ /// The "atom:contributor" element is a Person construct that indicates a
+ /// person or other entity who contributed to the entry or feed.
+ public var contributors: [AtomFeedEntryContributor]?
+
+ /// The "atom:link" element defines a reference from an entry or feed to
+ /// a Web resource. This specification assigns no meaning to the content
+ /// (if any) of this element.
+ public var links: [AtomFeedEntryLink]?
+
+ /// The "atom:updated" element is a Date construct indicating the most
+ /// recent instant in time when an entry or feed was modified in a way
+ /// the publisher considers significant. Therefore, not all
+ /// modifications necessarily result in a changed atom:updated value.
+ ///
+ /// Publishers MAY change the value of this element over time.
+ public var updated: Date?
+
+ /// The "atom:category" element conveys information about a category
+ /// associated with an entry or feed. This specification assigns no
+ /// meaning to the content (if any) of this element.
+ public var categories: [AtomFeedEntryCategory]?
+
+ /// The "atom:id" element conveys a permanent, universally unique
+ /// identifier for an entry or feed.
+ ///
+ /// Its content MUST be an IRI, as defined by [RFC3987]. Note that the
+ /// definition of "IRI" excludes relative references. Though the IRI
+ /// might use a dereferencable scheme, Atom Processors MUST NOT assume it
+ /// can be dereferenced.
+ ///
+ /// When an Atom Document is relocated, migrated, syndicated,
+ /// republished, exported, or imported, the content of its atom:id
+ /// element MUST NOT change. Put another way, an atom:id element
+ /// pertains to all instantiations of a particular Atom entry or feed;
+ /// revisions retain the same content in their atom:id elements. It is
+ /// suggested that the atom:id element be stored along with the
+ /// associated resource.
+ ///
+ /// The content of an atom:id element MUST be created in a way that
+ /// assures uniqueness.
+ ///
+ /// Because of the risk of confusion between IRIs that would be
+ /// equivalent if they were mapped to URIs and dereferenced, the
+ /// following normalization strategy SHOULD be applied when generating
+ /// atom:id elements:
+ ///
+ /// - Provide the scheme in lowercase characters.
+ /// - Provide the host, if any, in lowercase characters.
+ /// - Only perform percent-encoding where it is essential.
+ /// - Use uppercase A through F characters when percent-encoding.
+ /// - Prevent dot-segments from appearing in paths.
+ /// - For schemes that define a default authority, use an empty
+ /// authority if the default is desired.
+ /// - For schemes that define an empty path to be equivalent to a path
+ /// of "/", use "/".
+ /// - For schemes that define a port, use an empty port if the default
+ /// is desired.
+ /// - Preserve empty fragment identifiers and queries.
+ /// - Ensure that all components of the IRI are appropriately character
+ /// normalized, e.g., by using NFC or NFKC.
+ public var id: String?
+
+ /// The "atom:content" element either contains or links to the content of
+ /// the entry. The content of atom:content is Language-Sensitive.
+ public var content: AtomFeedEntryContent?
+
+ /// The "atom:published" element is a Date construct indicating an
+ /// instant in time associated with an event early in the life cycle of
+ /// the entry.
+ ///
+ /// Typically, atom:published will be associated with the initial
+ /// creation or first availability of the resource.
+ public var published: Date?
+
+ /// If an atom:entry is copied from one feed into another feed, then the
+ /// source atom:feed's metadata (all child elements of atom:feed other
+ /// than the atom:entry elements) MAY be preserved within the copied
+ /// entry by adding an atom:source child element, if it is not already
+ /// present in the entry, and including some or all of the source feed's
+ /// Metadata elements as the atom:source element's children. Such
+ /// metadata SHOULD be preserved if the source atom:feed contains any of
+ /// the child elements atom:author, atom:contributor, atom:rights, or
+ /// atom:category and those child elements are not present in the source
+ /// atom:entry.
+ ///
+ /// The atom:source element is designed to allow the aggregation of
+ /// entries from different feeds while retaining information about an
+ /// entry's source feed. For this reason, Atom Processors that are
+ /// performing such aggregation SHOULD include at least the required
+ /// feed-level Metadata elements (atom:id, atom:title, and atom:updated)
+ /// in the atom:source element.
+ public var source: AtomFeedEntrySource?
+
+ /// The "atom:rights" element is a Text construct that conveys
+ /// information about rights held in and over an entry or feed.
+ ///
+ /// The atom:rights element SHOULD NOT be used to convey machine-readable
+ /// licensing information.
+ ///
+ /// If an atom:entry element does not contain an atom:rights element,
+ /// then the atom:rights element of the containing atom:feed element, if
+ /// present, is considered to apply to the entry.
+ public var rights: String?
+
+ /// Media RSS is a new RSS module that supplements the
+ /// capabilities of RSS 2.0.
+ public var media: MediaNamespace?
+
+}
+
+// MARK: - Equatable
+
+extension AtomFeedEntry: Equatable {
+
+ public static func ==(lhs: AtomFeedEntry, rhs: AtomFeedEntry) -> Bool {
+ return
+ lhs.title == rhs.title &&
+ lhs.summary == rhs.summary &&
+ lhs.authors == rhs.authors &&
+ lhs.contributors == rhs.contributors &&
+ lhs.links == rhs.links &&
+ lhs.updated == rhs.updated &&
+ lhs.categories == rhs.categories &&
+ lhs.id == rhs.id &&
+ lhs.content == rhs.content &&
+ lhs.published == rhs.published &&
+ lhs.source == rhs.source &&
+ lhs.rights == rhs.rights
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntryAuthor.swift b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntryAuthor.swift
new file mode 100644
index 0000000..0cc9d85
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntryAuthor.swift
@@ -0,0 +1,68 @@
+//
+// AtomFeedEntryAuthor.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The "atom:author" element is a Person construct that indicates the
+/// author of the entry or feed.
+///
+/// If an atom:entry element does not contain atom:author elements, then
+/// the atom:author elements of the contained atom:source element are
+/// considered to apply. In an Atom Feed Document, the atom:author
+/// elements of the containing atom:feed element are considered to apply
+/// to the entry if there are no atom:author elements in the locations
+/// described above.
+public class AtomFeedEntryAuthor {
+
+ /// The "atom:name" element's content conveys a human-readable name for
+ /// the person. The content of atom:name is Language-Sensitive. Person
+ /// constructs MUST contain exactly one "atom:name" element.
+ public var name: String?
+
+ /// The "atom:email" element's content conveys an e-mail address
+ /// associated with the person. Person constructs MAY contain an
+ /// atom:email element, but MUST NOT contain more than one. Its content
+ /// MUST conform to the "addr-spec" production in [RFC2822].
+ public var email: String?
+
+ /// The "atom:uri" element's content conveys an IRI associated with the
+ /// person. Person constructs MAY contain an atom:uri element, but MUST
+ /// NOT contain more than one. The content of atom:uri in a Person
+ /// construct MUST be an IRI reference [RFC3987].
+ public var uri: String?
+
+}
+
+// MARK: - Equatable
+
+extension AtomFeedEntryAuthor: Equatable {
+
+ public static func ==(lhs: AtomFeedEntryAuthor, rhs: AtomFeedEntryAuthor) -> Bool {
+ return
+ lhs.name == rhs.name &&
+ lhs.email == rhs.email &&
+ lhs.uri == rhs.uri
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntryCategory.swift b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntryCategory.swift
new file mode 100644
index 0000000..2f74ba2
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntryCategory.swift
@@ -0,0 +1,106 @@
+//
+// AtomFeedEntryCategory.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The "atom:category" element conveys information about a category
+/// associated with an entry or feed. This specification assigns no
+/// meaning to the content (if any) of this element.
+public class AtomFeedEntryCategory {
+
+ /// The element's attributes
+ public class Attributes {
+
+ /// The "term" attribute is a string that identifies the category to
+ /// which the entry or feed belongs. Category elements MUST have a
+ /// "term" attribute.
+ public var term: String?
+
+ /// The "scheme" attribute is an IRI that identifies a categorization
+ /// scheme. Category elements MAY have a "scheme" attribute.
+ public var scheme: String?
+
+ /// The "label" attribute provides a human-readable label for display in
+ /// end-user applications. The content of the "label" attribute is
+ /// Language-Sensitive. Entities such as "&" and "<" represent
+ /// their corresponding characters ("&" and "<", respectively), not
+ /// markup. Category elements MAY have a "label" attribute.
+ public var label: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+}
+
+// MARK: - Initializers
+
+extension AtomFeedEntryCategory {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = AtomFeedEntryCategory.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension AtomFeedEntryCategory.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.term = attributeDict["term"]
+ self.scheme = attributeDict["scheme"]
+ self.label = attributeDict["label"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension AtomFeedEntryCategory: Equatable {
+
+ public static func ==(lhs: AtomFeedEntryCategory, rhs: AtomFeedEntryCategory) -> Bool {
+ return lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension AtomFeedEntryCategory.Attributes: Equatable {
+
+ public static func ==(lhs: AtomFeedEntryCategory.Attributes, rhs: AtomFeedEntryCategory.Attributes) -> Bool {
+ return
+ lhs.term == rhs.term &&
+ lhs.scheme == rhs.scheme &&
+ lhs.label == rhs.label
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntryContent.swift b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntryContent.swift
new file mode 100644
index 0000000..919ff5c
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntryContent.swift
@@ -0,0 +1,114 @@
+//
+// AtomFeedEntryContent.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The "atom:content" element either contains or links to the content of
+/// the entry. The content of atom:content is Language-Sensitive.
+public class AtomFeedEntryContent {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// On the atom:content element, the value of the "type" attribute MAY be
+ /// one of "text", "html", or "xhtml". Failing that, it MUST conform to
+ /// the syntax of a MIME media type, but MUST NOT be a composite type
+ /// (see Section 4.2.6 of [MIMEREG]). If neither the type attribute nor
+ /// the src attribute is provided, Atom Processors MUST behave as though
+ /// the type attribute were present with a value of "text".
+ public var type: String?
+
+ /// The atom:content MAY have a "src" attribute, whose value MUST be an IRI
+ /// reference [RFC3987]. If the "src" attribute is present, atom:content
+ /// MUST be empty. Atom Processors MAY use the IRI to retrieve the
+ /// content and MAY choose to ignore remote content or to present it in a
+ /// different manner than local content.
+ ///
+ /// If the "src" attribute is present, the "type" attribute SHOULD be
+ /// provided and MUST be a MIME media type [MIMEREG], rather than "text",
+ /// "html", or "xhtml". The value is advisory; that is to say, when the
+ /// corresponding URI (mapped from an IRI, if necessary) is dereferenced,
+ /// if the server providing that content also provides a media type, the
+ /// server-provided media type is authoritative.
+ public var src: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension AtomFeedEntryContent {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = AtomFeedEntryContent.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension AtomFeedEntryContent.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.type = attributeDict["type"]
+ self.src = attributeDict["src"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension AtomFeedEntryContent: Equatable {
+
+ public static func ==(lhs: AtomFeedEntryContent, rhs: AtomFeedEntryContent) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension AtomFeedEntryContent.Attributes: Equatable {
+
+ public static func ==(lhs: AtomFeedEntryContent.Attributes, rhs: AtomFeedEntryContent.Attributes) -> Bool {
+ return
+ lhs.type == rhs.type &&
+ lhs.src == rhs.src
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntryContributor.swift b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntryContributor.swift
new file mode 100644
index 0000000..db71845
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntryContributor.swift
@@ -0,0 +1,61 @@
+//
+// AtomFeedEntryContributor.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The "atom:contributor" element is a Person construct that indicates a
+/// person or other entity who contributed to the entry or feed.
+public class AtomFeedEntryContributor {
+
+ /// The "atom:name" element's content conveys a human-readable name for
+ /// the person. The content of atom:name is Language-Sensitive. Person
+ /// constructs MUST contain exactly one "atom:name" element.
+ public var name: String?
+
+ /// The "atom:email" element's content conveys an e-mail address
+ /// associated with the person. Person constructs MAY contain an
+ /// atom:email element, but MUST NOT contain more than one. Its content
+ /// MUST conform to the "addr-spec" production in [RFC2822].
+ public var email: String?
+
+ /// The "atom:uri" element's content conveys an IRI associated with the
+ /// person. Person constructs MAY contain an atom:uri element, but MUST
+ /// NOT contain more than one. The content of atom:uri in a Person
+ /// construct MUST be an IRI reference [RFC3987].
+ public var uri: String?
+
+}
+
+// MARK: - Equatable
+
+extension AtomFeedEntryContributor: Equatable {
+
+ public static func ==(lhs: AtomFeedEntryContributor, rhs: AtomFeedEntryContributor) -> Bool {
+ return
+ lhs.name == rhs.name &&
+ lhs.email == rhs.email &&
+ lhs.uri == rhs.uri
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntryLink.swift b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntryLink.swift
new file mode 100644
index 0000000..b9f3d5e
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntryLink.swift
@@ -0,0 +1,183 @@
+//
+// AtomFeedEntryLink.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The "atom:link" element defines a reference from an entry or feed to
+/// a Web resource. This specification assigns no meaning to the content
+/// (if any) of this element.
+public class AtomFeedEntryLink {
+
+ /// The element's attributes
+ public class Attributes {
+
+ /// The "href" attribute contains the link's IRI. atom:link elements MUST
+ /// have an href attribute, whose value MUST be a IRI reference
+ /// [RFC3987].
+ public var href: String?
+
+ /// The atom:link elements MAY have a "rel" attribute that indicates the link
+ /// relation type. If the "rel" attribute is not present, the link
+ /// element MUST be interpreted as if the link relation type is
+ /// "alternate".
+ ///
+ /// The value of "rel" MUST be a string that is non-empty and matches
+ /// either the "isegment-nz-nc" or the "IRI" production in [RFC3987].
+ /// Note that use of a relative reference other than a simple name is not
+ /// allowed. If a name is given, implementations MUST consider the link
+ /// relation type equivalent to the same name registered within the IANA
+ /// Registry of Link Relations (Section 7), and thus to the IRI that
+ /// would be obtained by appending the value of the rel attribute to the
+ /// string "http://www.iana.org/assignments/relation/". The value of
+ /// "rel" describes the meaning of the link, but does not impose any
+ /// behavioral requirements on Atom Processors.
+ ///
+ /// This document defines five initial values for the Registry of Link
+ /// Relations:
+ ///
+ /// 1. The value "alternate" signifies that the IRI in the value of the
+ /// href attribute identifies an alternate version of the resource
+ /// described by the containing element.
+ ///
+ /// 2. The value "related" signifies that the IRI in the value of the
+ /// href attribute identifies a resource related to the resource
+ /// described by the containing element. For example, the feed for a
+ /// site that discusses the performance of the search engine at
+ /// "http://search.example.com" might contain, as a child of
+ /// atom:feed:
+ ///
+ ///
+ ///
+ /// An identical link might appear as a child of any atom:entry whose
+ /// content contains a discussion of that same search engine.
+ ///
+ /// 3. The value "self" signifies that the IRI in the value of the href
+ /// attribute identifies a resource equivalent to the containing
+ /// element.
+ ///
+ /// 4. The value "enclosure" signifies that the IRI in the value of the
+ /// href attribute identifies a related resource that is potentially
+ /// large in size and might require special handling. For atom:link
+ /// elements with rel="enclosure", the length attribute SHOULD be
+ /// provided.
+ ///
+ /// 5. The value "via" signifies that the IRI in the value of the href
+ /// attribute identifies a resource that is the source of the
+ /// information provided in the containing element.
+ public var rel: String?
+
+ /// On the link element, the "type" attribute's value is an advisory
+ /// media type: it is a hint about the type of the representation that is
+ /// expected to be returned when the value of the href attribute is
+ /// dereferenced. Note that the type attribute does not override the
+ /// actual media type returned with the representation. Link elements
+ /// MAY have a type attribute, whose value MUST conform to the syntax of
+ /// a MIME media type [MIMEREG].
+ public var type: String?
+
+ /// The "hreflang" attribute's content describes the language of the
+ /// resource pointed to by the href attribute. When used together with
+ /// the rel="alternate", it implies a translated version of the entry.
+ /// Link elements MAY have an hreflang attribute, whose value MUST be a
+ /// language tag [RFC3066].
+ public var hreflang: String?
+
+ /// The "title" attribute conveys human-readable information about the
+ /// link. The content of the "title" attribute is Language-Sensitive.
+ /// Entities such as "&" and "<" represent their corresponding
+ /// characters ("&" and "<", respectively), not markup. Link elements
+ /// MAY have a title attribute.
+ public var title: String?
+
+ /// The "length" attribute indicates an advisory length of the linked
+ /// content in octets; it is a hint about the content length of the
+ /// representation returned when the IRI in the href attribute is mapped
+ /// to a URI and dereferenced. Note that the length attribute does not
+ /// override the actual content length of the representation as reported
+ /// by the underlying protocol. Link elements MAY have a length
+ /// attribute.
+ public var length: Int64?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+}
+
+// MARK: - Initializers
+
+extension AtomFeedEntryLink {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = AtomFeedEntryLink.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension AtomFeedEntryLink.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.href = attributeDict["href"]
+ self.hreflang = attributeDict["hreflang"]
+ self.type = attributeDict["type"]
+ self.rel = attributeDict["rel"]
+ self.title = attributeDict["title"]
+ self.length = Int64(attributeDict["length"] ?? "")
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension AtomFeedEntryLink: Equatable {
+
+ public static func ==(lhs: AtomFeedEntryLink, rhs: AtomFeedEntryLink) -> Bool {
+ return lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension AtomFeedEntryLink.Attributes: Equatable {
+
+ public static func ==(lhs: AtomFeedEntryLink.Attributes, rhs: AtomFeedEntryLink.Attributes) -> Bool {
+ return
+ lhs.href == rhs.href &&
+ lhs.hreflang == rhs.hreflang &&
+ lhs.type == rhs.type &&
+ lhs.rel == rhs.rel &&
+ lhs.title == rhs.title &&
+ lhs.length == rhs.length
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntrySource.swift b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntrySource.swift
new file mode 100644
index 0000000..9dd1fcf
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntrySource.swift
@@ -0,0 +1,73 @@
+//
+// AtomFeedEntrySource.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// If an atom:entry is copied from one feed into another feed, then the
+/// source atom:feed's metadata (all child elements of atom:feed other
+/// than the atom:entry elements) MAY be preserved within the copied
+/// entry by adding an atom:source child element, if it is not already
+/// present in the entry, and including some or all of the source feed's
+/// Metadata elements as the atom:source element's children. Such
+/// metadata SHOULD be preserved if the source atom:feed contains any of
+/// the child elements atom:author, atom:contributor, atom:rights, or
+/// atom:category and those child elements are not present in the source
+/// atom:entry.
+///
+/// The atom:source element is designed to allow the aggregation of
+/// entries from different feeds while retaining information about an
+/// entry's source feed. For this reason, Atom Processors that are
+/// performing such aggregation SHOULD include at least the required
+/// feed-level Metadata elements (atom:id, atom:title, and atom:updated)
+/// in the atom:source element.
+public class AtomFeedEntrySource {
+
+ /// The "atom:id" element conveys a permanent, universally unique
+ /// identifier for an entry or feed.
+ public var id: String?
+
+ /// The "atom:title" element is a Text construct that conveys a human-
+ /// readable title for an entry or feed.
+ public var title: String?
+
+ /// The "atom:updated" element is a Date construct indicating the most
+ /// recent instant in time when an entry or feed was modified in a way
+ /// the publisher considers significant. Therefore, not all
+ /// modifications necessarily result in a changed atom:updated value.
+ public var updated: Date?
+
+}
+
+// MARK: - Equatable
+
+extension AtomFeedEntrySource: Equatable {
+
+ public static func ==(lhs: AtomFeedEntrySource, rhs: AtomFeedEntrySource) -> Bool {
+ return
+ lhs.id == rhs.id &&
+ lhs.title == rhs.title &&
+ lhs.updated == rhs.updated
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntrySummary.swift b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntrySummary.swift
new file mode 100644
index 0000000..0e8ecf1
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedEntrySummary.swift
@@ -0,0 +1,101 @@
+//
+// AtomFeedEntrySummary.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The "atom:summary" element is a Text construct that conveys a short
+/// summary, abstract, or excerpt of an enactry.
+///
+/// atomSummary = element atom:summary { atomTextConstruct }
+///
+/// It is not advisable for the atom:summary element to duplicate
+/// atom:title or atom:content because Atom Processors might assume there
+/// is a useful summary when there is none.
+public class AtomFeedEntrySummary {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// Text constructs MAY have a "type" attribute. When present, the value
+ /// MUST be one of "text", "html", or "xhtml". If the "type" attribute
+ /// is not provided, Atom Processors MUST behave as though it were
+ /// present with a value of "text".
+ public var type: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension AtomFeedEntrySummary {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = AtomFeedEntrySummary.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension AtomFeedEntrySummary.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.type = attributeDict["type"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension AtomFeedEntrySummary: Equatable {
+
+ public static func ==(lhs: AtomFeedEntrySummary, rhs: AtomFeedEntrySummary) -> Bool {
+ return
+ lhs.attributes == rhs.attributes &&
+ lhs.value == rhs.value
+ }
+
+}
+
+extension AtomFeedEntrySummary.Attributes: Equatable {
+
+ public static func ==(lhs: AtomFeedEntrySummary.Attributes, rhs: AtomFeedEntrySummary.Attributes) -> Bool {
+ return lhs.type == rhs.type
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedGenerator.swift b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedGenerator.swift
new file mode 100644
index 0000000..487d55e
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedGenerator.swift
@@ -0,0 +1,115 @@
+//
+// AtomFeedGenerator.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The "atom:generator" element's content identifies the agent used to
+/// generate a feed, for debugging and other purposes.
+///
+/// The content of this element, when present, MUST be a string that is a
+/// human-readable name for the generating agent. Entities such as
+/// "&" and "<" represent their corresponding characters ("&" and
+/// "<" respectively), not markup.
+///
+/// The atom:generator element MAY have a "uri" attribute whose value
+/// MUST be an IRI reference [RFC3987]. When dereferenced, the resulting
+/// URI (mapped from an IRI, if necessary) SHOULD produce a
+/// representation that is relevant to that agent.
+///
+/// The atom:generator element MAY have a "version" attribute that
+/// indicates the version of the generating agent.
+public class AtomFeedGenerator {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// The atom:generator element MAY have a "uri" attribute whose value
+ /// MUST be an IRI reference [RFC3987]. When dereferenced, the resulting
+ /// URI (mapped from an IRI, if necessary) SHOULD produce a
+ /// representation that is relevant to that agent.
+ public var uri: String?
+
+ /// The atom:generator element MAY have a "version" attribute that
+ /// indicates the version of the generating agent.
+ public var version: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension AtomFeedGenerator {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = AtomFeedGenerator.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension AtomFeedGenerator.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.uri = attributeDict["uri"]
+ self.version = attributeDict["version"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension AtomFeedGenerator: Equatable {
+
+ public static func ==(lhs: AtomFeedGenerator, rhs: AtomFeedGenerator) -> Bool {
+ return
+ lhs.attributes == rhs.attributes &&
+ lhs.value == rhs.value
+ }
+
+}
+
+extension AtomFeedGenerator.Attributes: Equatable {
+
+ public static func ==(lhs: AtomFeedGenerator.Attributes, rhs: AtomFeedGenerator.Attributes) -> Bool {
+ return
+ lhs.uri == rhs.uri &&
+ lhs.version == rhs.version
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedLink.swift b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedLink.swift
new file mode 100644
index 0000000..05f6b52
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedLink.swift
@@ -0,0 +1,183 @@
+//
+// AtomFeedLink.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The "atom:link" element defines a reference from an entry or feed to
+/// a Web resource. This specification assigns no meaning to the content
+/// (if any) of this element.
+public class AtomFeedLink {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// The "href" attribute contains the link's IRI. atom:link elements MUST
+ /// have an href attribute, whose value MUST be a IRI reference
+ /// [RFC3987].
+ public var href: String?
+
+ /// The atom:link elements MAY have a "rel" attribute that indicates the link
+ /// relation type. If the "rel" attribute is not present, the link
+ /// element MUST be interpreted as if the link relation type is
+ /// "alternate".
+ ///
+ /// The value of "rel" MUST be a string that is non-empty and matches
+ /// either the "isegment-nz-nc" or the "IRI" production in [RFC3987].
+ /// Note that use of a relative reference other than a simple name is not
+ /// allowed. If a name is given, implementations MUST consider the link
+ /// relation type equivalent to the same name registered within the IANA
+ /// Registry of Link Relations (Section 7), and thus to the IRI that
+ /// would be obtained by appending the value of the rel attribute to the
+ /// string "http://www.iana.org/assignments/relation/". The value of
+ /// "rel" describes the meaning of the link, but does not impose any
+ /// behavioral requirements on Atom Processors.
+ ///
+ /// This document defines five initial values for the Registry of Link
+ /// Relations:
+ ///
+ /// 1. The value "alternate" signifies that the IRI in the value of the
+ /// href attribute identifies an alternate version of the resource
+ /// described by the containing element.
+ ///
+ /// 2. The value "related" signifies that the IRI in the value of the
+ /// href attribute identifies a resource related to the resource
+ /// described by the containing element. For example, the feed for a
+ /// site that discusses the performance of the search engine at
+ /// "http://search.example.com" might contain, as a child of
+ /// atom:feed:
+ ///
+ ///
+ ///
+ /// An identical link might appear as a child of any atom:entry whose
+ /// content contains a discussion of that same search engine.
+ ///
+ /// 3. The value "self" signifies that the IRI in the value of the href
+ /// attribute identifies a resource equivalent to the containing
+ /// element.
+ ///
+ /// 4. The value "enclosure" signifies that the IRI in the value of the
+ /// href attribute identifies a related resource that is potentially
+ /// large in size and might require special handling. For atom:link
+ /// elements with rel="enclosure", the length attribute SHOULD be
+ /// provided.
+ ///
+ /// 5. The value "via" signifies that the IRI in the value of the href
+ /// attribute identifies a resource that is the source of the
+ /// information provided in the containing element.
+ public var rel: String?
+
+ /// On the link element, the "type" attribute's value is an advisory
+ /// media type: it is a hint about the type of the representation that is
+ /// expected to be returned when the value of the href attribute is
+ /// dereferenced. Note that the type attribute does not override the
+ /// actual media type returned with the representation. Link elements
+ /// MAY have a type attribute, whose value MUST conform to the syntax of
+ /// a MIME media type [MIMEREG].
+ public var type: String?
+
+ /// The "hreflang" attribute's content describes the language of the
+ /// resource pointed to by the href attribute. When used together with
+ /// the rel="alternate", it implies a translated version of the entry.
+ /// Link elements MAY have an hreflang attribute, whose value MUST be a
+ /// language tag [RFC3066].
+ public var hreflang: String?
+
+ /// The "title" attribute conveys human-readable information about the
+ /// link. The content of the "title" attribute is Language-Sensitive.
+ /// Entities such as "&" and "<" represent their corresponding
+ /// characters ("&" and "<", respectively), not markup. Link elements
+ /// MAY have a title attribute.
+ public var title: String?
+
+ /// The "length" attribute indicates an advisory length of the linked
+ /// content in octets; it is a hint about the content length of the
+ /// representation returned when the IRI in the href attribute is mapped
+ /// to a URI and dereferenced. Note that the length attribute does not
+ /// override the actual content length of the representation as reported
+ /// by the underlying protocol. Link elements MAY have a length
+ /// attribute.
+ public var length: Int64?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+}
+
+// MARK: - Initializers
+
+extension AtomFeedLink {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = AtomFeedLink.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension AtomFeedLink.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.href = attributeDict["href"]
+ self.hreflang = attributeDict["hreflang"]
+ self.type = attributeDict["type"]
+ self.rel = attributeDict["rel"]
+ self.title = attributeDict["title"]
+ self.length = Int64(attributeDict["length"] ?? "")
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension AtomFeedLink: Equatable {
+
+ public static func ==(lhs: AtomFeedLink, rhs: AtomFeedLink) -> Bool {
+ return lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension AtomFeedLink.Attributes: Equatable {
+
+ public static func ==(lhs: AtomFeedLink.Attributes, rhs: AtomFeedLink.Attributes) -> Bool {
+ return
+ lhs.href == rhs.href &&
+ lhs.hreflang == rhs.hreflang &&
+ lhs.type == rhs.type &&
+ lhs.rel == rhs.rel &&
+ lhs.title == rhs.title &&
+ lhs.length == rhs.length
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedSubtitle.swift b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedSubtitle.swift
new file mode 100644
index 0000000..2c9013a
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomFeedSubtitle.swift
@@ -0,0 +1,95 @@
+//
+// AtomFeedSubtitle.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The "atom:subtitle" element is a Text construct that conveys a human-
+/// readable description or subtitle for a feed.
+public class AtomFeedSubtitle {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// Text constructs MAY have a "type" attribute. When present, the value
+ /// MUST be one of "text", "html", or "xhtml". If the "type" attribute
+ /// is not provided, Atom Processors MUST behave as though it were
+ /// present with a value of "text".
+ public var type: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension AtomFeedSubtitle {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = AtomFeedSubtitle.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension AtomFeedSubtitle.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.type = attributeDict["type"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension AtomFeedSubtitle: Equatable {
+
+ public static func ==(lhs: AtomFeedSubtitle, rhs: AtomFeedSubtitle) -> Bool {
+ return
+ lhs.attributes == rhs.attributes &&
+ lhs.value == rhs.value
+ }
+
+}
+
+extension AtomFeedSubtitle.Attributes: Equatable {
+
+ public static func ==(lhs: AtomFeedSubtitle.Attributes, rhs: AtomFeedSubtitle.Attributes) -> Bool {
+ return lhs.type == rhs.type
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomPath.swift b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomPath.swift
new file mode 100644
index 0000000..3a9f7b2
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Atom/AtomPath.swift
@@ -0,0 +1,110 @@
+//
+// AtomPath.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Describes the individual path for each XML DOM element of an Atom feed.
+///
+/// See https://tools.ietf.org/html/rfc4287
+enum AtomPath: String {
+
+ case feed = "/feed"
+ case feedTitle = "/feed/title"
+ case feedSubtitle = "/feed/subtitle"
+ case feedLink = "/feed/link"
+ case feedUpdated = "/feed/updated"
+ case feedCategory = "/feed/category"
+ case feedAuthor = "/feed/author"
+ case feedAuthorName = "/feed/author/name"
+ case feedAuthorEmail = "/feed/author/email"
+ case feedAuthorUri = "/feed/author/uri"
+ case feedContributor = "/feed/contributor"
+ case feedContributorName = "/feed/contributor/name"
+ case feedContributorEmail = "/feed/contributor/email"
+ case feedContributorUri = "/feed/contributor/uri"
+ case feedID = "/feed/id"
+ case feedGenerator = "/feed/generator"
+ case feedIcon = "/feed/icon"
+ case feedLogo = "/feed/logo"
+ case feedRights = "/feed/rights"
+ case feedEntry = "/feed/entry"
+ case feedEntryTitle = "/feed/entry/title"
+ case feedEntrySummary = "/feed/entry/summary"
+ case feedEntryLink = "/feed/entry/link"
+ case feedEntryUpdated = "/feed/entry/updated"
+ case feedEntryCategory = "/feed/entry/category"
+ case feedEntryID = "/feed/entry/id"
+ case feedEntryContent = "/feed/entry/content"
+ case feedEntryPublished = "/feed/entry/published"
+ case feedEntrySource = "/feed/entry/source"
+ case feedEntrySourceID = "/feed/entry/source/id"
+ case feedEntrySourceTitle = "/feed/entry/source/title"
+ case feedEntrySourceUpdated = "/feed/entry/source/updated"
+ case feedEntryRights = "/feed/entry/rights"
+ case feedEntryAuthor = "/feed/entry/author"
+ case feedEntryAuthorName = "/feed/entry/author/name"
+ case feedEntryAuthorEmail = "/feed/entry/author/email"
+ case feedEntryAuthorUri = "/feed/entry/author/uri"
+ case feedEntryContributor = "/feed/entry/contributor"
+ case feedEntryContributorName = "/feed/entry/contributor/name"
+ case feedEntryContributorEmail = "/feed/entry/contributor/email"
+ case feedEntryContributorUri = "/feed/entry/contributor/uri"
+
+ // MARK: Media
+
+ case feedEntryMediaThumbnail = "/feed/entry/media:thumbnail"
+ case feedEntryMediaContent = "/feed/entry/media:content"
+ case feedEntryMediaCommunity = "/feed/entry/media:community"
+ case feedEntryMediaCommunityMediaStarRating = "/feed/entry/media:community/media:starRating"
+ case feedEntryMediaCommunityMediaStatistics = "/feed/entry/media:community/media:statistics"
+ case feedEntryMediaCommunityMediaTags = "/feed/entry/media:community/media:tags"
+ case feedEntryMediaComments = "/feed/entry/media:comments"
+ case feedEntryMediaCommentsMediaComment = "/feed/entry/media:comments/media:comment"
+ case feedEntryMediaEmbed = "/feed/entry/media:embed"
+ case feedEntryMediaEmbedMediaParam = "/feed/entry/media:embed/media:param"
+ case feedEntryMediaResponses = "/feed/entry/media:responses"
+ case feedEntryMediaResponsesMediaResponse = "/feed/entry/media:responses/media:response"
+ case feedEntryMediaBackLinks = "/feed/entry/media:backLinks"
+ case feedEntryMediaBackLinksBackLink = "/feed/entry/media:backLinks/media:backLink"
+ case feedEntryMediaStatus = "/feed/entry/media:status"
+ case feedEntryMediaPrice = "/feed/entry/media:price"
+ case feedEntryMediaLicense = "/feed/entry/media:license"
+ case feedEntryMediaSubTitle = "/feed/entry/media:subTitle"
+ case feedEntryMediaPeerLink = "/feed/entry/media:peerLink"
+ case feedEntryMediaLocation = "/feed/entry/media:location"
+ case feedEntryMediaLocationPosition = "/feed/entry/media:location/georss:where/gml:Point/gml:pos"
+ case feedEntryMediaRestriction = "/feed/entry/media:restriction"
+ case feedEntryMediaScenes = "/feed/entry/media:scenes"
+ case feedEntryMediaScenesMediaScene = "/feed/entry/media:scenes/media:scene"
+ case feedEntryMediaScenesMediaSceneSceneTitle = "/feed/entry/media:scenes/media:scene/sceneTitle"
+ case feedEntryMediaScenesMediaSceneSceneDescription = "/feed/entry/media:scenes/media:scene/sceneDescription"
+ case feedEntryMediaScenesMediaSceneSceneStartTime = "/feed/entry/media:scenes/media:scene/sceneStartTime"
+ case feedEntryMediaScenesMediaSceneSceneEndTime = "/feed/entry/media:scenes/media:scene/sceneEndTime"
+ case feedEntryMediaGroup = "/feed/entry/media:group"
+ case feedEntryMediaGroupMediaCredit = "/feed/entry/media:group/media:credit"
+ case feedEntryMediaGroupMediaCategory = "/feed/entry/media:group/media:category"
+ case feedEntryMediaGroupMediaRating = "/feed/entry/media:group/media:rating"
+ case feedEntryMediaGroupMediaContent = "/feed/entry/media:group/media:content"
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/JSON/JSONFeed.swift b/Pods/FeedKit/Sources/FeedKit/Models/JSON/JSONFeed.swift
new file mode 100644
index 0000000..b77000b
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/JSON/JSONFeed.swift
@@ -0,0 +1,190 @@
+//
+// JSONFeed.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The JSON Feed format is a pragmatic syndication format, like RSS and Atom,
+/// but with one big difference: it's JSON instead of XML.
+/// See https://jsonfeed.org/version/1
+public class JSONFeed {
+
+ /// (required, string) is the URL of the version of the format the feed
+ /// uses. This should appear at the very top, though we recognize that not all
+ /// JSON generators allow for ordering.
+ public var version: String?
+
+ /// (required, string) is the name of the feed, which will often correspond to
+ /// the name of the website (blog, for instance), though not necessarily.
+ public var title: String?
+
+ /// (optional but strongly recommended, string) is the URL of the resource that
+ /// the feed describes. This resource may or may not actually be a "home" page,
+ /// but it should be an HTML page. If a feed is published on the public web,
+ /// this should be considered as required. But it may not make sense in the
+ /// case of a file created on a desktop computer, when that file is not shared
+ /// or is shared only privately.
+ public var homePageURL: String?
+
+ /// (optional but strongly recommended, string) is the URL of the feed, and
+ /// serves as the unique identifier for the feed. As with home_page_url, this
+ /// should be considered required for feeds on the public web.
+ public var feedUrl: String?
+
+ /// (optional, string) provides more detail, beyond the title, on what the feed
+ /// is about. A feed reader may display this text.
+ public var description: String?
+
+ /// (optional, string) is a description of the purpose of the feed. This is for
+ /// the use of people looking at the raw JSON, and should be ignored by feed
+ /// readers.
+ public var userComment: String?
+
+ /// (optional, string) is the URL of a feed that provides the next n items,
+ /// where n is determined by the publisher. This allows for pagination, but
+ /// with the expectation that reader software is not required to use it and
+ /// probably won't use it very often. next_url must not be the same as
+ /// feed_url, and it must not be the same as a previous next_url (to avoid
+ /// infinite loops).
+ public var nextUrl: String?
+
+ /// (optional, string) is the URL of an image for the feed suitable to be used
+ /// in a timeline, much the way an avatar might be used. It should be square
+ /// and relatively large - such as 512 x 512 - so that it can be scaled-down
+ /// and so that it can look good on retina displays. It should use transparency
+ /// where appropriate, since it may be rendered on a non-white background.
+ public var icon: String?
+
+ /// (optional, string) is the URL of an image for the feed suitable to be used
+ /// in a source list. It should be square and relatively small, but not smaller
+ /// than 64 x 64 (so that it can look good on retina displays). As with icon,
+ /// this image should use transparency where appropriate, since it may be
+ /// rendered on a non-white background.
+ public var favicon: String?
+
+ /// (optional, object) specifies the feed author. The author object has
+ /// several members. These are all optional - but if you provide an author
+ /// object, then at least one is required.
+ public var author: JSONFeedAuthor?
+
+ /// (optional, boolean) says whether or not the feed is finished - that is,
+ /// whether or not it will ever update again. A feed for a temporary event,
+ /// such as an instance of the Olympics, could expire. If the value is true,
+ /// then it's expired. Any other value, or the absence of expired, means the
+ /// feed may continue to update.
+ public var expired: Bool?
+
+ /// (very optional, array of objects) describes endpoints that can be used to
+ /// subscribe to real-time notifications from the publisher of this feed. Each
+ /// object has a type and url, both of which are required.
+ public var hubs: [JSONFeedHub]?
+
+ /// The JSONFeed items.
+ public var items: [JSONFeedItem]?
+
+ /// Publisher's custom objects.
+ ///
+ /// If you find the need to use these extensions please do so as a temporary
+ /// solution and open an issue on github so that direct support can be added
+ /// through a strongly typed model.
+ public var extensions: [String: Any?]?
+
+}
+
+// MARK: - Initializers
+
+extension JSONFeed {
+
+ convenience init?(dictionary: [String : Any?]) {
+
+ if dictionary.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.version = dictionary["version"] as? String
+ self.title = dictionary["title"] as? String
+ self.userComment = dictionary["user_comment"] as? String
+ self.homePageURL = dictionary["home_page_url"] as? String
+ self.description = dictionary["description"] as? String
+ self.feedUrl = dictionary["feed_url"] as? String
+ self.nextUrl = dictionary["next_url"] as? String
+ self.icon = dictionary["icon"] as? String
+ self.favicon = dictionary["favicon"] as? String
+ self.expired = dictionary["expired"] as? Bool
+
+ if let items = dictionary["items"] as? [[String: Any?]] {
+ self.items = items.flatMap({ (item) -> JSONFeedItem? in
+ return JSONFeedItem(dictionary: item)
+ })
+ }
+
+ if let authorDictionary = dictionary["author"] as? [String: Any] {
+ self.author = JSONFeedAuthor(dictionary: authorDictionary)
+ }
+
+ if let hubs = dictionary["hubs"] as? [[String: Any?]] {
+ self.hubs = hubs.flatMap({ (hub) -> JSONFeedHub? in
+ return JSONFeedHub(dictionary: hub)
+ })
+ }
+
+ let privateExtensionKeys = dictionary.keys.flatMap { (key) -> String? in
+ return key.hasPrefix("_") ? key : nil
+ }
+
+ if !privateExtensionKeys.isEmpty {
+ extensions = [:]
+ privateExtensionKeys.forEach { (key) in
+ extensions?[key] = dictionary[key]
+ }
+ }
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension JSONFeed: Equatable {
+
+ public static func ==(lhs: JSONFeed, rhs: JSONFeed) -> Bool {
+ return
+ lhs.title == rhs.title &&
+ lhs.version == rhs.version &&
+ lhs.title == rhs.title &&
+ lhs.userComment == rhs.userComment &&
+ lhs.homePageURL == rhs.homePageURL &&
+ lhs.description == rhs.description &&
+ lhs.feedUrl == rhs.feedUrl &&
+ lhs.nextUrl == rhs.nextUrl &&
+ lhs.icon == rhs.icon &&
+ lhs.favicon == rhs.favicon &&
+ lhs.expired == rhs.expired &&
+ lhs.items == rhs.items &&
+ lhs.author == rhs.author &&
+ lhs.hubs == rhs.hubs
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/JSON/JSONFeedAttachment.swift b/Pods/FeedKit/Sources/FeedKit/Models/JSON/JSONFeedAttachment.swift
new file mode 100644
index 0000000..1ec1418
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/JSON/JSONFeedAttachment.swift
@@ -0,0 +1,88 @@
+//
+// JSONFeedAttachment.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Describes optional attatchments of a JSON Feed item.
+public class JSONFeedAttachment {
+
+ /// (required, string) specifies the location of the attachment.
+ public var url: String?
+
+ /// (required, string) specifies the type of the attachment, such as
+ /// "audio/mpeg."
+ public var mimeType: String?
+
+ /// (optional, string) is a name for the attachment. Important: if there are
+ /// multiple attachments, and two or more have the exact same title (when title
+ /// is present), then they are considered as alternate representations of the
+ /// same thing. In this way a podcaster, for instance, might provide an audio
+ /// recording in different formats.
+ public var title: String?
+
+ /// (optional, number) specifies how large the file is.
+ public var sizeInBytes: Int?
+
+ /// (optional, number) specifies how long it takes to listen to or watch, when
+ /// played at normal speed.
+ public var durationInSeconds: TimeInterval?
+
+}
+
+// MARK: - Initializers
+
+extension JSONFeedAttachment {
+
+ convenience init?(dictionary: [String : Any?]) {
+
+ if dictionary.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.title = dictionary["title"] as? String
+ self.url = dictionary["url"] as? String
+ self.mimeType = dictionary["mime_type"] as? String
+ self.sizeInBytes = dictionary["size_in_bytes"] as? Int
+ self.durationInSeconds = dictionary["duration_in_seconds"] as? TimeInterval
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension JSONFeedAttachment: Equatable {
+
+ public static func ==(lhs: JSONFeedAttachment, rhs: JSONFeedAttachment) -> Bool {
+ return
+ lhs.title == rhs.title &&
+ lhs.url == rhs.url &&
+ lhs.mimeType == rhs.mimeType &&
+ lhs.sizeInBytes == rhs.sizeInBytes &&
+ lhs.durationInSeconds == rhs.durationInSeconds
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/JSON/JSONFeedAuthor.swift b/Pods/FeedKit/Sources/FeedKit/Models/JSON/JSONFeedAuthor.swift
new file mode 100644
index 0000000..85c2f17
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/JSON/JSONFeedAuthor.swift
@@ -0,0 +1,80 @@
+//
+// JSONFeedAuthor.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// (optional, object) specifies the feed author. The author object has several
+/// members. These are all optional - but if you provide an author object, then at
+/// least one is required:
+public class JSONFeedAuthor {
+
+ /// (optional, string) is the author's name.
+ public var name: String?
+
+ /// (optional, string) is the URL of a site owned by the author. It could be a
+ /// blog, micro-blog, Twitter account, and so on. Ideally the linked-to page
+ /// provides a way to contact the author, but that's not required. The URL
+ /// could be a mailto: link, though we suspect that will be rare.
+ public var url: String?
+
+ /// (optional, string) is the URL for an image for the author. As with icon,
+ /// it should be square and relatively large - such as 512 x 512 - and should
+ /// use transparency where appropriate, since it may be rendered on a non-white
+ /// background.
+ public var avatar: String?
+
+}
+
+// MARK: - Initializers
+
+extension JSONFeedAuthor {
+
+ convenience init?(dictionary: [String : Any?]) {
+
+ if dictionary.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.name = dictionary["name"] as? String
+ self.url = dictionary["url"] as? String
+ self.avatar = dictionary["avatar"] as? String
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension JSONFeedAuthor: Equatable {
+
+ public static func ==(lhs: JSONFeedAuthor, rhs: JSONFeedAuthor) -> Bool {
+ return
+ lhs.name == rhs.name &&
+ lhs.url == rhs.url &&
+ lhs.avatar == rhs.avatar
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/JSON/JSONFeedHub.swift b/Pods/FeedKit/Sources/FeedKit/Models/JSON/JSONFeedHub.swift
new file mode 100644
index 0000000..95b08e9
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/JSON/JSONFeedHub.swift
@@ -0,0 +1,69 @@
+//
+// JSONFeedHub.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Describes an endpoints that can be used to subscribe to real-time notifications
+/// from the publisher of this feed. Each object has a type and url, both of which
+/// are required.
+public class JSONFeedHub {
+
+ /// The protocol used to talk with the hub, such as "rssCloud" or "WebSub."
+ public var type: String?
+
+ /// The hub's url.
+ public var url: String?
+
+}
+
+// MARK: - Initializers
+
+extension JSONFeedHub {
+
+ convenience init?(dictionary: [String : Any?]) {
+
+ if dictionary.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.type = dictionary["type"] as? String
+ self.url = dictionary["url"] as? String
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension JSONFeedHub: Equatable {
+
+ public static func ==(lhs: JSONFeedHub, rhs: JSONFeedHub) -> Bool {
+ return
+ lhs.type == rhs.type &&
+ lhs.url == rhs.url
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/JSON/JSONFeedItem.swift b/Pods/FeedKit/Sources/FeedKit/Models/JSON/JSONFeedItem.swift
new file mode 100644
index 0000000..78ae197
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/JSON/JSONFeedItem.swift
@@ -0,0 +1,189 @@
+//
+// JSONFeedItem.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// An individual item of a JSON Feed, acting as a container for metadata and data
+/// associated with the item.
+public class JSONFeedItem {
+
+ /// (required, string) is unique for that item for that feed over time. If an
+ /// item is ever updated, the id should be unchanged. New items should never
+ /// use a previously-used id. If an id is presented as a number or other type,
+ /// a JSON Feed reader must coerce it to a string. Ideally, the id is the full
+ /// URL of the resource described by the item, since URLs make great unique
+ /// identifiers.
+ public var id: String?
+
+ /// (optional, string) is the URL of the resource described by the item. It's
+ /// the permalink. This may be the same as the id - but should be present
+ /// regardless.
+ public var url: String?
+
+ /// (very optional, string) is the URL of a page elsewhere. This is especially
+ /// useful for linkblogs. If url links to where you're talking about a thing,
+ /// then external_url links to the thing you're talking about.
+ public var externalUrl: String?
+
+ /// (optional, string) is plain text. Microblog items in particular may omit
+ /// titles.
+ public var title: String?
+
+ /// content_html and content_text are each optional strings - but one or both
+ /// must be present. This is the HTML or plain text of the item. Important:
+ /// the only place HTML is allowed in this format is in content_html. A
+ /// Twitter-like service might use content_text, while a blog might use
+ /// content_html. Use whichever makes sense for your resource. (It doesn't
+ /// even have to be the same for each item in a feed.)
+ public var contentText: String?
+
+ /// content_html and content_text are each optional strings - but one or both
+ /// must be present. This is the HTML or plain text of the item. Important:
+ /// the only place HTML is allowed in this format is in content_html. A
+ /// Twitter-like service might use content_text, while a blog might use
+ /// content_html. Use whichever makes sense for your resource. (It doesn't
+ /// even have to be the same for each item in a feed.)
+ public var contentHtml: String?
+
+ /// (optional, string) is a plain text sentence or two describing the item.
+ /// This might be presented in a timeline, for instance, where a detail view
+ /// would display all of content_html or content_text.
+ public var summary: String?
+
+ /// (optional, string) is the URL of the main image for the item. This image
+ /// may also appear in the content_html - if so, it's a hint to the feed reader
+ /// that this is the main, featured image. Feed readers may use the image as a
+ /// preview (probably resized as a thumbnail and placed in a timeline).
+ public var image: String?
+
+ /// (optional, string) is the URL of an image to use as a banner. Some blogging
+ /// systems (such as Medium) display a different banner image chosen to go with
+ /// each post, but that image wouldn't otherwise appear in the content_html.
+ /// A feed reader with a detail view may choose to show this banner image at
+ /// the top of the detail view, possibly with the title overlaid.
+ public var bannerImage: String?
+
+ /// (optional, string) specifies the date in RFC 3339 format.
+ /// (Example: 2010-02-07T14:04:00-05:00.)
+ public var datePublished: Date?
+
+ /// (optional, string) specifies the modification date in RFC 3339 format.
+ public var dateModified: Date?
+
+ /// (optional, object) has the same structure as the top-level author.
+ /// If not specified in an item, then the top-level author, if present, is the
+ /// author of the item.
+ public var author: JSONFeedAuthor?
+
+ /// (optional, array of strings) can have any plain text values you want. Tags
+ /// tend to be just one word, but they may be anything. Note: they are not the
+ /// equivalent of Twitter hashtags. Some blogging systems and other feed
+ /// formats call these categories.
+ public var tags: [String]?
+
+ /// (optional, array) lists related resources.
+ public var attachments: [JSONFeedAttachment]?
+
+ /// Publisher's custom objects.
+ ///
+ /// If you find the need to use these extensions please do so as a temporary
+ /// solution and open an issue on github so that direct support can be added
+ /// through a strongly typed model.
+ public var extensions: [String: Any?]?
+
+}
+
+// MARK: - Initializers
+
+extension JSONFeedItem {
+
+ convenience init?(dictionary: [String : Any?]) {
+
+ if dictionary.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.id = dictionary["id"] as? String
+ self.title = dictionary["title"] as? String
+ self.url = dictionary["url"] as? String
+ self.externalUrl = dictionary["external_url"] as? String
+ self.contentText = dictionary["content_text"] as? String
+ self.contentHtml = dictionary["content_html"] as? String
+ self.summary = dictionary["summary"] as? String
+ self.image = dictionary["image"] as? String
+ self.bannerImage = dictionary["banner_image"] as? String
+ self.datePublished = (dictionary["date_published"] as? String)?.toDate(from: .rfc3999)
+ self.dateModified = (dictionary["date_modified"] as? String)?.toDate(from: .rfc3999)
+ self.tags = dictionary["tags"] as? [String]
+
+ if let authorDictionary = dictionary["author"] as? [String: Any] {
+ self.author = JSONFeedAuthor(dictionary: authorDictionary)
+ }
+
+ if let attachments = dictionary["attachments"] as? [[String: Any?]] {
+ self.attachments = attachments.flatMap({ (attachment) -> JSONFeedAttachment? in
+ return JSONFeedAttachment(dictionary: attachment)
+ })
+ }
+
+ let privateExtensionKeys = dictionary.keys.flatMap { (key) -> String? in
+ return key.hasPrefix("_") ? key : nil
+ }
+
+ if !privateExtensionKeys.isEmpty {
+ extensions = [:]
+ privateExtensionKeys.forEach { (key) in
+ extensions?[key] = dictionary[key]
+ }
+ }
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension JSONFeedItem: Equatable {
+
+ public static func ==(lhs: JSONFeedItem, rhs: JSONFeedItem) -> Bool {
+ return
+ lhs.id == rhs.id &&
+ lhs.title == rhs.title &&
+ lhs.url == rhs.url &&
+ lhs.externalUrl == rhs.externalUrl &&
+ lhs.contentText == rhs.contentText &&
+ lhs.contentHtml == rhs.contentHtml &&
+ lhs.summary == rhs.summary &&
+ lhs.image == rhs.image &&
+ lhs.bannerImage == rhs.bannerImage &&
+ lhs.datePublished == rhs.datePublished &&
+ lhs.dateModified == rhs.dateModified &&
+ lhs.tags == rhs.tags &&
+ lhs.author == rhs.author &&
+ lhs.attachments == rhs.attachments
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Content/ContentNamespace.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Content/ContentNamespace.swift
new file mode 100755
index 0000000..e4d52c6
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Content/ContentNamespace.swift
@@ -0,0 +1,49 @@
+//
+// ContentNamespace.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// A module for the actual content of websites, in multiple formats.
+/// See http://web.resource.org/rss/1.0/modules/content/
+public class ContentNamespace {
+
+ /// An element whose contents are the entity-encoded or CDATA-escaped version
+ /// of the content of the item.
+ ///
+ /// Example:
+ /// What a beautiful day!
]]>
+ ///
+ public var contentEncoded: String?
+
+}
+
+// MARK: - Equatable
+
+extension ContentNamespace: Equatable {
+
+ public static func ==(lhs: ContentNamespace, rhs: ContentNamespace) -> Bool {
+ return lhs.contentEncoded == rhs.contentEncoded
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Dublin Core/DublinCoreNamespace.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Dublin Core/DublinCoreNamespace.swift
new file mode 100755
index 0000000..4fc92fe
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Dublin Core/DublinCoreNamespace.swift
@@ -0,0 +1,163 @@
+//
+// DublinCoreNamespace.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The Dublin Core Metadata Element Set is a standard for cross-domain
+/// resource description.
+///
+/// See https://tools.ietf.org/html/rfc5013
+public class DublinCoreNamespace {
+
+ /// A name given to the resource.
+ public var dcTitle: String?
+
+ /// An entity primarily responsible for making the content of the resource
+ ///
+ /// Examples of a Creator include a person, an organization, or a service.
+ /// Typically, the name of a Creator should be used to indicate the entity.
+ public var dcCreator: String?
+
+ /// The topic of the content of the resource
+ ///
+ /// Typically, the subject will be represented using keywords, key phrases,
+ /// or classification codes. Recommended best practice is to use a controlled
+ /// vocabulary. To describe the spatial or temporal topic of the resource,
+ /// use the Coverage element.
+ public var dcSubject: String?
+
+ /// An account of the content of the resource
+ ///
+ /// Description may include but is not limited to: an abstract, a table of
+ /// contents, a graphical representation, or a free-text account of the
+ /// resource.
+ public var dcDescription: String?
+
+ /// An entity responsible for making the resource available
+ ///
+ /// Examples of a Publisher include a person, an organization, or a service.
+ /// Typically, the name of a Publisher should be used to indicate the entity.
+ public var dcPublisher: String?
+
+ /// An entity responsible for making contributions to the content of the
+ /// resource
+ ///
+ /// Examples of a Contributor include a person, an organization, or a service.
+ /// Typically, the name of a Contributor should be used to indicate the entity.
+ public var dcContributor: String?
+
+ /// A point or period of time associated with an event in the lifecycle of the
+ /// resource.
+ ///
+ /// Date may be used to express temporal information at any level of
+ /// granularity. Recommended best practice is to use an encoding scheme, such
+ /// as the W3CDTF profile of ISO 8601 [W3CDTF].
+ public var dcDate: Date?
+
+ /// The nature or genre of the content of the resource
+ ///
+ /// Recommended best practice is to use a controlled vocabulary such as the
+ /// DCMI Type Vocabulary [DCTYPE]. To describe the file format, physical
+ /// medium, or dimensions of the resource, use the Format element.
+ public var dcType: String?
+
+ /// The file format, physical medium, or dimensions of the resource.
+ ///
+ /// Examples of dimensions include size and duration. Recommended best
+ /// practice is to use a controlled vocabulary such as the list of Internet
+ /// Media Types [MIME].
+ public var dcFormat: String?
+
+ /// An unambiguous reference to the resource within a given context.
+ ///
+ /// Recommended best practice is to identify the resource by means of a string
+ /// conforming to a formal identification system.
+ public var dcIdentifier: String?
+
+ /// A Reference to a resource from which the present resource is derived
+ ///
+ /// The described resource may be derived from the related resource in whole
+ /// or in part. Recommended best practice is to identify the related resource
+ /// by means of a string conforming to a formal identification system.
+ public var dcSource: String?
+
+ /// A language of the resource.
+ ///
+ /// Recommended best practice is to use a controlled vocabulary such as
+ /// RFC 4646 [RFC4646].
+ public var dcLanguage: String?
+
+ /// A related resource.
+ ///
+ /// Recommended best practice is to identify the related resource by means of
+ /// a string conforming to a formal identification system.
+ public var dcRelation: String?
+
+ /// The spatial or temporal topic of the resource, the spatial applicability
+ /// of the resource, or the jurisdiction under which the resource is
+ /// relevant.
+ ///
+ /// Spatial topic and spatial applicability may be a named place or a location
+ /// specified by its geographic coordinates. Temporal topic may be a named
+ /// period, date, or date range. A jurisdiction may be a named administrative
+ /// entity or a geographic place to which the resource applies. Recommended
+ /// best practice is to use a controlled vocabulary such as the Thesaurus of
+ /// Geographic Names [TGN]. Where appropriate, named places or time periods
+ /// can be used in preference to numeric identifiers such as sets of
+ /// coordinates or date ranges.
+ public var dcCoverage: String?
+
+ /// Information about rights held in and over the resource.
+ ///
+ /// Typically, rights information includes a statement about various property
+ /// rights associated with the resource, including intellectual property
+ /// rights.
+ public var dcRights: String?
+
+}
+
+// MARK: - Equatable
+
+extension DublinCoreNamespace: Equatable {
+
+ public static func ==(lhs: DublinCoreNamespace, rhs: DublinCoreNamespace) -> Bool {
+ return
+ lhs.dcTitle == rhs.dcTitle &&
+ lhs.dcCreator == rhs.dcCreator &&
+ lhs.dcSubject == rhs.dcSubject &&
+ lhs.dcDescription == rhs.dcDescription &&
+ lhs.dcPublisher == rhs.dcPublisher &&
+ lhs.dcContributor == rhs.dcContributor &&
+ lhs.dcDate == rhs.dcDate &&
+ lhs.dcType == rhs.dcType &&
+ lhs.dcFormat == rhs.dcFormat &&
+ lhs.dcIdentifier == rhs.dcIdentifier &&
+ lhs.dcSource == rhs.dcSource &&
+ lhs.dcLanguage == rhs.dcLanguage &&
+ lhs.dcRelation == rhs.dcRelation &&
+ lhs.dcCoverage == rhs.dcCoverage &&
+ lhs.dcRights == rhs.dcRights
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaCategory.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaCategory.swift
new file mode 100644
index 0000000..394947b
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaCategory.swift
@@ -0,0 +1,102 @@
+//
+// MediaCategory.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Allows a taxonomy to be set that gives an indication of the type of media
+/// content, and its particular contents. It has two optional attributes.
+public class MediaCategory {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// The URI that identifies the categorization scheme. It is an optional
+ /// attribute. If this attribute is not included, the default scheme
+ /// is "http://search.yahoo.com/mrss/category_schema".
+ public var scheme: String?
+
+ /// The human readable label that can be displayed in end user
+ /// applications. It is an optional attribute.
+ public var label: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension MediaCategory {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaCategory.Attributes(attributes: attributeDict)
+ }
+
+}
+
+
+extension MediaCategory.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.scheme = attributeDict["scheme"]
+ self.label = attributeDict["label"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaCategory: Equatable {
+
+ public static func ==(lhs: MediaCategory, rhs: MediaCategory) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaCategory.Attributes: Equatable {
+
+ public static func ==(lhs: MediaCategory.Attributes, rhs: MediaCategory.Attributes) -> Bool {
+ return
+ lhs.scheme == rhs.scheme &&
+ lhs.label == rhs.label
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaCommunity.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaCommunity.swift
new file mode 100644
index 0000000..b351021
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaCommunity.swift
@@ -0,0 +1,60 @@
+//
+// MediaCommunity.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// This element stands for the community related content. This allows
+/// inclusion of the user perception about a media object in the form of view
+/// count, ratings and tags.
+public class MediaCommunity {
+
+ /// This element specifies the rating-related information about a media object.
+ /// Valid attributes are average, count, min and max.
+ public var mediaStarRating: MediaStarRating?
+
+ /// This element specifies various statistics about a media object like the
+ /// view count and the favorite count. Valid attributes are views and favorites.
+ public var mediaStatistics: MediaStatistics?
+
+ /// This element contains user-generated tags separated by commas in the
+ /// decreasing order of each tag's weight. Each tag can be assigned an integer
+ /// weight in tag_name:weight format. It's up to the provider to choose the way
+ /// weight is determined for a tag; for example, number of occurences can be
+ /// one way to decide weight of a particular tag. Default weight is 1.
+ public var mediaTags: [MediaTag]?
+
+}
+
+// MARK: - Equatable
+
+extension MediaCommunity: Equatable {
+
+ public static func ==(lhs: MediaCommunity, rhs: MediaCommunity) -> Bool {
+ return
+ lhs.mediaStarRating == rhs.mediaStarRating &&
+ lhs.mediaStatistics == rhs.mediaStatistics &&
+ lhs.mediaTags == rhs.mediaTags
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaContent.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaContent.swift
new file mode 100644
index 0000000..57da7aa
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaContent.swift
@@ -0,0 +1,176 @@
+//
+// MediaContent.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// is a sub-element of either - or .
+/// Media objects that are not the same content should not be included
+/// in the same element. The sequence of these items implies
+/// the order of presentation. While many of the attributes appear to be
+/// audio/video specific, this element can be used to publish any type of
+/// media. It contains 14 attributes, most of which are optional.
+public class MediaContent {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// Should specify the direct URL to the media object. If not included,
+ /// a element must be specified.
+ public var url: String?
+
+ /// The number of bytes of the media object. It is an optional
+ /// attribute.
+ public var fileSize: Int?
+
+ /// The standard MIME type of the object. It is an optional attribute.
+ public var type: String?
+
+ /// Tpe of object (image | audio | video | document | executable).
+ /// 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.
+ public var medium: String?
+
+ /// Determines if this is the default object that should be used for
+ /// the . There should only be one default object per
+ /// . It is an optional attribute.
+ public var isDefault: Bool?
+
+ /// Determines if the object is a sample or the full version of the
+ /// object, or even if it is a continuous stream (sample | full | nonstop).
+ /// Default value is "full". It is an optional attribute.
+ public var expression: String?
+
+ /// The kilobits per second rate of media. It is an optional attribute.
+ public var bitrate: Int?
+
+ /// The number of frames per second for the media object. It is an
+ /// optional attribute.
+ public var framerate: Double?
+
+ /// The number of samples per second taken to create the media object.
+ /// It is expressed in thousands of samples per second (kHz).
+ /// It is an optional attribute.
+ public var samplingrate: Double?
+
+ /// The number of audio channels in the media object. It is an
+ /// optional attribute.
+ public var channels: Int?
+
+ /// The number of seconds the media object plays. It is an
+ /// optional attribute.
+ public var duration: Int?
+
+ /// The height of the media object. It is an optional attribute.
+ public var height: Int?
+
+ /// The width of the media object. It is an optional attribute.
+ public var width: Int?
+
+ /// The primary language encapsulated in the media object.
+ /// Language codes possible are detailed in RFC 3066. This attribute
+ /// is used similar to the xml:lang attribute detailed in the
+ /// XML 1.0 Specification (Third Edition). It is an optional
+ /// attribute.
+ public var lang: String?
+
+ }
+
+ /// The element's attributes
+ public var attributes: Attributes?
+
+}
+
+// MARK: - Initializers
+
+extension MediaContent {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaContent.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension MediaContent.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.url = attributeDict["url"]
+ self.fileSize = Int(attributeDict["fileSize"] ?? "")
+ self.type = attributeDict["type"]
+ self.medium = attributeDict["medium"]
+ self.isDefault = attributeDict["isDefault"]?.toBool()
+ self.expression = attributeDict["expression"]
+ self.bitrate = Int(attributeDict["bitrate"] ?? "")
+ self.framerate = Double(attributeDict["framerate"] ?? "")
+ self.samplingrate = Double(attributeDict["samplingrate"] ?? "")
+ self.channels = Int(attributeDict["channels"] ?? "")
+ self.duration = Int(attributeDict["duration"] ?? "")
+ self.height = Int(attributeDict["height"] ?? "")
+ self.width = Int(attributeDict["width"] ?? "")
+ self.lang = attributeDict["lang"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaContent: Equatable {
+
+ public static func ==(lhs: MediaContent, rhs: MediaContent) -> Bool {
+ return lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaContent.Attributes: Equatable {
+
+ public static func ==(lhs: MediaContent.Attributes, rhs: MediaContent.Attributes) -> Bool {
+ return
+ lhs.bitrate == rhs.bitrate &&
+ lhs.channels == rhs.channels &&
+ lhs.duration == rhs.duration &&
+ lhs.expression == rhs.expression &&
+ lhs.isDefault == rhs.isDefault &&
+ lhs.fileSize == rhs.fileSize &&
+ lhs.framerate == rhs.framerate &&
+ lhs.height == rhs.height &&
+ lhs.lang == rhs.lang &&
+ lhs.medium == rhs.medium &&
+ lhs.samplingrate == rhs.samplingrate &&
+ lhs.type == rhs.type &&
+ lhs.url == rhs.url &&
+ lhs.width == rhs.width
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaCopyright.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaCopyright.swift
new file mode 100644
index 0000000..ed3a73f
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaCopyright.swift
@@ -0,0 +1,95 @@
+//
+// MediaCopyright.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Copyright information for the media object. It has one optional attribute.
+public class MediaCopyright {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// The URL for a terms of use page or additional copyright information.
+ /// If the media is operating under a Creative Commons license, the
+ /// Creative Commons module should be used instead. It is an optional
+ /// attribute.
+ public var url: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension MediaCopyright {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaCopyright.Attributes(attributes: attributeDict)
+ }
+
+}
+
+
+extension MediaCopyright.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.url = attributeDict["url"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaCopyright: Equatable {
+
+ public static func ==(lhs: MediaCopyright, rhs: MediaCopyright) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaCopyright.Attributes: Equatable {
+
+ public static func ==(lhs: MediaCopyright.Attributes, rhs: MediaCopyright.Attributes) -> Bool {
+ return lhs.url == rhs.url
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaCredit.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaCredit.swift
new file mode 100644
index 0000000..1e69625
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaCredit.swift
@@ -0,0 +1,107 @@
+//
+// MediaCredit.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Notable entity and the contribution to the creation of the media object.
+/// Current entities can include people, companies, locations, etc. Specific
+/// entities can have multiple roles, and several entities can have the same
+/// role. These should appear as distinct elements. It has two
+/// optional attributes.
+public class MediaCredit {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// Specifies the role the entity played. Must be lowercase. It is an
+ /// optional attribute.
+ public var role: String?
+
+ /// The URI that identifies the role scheme. It is an optional attribute
+ /// and possible values for this attribute are ( urn:ebu | urn:yvs ) . The
+ /// default scheme is "urn:ebu". The list of roles supported under urn:ebu
+ /// scheme can be found at European Broadcasting Union Role Codes. The
+ /// roles supported under urn:yvs scheme are ( uploader | owner ).
+ public var scheme: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension MediaCredit {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaCredit.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension MediaCredit.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.role = attributeDict["role"]
+ self.scheme = attributeDict["scheme"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaCredit: Equatable {
+
+ public static func ==(lhs: MediaCredit, rhs: MediaCredit) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaCredit.Attributes: Equatable {
+
+ public static func ==(lhs: MediaCredit.Attributes, rhs: MediaCredit.Attributes) -> Bool {
+ return
+ lhs.role == rhs.role &&
+ lhs.scheme == rhs.scheme
+ }
+
+}
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaDescription.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaDescription.swift
new file mode 100644
index 0000000..af6462a
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaDescription.swift
@@ -0,0 +1,94 @@
+//
+// MediaDescription.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Short description describing the media object typically a sentence in
+/// length. It has one optional attribute.
+public class MediaDescription {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// Specifies the type of text embedded. Possible values are either "plain" or "html".
+ /// Default value is "plain". All HTML must be entity-encoded. It is an optional attribute.
+ public var type: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension MediaDescription {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaDescription.Attributes(attributes: attributeDict)
+ }
+
+}
+
+
+extension MediaDescription.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.type = attributeDict["type"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaDescription: Equatable {
+
+ public static func ==(lhs: MediaDescription, rhs: MediaDescription) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaDescription.Attributes: Equatable {
+
+ public static func ==(lhs: MediaDescription.Attributes, rhs: MediaDescription.Attributes) -> Bool {
+ return lhs.type == rhs.type
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaEmbed.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaEmbed.swift
new file mode 100644
index 0000000..1fac259
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaEmbed.swift
@@ -0,0 +1,104 @@
+//
+// MediaEmbed.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Sometimes player-specific embed code is needed for a player to play any
+/// video. allows inclusion of such information in the form of
+/// key-value pairs.
+public class MediaEmbed {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// The location of the embeded media.
+ public var url: String?
+
+ /// The width size for the embeded Media.
+ public var width: Int?
+
+ /// The height size for the embeded Media.
+ public var height: Int?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// Key-Value pairs with aditional parameters for the embeded Media.
+ public var mediaParams: [MediaParam]?
+
+}
+
+// MARK: - Initializers
+
+extension MediaEmbed {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaEmbed.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension MediaEmbed.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.url = attributeDict["url"]
+ self.width = Int(attributeDict["width"] ?? "")
+ self.height = Int(attributeDict["height"] ?? "")
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaEmbed: Equatable {
+
+ public static func ==(lhs: MediaEmbed, rhs: MediaEmbed) -> Bool {
+ return
+ lhs.mediaParams == rhs.mediaParams &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaEmbed.Attributes: Equatable {
+
+ public static func ==(lhs: MediaEmbed.Attributes, rhs: MediaEmbed.Attributes) -> Bool {
+ return
+ lhs.url == rhs.url &&
+ lhs.width == rhs.width &&
+ lhs.height == rhs.height
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaGroup.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaGroup.swift
new file mode 100644
index 0000000..99ee530
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaGroup.swift
@@ -0,0 +1,72 @@
+//
+// MediaGroup.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The element is a sub-element of
- . It allows grouping
+/// of elements that are effectively the same content,
+/// yet different representations. For instance: the same song recorded
+/// in both the WAV and MP3 format. It's an optional element that must
+/// only be used for this purpose.
+public class MediaGroup {
+
+ /// is a sub-element of either
- or .
+ /// Media objects that are not the same content should not be included
+ /// in the same element. The sequence of these items implies
+ /// the order of presentation. While many of the attributes appear to be
+ /// audio/video specific, this element can be used to publish any type of
+ /// media. It contains 14 attributes, most of which are optional.
+ public var mediaContents: [MediaContent]?
+
+ /// Notable entity and the contribution to the creation of the media object.
+ /// Current entities can include people, companies, locations, etc. Specific
+ /// entities can have multiple roles, and several entities can have the same
+ /// role. These should appear as distinct elements. It has two
+ /// optional attributes.
+ public var mediaCredits: [MediaCredit]?
+
+ /// Allows a taxonomy to be set that gives an indication of the type of media
+ /// content, and its particular contents. It has two optional attributes.
+ public var mediaCategory: MediaCategory?
+
+ /// This allows the permissible audience to be declared. If this element is not
+ /// included, it assumes that no restrictions are necessary. It has one
+ /// optional attribute.
+ public var mediaRating: MediaRating?
+
+}
+
+// MARK: - Equatable
+
+extension MediaGroup: Equatable {
+
+ public static func ==(lhs: MediaGroup, rhs: MediaGroup) -> Bool {
+ return
+ lhs.mediaContents == rhs.mediaContents &&
+ lhs.mediaCredits == rhs.mediaCredits &&
+ lhs.mediaCategory == rhs.mediaCategory &&
+ lhs.mediaRating == rhs.mediaRating
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaHash.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaHash.swift
new file mode 100644
index 0000000..397dfcf
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaHash.swift
@@ -0,0 +1,95 @@
+//
+// MediaHash.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// This is the hash of the binary media file. It can appear multiple times as
+/// long as each instance is a different algo. It has one optional attribute.
+public class MediaHash {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// This is the hash of the binary media file. It can appear multiple times as long as
+ /// each instance is a different algo. It has one optional attribute.
+ public var algo: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension MediaHash {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaHash.Attributes(attributes: attributeDict)
+ }
+
+}
+
+
+extension MediaHash.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.algo = attributeDict["algo"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaHash: Equatable {
+
+ public static func ==(lhs: MediaHash, rhs: MediaHash) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaHash.Attributes: Equatable {
+
+ public static func ==(lhs: MediaHash.Attributes, rhs: MediaHash.Attributes) -> Bool {
+ return lhs.algo == rhs.algo
+ }
+
+}
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaLicence.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaLicence.swift
new file mode 100644
index 0000000..01ee611
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaLicence.swift
@@ -0,0 +1,99 @@
+//
+// MediaLicence.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Optional link to specify the machine-readable license associated with the
+/// content.
+public class MediaLicence {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// The licence type.
+ public var type: String?
+
+ /// The location of the licence.
+ public var href: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension MediaLicence {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaLicence.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension MediaLicence.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.type = attributeDict["type"]
+ self.href = attributeDict["href"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaLicence: Equatable {
+
+ public static func ==(lhs: MediaLicence, rhs: MediaLicence) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaLicence.Attributes: Equatable {
+
+ public static func ==(lhs: MediaLicence.Attributes, rhs: MediaLicence.Attributes) -> Bool {
+ return
+ lhs.type == rhs.type &&
+ lhs.href == rhs.href
+ }
+
+}
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaLocation.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaLocation.swift
new file mode 100644
index 0000000..df6eed3
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaLocation.swift
@@ -0,0 +1,126 @@
+//
+// MediaLocation.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Optional element to specify geographical information about various
+/// locations captured in the content of a media object. The format conforms
+/// to geoRSS.
+public class MediaLocation {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// Description of the place whose location is being specified.
+ public var description: String?
+
+ /// Time at which the reference to a particular location starts in the
+ /// media object.
+ public var start: TimeInterval?
+
+ /// Time at which the reference to a particular location ends in the media
+ /// object.
+ public var end: TimeInterval?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The geoRSS's location latitude.
+ public var latitude: Double?
+
+ /// The geoRSS's location longitude.
+ public var longitude: Double?
+
+}
+
+// MARK: - Initializers
+
+extension MediaLocation {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaLocation.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension MediaLocation.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.description = attributeDict["description"]
+ self.start = attributeDict["start"]?.toDuration()
+ self.end = attributeDict["end"]?.toDuration()
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaLocation: Equatable {
+
+ public static func ==(lhs: MediaLocation, rhs: MediaLocation) -> Bool {
+ return
+ lhs.latitude == rhs.latitude &&
+ lhs.longitude == rhs.longitude &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaLocation.Attributes: Equatable {
+
+ public static func ==(lhs: MediaLocation.Attributes, rhs: MediaLocation.Attributes) -> Bool {
+ return
+ lhs.description == rhs.description &&
+ lhs.start == rhs.start &&
+ lhs.end == rhs.end
+ }
+
+}
+
+// MARK: - Helpers
+
+extension MediaLocation {
+
+ func mapFrom(latLng: String) {
+
+ let components = latLng.components(separatedBy: " ")
+ if components.count == 2 {
+ self.latitude = Double(components.first ?? "")
+ self.longitude = Double(components.last ?? "")
+ }
+
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaNamespace.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaNamespace.swift
new file mode 100644
index 0000000..af08d24
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaNamespace.swift
@@ -0,0 +1,215 @@
+//
+// MediaNamespace.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Media RSS is a new RSS module that supplements the
+/// capabilities of RSS 2.0. RSS enclosures are already being used to
+/// syndicate audio files and images. Media RSS extends enclosures to
+/// handle other media types, such as short films or TV, as well as
+/// provide additional metadata with the media. Media RSS enables
+/// content publishers and bloggers to syndicate multimedia content
+/// such as TV and video clips, movies, images and audio.
+public class MediaNamespace {
+
+ /// The element is a sub-element of
- . It allows grouping
+ /// of elements that are effectively the same content,
+ /// yet different representations. For instance: the same song recorded
+ /// in both the WAV and MP3 format. It's an optional element that must
+ /// only be used for this purpose.
+ public var mediaGroup: MediaGroup?
+
+ /// is a sub-element of either
- or .
+ /// Media objects that are not the same content should not be included
+ /// in the same element. The sequence of these items implies
+ /// the order of presentation. While many of the attributes appear to be
+ /// audio/video specific, this element can be used to publish any type of
+ /// media. It contains 14 attributes, most of which are optional.
+ public var mediaContents: [MediaContent]?
+
+ /// This allows the permissible audience to be declared. If this element is not
+ /// included, it assumes that no restrictions are necessary. It has one
+ /// optional attribute.
+ public var mediaRating: MediaRating?
+
+ /// The title of the particular media object. It has one optional attribute.
+ public var mediaTitle: MediaTitle?
+
+ /// Short description describing the media object typically a sentence in
+ /// length. It has one optional attribute.
+ public var mediaDescription: MediaDescription?
+
+ /// Highly relevant keywords describing the media object with typically a
+ /// maximum of 10 words. The keywords and phrases should be comma-delimited.
+ public var mediaKeywords: [String]?
+
+ /// Allows particular images to be used as representative images for the
+ /// media object. If multiple thumbnails are included, and time coding is not
+ /// at play, it is assumed that the images are in order of importance. It has
+ /// one required attribute and three optional attributes.
+ public var mediaThumbnails: [MediaThumbnail]?
+
+ /// Allows a taxonomy to be set that gives an indication of the type of media
+ /// content, and its particular contents. It has two optional attributes.
+ public var mediaCategory: MediaCategory?
+
+ /// This is the hash of the binary media file. It can appear multiple times as
+ /// long as each instance is a different algo. It has one optional attribute.
+ public var mediaHash: MediaHash?
+
+ /// Allows the media object to be accessed through a web browser media player
+ /// console. This element is required only if a direct media url attribute is
+ /// not specified in the element. It has one required attribute
+ /// and two optional attributes.
+ public var mediaPlayer: MediaPlayer?
+
+ /// Notable entity and the contribution to the creation of the media object.
+ /// Current entities can include people, companies, locations, etc. Specific
+ /// entities can have multiple roles, and several entities can have the same
+ /// role. These should appear as distinct elements. It has two
+ /// optional attributes.
+ public var mediaCredits: [MediaCredit]?
+
+ /// Copyright information for the media object. It has one optional attribute.
+ public var mediaCopyright: MediaCopyright?
+
+ /// Allows the inclusion of a text transcript, closed captioning or lyrics of
+ /// the media content. Many of these elements are permitted to provide a time
+ /// series of text. In such cases, it is encouraged, but not required, that the
+ /// elements be grouped by language and appear in time sequence order based on
+ /// the start time. Elements can have overlapping start and end times. It has
+ /// four optional attributes.
+ public var mediaText: MediaText?
+
+ /// Allows restrictions to be placed on the aggregator rendering the media in
+ /// the feed. Currently, restrictions are based on distributor (URI), country
+ /// codes and sharing of a media object. This element is purely informational
+ /// and no obligation can be assumed or implied. Only one
+ /// element of the same type can be applied to a media object -- all others
+ /// will be ignored. Entities in this element should be space-separated.
+ /// To allow the producer to explicitly declare his/her intentions, two
+ /// literals are reserved: "all", "none". These literals can only be used once.
+ /// This element has one required attribute and one optional attribute (with
+ /// strict requirements for its exclusion).
+ public var mediaRestriction: MediaRestriction?
+
+ /// This element stands for the community related content. This allows
+ /// inclusion of the user perception about a media object in the form of view
+ /// count, ratings and tags.
+ public var mediaCommunity: MediaCommunity?
+
+ /// Allows inclusion of all the comments a media object has received.
+ public var mediaComments: [String]?
+
+ /// Sometimes player-specific embed code is needed for a player to play any
+ /// video. allows inclusion of such information in the form of
+ /// key-value pairs.
+ public var mediaEmbed: MediaEmbed?
+
+ /// Allows inclusion of a list of all media responses a media object has
+ /// received.
+ public var mediaResponses: [String]?
+
+ /// Allows inclusion of all the URLs pointing to a media object.
+ public var mediaBackLinks: [String]?
+
+ /// Optional tag to specify the status of a media object -- whether it's still
+ /// active or it has been blocked/deleted.
+ public var mediaStatus: MediaStatus?
+
+ /// Optional tag to include pricing information about a media object. If this
+ /// tag is not present, the media object is supposed to be free. One media
+ /// object can have multiple instances of this tag for including different
+ /// pricing structures. The presence of this tag would mean that media object
+ /// is not free.
+ public var mediaPrices: [MediaPrice]?
+
+ /// Optional link to specify the machine-readable license associated with the
+ /// content.
+ public var mediaLicense: MediaLicence?
+
+ /// Optional link to specify the machine-readable license associated with the
+ /// content.
+ public var mediaSubTitle: MediaSubTitle?
+
+ /// Optional element for P2P link.
+ public var mediaPeerLink: MediaPeerLink?
+
+ /// Optional element to specify geographical information about various
+ /// locations captured in the content of a media object. The format conforms
+ /// to geoRSS.
+ public var mediaLocation: MediaLocation?
+
+ /// Optional element to specify the rights information of a media object.
+ public var mediaRights: MediaRights?
+
+ /// Optional element to specify various scenes within a media object. It can
+ /// have multiple child elements, where each
+ /// element contains information about a particular scene. has
+ /// the optional sub-elements , ,
+ /// and , which contains title, description,
+ /// start and end time of a particular scene in the media, respectively.
+ public var mediaScenes: [MediaScene]?
+
+
+}
+
+// MARK: - Equatable
+
+extension MediaNamespace: Equatable {
+
+ public static func ==(lhs: MediaNamespace, rhs: MediaNamespace) -> Bool {
+ return
+ lhs.mediaGroup == rhs.mediaGroup &&
+ lhs.mediaContents == rhs.mediaContents &&
+ lhs.mediaRating == rhs.mediaRating &&
+ lhs.mediaTitle == rhs.mediaTitle &&
+ lhs.mediaDescription == rhs.mediaDescription &&
+ lhs.mediaKeywords == rhs.mediaKeywords &&
+ lhs.mediaThumbnails == rhs.mediaThumbnails &&
+ lhs.mediaCategory == rhs.mediaCategory &&
+ lhs.mediaHash == rhs.mediaHash &&
+ lhs.mediaPlayer == rhs.mediaPlayer &&
+ lhs.mediaCredits == rhs.mediaCredits &&
+ lhs.mediaCopyright == rhs.mediaCopyright &&
+ lhs.mediaText == rhs.mediaText &&
+ lhs.mediaRestriction == rhs.mediaRestriction &&
+ lhs.mediaCommunity == rhs.mediaCommunity &&
+ lhs.mediaComments == rhs.mediaComments &&
+ lhs.mediaEmbed == rhs.mediaEmbed &&
+ lhs.mediaResponses == rhs.mediaResponses &&
+ lhs.mediaBackLinks == rhs.mediaBackLinks &&
+ lhs.mediaStatus == rhs.mediaStatus &&
+ lhs.mediaPrices == rhs.mediaPrices &&
+ lhs.mediaLicense == rhs.mediaLicense &&
+ lhs.mediaSubTitle == rhs.mediaSubTitle &&
+ lhs.mediaPeerLink == rhs.mediaPeerLink &&
+ lhs.mediaLocation == rhs.mediaLocation &&
+ lhs.mediaRights == rhs.mediaRights &&
+ lhs.mediaScenes == rhs.mediaScenes
+ }
+
+}
+
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaParam.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaParam.swift
new file mode 100644
index 0000000..ffa5fd1
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaParam.swift
@@ -0,0 +1,92 @@
+//
+// MediaParam.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Key-Value pairs with aditional parameters for the embeded Media.
+public class MediaParam {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// The parameter's key name.
+ public var name: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension MediaParam {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaParam.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension MediaParam.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.name = attributeDict["name"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaParam: Equatable {
+
+ public static func ==(lhs: MediaParam, rhs: MediaParam) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaParam.Attributes: Equatable {
+
+ public static func ==(lhs: MediaParam.Attributes, rhs: MediaParam.Attributes) -> Bool {
+ return lhs.name == rhs.name
+ }
+
+}
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaPeerLink.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaPeerLink.swift
new file mode 100644
index 0000000..b243f8b
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaPeerLink.swift
@@ -0,0 +1,98 @@
+//
+// MediaPeerLink.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Optional element for P2P link.
+public class MediaPeerLink {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// The peer link's type.
+ public var type: String?
+
+ /// The location of the peer link provider.
+ public var href: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension MediaPeerLink {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaPeerLink.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension MediaPeerLink.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.type = attributeDict["type"]
+ self.href = attributeDict["href"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaPeerLink: Equatable {
+
+ public static func ==(lhs: MediaPeerLink, rhs: MediaPeerLink) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaPeerLink.Attributes: Equatable {
+
+ public static func ==(lhs: MediaPeerLink.Attributes, rhs: MediaPeerLink.Attributes) -> Bool {
+ return
+ lhs.type == rhs.type &&
+ lhs.href == rhs.href
+ }
+
+}
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaPlayer.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaPlayer.swift
new file mode 100644
index 0000000..9421a9c
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaPlayer.swift
@@ -0,0 +1,108 @@
+//
+// MediaPlayer.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Allows the media object to be accessed through a web browser media player
+/// console. This element is required only if a direct media url attribute is
+/// not specified in the element. It has one required attribute
+/// and two optional attributes.
+public class MediaPlayer {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// The URL of the player console that plays the media. It is a required attribute.
+ public var url: String?
+
+ /// The width of the browser window that the URL should be opened in. It is
+ /// an optional attribute.
+ public var width: Int?
+
+ /// The height of the browser window that the URL should be opened in. It is an
+ /// optional attribute.
+ public var height: Int?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension MediaPlayer {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaPlayer.Attributes(attributes: attributeDict)
+ }
+
+}
+
+
+extension MediaPlayer.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.url = attributeDict["algo"]
+ self.height = Int(attributeDict["height"] ?? "")
+ self.width = Int(attributeDict["width"] ?? "")
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaPlayer: Equatable {
+
+ public static func ==(lhs: MediaPlayer, rhs: MediaPlayer) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaPlayer.Attributes: Equatable {
+
+ public static func ==(lhs: MediaPlayer.Attributes, rhs: MediaPlayer.Attributes) -> Bool {
+ return
+ lhs.width == rhs.width &&
+ lhs.height == rhs.height &&
+ lhs.url == rhs.url
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaPrice.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaPrice.swift
new file mode 100644
index 0000000..14bc8a2
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaPrice.swift
@@ -0,0 +1,114 @@
+//
+// MediaPrice.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Optional tag to include pricing information about a media object. If this
+/// tag is not present, the media object is supposed to be free. One media
+/// object can have multiple instances of this tag for including different
+/// pricing structures. The presence of this tag would mean that media object
+/// is not free.
+public class MediaPrice {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// Valid values are "rent", "purchase", "package" or "subscription". If
+ /// nothing is specified, then the media is free.
+ public var type: String?
+
+ /// The price of the media object. This is an optional attribute.
+ public var price: Double?
+
+ /// If the type is "package" or "subscription", then info is a URL pointing
+ /// to package or subscription information. This is an optional attribute.
+ public var info: String?
+
+ /// Use [ISO 4217] for currency codes. This is an optional attribute.
+ public var currency: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension MediaPrice {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaPrice.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension MediaPrice.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.type = attributeDict["type"]
+ self.price = Double(attributeDict["price"] ?? "")
+ self.info = attributeDict["info"]
+ self.currency = attributeDict["currency"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaPrice: Equatable {
+
+ public static func ==(lhs: MediaPrice, rhs: MediaPrice) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaPrice.Attributes: Equatable {
+
+ public static func ==(lhs: MediaPrice.Attributes, rhs: MediaPrice.Attributes) -> Bool {
+ return
+ lhs.type == rhs.type &&
+ lhs.price == rhs.price &&
+ lhs.info == rhs.info &&
+ lhs.currency == rhs.currency
+ }
+
+}
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaRating.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaRating.swift
new file mode 100644
index 0000000..afd8b2b
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaRating.swift
@@ -0,0 +1,96 @@
+//
+// MediaRating.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// This allows the permissible audience to be declared. If this element is not
+/// included, it assumes that no restrictions are necessary. It has one optional
+/// attribute.
+public class MediaRating {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// The URI that identifies the rating scheme. It is an optional attribute.
+ /// If this attribute is not included, the default scheme is urn:simple (adult | nonadult).
+ public var scheme: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension MediaRating {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaRating.Attributes(attributes: attributeDict)
+ }
+
+}
+
+
+extension MediaRating.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.scheme = attributeDict["scheme"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaRating: Equatable {
+
+ public static func ==(lhs: MediaRating, rhs: MediaRating) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaRating.Attributes: Equatable {
+
+ public static func ==(lhs: MediaRating.Attributes, rhs: MediaRating.Attributes) -> Bool {
+ return lhs.scheme == rhs.scheme
+ }
+
+}
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaRestriction.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaRestriction.swift
new file mode 100644
index 0000000..9f319e5
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaRestriction.swift
@@ -0,0 +1,116 @@
+//
+// MediaRestriction.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Allows restrictions to be placed on the aggregator rendering the media in
+/// the feed. Currently, restrictions are based on distributor (URI), country
+/// codes and sharing of a media object. This element is purely informational
+/// and no obligation can be assumed or implied. Only one
+/// element of the same type can be applied to a media object -- all others
+/// will be ignored. Entities in this element should be space-separated.
+/// To allow the producer to explicitly declare his/her intentions, two
+/// literals are reserved: "all", "none". These literals can only be used once.
+/// This element has one required attribute and one optional attribute (with
+/// strict requirements for its exclusion).
+public class MediaRestriction {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// Indicates the type of relationship that the restriction represents
+ /// (allow | deny). In the example above, the media object should only be
+ /// syndicated in Australia and the United States. It is a required
+ /// attribute.
+ ///
+ /// Note: If the "allow" element is empty and the type of relationship is
+ /// "allow", it is assumed that the empty list means "allow nobody" and
+ /// the media should not be syndicated.
+ public var relationship: String?
+
+ /// Specifies the type of restriction (country | uri | sharing ) that the
+ /// media can be syndicated. It is an optional attribute; however can only
+ /// be excluded when using one of the literal values "all" or "none".
+ public var type: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension MediaRestriction {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaRestriction.Attributes(attributes: attributeDict)
+ }
+
+}
+
+
+extension MediaRestriction.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.relationship = attributeDict["relationship"]
+ self.type = attributeDict["type"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaRestriction: Equatable {
+
+ public static func ==(lhs: MediaRestriction, rhs: MediaRestriction) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaRestriction.Attributes: Equatable {
+
+ public static func ==(lhs: MediaRestriction.Attributes, rhs: MediaRestriction.Attributes) -> Bool {
+ return
+ lhs.relationship == rhs.relationship &&
+ lhs.type == rhs.type
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaRights.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaRights.swift
new file mode 100644
index 0000000..aaf24fe
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaRights.swift
@@ -0,0 +1,89 @@
+//
+// MediaRights.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Optional element to specify the rights information of a media object.
+public class MediaRights {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// Is the status of the media object saying whether a media object has
+ /// been created by the publisher or they have rights to circulate it.
+ /// Supported values are "userCreated" and "official".
+ public var status: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+
+}
+
+// MARK: - Initializers
+
+extension MediaRights {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaRights.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension MediaRights.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.status = attributeDict["status"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaRights: Equatable {
+
+ public static func ==(lhs: MediaRights, rhs: MediaRights) -> Bool {
+ return lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaRights.Attributes: Equatable {
+
+ public static func ==(lhs: MediaRights.Attributes, rhs: MediaRights.Attributes) -> Bool {
+ return lhs.status == rhs.status
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaScene.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaScene.swift
new file mode 100644
index 0000000..2025954
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaScene.swift
@@ -0,0 +1,61 @@
+//
+// MediaScene.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Optional element to specify various scenes within a media object. It can
+/// have multiple child elements, where each
+/// element contains information about a particular scene. has
+/// the optional sub-elements , ,
+/// and , which contains title, description,
+/// start and end time of a particular scene in the media, respectively.
+public class MediaScene {
+
+ /// The scene's title.
+ public var sceneTitle: String?
+
+ /// The scene's description.
+ public var sceneDescription: String?
+
+ /// The scene's start time.
+ public var sceneStartTime: TimeInterval?
+
+ /// The scene's end time.
+ public var sceneEndTime: TimeInterval?
+
+}
+
+// MARK: - Equatable
+
+extension MediaScene: Equatable {
+
+ public static func ==(lhs: MediaScene, rhs: MediaScene) -> Bool {
+ return
+ lhs.sceneTitle == rhs.sceneTitle &&
+ lhs.sceneDescription == rhs.sceneDescription &&
+ lhs.sceneStartTime == rhs.sceneStartTime &&
+ lhs.sceneEndTime == rhs.sceneEndTime
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaStarRating.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaStarRating.swift
new file mode 100644
index 0000000..b4c46ca
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaStarRating.swift
@@ -0,0 +1,104 @@
+//
+// MediaStarRating.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// This element specifies the rating-related information about a media object.
+/// Valid attributes are average, count, min and max.
+public class MediaStarRating {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// The star rating's average.
+ public var average: Double?
+
+ /// The star rating's total count.
+ public var count: Int?
+
+ /// The star rating's minimum value.
+ public var min: Int?
+
+ /// The star rating's maximum value.
+ public var max: Int?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+}
+
+// MARK: - Initializers
+
+extension MediaStarRating {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaStarRating.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension MediaStarRating.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.average = Double(attributeDict["average"] ?? "")
+ self.count = Int(attributeDict["count"] ?? "")
+ self.min = Int(attributeDict["min"] ?? "")
+ self.max = Int(attributeDict["max"] ?? "")
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaStarRating: Equatable {
+
+ public static func ==(lhs: MediaStarRating, rhs: MediaStarRating) -> Bool {
+ return lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaStarRating.Attributes: Equatable {
+
+ public static func ==(lhs: MediaStarRating.Attributes, rhs: MediaStarRating.Attributes) -> Bool {
+ return
+ lhs.average == rhs.average &&
+ lhs.count == rhs.count &&
+ lhs.min == rhs.min &&
+ lhs.max == rhs.max
+ }
+
+}
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaStatistics.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaStatistics.swift
new file mode 100644
index 0000000..283c8f1
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaStatistics.swift
@@ -0,0 +1,94 @@
+//
+// MediaStatistics.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// This element specifies various statistics about a media object like the
+/// view count and the favorite count. Valid attributes are views and favorites.
+public class MediaStatistics {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// The number of views.
+ public var views: Int?
+
+ /// The number fo favorites.
+ public var favorites: Int?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+}
+
+// MARK: - Initializers
+
+extension MediaStatistics {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaStatistics.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension MediaStatistics.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.views = Int(attributeDict["views"] ?? "")
+ self.favorites = Int(attributeDict["favorites"] ?? "")
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaStatistics: Equatable {
+
+ public static func ==(lhs: MediaStatistics, rhs: MediaStatistics) -> Bool {
+ return lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaStatistics.Attributes: Equatable {
+
+ public static func ==(lhs: MediaStatistics.Attributes, rhs: MediaStatistics.Attributes) -> Bool {
+ return
+ lhs.views == rhs.views &&
+ lhs.favorites == rhs.favorites
+ }
+
+}
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaStatus.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaStatus.swift
new file mode 100644
index 0000000..74c9d64
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaStatus.swift
@@ -0,0 +1,98 @@
+//
+// MediaStatus.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Optional tag to specify the status of a media object -- whether it's still
+/// active or it has been blocked/deleted.
+public class MediaStatus {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// State can have values "active", "blocked" or "deleted". "active" means
+ /// a media object is active in the system, "blocked" means a media object
+ /// is blocked by the publisher, "deleted" means a media object has been
+ /// deleted by the publisher.
+ public var state: String?
+
+ /// A reason explaining why a media object has been blocked/deleted. It can
+ /// be plain text or a URL.
+ public var reason: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+}
+
+// MARK: - Initializers
+
+extension MediaStatus {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaStatus.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension MediaStatus.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.state = attributeDict["state"]
+ self.reason = attributeDict["reason"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaStatus: Equatable {
+
+ public static func ==(lhs: MediaStatus, rhs: MediaStatus) -> Bool {
+ return lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaStatus.Attributes: Equatable {
+
+ public static func ==(lhs: MediaStatus.Attributes, rhs: MediaStatus.Attributes) -> Bool {
+ return
+ lhs.state == rhs.state &&
+ lhs.reason == rhs.reason
+ }
+
+}
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaSubTitle.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaSubTitle.swift
new file mode 100644
index 0000000..c4b389d
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaSubTitle.swift
@@ -0,0 +1,98 @@
+//
+// MediaSubTitle.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Optional link to specify the machine-readable license associated with the
+/// content.
+public class MediaSubTitle {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// The type of the subtitle.
+ public var type: String?
+
+ /// The subtitle language based on the RFC 3066.
+ public var lang: String?
+
+ /// The location of the subtitle.
+ public var href: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+}
+
+// MARK: - Initializers
+
+extension MediaSubTitle {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaSubTitle.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension MediaSubTitle.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.type = attributeDict["type"]
+ self.lang = attributeDict["lang"]
+ self.href = attributeDict["href"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaSubTitle: Equatable {
+
+ public static func ==(lhs: MediaSubTitle, rhs: MediaSubTitle) -> Bool {
+ return lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaSubTitle.Attributes: Equatable {
+
+ public static func ==(lhs: MediaSubTitle.Attributes, rhs: MediaSubTitle.Attributes) -> Bool {
+ return
+ lhs.type == rhs.type &&
+ lhs.lang == rhs.lang &&
+ lhs.href == rhs.href
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaTag.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaTag.swift
new file mode 100644
index 0000000..8832105
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaTag.swift
@@ -0,0 +1,88 @@
+//
+// MediaTag.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// This element contains user-generated tags separated by commas in the decreasing
+/// order of each tag's weight. Each tag can be assigned an integer weight in
+/// tag_name:weight format. It's up to the provider to choose the way weight is
+/// determined for a tag; for example, number of occurences can be one way to
+/// decide weight of a particular tag. Default weight is 1.
+public class MediaTag {
+
+ /// The tag name.
+ public var tag: String?
+
+ /// The tag weight. Default to 1 if not specified.
+ public var weight: Int? = 1
+
+}
+
+// MARK: - Initializers
+
+extension MediaTag {
+
+ convenience init(tag: String, weight: Int = 1) {
+
+ self.init()
+
+ self.tag = tag
+ self.weight = weight
+
+ }
+
+ static func tagsFrom(string: String) -> [MediaTag]? {
+
+ return string.components(separatedBy: ",").flatMap({ (value) -> MediaTag? in
+
+ let mediaTag = MediaTag()
+ let components = value.components(separatedBy: ":")
+
+ if components.count > 0 {
+ mediaTag.tag = components.first?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
+ }
+
+ if components.count > 1 {
+ mediaTag.weight = Int(components.last?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) ?? "")
+ }
+
+ return mediaTag
+
+ })
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaTag: Equatable {
+
+ public static func ==(lhs: MediaTag, rhs: MediaTag) -> Bool {
+ return
+ lhs.tag == rhs.tag &&
+ lhs.weight == rhs.weight
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaText.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaText.swift
new file mode 100644
index 0000000..0d9153e
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaText.swift
@@ -0,0 +1,124 @@
+//
+// MediaText.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Allows the inclusion of a text transcript, closed captioning or lyrics of
+/// the media content. Many of these elements are permitted to provide a time
+/// series of text. In such cases, it is encouraged, but not required, that the
+/// elements be grouped by language and appear in time sequence order based on
+/// the start time. Elements can have overlapping start and end times. It has
+/// four optional attributes.
+public class MediaText {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// Specifies the type of text embedded. Possible values are either "plain"
+ /// or "html". Default value is "plain". All HTML must be entity-encoded.
+ /// It is an optional attribute.
+ public var type: String?
+
+ /// The primary language encapsulated in the media object. Language codes
+ /// possible are detailed in RFC 3066. This attribute is used similar to
+ /// the xml:lang attribute detailed in the XML 1.0 Specification (Third
+ /// Edition). It is an optional attribute.
+ public var lang: String?
+
+ /// Specifies the start time offset that the text starts being relevant to
+ /// the media object. An example of this would be for closed captioning.
+ /// It uses the NTP time code format (see: the time attribute used in
+ /// ). It is an optional attribute.
+ public var start: String?
+
+ /// Specifies the end time that the text is relevant. If this attribute is
+ /// not provided, and a start time is used, it is expected that the end
+ /// time is either the end of the clip or the start of the next
+ /// element.
+ public var end: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension MediaText {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaText.Attributes(attributes: attributeDict)
+ }
+
+}
+
+
+extension MediaText.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.type = attributeDict["type"]
+ self.lang = attributeDict["lang"]
+ self.start = attributeDict["start"]
+ self.end = attributeDict["end"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaText: Equatable {
+
+ public static func ==(lhs: MediaText, rhs: MediaText) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaText.Attributes: Equatable {
+
+ public static func ==(lhs: MediaText.Attributes, rhs: MediaText.Attributes) -> Bool {
+ return
+ lhs.type == rhs.type &&
+ lhs.lang == rhs.lang &&
+ lhs.start == rhs.start &&
+ lhs.end == rhs.end
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaThumbnail.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaThumbnail.swift
new file mode 100644
index 0000000..2db96aa
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaThumbnail.swift
@@ -0,0 +1,114 @@
+//
+// MediaThumbnail.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Allows particular images to be used as representative images for the
+/// media object. If multiple thumbnails are included, and time coding is not
+/// at play, it is assumed that the images are in order of importance. It has
+/// one required attribute and three optional attributes.
+public class MediaThumbnail {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// Specifies the url of the thumbnail. It is a required attribute.
+ public var url: String?
+
+ /// Specifies the height of the thumbnail. It is an optional attribute.
+ public var width: String?
+
+ /// Specifies the width of the thumbnail. It is an optional attribute.
+ public var height: String?
+
+ /// Specifies the time offset in relation to the media object. Typically this
+ /// is used when creating multiple keyframes within a single video. The format
+ /// for this attribute should be in the DSM-CC's Normal Play Time (NTP) as used in
+ /// RTSP [RFC 2326 3.6 Normal Play Time]. It is an optional attribute.
+ public var time: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension MediaThumbnail {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaThumbnail.Attributes(attributes: attributeDict)
+ }
+
+}
+
+
+extension MediaThumbnail.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.url = attributeDict["url"]
+ self.height = attributeDict["height"]
+ self.width = attributeDict["width"]
+ self.time = attributeDict["time"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaThumbnail: Equatable {
+
+ public static func ==(lhs: MediaThumbnail, rhs: MediaThumbnail) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaThumbnail.Attributes: Equatable {
+
+ public static func ==(lhs: MediaThumbnail.Attributes, rhs: MediaThumbnail.Attributes) -> Bool {
+ return
+ lhs.url == rhs.url &&
+ lhs.height == rhs.height &&
+ lhs.width == rhs.height &&
+ lhs.time == rhs.time
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaTitle.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaTitle.swift
new file mode 100644
index 0000000..6fac865
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Media/MediaTitle.swift
@@ -0,0 +1,94 @@
+//
+// MediaTitle.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The title of the particular media object. It has one optional attribute.
+public class MediaTitle {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// Specifies the type of text embedded. Possible values are either "plain" or "html".
+ /// Default value is "plain". All HTML must be entity-encoded. It is an optional attribute.
+ public var type: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension MediaTitle {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = MediaTitle.Attributes(attributes: attributeDict)
+ }
+
+}
+
+
+extension MediaTitle.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.type = attributeDict["type"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension MediaTitle: Equatable {
+
+ public static func ==(lhs: MediaTitle, rhs: MediaTitle) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension MediaTitle.Attributes: Equatable {
+
+ public static func ==(lhs: MediaTitle.Attributes, rhs: MediaTitle.Attributes) -> Bool {
+ return lhs.type == rhs.type
+ }
+
+}
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Syndication/SyndicationNamespace.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Syndication/SyndicationNamespace.swift
new file mode 100644
index 0000000..6c4164a
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Syndication/SyndicationNamespace.swift
@@ -0,0 +1,67 @@
+//
+// SyndicationNamespace.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Provides syndication hints to aggregators and others picking up this RDF Site
+/// Summary (RSS) feed regarding how often it is updated. For example, if you
+/// updated your file twice an hour, updatePeriod would be "hourly" and
+/// updateFrequency would be "2". The syndication module borrows from Ian Davis's
+/// Open Content Syndication (OCS) directory format. It supercedes the RSS 0.91
+/// skipDay and skipHour elements.
+///
+/// See http://web.resource.org/rss/1.0/modules/syndication/
+public class SyndicationNamespace {
+
+ /// Describes the period over which the channel format is updated. Acceptable
+ /// values are: hourly, daily, weekly, monthly, yearly. If omitted, daily is
+ /// assumed.
+ public var 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. For example, an updatePeriod of daily, and an updateFrequency of
+ /// 2 indicates the channel format is updated twice daily. If omitted a value
+ /// of 1 is assumed.
+ public var syUpdateFrequency: Int?
+
+ /// Defines a base date to be used in concert with updatePeriod and
+ /// updateFrequency to calculate the publishing schedule. The date format takes
+ /// the form: yyyy-mm-ddThh:mm
+ public var syUpdateBase: Date?
+
+}
+
+// MARK: - Equatable
+
+extension SyndicationNamespace: Equatable {
+
+ public static func ==(lhs: SyndicationNamespace, rhs: SyndicationNamespace) -> Bool {
+ return
+ lhs.syUpdatePeriod == rhs.syUpdatePeriod &&
+ lhs.syUpdateFrequency == rhs.syUpdateFrequency &&
+ lhs.syUpdateBase == rhs.syUpdateBase
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Syndication/SyndicationUpdatePeriod.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Syndication/SyndicationUpdatePeriod.swift
new file mode 100644
index 0000000..40e8079
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/Syndication/SyndicationUpdatePeriod.swift
@@ -0,0 +1,69 @@
+//
+// SyndicationUpdatePeriod.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Describes the period over which the channel format is updated. Acceptable
+/// values are: hourly, daily, weekly, monthly, yearly. If omitted, daily is
+/// assumed.
+///
+/// - hourly: Every hour, the channel is updated the number of times specified
+/// by `syUpdateFrequency`
+///
+/// - daily: Every day, the channel is updated the number of times specified
+/// by `syUpdateFrequency`
+///
+/// - weekly: Every week, the channel is updated the number of times specified
+/// by `syUpdateFrequency`
+///
+/// - monthly: Every month, the channel is updated the number of times specified
+/// by `syUpdateFrequency`
+///
+/// - yearly: Every year, the channel is updated the number of times specified
+public enum SyndicationUpdatePeriod: String {
+ case hourly = "hourly"
+ case daily = "daily"
+ case weekly = "weekly"
+ case monthly = "monthly"
+ case yearly = "yearly"
+}
+
+extension SyndicationUpdatePeriod {
+
+ /// Lowercase the incoming `rawValue` string to try and match the
+ /// `SyUpdatePeriod`'s `rawValue`
+ ///
+ /// - Parameter rawValue: The raw value.
+ public init?(rawValue: String) {
+ switch rawValue.lowercased() {
+ case "hourly": self = .hourly
+ case "daily": self = .daily
+ case "weekly": self = .weekly
+ case "monthly": self = .monthly
+ case "yearly": self = .yearly
+ default: return nil
+ }
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/iTunes/iTunesCategory.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/iTunes/iTunesCategory.swift
new file mode 100644
index 0000000..28c236f
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/iTunes/iTunesCategory.swift
@@ -0,0 +1,119 @@
+//
+// iTunesCategory.swift
+//
+// Copyright (c) 2017 Ben Murphy
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Users can browse podcast subject categories in the iTunes Store by choosing
+/// a category from the Podcasts pop-up menu in the navigation bar. Use the
+/// tag to specify the browsing category for your podcast.
+///
+/// You can also define a subcategory if one is available within your category.
+/// Although you can specify more than one category and subcategory in your
+/// feed, the iTunes Store only recognizes the first category and subcategory.
+/// For a complete list of categories and subcategories, see Podcasts Connect
+/// categories.
+///
+/// Note: When specifying categories and subcategories, be sure to properly
+/// escape ampersands:
+///
+/// Single category:
+///
+///
+/// Category with ampersand:
+///
+///
+/// Category with subcategory:
+///
+///
+///
+///
+/// Multiple categories:
+///
+///
+///
+///
+///
+///
+public class ITunesCategory {
+
+ /// The attributes of the element.
+ public class Attributes {
+
+ /// The primary iTunes Category.
+ public var text: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The iTunes SubCategory.
+ public var subcategory: ITunesSubCategory?
+
+}
+
+// MARK: - Initializers
+
+extension ITunesCategory {
+
+ convenience init(attributes attributesDict: [String: String]) {
+ self.init()
+ self.attributes = ITunesCategory.Attributes(attributes: attributesDict)
+ }
+}
+
+extension ITunesCategory.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.text = attributeDict["text"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension ITunesCategory: Equatable {
+
+ public static func ==(lhs: ITunesCategory, rhs: ITunesCategory) -> Bool {
+ return lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension ITunesCategory.Attributes: Equatable {
+
+ public static func ==(lhs: ITunesCategory.Attributes, rhs: ITunesCategory.Attributes) -> Bool {
+ return lhs.text == rhs.text
+ }
+
+}
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/iTunes/iTunesImage.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/iTunes/iTunesImage.swift
new file mode 100644
index 0000000..c15c7af
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/iTunes/iTunesImage.swift
@@ -0,0 +1,110 @@
+//
+// ITunesImage.swift
+//
+// Copyright (c) 2017 Ben Murphy
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Specify your podcast artwork using the attribute in the
+/// tag. If you do not specify the tag, the
+/// iTunes Store uses the content specified in the RSS feed image tag and Apple
+/// does not consider your podcast for feature placement on the iTunes Store or
+/// Podcasts.
+///
+/// Depending on their device, subscribers see your podcast artwork in varying
+/// sizes. Therefore, make sure your design is effective at both its original
+/// size and at thumbnail size. Apple recommends including a title, brand, or
+/// source name as part of your podcast artwork. For examples of podcast
+/// artwork, see the Top Podcasts. To avoid technical issues when you update
+/// your podcast artwork, be sure to:
+///
+/// Change the artwork file name and URL at the same time
+/// Verify the web server hosting your artwork allows HTTP head requests
+/// The tag is also supported at the
- (episode) level.
+/// For best results, Apple recommends embedding the same artwork within the
+/// metadata for that episode's media file prior to uploading to your host
+/// server; using Garageband or another content-creation tool to edit your
+/// media file if needed.
+///
+/// Note: Artwork must be a minimum size of 1400 x 1400 pixels and a maximum
+/// size of 3000 x 3000 pixels, in JPEG or PNG format, 72 dpi, with appropriate
+/// file extensions (.jpg, .png), and in the RGB colorspace. These requirements
+/// are different from the standard RSS image tag specifications.
+public class ITunesImage {
+
+ /// The attributes of the element.
+ public class Attributes {
+
+ /// The image's url.
+ public var href: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+}
+
+// MARK: - Initializers
+
+extension ITunesImage {
+
+ convenience init(attributes attributesDict: [String: String]) {
+ self.init()
+ self.attributes = ITunesImage.Attributes(attributes: attributesDict)
+ }
+}
+
+extension ITunesImage.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.href = attributeDict["href"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension ITunesImage: Equatable {
+
+ public static func ==(lhs: ITunesImage, rhs: ITunesImage) -> Bool {
+ return lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension ITunesImage.Attributes: Equatable {
+
+ public static func ==(lhs: ITunesImage.Attributes, rhs: ITunesImage.Attributes) -> Bool {
+ return lhs.href == rhs.href
+ }
+
+}
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/iTunes/iTunesNamespace.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/iTunes/iTunesNamespace.swift
new file mode 100644
index 0000000..2c154c4
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/iTunes/iTunesNamespace.swift
@@ -0,0 +1,235 @@
+//
+// iTunesNamespace.swift
+//
+// Copyright (c) 2017 Ben Murphy
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// iTunes Podcasting Tags are de facto standard for podcast syndication. For more
+/// information see https://help.apple.com/itc/podcasts_connect/#/itcb54353390
+public class ITunesNamespace {
+
+ /// The content you specify in the tag appears in the Artist
+ /// column on the iTunes Store. If the tag is not present, the iTunes Store
+ /// uses the contents of the tag. If is not present
+ /// at the RSS feed level, the iTunes Store uses the contents of the
+ /// tag.
+ public var iTunesAuthor: String?
+
+ /// Specifying the tag with a Yes value in:
+ ///
+ /// - A tag (podcast), prevents the entire podcast from appearing on
+ /// the iTunes Store podcast directory
+ ///
+ /// - An
- tag (episode), prevents that episode from appearing on the
+ /// iTunes Store podcast directory
+ ///
+ /// For example, you might want to block a specific episode if you know that
+ /// its content would otherwise cause the entire podcast to be removed from
+ /// the iTunes Store. Specifying any value other than Yes has no effect.
+ public var iTunesBlock: String?
+
+ /// Users can browse podcast subject categories in the iTunes Store by choosing
+ /// a category from the Podcasts pop-up menu in the navigation bar. Use the
+ /// tag to specify the browsing category for your podcast.
+ ///
+ /// You can also define a subcategory if one is available within your category.
+ /// Although you can specify more than one category and subcategory in your
+ /// feed, the iTunes Store only recognizes the first category and subcategory.
+ /// For a complete list of categories and subcategories, see Podcasts Connect
+ /// categories.
+ ///
+ /// Note: When specifying categories and subcategories, be sure to properly
+ /// escape ampersands:
+ ///
+ /// Single category:
+ ///
+ ///
+ /// Category with ampersand:
+ ///
+ ///
+ /// Category with subcategory:
+ ///
+ ///
+ ///
+ ///
+ /// Multiple categories:
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public var iTunesCategories: [ITunesCategory]?
+
+ /// Specify your podcast artwork using the attribute in the
+ /// tag. If you do not specify the tag, the
+ /// iTunes Store uses the content specified in the RSS feed image tag and Apple
+ /// does not consider your podcast for feature placement on the iTunes Store or
+ /// Podcasts.
+ ///
+ /// Depending on their device, subscribers see your podcast artwork in varying
+ /// sizes. Therefore, make sure your design is effective at both its original
+ /// size and at thumbnail size. Apple recommends including a title, brand, or
+ /// source name as part of your podcast artwork. For examples of podcast
+ /// artwork, see the Top Podcasts. To avoid technical issues when you update
+ /// your podcast artwork, be sure to:
+ ///
+ /// Change the artwork file name and URL at the same time
+ /// Verify the web server hosting your artwork allows HTTP head requests
+ /// The tag is also supported at the
- (episode) level.
+ /// For best results, Apple recommends embedding the same artwork within the
+ /// metadata for that episode's media file prior to uploading to your host
+ /// server; using Garageband or another content-creation tool to edit your
+ /// media file if needed.
+ ///
+ /// Note: Artwork must be a minimum size of 1400 x 1400 pixels and a maximum
+ /// size of 3000 x 3000 pixels, in JPEG or PNG format, 72 dpi, with appropriate
+ /// file extensions (.jpg, .png), and in the RGB colorspace. These requirements
+ /// are different from the standard RSS image tag specifications.
+ public var iTunesImage: ITunesImage?
+
+ /// The content you specify in the tag appears in the Time
+ /// column in the List View on the iTunes Store.
+ ///
+ /// Specify one of the following formats for the tag value:
+ ///
+ /// HH:MM:SS
+ /// H:MM:SS
+ /// MM:SS
+ /// M:SS
+ ///
+ /// Where H = hours, M = minutes, and S = seconds.
+ ///
+ /// If you specify a single number as a value (without colons), the iTunes
+ /// Store displays the value as seconds. If you specify one colon, the iTunes
+ /// Store displays the number to the left as minutes and the number to the
+ /// right as seconds. If you specify more then two colons, the iTunes Store
+ /// ignores the numbers farthest to the right.
+ public var iTunesDuration: TimeInterval?
+
+ /// The tag indicates whether your podcast contains explicit
+ /// material. You can specify the following values:
+ ///
+ /// Yes | Explicit | True. If you specify yes, explicit, or true, indicating
+ /// the presence of explicit content, the iTunes Store displays an Explicit
+ /// parental advisory graphic for your podcast.
+ /// Clean | No | False. If you specify clean, no, or false, indicating that
+ /// none of your podcast episodes contain explicit language or adult content,
+ /// the iTunes Store displays a Clean parental advisory graphic for your
+ /// podcast.
+ ///
+ /// Note: Podcasts containing explicit material are not available in some
+ /// iTunes Store territories.
+ public var iTunesExplicit: String?
+
+ /// Specifying the tag with a Yes value indicates
+ /// that the video podcast episode is embedded with closed captioning and the
+ /// iTunes Store should display a closed-caption icon next to the corresponding
+ /// episode. This tag is only supported at the
- level (episode).
+ ///
+ /// Note: If you specify a value other than Yes, no closed-caption indicator
+ /// appears.
+ public var isClosedCaptioned: String?
+
+ /// Use the tag to specify the number value in which you would
+ /// like the episode to appear and override the default ordering of episodes
+ /// on the iTunes Store.
+ ///
+ /// For example, if you want an
- to appear as the first episode of your
+ /// podcast, specify the tag with 1. If conflicting order
+ /// values are present in multiple episodes, the iTunes Store uses .
+ public var iTunesOrder: Int?
+
+ /// Specifying the tag with a Yes value indicates that a
+ /// podcast is complete and you will not post any more episodes in the future.
+ /// This tag is only supported at the level (podcast).
+ ///
+ /// Note: If you specify a value other than Yes, nothing happens.
+ public var iTunesComplete: String?
+
+ /// Use the tag to manually change the URL where your
+ /// podcast is located. This tag is only supported at a level
+ /// (podcast).
+ ///
+ /// http://newlocation.com/example.rss
+ /// Note: You should maintain your old feed until you have migrated your e
+ /// xisting subscribers. For more information, see Update your RSS feed URL.
+ public var iTunesNewFeedURL: String?
+
+ /// Use the tag to specify contact information for the podcast
+ /// owner. Include the email address of the owner in a nested
+ /// tag and the name of the owner in a nested tag.
+ ///
+ /// The tag information is for administrative communication
+ /// about the podcast and is not displayed on the iTunes Store.
+ public var iTunesOwner: ITunesOwner?
+
+ /// The content you specify in the tag appears in the
+ /// Description column on the iTunes Store. For best results, choose a subtitle
+ /// that is only a few words long.
+ public var iTunesSubtitle: String?
+
+ /// The content you specify in the tag appears on the iTunes
+ /// Store page for your podcast. You can specify up to 4000 characters. The
+ /// information also appears in a separate window if a users clicks the
+ /// Information icon (Information icon) in the Description column. If you do
+ /// not specify a tag, the iTunes Store uses the information
+ /// in the tag.
+ public var iTunesSummary: String?
+
+ /// Note: The keywords tag is deprecated by Apple and no longer documented in
+ /// the official list of tags. However many podcasts still use the tags and it
+ /// may be of use for developers building directory or search functionality so
+ /// it is included.
+ ///
+ ///
+ /// This tag allows users to search on text keywords.
+ /// Limited to 255 characters or less, plain text, no HTML, words must be
+ /// separated by spaces.
+ /// This tag is applicable to the Item element only.
+ public var iTunesKeywords: String?
+}
+
+// MARK: - Equatable
+
+extension ITunesNamespace: Equatable {
+
+ public static func ==(lhs: ITunesNamespace, rhs: ITunesNamespace) -> Bool {
+ return
+ lhs.iTunesAuthor == rhs.iTunesAuthor &&
+ lhs.iTunesBlock == rhs.iTunesBlock &&
+ lhs.iTunesCategories == rhs.iTunesCategories &&
+ lhs.iTunesImage == rhs.iTunesImage &&
+ lhs.iTunesDuration == rhs.iTunesDuration &&
+ lhs.iTunesExplicit == rhs.iTunesExplicit &&
+ lhs.isClosedCaptioned == rhs.isClosedCaptioned &&
+ lhs.iTunesOrder == rhs.iTunesOrder &&
+ lhs.iTunesComplete == rhs.iTunesComplete &&
+ lhs.iTunesNewFeedURL == rhs.iTunesNewFeedURL &&
+ lhs.iTunesOwner == rhs.iTunesOwner &&
+ lhs.iTunesSubtitle == rhs.iTunesSubtitle &&
+ lhs.iTunesSummary == rhs.iTunesSummary &&
+ lhs.iTunesKeywords == rhs.iTunesKeywords
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/iTunes/iTunesOwner.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/iTunes/iTunesOwner.swift
new file mode 100644
index 0000000..cf5709e
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/iTunes/iTunesOwner.swift
@@ -0,0 +1,53 @@
+//
+// iTunesOwner.swift
+//
+// Copyright (c) 2017 Ben Murphy
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Use the tag to specify contact information for the podcast
+/// owner. Include the email address of the owner in a nested tag
+/// and the name of the owner in a nested tag.
+///
+/// The tag information is for administrative communication about
+/// the podcast and is not displayed on the iTunes Store.
+public class ITunesOwner {
+
+ /// The email address of the owner.
+ public var email: String?
+
+ /// The name of the owner.
+ public var name: String?
+
+}
+
+// MARK: - Equatable
+
+extension ITunesOwner: Equatable {
+
+ public static func ==(lhs: ITunesOwner, rhs: ITunesOwner) -> Bool {
+ return
+ lhs.email == rhs.email &&
+ lhs.name == rhs.name
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/iTunes/iTunesSubCategory.swift b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/iTunes/iTunesSubCategory.swift
new file mode 100644
index 0000000..4195741
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/Namespaces/iTunes/iTunesSubCategory.swift
@@ -0,0 +1,117 @@
+//
+// ITunesSubCategory.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Users can browse podcast subject categories in the iTunes Store by choosing
+/// a category from the Podcasts pop-up menu in the navigation bar. Use the
+/// tag to specify the browsing category for your podcast.
+///
+/// You can also define a subcategory if one is available within your category.
+/// Although you can specify more than one category and subcategory in your
+/// feed, the iTunes Store only recognizes the first category and subcategory.
+/// For a complete list of categories and subcategories, see Podcasts Connect
+/// categories.
+///
+/// Note: When specifying categories and subcategories, be sure to properly
+/// escape ampersands:
+///
+/// Single category:
+///
+///
+/// Category with ampersand:
+///
+///
+/// Category with subcategory:
+///
+///
+///
+///
+/// Multiple categories:
+///
+///
+///
+///
+///
+///
+public class ITunesSubCategory {
+
+ /// The attributes of the element.
+ public class Attributes {
+
+ /// The primary iTunes Category.
+ public var text: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+
+}
+
+// MARK: - Initializers
+
+extension ITunesSubCategory {
+
+ convenience init(attributes attributesDict: [String: String]) {
+ self.init()
+ self.attributes = ITunesSubCategory.Attributes(attributes: attributesDict)
+ }
+}
+
+extension ITunesSubCategory.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.text = attributeDict["text"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension ITunesSubCategory: Equatable {
+
+ public static func ==(lhs: ITunesSubCategory, rhs: ITunesSubCategory) -> Bool {
+ return lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension ITunesSubCategory.Attributes: Equatable {
+
+ public static func ==(lhs: ITunesSubCategory.Attributes, rhs: ITunesSubCategory.Attributes) -> Bool {
+ return lhs.text == rhs.text
+ }
+
+}
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/RSS/RDFPath.swift b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RDFPath.swift
new file mode 100644
index 0000000..d0afa6e
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RDFPath.swift
@@ -0,0 +1,89 @@
+//
+// RDFPath.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Describes the individual path for each XML DOM element of an RDF feed
+///
+/// See http://www.rssboard.org/rss-0-9-0
+enum RDFPath: String {
+
+ case rdf = "/rdf:RDF"
+ case rdfChannel = "/rdf:RDF/channel"
+ case rdfChannelTitle = "/rdf:RDF/channel/title"
+ case rdfChannelLink = "/rdf:RDF/channel/link"
+ case rdfChannelDescription = "/rdf:RDF/channel/description"
+ case rdfChannelImage = "/rdf:RDF/channel/image"
+ case rdfChannelItems = "/rdf:RDF/channel/items"
+ case rdfChannelItemsRdfSeq = "/rdf:RDF/channel/items/rdf:Seq"
+ case rdfChannelItemsRdfSeqRdfLi = "/rdf:RDF/channel/items/rdf:Seq/rdf:li"
+ case rdfImage = "/rdf:RDF/image"
+ case rdfImageTitle = "/rdf:RDF/image/title"
+ case rdfImageURL = "/rdf:RDF/image/url"
+ case rdfImageLink = "/rdf:RDF/image/link"
+ case rdfItem = "/rdf:RDF/item"
+ case rdfItemTitle = "/rdf:RDF/item/title"
+ case rdfItemLink = "/rdf:RDF/item/link"
+ case rdfItemDescription = "/rdf:RDF/item/description"
+
+ // Syndication
+
+ case rdfChannelSyndicationUpdatePeriod = "/rdf:RDF/channel/sy:updatePeriod"
+ case rdfChannelSyndicationUpdateFrequency = "/rdf:RDF/channel/sy:updateFrequency"
+ case rdfChannelSyndicationUpdateBase = "/rdf:RDF/channel/sy:updateBase"
+
+ // Dublin Core
+
+ case rdfChannelDublinCoreTitle = "/rdf:RDF/channel/dc:title"
+ case rdfChannelDublinCoreCreator = "/rdf:RDF/channel/dc:creator"
+ case rdfChannelDublinCoreSubject = "/rdf:RDF/channel/dc:subject"
+ case rdfChannelDublinCoreDescription = "/rdf:RDF/channel/dc:description"
+ case rdfChannelDublinCorePublisher = "/rdf:RDF/channel/dc:publisher"
+ case rdfChannelDublinCoreContributor = "/rdf:RDF/channel/dc:contributor"
+ case rdfChannelDublinCoreDate = "/rdf:RDF/channel/dc:date"
+ case rdfChannelDublinCoreType = "/rdf:RDF/channel/dc:type"
+ case rdfChannelDublinCoreFormat = "/rdf:RDF/channel/dc:format"
+ case rdfChannelDublinCoreIdentifier = "/rdf:RDF/channel/dc:identifier"
+ case rdfChannelDublinCoreSource = "/rdf:RDF/channel/dc:source"
+ case rdfChannelDublinCoreLanguage = "/rdf:RDF/channel/dc:language"
+ case rdfChannelDublinCoreRelation = "/rdf:RDF/channel/dc:relation"
+ case rdfChannelDublinCoreCoverage = "/rdf:RDF/channel/dc:coverage"
+ case rdfChannelDublinCoreRights = "/rdf:RDF/channel/dc:rights"
+ case rdfItemDublinCoreTitle = "/rdf:RDF/item/dc:title"
+ case rdfItemDublinCoreCreator = "/rdf:RDF/item/dc:creator"
+ case rdfItemDublinCoreSubject = "/rdf:RDF/item/dc:subject"
+ case rdfItemDublinCoreDescription = "/rdf:RDF/item/dc:description"
+ case rdfItemDublinCorePublisher = "/rdf:RDF/item/dc:publisher"
+ case rdfItemDublinCoreContributor = "/rdf:RDF/item/dc:contributor"
+ case rdfItemDublinCoreDate = "/rdf:RDF/item/dc:date"
+ case rdfItemDublinCoreType = "/rdf:RDF/item/dc:type"
+ case rdfItemDublinCoreFormat = "/rdf:RDF/item/dc:format"
+ case rdfItemDublinCoreIdentifier = "/rdf:RDF/item/dc:identifier"
+ case rdfItemDublinCoreSource = "/rdf:RDF/item/dc:source"
+ case rdfItemDublinCoreLanguage = "/rdf:RDF/item/dc:language"
+ case rdfItemDublinCoreRelation = "/rdf:RDF/item/dc:relation"
+ case rdfItemDublinCoreCoverage = "/rdf:RDF/item/dc:coverage"
+ case rdfItemDublinCoreRights = "/rdf:RDF/item/dc:rights"
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeed + mapAttributes.swift b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeed + mapAttributes.swift
new file mode 100644
index 0000000..ed4c70b
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeed + mapAttributes.swift
@@ -0,0 +1,532 @@
+//
+// RSSFeed + mapAttributes.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+extension RSSFeed {
+
+ /// Maps the attributes of the specified dictionary for a given `RSSPath`
+ /// to the `RSSFeed` model,
+ ///
+ /// - Parameters:
+ /// - attributes: The attribute dictionary to map to the model.
+ /// - path: The path of feed's element.
+ func map(_ attributes: [String : String], for path: RSSPath) {
+
+ switch path {
+
+ case .rssChannelItem:
+
+ if self.items == nil {
+ self.items = []
+ }
+
+ self.items?.append(RSSFeedItem())
+
+ case .rssChannelImage:
+
+ if self.image == nil {
+ self.image = RSSFeedImage()
+ }
+
+ case .rssChannelSkipDays:
+
+ if self.skipDays == nil {
+ self.skipDays = []
+ }
+
+ case .rssChannelSkipHours:
+
+ if self.skipHours == nil {
+ self.skipHours = []
+ }
+
+ case .rssChannelTextInput:
+
+ if self.textInput == nil {
+ self.textInput = RSSFeedTextInput()
+ }
+
+ case .rssChannelCategory:
+
+ if self.categories == nil {
+ self.categories = []
+ }
+
+ self.categories?.append(RSSFeedCategory(attributes: attributes))
+
+ case .rssChannelCloud:
+
+ if self.cloud == nil {
+ self.cloud = RSSFeedCloud(attributes: attributes)
+ }
+
+ case .rssChannelItemCategory:
+
+ if self.items?.last?.categories == nil {
+ self.items?.last?.categories = []
+ }
+
+ self.items?.last?.categories?.append(RSSFeedItemCategory(attributes: attributes))
+
+ case .rssChannelItemEnclosure:
+
+ if self.items?.last?.enclosure == nil {
+ self.items?.last?.enclosure = RSSFeedItemEnclosure(attributes: attributes)
+ }
+
+ case .rssChannelItemGUID:
+
+ if self.items?.last?.guid == nil {
+ self.items?.last?.guid = RSSFeedItemGUID(attributes: attributes)
+ }
+
+ case .rssChannelItemSource:
+
+ if self.items?.last?.source == nil {
+ self.items?.last?.source = RSSFeedItemSource(attributes: attributes)
+ }
+
+ case .rssChannelItemContentEncoded:
+
+ if self.items?.last?.content == nil {
+ self.items?.last?.content = ContentNamespace()
+ }
+
+
+ case
+ .rssChannelSyndicationUpdateBase,
+ .rssChannelSyndicationUpdatePeriod,
+ .rssChannelSyndicationUpdateFrequency:
+
+ if self.syndication == nil {
+ self.syndication = SyndicationNamespace()
+ }
+
+ case
+ .rssChannelDublinCoreTitle,
+ .rssChannelDublinCoreCreator,
+ .rssChannelDublinCoreSubject,
+ .rssChannelDublinCoreDescription,
+ .rssChannelDublinCorePublisher,
+ .rssChannelDublinCoreContributor,
+ .rssChannelDublinCoreDate,
+ .rssChannelDublinCoreType,
+ .rssChannelDublinCoreFormat,
+ .rssChannelDublinCoreIdentifier,
+ .rssChannelDublinCoreSource,
+ .rssChannelDublinCoreLanguage,
+ .rssChannelDublinCoreRelation,
+ .rssChannelDublinCoreCoverage,
+ .rssChannelDublinCoreRights:
+
+ if self.dublinCore == nil {
+ self.dublinCore = DublinCoreNamespace()
+ }
+
+ case
+ .rssChannelItemDublinCoreTitle,
+ .rssChannelItemDublinCoreCreator,
+ .rssChannelItemDublinCoreSubject,
+ .rssChannelItemDublinCoreDescription,
+ .rssChannelItemDublinCorePublisher,
+ .rssChannelItemDublinCoreContributor,
+ .rssChannelItemDublinCoreDate,
+ .rssChannelItemDublinCoreType,
+ .rssChannelItemDublinCoreFormat,
+ .rssChannelItemDublinCoreIdentifier,
+ .rssChannelItemDublinCoreSource,
+ .rssChannelItemDublinCoreLanguage,
+ .rssChannelItemDublinCoreRelation,
+ .rssChannelItemDublinCoreCoverage,
+ .rssChannelItemDublinCoreRights:
+
+ if self.items?.last?.dublinCore == nil {
+ self.items?.last?.dublinCore = DublinCoreNamespace()
+ }
+
+ case
+ .rssChannelItunesAuthor,
+ .rssChannelItunesBlock,
+ .rssChannelItunesCategory,
+ .rssChannelItunesSubcategory,
+ .rssChannelItunesImage,
+ .rssChannelItunesExplicit,
+ .rssChannelItunesComplete,
+ .rssChannelItunesNewFeedURL,
+ .rssChannelItunesOwner,
+ .rssChannelItunesOwnerName,
+ .rssChannelItunesOwnerEmail,
+ .rssChannelItunesSubtitle,
+ .rssChannelItunesSummary,
+ .rssChannelItunesKeywords:
+
+ if self.iTunes == nil {
+ self.iTunes = ITunesNamespace()
+ }
+
+ switch path {
+
+ case .rssChannelItunesCategory:
+
+ if self.iTunes?.iTunesCategories == nil {
+ self.iTunes?.iTunesCategories = []
+ }
+
+ self.iTunes?.iTunesCategories?.append(ITunesCategory(attributes: attributes))
+
+ case .rssChannelItunesSubcategory:
+
+ self.iTunes?.iTunesCategories?.last?.subcategory = ITunesSubCategory(attributes: attributes)
+
+ case .rssChannelItunesImage:
+
+ self.iTunes?.iTunesImage = ITunesImage(attributes: attributes)
+
+ case .rssChannelItunesOwner:
+
+ if self.iTunes?.iTunesOwner == nil {
+ self.iTunes?.iTunesOwner = ITunesOwner()
+ }
+
+ default: break
+
+ }
+
+ case
+ .rssChannelItemItunesAuthor,
+ .rssChannelItemItunesBlock,
+ .rssChannelItemItunesDuration,
+ .rssChannelItemItunesImage,
+ .rssChannelItemItunesExplicit,
+ .rssChannelItemItunesIsClosedCaptioned,
+ .rssChannelItemItunesOrder,
+ .rssChannelItemItunesSubtitle,
+ .rssChannelItemItunesSummary,
+ .rssChannelItemItunesKeywords:
+
+ if self.items?.last?.iTunes == nil {
+ self.items?.last?.iTunes = ITunesNamespace()
+ }
+
+ switch path {
+
+ case .rssChannelItemItunesImage:
+
+ self.items?.last?.iTunes?.iTunesImage = ITunesImage(attributes: attributes)
+
+ default: break
+
+ }
+
+ // MARK: Media
+
+ case
+ .rssChannelItemMediaThumbnail,
+ .rssChannelItemMediaContent,
+ .rssChannelItemMediaCommunity,
+ .rssChannelItemMediaCommunityMediaStarRating,
+ .rssChannelItemMediaCommunityMediaStatistics,
+ .rssChannelItemMediaCommunityMediaTags,
+ .rssChannelItemMediaComments,
+ .rssChannelItemMediaCommentsMediaComment,
+ .rssChannelItemMediaEmbed,
+ .rssChannelItemMediaEmbedMediaParam,
+ .rssChannelItemMediaResponses,
+ .rssChannelItemMediaResponsesMediaResponse,
+ .rssChannelItemMediaBackLinks,
+ .rssChannelItemMediaBackLinksBackLink,
+ .rssChannelItemMediaStatus,
+ .rssChannelItemMediaPrice,
+ .rssChannelItemMediaLicense,
+ .rssChannelItemMediaSubTitle,
+ .rssChannelItemMediaPeerLink,
+ .rssChannelItemMediaLocation,
+ .rssChannelItemMediaLocationPosition,
+ .rssChannelItemMediaRestriction,
+ .rssChannelItemMediaScenes,
+ .rssChannelItemMediaScenesMediaScene,
+ .rssChannelItemMediaGroup,
+ .rssChannelItemMediaGroupMediaCategory,
+ .rssChannelItemMediaGroupMediaCredit,
+ .rssChannelItemMediaGroupMediaRating,
+ .rssChannelItemMediaGroupMediaContent:
+
+ if self.items?.last?.media == nil {
+ self.items?.last?.media = MediaNamespace()
+ }
+
+ switch path {
+
+ case .rssChannelItemMediaThumbnail:
+
+ if self.items?.last?.media?.mediaThumbnails == nil {
+ self.items?.last?.media?.mediaThumbnails = []
+ }
+
+ self.items?.last?.media?.mediaThumbnails?.append(MediaThumbnail(attributes: attributes))
+
+ case .rssChannelItemMediaContent:
+
+ if self.items?.last?.media?.mediaContents == nil {
+ self.items?.last?.media?.mediaContents = []
+ }
+
+ self.items?.last?.media?.mediaContents?.append(MediaContent(attributes: attributes))
+
+ case .rssChannelItemMediaCommunity:
+
+ if self.items?.last?.media?.mediaCommunity == nil {
+ self.items?.last?.media?.mediaCommunity = MediaCommunity()
+ }
+
+ case .rssChannelItemMediaCommunityMediaStarRating:
+
+ if self.items?.last?.media?.mediaCommunity?.mediaStarRating == nil {
+ self.items?.last?.media?.mediaCommunity?.mediaStarRating = MediaStarRating(attributes: attributes)
+ }
+
+ case .rssChannelItemMediaCommunityMediaStatistics:
+
+ if self.items?.last?.media?.mediaCommunity?.mediaStatistics == nil {
+ self.items?.last?.media?.mediaCommunity?.mediaStatistics = MediaStatistics(attributes: attributes)
+ }
+
+ case .rssChannelItemMediaCommunityMediaTags:
+
+ if self.items?.last?.media?.mediaCommunity?.mediaTags == nil {
+ self.items?.last?.media?.mediaCommunity?.mediaTags = []
+ }
+
+ case .rssChannelItemMediaComments:
+
+ if self.items?.last?.media?.mediaComments == nil {
+ self.items?.last?.media?.mediaComments = []
+ }
+
+ case .rssChannelItemMediaEmbed:
+
+ if self.items?.last?.media?.mediaEmbed == nil {
+ self.items?.last?.media?.mediaEmbed = MediaEmbed(attributes: attributes)
+ }
+
+ case .rssChannelItemMediaEmbedMediaParam:
+
+ if self.items?.last?.media?.mediaEmbed?.mediaParams == nil {
+ self.items?.last?.media?.mediaEmbed?.mediaParams = []
+ }
+
+ self.items?.last?.media?.mediaEmbed?.mediaParams?.append(MediaParam(attributes: attributes))
+
+ case .rssChannelItemMediaResponses:
+
+ if self.items?.last?.media?.mediaResponses == nil {
+ self.items?.last?.media?.mediaResponses = []
+ }
+
+ case .rssChannelItemMediaBackLinks:
+
+ if self.items?.last?.media?.mediaBackLinks == nil {
+ self.items?.last?.media?.mediaBackLinks = []
+ }
+
+ case .rssChannelItemMediaStatus:
+
+ if self.items?.last?.media?.mediaStatus == nil {
+ self.items?.last?.media?.mediaStatus = MediaStatus(attributes: attributes)
+ }
+
+ case .rssChannelItemMediaPrice:
+
+ if self.items?.last?.media?.mediaPrices == nil {
+ self.items?.last?.media?.mediaPrices = []
+ }
+
+ self.items?.last?.media?.mediaPrices?.append(MediaPrice(attributes: attributes))
+
+ case .rssChannelItemMediaLicense:
+
+ if self.items?.last?.media?.mediaLicense == nil {
+ self.items?.last?.media?.mediaLicense = MediaLicence(attributes: attributes)
+ }
+
+ case .rssChannelItemMediaSubTitle:
+
+ if self.items?.last?.media?.mediaSubTitle == nil {
+ self.items?.last?.media?.mediaSubTitle = MediaSubTitle(attributes: attributes)
+ }
+
+ case .rssChannelItemMediaPeerLink:
+
+ if self.items?.last?.media?.mediaPeerLink == nil {
+ self.items?.last?.media?.mediaPeerLink = MediaPeerLink(attributes: attributes)
+ }
+
+ case .rssChannelItemMediaLocation:
+
+ if self.items?.last?.media?.mediaLocation == nil {
+ self.items?.last?.media?.mediaLocation = MediaLocation(attributes: attributes)
+ }
+
+ case .rssChannelItemMediaRestriction:
+
+ if self.items?.last?.media?.mediaRestriction == nil {
+ self.items?.last?.media?.mediaRestriction = MediaRestriction(attributes: attributes)
+ }
+
+ case .rssChannelItemMediaScenes:
+
+ if self.items?.last?.media?.mediaScenes == nil {
+ self.items?.last?.media?.mediaScenes = []
+ }
+
+ case .rssChannelItemMediaScenesMediaScene:
+
+ if self.items?.last?.media?.mediaScenes == nil {
+ self.items?.last?.media?.mediaScenes = []
+ }
+
+ self.items?.last?.media?.mediaScenes?.append(MediaScene())
+
+ case .rssChannelItemMediaGroup:
+
+ if self.items?.last?.media?.mediaGroup == nil {
+ self.items?.last?.media?.mediaGroup = MediaGroup()
+ }
+
+ case .rssChannelItemMediaGroupMediaCategory:
+
+ if self.items?.last?.media?.mediaGroup?.mediaCategory == nil {
+ self.items?.last?.media?.mediaGroup?.mediaCategory = MediaCategory(attributes: attributes)
+ }
+
+ case .rssChannelItemMediaGroupMediaCredit:
+
+ if self.items?.last?.media?.mediaGroup?.mediaCredits == nil {
+ self.items?.last?.media?.mediaGroup?.mediaCredits = []
+ }
+
+ self.items?.last?.media?.mediaGroup?.mediaCredits?.append(MediaCredit(attributes: attributes))
+
+ case .rssChannelItemMediaGroupMediaRating:
+
+ if self.items?.last?.media?.mediaGroup?.mediaRating == nil {
+ self.items?.last?.media?.mediaGroup?.mediaRating = MediaRating(attributes: attributes)
+ }
+
+ case .rssChannelItemMediaGroupMediaContent:
+
+ if self.items?.last?.media?.mediaGroup?.mediaContents == nil {
+ self.items?.last?.media?.mediaGroup?.mediaContents = []
+ }
+
+ self.items?.last?.media?.mediaGroup?.mediaContents?.append(MediaContent(attributes: attributes))
+
+ default: break
+
+ }
+
+
+ default: break
+
+
+ }
+
+
+ }
+
+ /// Maps the attributes of the specified dictionary for a given `RSSPath`
+ /// to the `RSSFeed` model,
+ ///
+ /// - Parameters:
+ /// - attributes: The attribute dictionary to map to the model.
+ /// - path: The path of feed's element.
+ func map(_ attributes: [String : String], for path: RDFPath) {
+
+ switch path {
+
+ case .rdfItem:
+ if self.items == nil {
+ self.items = []
+ }
+
+ self.items?.append(RSSFeedItem())
+
+ case
+ .rdfChannelSyndicationUpdateBase,
+ .rdfChannelSyndicationUpdatePeriod,
+ .rdfChannelSyndicationUpdateFrequency:
+
+ if self.syndication == nil {
+ self.syndication = SyndicationNamespace()
+ }
+
+ case
+ .rdfChannelDublinCoreTitle,
+ .rdfChannelDublinCoreCreator,
+ .rdfChannelDublinCoreSubject,
+ .rdfChannelDublinCoreDescription,
+ .rdfChannelDublinCorePublisher,
+ .rdfChannelDublinCoreContributor,
+ .rdfChannelDublinCoreDate,
+ .rdfChannelDublinCoreType,
+ .rdfChannelDublinCoreFormat,
+ .rdfChannelDublinCoreIdentifier,
+ .rdfChannelDublinCoreSource,
+ .rdfChannelDublinCoreLanguage,
+ .rdfChannelDublinCoreRelation,
+ .rdfChannelDublinCoreCoverage,
+ .rdfChannelDublinCoreRights:
+
+ if self.dublinCore == nil {
+ self.dublinCore = DublinCoreNamespace()
+ }
+
+ case
+ .rdfItemDublinCoreTitle,
+ .rdfItemDublinCoreCreator,
+ .rdfItemDublinCoreSubject,
+ .rdfItemDublinCoreDescription,
+ .rdfItemDublinCorePublisher,
+ .rdfItemDublinCoreContributor,
+ .rdfItemDublinCoreDate,
+ .rdfItemDublinCoreType,
+ .rdfItemDublinCoreFormat,
+ .rdfItemDublinCoreIdentifier,
+ .rdfItemDublinCoreSource,
+ .rdfItemDublinCoreLanguage,
+ .rdfItemDublinCoreRelation,
+ .rdfItemDublinCoreCoverage,
+ .rdfItemDublinCoreRights:
+
+ if self.items?.last?.dublinCore == nil {
+ self.items?.last?.dublinCore = DublinCoreNamespace()
+ }
+
+ default: break
+ }
+
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeed + mapCharacters.swift b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeed + mapCharacters.swift
new file mode 100644
index 0000000..e5e849f
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeed + mapCharacters.swift
@@ -0,0 +1,205 @@
+//
+// RSSFeed + mapCharacters.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+extension RSSFeed {
+
+ /// Maps the characters in the specified string to the `RSSFeed` model.
+ ///
+ /// - Parameters:
+ /// - string: The string to map to the model.
+ /// - path: The path of feed's element.
+ func map(_ string: String, for path: RSSPath) {
+
+ switch path {
+ case .rssChannelTitle: self.title = self.title?.appending(string) ?? string
+ case .rssChannelLink: self.link = self.link?.appending(string) ?? string
+ case .rssChannelDescription: self.description = self.description?.appending(string) ?? string
+ case .rssChannelLanguage: self.language = self.language?.appending(string) ?? string
+ case .rssChannelCopyright: self.copyright = self.copyright?.appending(string) ?? string
+ case .rssChannelManagingEditor: self.managingEditor = self.managingEditor?.appending(string) ?? string
+ case .rssChannelWebMaster: self.webMaster = self.webMaster?.appending(string) ?? string
+ case .rssChannelPubDate: self.pubDate = string.toDate(from: .rfc822)
+ case .rssChannelLastBuildDate: self.lastBuildDate = string.toDate(from: .rfc822)
+ case .rssChannelCategory: self.categories?.last?.value = self.categories?.last?.value?.appending(string) ?? string
+ case .rssChannelGenerator: self.generator = self.generator?.appending(string) ?? string
+ case .rssChannelDocs: self.docs = self.docs?.appending(string) ?? string
+ case .rssChannelRating: self.rating = self.rating?.appending(string) ?? string
+ case .rssChannelTTL: self.ttl = Int(string)
+ case .rssChannelImageURL: self.image?.url = self.image?.url?.appending(string) ?? string
+ case .rssChannelImageTitle: self.image?.title = self.image?.title?.appending(string) ?? string
+ case .rssChannelImageLink: self.image?.link = self.image?.link?.appending(string) ?? string
+ case .rssChannelImageWidth: self.image?.width = Int(string)
+ case .rssChannelImageHeight: self.image?.height = Int(string)
+ case .rssChannelImageDescription: self.image?.description = self.image?.description?.appending(string) ?? string
+ case .rssChannelTextInputTitle: self.textInput?.title = self.textInput?.title?.appending(string) ?? string
+ case .rssChannelTextInputDescription: self.textInput?.description = self.textInput?.description?.appending(string) ?? string
+ case .rssChannelTextInputName: self.textInput?.name = self.textInput?.name?.appending(string) ?? string
+ case .rssChannelTextInputLink: self.textInput?.link = self.textInput?.link?.appending(string) ?? string
+ case .rssChannelSkipHoursHour:
+ if let hour = RSSFeedSkipHour(string), 0...23 ~= hour {
+ self.skipHours?.append(hour)
+ }
+ case .rssChannelSkipDaysDay:
+ if let day = RSSFeedSkipDay(rawValue: string) {
+ self.skipDays?.append(day)
+ }
+ case .rssChannelItemTitle: self.items?.last?.title = self.items?.last?.title?.appending(string) ?? string
+ case .rssChannelItemLink: self.items?.last?.link = self.items?.last?.link?.appending(string) ?? string
+ case .rssChannelItemDescription: self.items?.last?.description = self.items?.last?.description?.appending(string) ?? string
+ case .rssChannelItemAuthor: self.items?.last?.author = self.items?.last?.author?.appending(string) ?? string
+ case .rssChannelItemCategory: self.items?.last?.categories?.last?.value = self.items?.last?.categories?.last?.value?.appending(string) ?? string
+ case .rssChannelItemComments: self.items?.last?.comments = self.items?.last?.comments?.appending(string) ?? string
+ case .rssChannelItemGUID: self.items?.last?.guid?.value = self.items?.last?.guid?.value?.appending(string) ?? string
+ case .rssChannelItemPubDate: self.items?.last?.pubDate = string.toDate(from: .rfc822)
+ case .rssChannelItemSource: self.items?.last?.source?.value = self.items?.last?.source?.value?.appending(string) ?? string
+ case .rssChannelItemContentEncoded: self.items?.last?.content?.contentEncoded = self.items?.last?.content?.contentEncoded?.appending(string) ?? string
+ case .rssChannelSyndicationUpdatePeriod: self.syndication?.syUpdatePeriod = SyndicationUpdatePeriod(rawValue: string)
+ case .rssChannelSyndicationUpdateFrequency: self.syndication?.syUpdateFrequency = Int(string)
+ case .rssChannelSyndicationUpdateBase: self.syndication?.syUpdateBase = string.toDate(from: .iso8601)
+ case .rssChannelDublinCoreTitle: self.dublinCore?.dcTitle = self.dublinCore?.dcTitle?.appending(string) ?? string
+ case .rssChannelDublinCoreCreator: self.dublinCore?.dcCreator = self.dublinCore?.dcCreator?.appending(string) ?? string
+ case .rssChannelDublinCoreSubject: self.dublinCore?.dcSubject = self.dublinCore?.dcSubject?.appending(string) ?? string
+ case .rssChannelDublinCoreDescription: self.dublinCore?.dcDescription = self.dublinCore?.dcDescription?.appending(string) ?? string
+ case .rssChannelDublinCorePublisher: self.dublinCore?.dcPublisher = self.dublinCore?.dcPublisher?.appending(string) ?? string
+ case .rssChannelDublinCoreContributor: self.dublinCore?.dcContributor = self.dublinCore?.dcContributor?.appending(string) ?? string
+ case .rssChannelDublinCoreDate: self.dublinCore?.dcDate = string.toDate(from: .iso8601)
+ case .rssChannelDublinCoreType: self.dublinCore?.dcType = self.dublinCore?.dcType?.appending(string) ?? string
+ case .rssChannelDublinCoreFormat: self.dublinCore?.dcFormat = self.dublinCore?.dcFormat?.appending(string) ?? string
+ case .rssChannelDublinCoreIdentifier: self.dublinCore?.dcIdentifier = self.dublinCore?.dcIdentifier?.appending(string) ?? string
+ case .rssChannelDublinCoreSource: self.dublinCore?.dcSource = self.dublinCore?.dcSource?.appending(string) ?? string
+ case .rssChannelDublinCoreLanguage: self.dublinCore?.dcLanguage = self.dublinCore?.dcLanguage?.appending(string) ?? string
+ case .rssChannelDublinCoreRelation: self.dublinCore?.dcRelation = self.dublinCore?.dcRelation?.appending(string) ?? string
+ case .rssChannelDublinCoreCoverage: self.dublinCore?.dcCoverage = self.dublinCore?.dcCoverage?.appending(string) ?? string
+ case .rssChannelDublinCoreRights: self.dublinCore?.dcRights = self.dublinCore?.dcRights?.appending(string) ?? string
+ case .rssChannelItemDublinCoreTitle: self.items?.last?.dublinCore?.dcTitle = self.items?.last?.dublinCore?.dcTitle?.appending(string) ?? string
+ case .rssChannelItemDublinCoreCreator: self.items?.last?.dublinCore?.dcCreator = self.items?.last?.dublinCore?.dcCreator?.appending(string) ?? string
+ case .rssChannelItemDublinCoreSubject: self.items?.last?.dublinCore?.dcSubject = self.items?.last?.dublinCore?.dcSubject?.appending(string) ?? string
+ case .rssChannelItemDublinCoreDescription: self.items?.last?.dublinCore?.dcDescription = self.items?.last?.dublinCore?.dcDescription?.appending(string) ?? string
+ case .rssChannelItemDublinCorePublisher: self.items?.last?.dublinCore?.dcPublisher = self.items?.last?.dublinCore?.dcPublisher?.appending(string) ?? string
+ case .rssChannelItemDublinCoreContributor: self.items?.last?.dublinCore?.dcContributor = self.items?.last?.dublinCore?.dcContributor?.appending(string) ?? string
+ case .rssChannelItemDublinCoreDate: self.items?.last?.dublinCore?.dcDate = string.toDate(from: .iso8601)
+ case .rssChannelItemDublinCoreType: self.items?.last?.dublinCore?.dcType = self.items?.last?.dublinCore?.dcType?.appending(string) ?? string
+ case .rssChannelItemDublinCoreFormat: self.items?.last?.dublinCore?.dcFormat = self.items?.last?.dublinCore?.dcFormat?.appending(string) ?? string
+ case .rssChannelItemDublinCoreIdentifier: self.items?.last?.dublinCore?.dcIdentifier = self.items?.last?.dublinCore?.dcIdentifier?.appending(string) ?? string
+ case .rssChannelItemDublinCoreSource: self.items?.last?.dublinCore?.dcSource = self.items?.last?.dublinCore?.dcSource?.appending(string) ?? string
+ case .rssChannelItemDublinCoreLanguage: self.items?.last?.dublinCore?.dcLanguage = self.items?.last?.dublinCore?.dcLanguage?.appending(string) ?? string
+ case .rssChannelItemDublinCoreRelation: self.items?.last?.dublinCore?.dcRelation = self.items?.last?.dublinCore?.dcRelation?.appending(string) ?? string
+ case .rssChannelItemDublinCoreCoverage: self.items?.last?.dublinCore?.dcCoverage = self.items?.last?.dublinCore?.dcCoverage?.appending(string) ?? string
+ case .rssChannelItemDublinCoreRights: self.items?.last?.dublinCore?.dcRights = self.items?.last?.dublinCore?.dcRights?.appending(string) ?? string
+ case .rssChannelItunesAuthor: self.iTunes?.iTunesAuthor = self.iTunes?.iTunesAuthor?.appending(string) ?? string
+ case .rssChannelItunesBlock: self.iTunes?.iTunesBlock = self.iTunes?.iTunesBlock?.appending(string) ?? string
+ case .rssChannelItunesExplicit: self.iTunes?.iTunesExplicit = self.iTunes?.iTunesExplicit?.appending(string) ?? string
+ case .rssChannelItunesComplete: self.iTunes?.iTunesComplete = self.iTunes?.iTunesComplete?.appending(string) ?? string
+ case .rssChannelItunesNewFeedURL: self.iTunes?.iTunesNewFeedURL = self.iTunes?.iTunesNewFeedURL?.appending(string) ?? string
+ case .rssChannelItunesOwnerName: self.iTunes?.iTunesOwner?.name = self.iTunes?.iTunesOwner?.name?.appending(string) ?? string
+ case .rssChannelItunesOwnerEmail: self.iTunes?.iTunesOwner?.email = self.iTunes?.iTunesOwner?.email?.appending(string) ?? string
+ case .rssChannelItunesSubtitle: self.iTunes?.iTunesSubtitle = self.iTunes?.iTunesSubtitle?.appending(string) ?? string
+ case .rssChannelItunesSummary: self.iTunes?.iTunesSummary = self.iTunes?.iTunesSummary?.appending(string) ?? string
+ case .rssChannelItunesKeywords: self.iTunes?.iTunesKeywords = self.iTunes?.iTunesKeywords?.appending(string) ?? string
+ case .rssChannelItemItunesAuthor: self.items?.last?.iTunes?.iTunesAuthor = self.items?.last?.iTunes?.iTunesAuthor?.appending(string) ?? string
+ case .rssChannelItemItunesBlock: self.items?.last?.iTunes?.iTunesBlock = self.items?.last?.iTunes?.iTunesBlock?.appending(string) ?? string
+ case .rssChannelItemItunesDuration: self.items?.last?.iTunes?.iTunesDuration = string.toDuration()
+ case .rssChannelItemItunesExplicit: self.items?.last?.iTunes?.iTunesExplicit = self.items?.last?.iTunes?.iTunesExplicit?.appending(string) ?? string
+ case .rssChannelItemItunesIsClosedCaptioned: self.items?.last?.iTunes?.isClosedCaptioned = self.items?.last?.iTunes?.isClosedCaptioned?.appending(string) ?? string
+ case .rssChannelItemItunesOrder: self.items?.last?.iTunes?.iTunesOrder = Int(string)
+ case .rssChannelItemItunesSubtitle: self.items?.last?.iTunes?.iTunesSubtitle = self.items?.last?.iTunes?.iTunesSubtitle?.appending(string) ?? string
+ case .rssChannelItemItunesSummary: self.items?.last?.iTunes?.iTunesSummary = self.items?.last?.iTunes?.iTunesSummary?.appending(string) ?? string
+ case .rssChannelItemItunesKeywords: self.items?.last?.iTunes?.iTunesKeywords = self.items?.last?.iTunes?.iTunesKeywords?.appending(string) ?? string
+ case .rssChannelItemMediaThumbnail: self.items?.last?.media?.mediaThumbnails?.last?.value = self.items?.last?.media?.mediaThumbnails?.last?.value?.appending(string) ?? string
+ case .rssChannelItemMediaLicense: self.items?.last?.media?.mediaLicense?.value = self.items?.last?.media?.mediaLicense?.value?.appending(string) ?? string
+ case .rssChannelItemMediaRestriction: self.items?.last?.media?.mediaRestriction?.value = self.items?.last?.media?.mediaRestriction?.value?.appending(string) ?? string
+ case .rssChannelItemMediaCommunityMediaTags: self.items?.last?.media?.mediaCommunity?.mediaTags = MediaTag.tagsFrom(string: string)
+ case .rssChannelItemMediaCommentsMediaComment: self.items?.last?.media?.mediaComments?.append(string)
+ case .rssChannelItemMediaEmbedMediaParam: self.items?.last?.media?.mediaEmbed?.mediaParams?.last?.value = self.items?.last?.media?.mediaEmbed?.mediaParams?.last?.value?.appending(string) ?? string
+ case .rssChannelItemMediaGroupMediaCredit: self.items?.last?.media?.mediaGroup?.mediaCredits?.last?.value = self.items?.last?.media?.mediaGroup?.mediaCredits?.last?.value?.appending(string) ?? string
+ case .rssChannelItemMediaGroupMediaCategory: self.items?.last?.media?.mediaGroup?.mediaCategory?.value = self.items?.last?.media?.mediaGroup?.mediaCategory?.value?.appending(string) ?? string
+ case .rssChannelItemMediaGroupMediaRating: self.items?.last?.media?.mediaGroup?.mediaRating?.value = self.items?.last?.media?.mediaGroup?.mediaRating?.value?.appending(string) ?? string
+ case .rssChannelItemMediaResponsesMediaResponse: self.items?.last?.media?.mediaResponses?.append(string)
+ case .rssChannelItemMediaBackLinksBackLink: self.items?.last?.media?.mediaBackLinks?.append(string)
+ case .rssChannelItemMediaLocationPosition: self.items?.last?.media?.mediaLocation?.mapFrom(latLng: string)
+ case .rssChannelItemMediaScenesMediaSceneSceneTitle: self.items?.last?.media?.mediaScenes?.last?.sceneTitle = self.items?.last?.media?.mediaScenes?.last?.sceneTitle?.appending(string) ?? string
+ case .rssChannelItemMediaScenesMediaSceneSceneDescription: self.items?.last?.media?.mediaScenes?.last?.sceneDescription = self.items?.last?.media?.mediaScenes?.last?.sceneDescription?.appending(string) ?? string
+ case .rssChannelItemMediaScenesMediaSceneSceneStartTime: self.items?.last?.media?.mediaScenes?.last?.sceneStartTime = string.toDuration()
+ case .rssChannelItemMediaScenesMediaSceneSceneEndTime: self.items?.last?.media?.mediaScenes?.last?.sceneEndTime = string.toDuration()
+ default: break
+ }
+
+ }
+
+ /// Maps the characters in the specified string to the `RSSFeed` model.
+ ///
+ /// - Parameters:
+ /// - string: The string to map to the model.
+ /// - path: The path of feed's element.
+ func map(_ string: String, for path: RDFPath) {
+
+ switch path {
+ case .rdfChannelTitle: self.title = self.title?.appending(string) ?? string
+ case .rdfChannelLink: self.link = self.link?.appending(string) ?? string
+ case .rdfChannelDescription: self.description = self.description?.appending(string) ?? string
+ case .rdfChannelImage: self.image?.url = self.image?.url?.appending(string) ?? string
+ case .rdfItemTitle: self.items?.last?.title = self.items?.last?.title?.appending(string) ?? string
+ case .rdfItemLink: self.items?.last?.link = self.items?.last?.link?.appending(string) ?? string
+ case .rdfItemDescription: self.items?.last?.description = self.items?.last?.description?.appending(string) ?? string
+ case .rdfChannelSyndicationUpdatePeriod: self.syndication?.syUpdatePeriod = SyndicationUpdatePeriod(rawValue: string)
+ case .rdfChannelSyndicationUpdateFrequency: self.syndication?.syUpdateFrequency = Int(string)
+ case .rdfChannelSyndicationUpdateBase: self.syndication?.syUpdateBase = string.toDate(from: .iso8601)
+ case .rdfChannelDublinCoreTitle: self.dublinCore?.dcTitle = self.dublinCore?.dcTitle?.appending(string) ?? string
+ case .rdfChannelDublinCoreCreator: self.dublinCore?.dcCreator = self.dublinCore?.dcCreator?.appending(string) ?? string
+ case .rdfChannelDublinCoreSubject: self.dublinCore?.dcSubject = self.dublinCore?.dcSubject?.appending(string) ?? string
+ case .rdfChannelDublinCoreDescription: self.dublinCore?.dcDescription = self.dublinCore?.dcDescription?.appending(string) ?? string
+ case .rdfChannelDublinCorePublisher: self.dublinCore?.dcPublisher = self.dublinCore?.dcPublisher?.appending(string) ?? string
+ case .rdfChannelDublinCoreContributor: self.dublinCore?.dcContributor = self.dublinCore?.dcContributor?.appending(string) ?? string
+ case .rdfChannelDublinCoreDate: self.dublinCore?.dcDate = string.toDate(from: .iso8601)
+ case .rdfChannelDublinCoreType: self.dublinCore?.dcType = self.dublinCore?.dcType?.appending(string) ?? string
+ case .rdfChannelDublinCoreFormat: self.dublinCore?.dcFormat = self.dublinCore?.dcFormat?.appending(string) ?? string
+ case .rdfChannelDublinCoreIdentifier: self.dublinCore?.dcIdentifier = self.dublinCore?.dcIdentifier?.appending(string) ?? string
+ case .rdfChannelDublinCoreSource: self.dublinCore?.dcSource = self.dublinCore?.dcSource?.appending(string) ?? string
+ case .rdfChannelDublinCoreLanguage: self.dublinCore?.dcLanguage = self.dublinCore?.dcLanguage?.appending(string) ?? string
+ case .rdfChannelDublinCoreRelation: self.dublinCore?.dcRelation = self.dublinCore?.dcRelation?.appending(string) ?? string
+ case .rdfChannelDublinCoreCoverage: self.dublinCore?.dcCoverage = self.dublinCore?.dcCoverage?.appending(string) ?? string
+ case .rdfChannelDublinCoreRights: self.dublinCore?.dcRights = self.dublinCore?.dcRights?.appending(string) ?? string
+ case .rdfItemDublinCoreTitle: self.items?.last?.dublinCore?.dcTitle = self.items?.last?.dublinCore?.dcTitle?.appending(string) ?? string
+ case .rdfItemDublinCoreCreator: self.items?.last?.dublinCore?.dcCreator = self.items?.last?.dublinCore?.dcCreator?.appending(string) ?? string
+ case .rdfItemDublinCoreSubject: self.items?.last?.dublinCore?.dcSubject = self.items?.last?.dublinCore?.dcSubject?.appending(string) ?? string
+ case .rdfItemDublinCoreDescription: self.items?.last?.dublinCore?.dcDescription = self.items?.last?.dublinCore?.dcDescription?.appending(string) ?? string
+ case .rdfItemDublinCorePublisher: self.items?.last?.dublinCore?.dcPublisher = self.items?.last?.dublinCore?.dcPublisher?.appending(string) ?? string
+ case .rdfItemDublinCoreContributor: self.items?.last?.dublinCore?.dcContributor = self.items?.last?.dublinCore?.dcContributor?.appending(string) ?? string
+ case .rdfItemDublinCoreDate: self.items?.last?.dublinCore?.dcDate = string.toDate(from: .iso8601)
+ case .rdfItemDublinCoreType: self.items?.last?.dublinCore?.dcType = self.items?.last?.dublinCore?.dcType?.appending(string) ?? string
+ case .rdfItemDublinCoreFormat: self.items?.last?.dublinCore?.dcFormat = self.items?.last?.dublinCore?.dcFormat?.appending(string) ?? string
+ case .rdfItemDublinCoreIdentifier: self.items?.last?.dublinCore?.dcIdentifier = self.items?.last?.dublinCore?.dcIdentifier?.appending(string) ?? string
+ case .rdfItemDublinCoreSource: self.items?.last?.dublinCore?.dcSource = self.items?.last?.dublinCore?.dcSource?.appending(string) ?? string
+ case .rdfItemDublinCoreLanguage: self.items?.last?.dublinCore?.dcLanguage = self.items?.last?.dublinCore?.dcLanguage?.appending(string) ?? string
+ case .rdfItemDublinCoreRelation: self.items?.last?.dublinCore?.dcRelation = self.items?.last?.dublinCore?.dcRelation?.appending(string) ?? string
+ case .rdfItemDublinCoreCoverage: self.items?.last?.dublinCore?.dcCoverage = self.items?.last?.dublinCore?.dcCoverage?.appending(string) ?? string
+ case .rdfItemDublinCoreRights: self.items?.last?.dublinCore?.dcRights = self.items?.last?.dublinCore?.dcRights?.appending(string) ?? string
+ default: break
+ }
+
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeed.swift b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeed.swift
new file mode 100755
index 0000000..37bee5c
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeed.swift
@@ -0,0 +1,286 @@
+//
+// RSSFeed.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Data model for the XML DOM of the RSS 2.0 Specification
+/// See http://cyber.law.harvard.edu/rss/rss.html
+///
+/// At the top level, a RSS document is a element, with a mandatory
+/// attribute called version, that specifies the version of RSS that the
+/// document conforms to. If it conforms to this specification, the version
+/// attribute must be 2.0.
+///
+/// Subordinate to the element is a single element, which
+/// contains information about the channel (metadata) and its contents.
+public class RSSFeed {
+
+ /// The name of the channel. It's how people refer to your service. If
+ /// you have an HTML website that contains the same information as your
+ /// RSS file, the title of your channel should be the same as the title
+ /// of your website.
+ ///
+ /// Example: GoUpstate.com News Headlines
+ public var title: String?
+
+ /// The URL to the HTML website corresponding to the channel.
+ ///
+ /// Example: http://www.goupstate.com/
+ public var link: String?
+
+ /// Phrase or sentence describing the channel.
+ ///
+ /// Example: The latest news from GoUpstate.com, a Spartanburg Herald-Journal
+ /// Web site.
+ public var description: String?
+
+ /// The language the channel is written in. This allows aggregators to group
+ /// all Italian language sites, for example, on a single page. A list of
+ /// allowable values for this element, as provided by Netscape, is here:
+ /// http://cyber.law.harvard.edu/rss/languages.html
+ ///
+ /// You may also use values defined by the W3C:
+ /// http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
+ ///
+ /// Example: en-us
+ public var language: String?
+
+ /// Copyright notice for content in the channel.
+ ///
+ /// Example: Copyright 2002, Spartanburg Herald-Journal
+ public var copyright: String?
+
+ /// Email address for person responsible for editorial content.
+ ///
+ /// Example: geo@herald.com (George Matesky)
+ public var managingEditor: String?
+
+ /// Email address for person responsible for technical issues relating to
+ /// channel.
+ ///
+ /// Example: betty@herald.com (Betty Guernsey)
+ public var webMaster: String?
+
+ /// The publication date for the content in the channel. For example, the
+ /// New York Times publishes on a daily basis, the publication date flips
+ /// once every 24 hours. That's when the pubDate of the channel changes.
+ /// All date-times in RSS conform to the Date and Time Specification of
+ /// RFC 822, with the exception that the year may be expressed with two
+ /// characters or four characters (four preferred).
+ ///
+ /// Example: Sat, 07 Sep 2002 00:00:01 GMT
+ public var pubDate: Date?
+
+ /// The last time the content of the channel changed.
+ ///
+ /// Example: Sat, 07 Sep 2002 09:42:31 GMT
+ public var lastBuildDate: Date?
+
+ /// Specify one or more categories that the channel belongs to. Follows the
+ /// same rules as the
- -level category element.
+ ///
+ /// Example: Newspapers
+ public var categories: [RSSFeedCategory]?
+
+ /// A string indicating the program used to generate the channel.
+ ///
+ /// Example: MightyInHouse Content System v2.3
+ public var generator: String?
+
+ /// A URL that points to the documentation for the format used in the RSS
+ /// file. It's probably a pointer to this page. It's for people who might
+ /// stumble across an RSS file on a Web server 25 years from now and wonder
+ /// what it is.
+ ///
+ /// Example: http://blogs.law.harvard.edu/tech/rss
+ public var docs: String?
+
+ /// Allows processes to register with a cloud to be notified of updates to
+ /// the channel, implementing a lightweight publish-subscribe protocol for
+ /// RSS feeds.
+ ///
+ /// Example:
+ ///
+ /// is an optional sub-element of .
+ ///
+ /// It specifies a web service that supports the rssCloud interface which can
+ /// be implemented in HTTP-POST, XML-RPC or SOAP 1.1.
+ ///
+ /// Its purpose is to allow processes to register with a cloud to be notified
+ /// of updates to the channel, implementing a lightweight publish-subscribe
+ /// protocol for RSS feeds.
+ ///
+ ///
+ ///
+ /// In this example, to request notification on the channel it appears in,
+ /// you would send an XML-RPC message to rpc.sys.com on port 80, with a path
+ /// of /RPC2. The procedure to call is myCloud.rssPleaseNotify.
+ ///
+ /// A full explanation of this element and the rssCloud interface is here:
+ /// http://cyber.law.harvard.edu/rss/soapMeetsRss.html#rsscloudInterface
+ public var cloud: RSSFeedCloud?
+
+ /// The PICS rating for the channel.
+ public var rating: String?
+
+ /// 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.
+ ///
+ /// Example: 60
+ ///
+ /// is an optional sub-element of .
+ ///
+ /// 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. This makes
+ /// it possible for RSS sources to be managed by a file-sharing network such
+ /// as Gnutella.
+ public var ttl: Int?
+
+ /// Specifies a GIF, JPEG or PNG image that can be displayed with the channel.
+ ///
+ /// is an optional sub-element of , which contains three
+ /// required and three optional sub-elements.
+ ///
+ /// is the URL of a GIF, JPEG or PNG image that represents the channel.
+ ///
+ /// describes the image, it's used in the ALT attribute of the HTML
+ ///
tag when the channel is rendered in HTML.
+ ///
+ /// is the URL of the site, when the channel is rendered, the image
+ /// is a link to the site. (Note, in practice the image and
+ /// should have the same value as the channel's and .
+ ///
+ /// Optional elements include and , numbers, indicating the
+ /// width and height of the image in pixels. contains text
+ /// that is included in the TITLE attribute of the link formed around the
+ /// image in the HTML rendering.
+ ///
+ /// Maximum value for width is 144, default value is 88.
+ ///
+ /// Maximum value for height is 400, default value is 31.
+ public var image: RSSFeedImage?
+
+ /// Specifies a text input box that can be displayed with the channel.
+ ///
+ /// A channel may optionally contain a sub-element, which contains
+ /// four required sub-elements.
+ ///
+ /// -- The label of the Submit button in the text input area.
+ ///
+ /// -- Explains the text input area.
+ ///
+ /// -- The name of the text object in the text input area.
+ ///
+ /// -- The URL of the CGI script that processes text input requests.
+ ///
+ /// The purpose of the element is something of a mystery. You can
+ /// use it to specify a search engine box. Or to allow a reader to provide
+ /// feedback. Most aggregators ignore it.
+ public var textInput: RSSFeedTextInput?
+
+ /// A hint for aggregators telling them which hours they can skip.
+ ///
+ /// An XML element that contains up to 24 sub-elements whose value is a
+ /// number between 0 and 23, representing a time in GMT, when aggregators, if they
+ /// support the feature, may not read the channel on hours listed in the skipHours
+ /// element.
+ ///
+ /// The hour beginning at midnight is hour zero.
+ public var skipHours: [RSSFeedSkipHour]?
+
+ /// A hint for aggregators telling them which days they can skip.
+ ///
+ /// An XML element that contains up to seven sub-elements whose value
+ /// is Monday, Tuesday, Wednesday, Thursday, Friday, Saturday or Sunday.
+ /// Aggregators may not read the channel during days listed in the skipDays
+ /// element.
+ public var skipDays: [RSSFeedSkipDay]?
+
+ /// A channel may contain any number of - s. An item may represent a
+ /// "story" -- much like a story in a newspaper or magazine; if so its
+ /// description is a synopsis of the story, and the link points to the full
+ /// story. An item may also be complete in itself, if so, the description
+ /// contains the text (entity-encoded HTML is allowed; see examples:
+ /// http://cyber.law.harvard.edu/rss/encodingDescriptions.html), and
+ /// the link and title may be omitted. All elements of an item are optional,
+ /// however at least one of title or description must be present.
+ public var items: [RSSFeedItem]?
+
+
+ // MARK: - Namespaces
+
+ /// The Dublin Core Metadata Element Set is a standard for cross-domain
+ /// resource description.
+ ///
+ /// See https://tools.ietf.org/html/rfc5013
+ public var dublinCore: DublinCoreNamespace?
+
+ /// Provides syndication hints to aggregators and others picking up this RDF Site
+ /// Summary (RSS) feed regarding how often it is updated. For example, if you
+ /// updated your file twice an hour, updatePeriod would be "hourly" and
+ /// updateFrequency would be "2". The syndication module borrows from Ian Davis's
+ /// Open Content Syndication (OCS) directory format. It supercedes the RSS 0.91
+ /// skipDay and skipHour elements.
+ ///
+ /// See http://web.resource.org/rss/1.0/modules/syndication/
+ public var syndication: SyndicationNamespace?
+
+ /// iTunes Podcasting Tags are de facto standard for podcast syndication.
+ /// See https://help.apple.com/itc/podcasts_connect/#/itcb54353390
+ public var iTunes: ITunesNamespace?
+
+
+}
+
+// MARK: - Equatable
+
+extension RSSFeed: Equatable {
+
+ public static func ==(lhs: RSSFeed, rhs: RSSFeed) -> Bool {
+ return
+ lhs.categories == rhs.categories &&
+ lhs.cloud == rhs.cloud &&
+ lhs.copyright == rhs.copyright &&
+ lhs.description == rhs.description &&
+ lhs.docs == rhs.docs &&
+ lhs.dublinCore == rhs.dublinCore &&
+ lhs.generator == rhs.generator &&
+ lhs.items == rhs.items &&
+ lhs.iTunes == rhs.iTunes &&
+ lhs.language == rhs.language &&
+ lhs.lastBuildDate == rhs.lastBuildDate &&
+ lhs.link == rhs.link &&
+ lhs.managingEditor == rhs.managingEditor &&
+ lhs.pubDate == rhs.pubDate &&
+ lhs.rating == rhs.rating &&
+ lhs.skipDays == rhs.skipDays &&
+ lhs.skipHours == rhs.skipHours &&
+ lhs.syndication == rhs.syndication &&
+ lhs.textInput == rhs.textInput &&
+ lhs.title == rhs.title &&
+ lhs.ttl == rhs.ttl &&
+ lhs.webMaster == rhs.webMaster
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedCategory.swift b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedCategory.swift
new file mode 100755
index 0000000..960a179
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedCategory.swift
@@ -0,0 +1,93 @@
+//
+// RSSFeedCategory.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The category of ``. Identifies a category or tag to which the feed
+/// belongs.
+public class RSSFeedCategory {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// A string that identifies a categorization taxonomy. It's an optional
+ /// attribute of ``. e.g. "http://www.fool.com/cusips"
+ public var domain: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension RSSFeedCategory {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = RSSFeedCategory.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension RSSFeedCategory.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.domain = attributeDict["domain"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension RSSFeedCategory: Equatable {
+
+ public static func ==(lhs: RSSFeedCategory, rhs: RSSFeedCategory) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension RSSFeedCategory.Attributes: Equatable {
+
+ public static func ==(lhs: RSSFeedCategory.Attributes, rhs: RSSFeedCategory.Attributes) -> Bool {
+ return lhs.domain == rhs.domain
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedCloud.swift b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedCloud.swift
new file mode 100755
index 0000000..1cb6046
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedCloud.swift
@@ -0,0 +1,132 @@
+//
+// RSSFeedCloud.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Allows processes to register with a cloud to be notified of updates to
+/// the channel, implementing a lightweight publish-subscribe protocol for
+/// RSS feeds.
+///
+/// Example:
+///
+/// is an optional sub-element of .
+///
+/// It specifies a web service that supports the rssCloud interface which can
+/// be implemented in HTTP-POST, XML-RPC or SOAP 1.1.
+///
+/// Its purpose is to allow processes to register with a cloud to be notified
+/// of updates to the channel, implementing a lightweight publish-subscribe
+/// protocol for RSS feeds.
+///
+///
+///
+/// In this example, to request notification on the channel it appears in,
+/// you would send an XML-RPC message to rpc.sys.com on port 80, with a path
+/// of /RPC2. The procedure to call is myCloud.rssPleaseNotify.
+///
+/// A full explanation of this element and the rssCloud interface is here:
+/// http://cyber.law.harvard.edu/rss/soapMeetsRss.html#rsscloudInterface
+public class RSSFeedCloud {
+
+ /// The attributes of the ``'s `` element.
+ public class Attributes {
+
+ /// The domain to register notification to.
+ public var domain: String?
+
+ /// The port to connect to.
+ public var port: Int?
+
+ /// The path to the RPC service. e.g. "/RPC2".
+ public var path: String?
+
+ /// The procedure to call. e.g. "myCloud.rssPleaseNotify" .
+ public var registerProcedure: String?
+
+ /// The `protocol` specification. Can be HTTP-POST, XML-RPC or SOAP 1.1 -
+ /// Note: "protocol" is a reserved keyword, so `protocolSpecification`
+ /// is used instead and refers to the `protocol` attribute of the `cloud`
+ /// element.
+ public var protocolSpecification: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+}
+
+// MARK: - Initializers
+
+extension RSSFeedCloud {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = RSSFeedCloud.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension RSSFeedCloud.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.domain = attributeDict["domain"]
+ self.port = Int(attributeDict["port"] ?? "")
+ self.path = attributeDict["path"]
+ self.registerProcedure = attributeDict["registerProcedure"]
+ self.protocolSpecification = attributeDict["protocol"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension RSSFeedCloud: Equatable {
+
+ public static func ==(lhs: RSSFeedCloud, rhs: RSSFeedCloud) -> Bool {
+ return lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension RSSFeedCloud.Attributes: Equatable {
+
+ public static func ==(lhs: RSSFeedCloud.Attributes, rhs: RSSFeedCloud.Attributes) -> Bool {
+ return
+ lhs.domain == rhs.domain &&
+ lhs.port == rhs.port &&
+ lhs.path == rhs.path &&
+ lhs.registerProcedure == rhs.registerProcedure &&
+ lhs.protocolSpecification == rhs.protocolSpecification
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedImage.swift b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedImage.swift
new file mode 100755
index 0000000..50cfe91
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedImage.swift
@@ -0,0 +1,91 @@
+//
+// RSSFeedImage.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Specifies a GIF, JPEG or PNG image that can be displayed with the channel.
+///
+/// is an optional sub-element of , which contains three
+/// required and three optional sub-elements.
+///
+/// is the URL of a GIF, JPEG or PNG image that represents the channel.
+///
+/// describes the image, it's used in the ALT attribute of the HTML
+///
tag when the channel is rendered in HTML.
+///
+/// is the URL of the site, when the channel is rendered, the image
+/// is a link to the site. (Note, in practice the image and
+/// should have the same value as the channel's and .
+///
+/// Optional elements include and , numbers, indicating the
+/// width and height of the image in pixels. contains text
+/// that is included in the TITLE attribute of the link formed around the
+/// image in the HTML rendering.
+///
+/// Maximum value for width is 144, default value is 88.
+///
+/// Maximum value for height is 400, default value is 31.
+public class RSSFeedImage {
+
+ /// The URL of a GIF, JPEG or PNG image that represents the channel.
+ public var url: String?
+
+ /// Describes the image, it's used in the ALT attribute of the HTML `
`
+ /// tag when the channel is rendered in HTML.
+ public var title: String?
+
+ /// The URL of the site, when the channel is rendered, the image is a link
+ /// to the site. (Note, in practice the image `` and `` should
+ /// have the same value as the channel's `` and ``.
+ public var link: String?
+
+ /// Optional element `` indicating the width of the image in pixels.
+ /// Maximum value for width is 144, default value is 88.
+ public var width: Int?
+
+ /// Optional element `` indicating the height of the image in pixels.
+ /// Maximum value for height is 400, default value is 31.
+ public var height: Int?
+
+ /// Contains text that is included in the TITLE attribute of the link formed
+ /// around the image in the HTML rendering.
+ public var description: String?
+
+}
+
+// MARK: - Equatable
+
+extension RSSFeedImage: Equatable {
+
+ public static func ==(lhs: RSSFeedImage, rhs: RSSFeedImage) -> Bool {
+ return
+ lhs.url == rhs.url &&
+ lhs.title == rhs.title &&
+ lhs.link == rhs.link &&
+ lhs.width == rhs.width &&
+ lhs.height == rhs.height &&
+ lhs.description == rhs.description
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedItem.swift b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedItem.swift
new file mode 100755
index 0000000..8fa1096
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedItem.swift
@@ -0,0 +1,218 @@
+//
+// RSSFeedItem.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// A channel may contain any number of - s. An item may represent a
+/// "story" -- much like a story in a newspaper or magazine; if so its
+/// description is a synopsis of the story, and the link points to the full
+/// story. An item may also be complete in itself, if so, the description
+/// contains the text (entity-encoded HTML is allowed; see examples:
+/// http://cyber.law.harvard.edu/rss/encodingDescriptions.html), and
+/// the link and title may be omitted. All elements of an item are optional,
+/// however at least one of title or description must be present.
+public class RSSFeedItem {
+
+ /// The title of the item.
+ ///
+ /// Example: Venice Film Festival Tries to Quit Sinking
+ public var title: String?
+
+ /// The URL of the item.
+ ///
+ /// Example: http://nytimes.com/2004/12/07FEST.html
+ public var link: String?
+
+ /// The item synopsis.
+ ///
+ /// Example: Some of the most heated chatter at the Venice Film Festival this
+ /// week was about the way that the arrival of the stars at the Palazzo del
+ /// Cinema was being staged.
+ public var description: String?
+
+ /// Email address of the author of the item.
+ ///
+ /// Example: oprah\@oxygen.net
+ ///
+ /// is an optional sub-element of
- .
+ ///
+ /// It's the email address of the author of the item. For newspapers and
+ /// magazines syndicating via RSS, the author is the person who wrote the
+ /// article that the
- describes. For collaborative weblogs, the author
+ /// of the item might be different from the managing editor or webmaster.
+ /// For a weblog authored by a single individual it would make sense to omit
+ /// the element.
+ ///
+ /// lawyer@boyer.net (Lawyer Boyer)
+ public var author: String?
+
+ /// Includes the item in one or more categories.
+ ///
+ /// is an optional sub-element of
- .
+ ///
+ /// It has one optional attribute, domain, a string that identifies a
+ /// categorization taxonomy.
+ ///
+ /// The value of the element is a forward-slash-separated string that
+ /// identifies a hierarchic location in the indicated taxonomy. Processors
+ /// may establish conventions for the interpretation of categories.
+ ///
+ /// Two examples are provided below:
+ ///
+ /// Grateful Dead
+ /// MSFT
+ ///
+ /// You may include as many category elements as you need to, for different
+ /// domains, and to have an item cross-referenced in different parts of the
+ /// same domain.
+ public var categories: [RSSFeedItemCategory]?
+
+ /// URL of a page for comments relating to the item.
+ ///
+ /// Example: http://www.myblog.org/cgi-local/mt/mt-comments.cgi?entry_id=290
+ ///
+ /// is an optional sub-element of
- .
+ ///
+ /// If present, it is the url of the comments page for the item.
+ ///
+ /// http://ekzemplo.com/entry/4403/comments
+ ///
+ /// More about comments here:
+ /// http://cyber.law.harvard.edu/rss/weblogComments.html
+ public var comments: String?
+
+ /// Describes a media object that is attached to the item.
+ ///
+ /// is an optional sub-element of
- .
+ ///
+ /// It has three required attributes. url says where the enclosure is located,
+ /// length says how big it is in bytes, and type says what its type is, a
+ /// standard MIME type.
+ ///
+ /// The url must be an http url.
+ ///
+ ///
+ public var enclosure: RSSFeedItemEnclosure?
+
+ /// A string that uniquely identifies the item.
+ ///
+ /// Example: http://inessential.com/2002/09/01.php#a2
+ ///
+ /// is an optional sub-element of
- .
+ ///
+ /// guid stands for globally unique identifier. It's a string that uniquely
+ /// identifies the item. When present, an aggregator may choose to use this
+ /// string to determine if an item is new.
+ ///
+ /// http://some.server.com/weblogItem3207
+ ///
+ /// There are no rules for the syntax of a guid. Aggregators must view them
+ /// as a string. It's up to the source of the feed to establish the
+ /// uniqueness of the string.
+ ///
+ /// If the guid element has an attribute named "isPermaLink" with a value of
+ /// true, the reader may assume that it is a permalink to the item, that is,
+ /// a url that can be opened in a Web browser, that points to the full item
+ /// described by the
- element. An example:
+ ///
+ /// http://inessential.com/2002/09/01.php#a2
+ ///
+ /// isPermaLink is optional, its default value is true. If its value is false,
+ /// the guid may not be assumed to be a url, or a url to anything in
+ /// particular.
+ public var guid: RSSFeedItemGUID?
+
+ /// Indicates when the item was published.
+ ///
+ /// Example: Sun, 19 May 2002 15:21:36 GMT
+ ///
+ /// is an optional sub-element of
- .
+ ///
+ /// Its value is a date, indicating when the item was published. If it's a
+ /// date in the future, aggregators may choose to not display the item until
+ /// that date.
+ public var pubDate: Date?
+
+ /// The RSS channel that the item came from.
+ ///
+ /// is an optional sub-element of
- .
+ ///
+ /// Its value is the name of the RSS channel that the item came from, derived
+ /// from its . It has one required attribute, url, which links to the
+ /// XMLization of the source.
+ ///
+ /// Tomalak's Realm
+ ///
+ /// The purpose of this element is to propagate credit for links, to
+ /// publicize the sources of news items. It can be used in the Post command
+ /// of an aggregator. It should be generated automatically when forwarding
+ /// an item from an aggregator to a weblog authoring tool.
+ public var source: RSSFeedItemSource?
+
+
+ // MARK: - Namespaces
+
+ /// The Dublin Core Metadata Element Set is a standard for cross-domain
+ /// resource description.
+ ///
+ /// See https://tools.ietf.org/html/rfc5013
+ public var dublinCore: DublinCoreNamespace?
+
+ /// A module for the actual content of websites, in multiple formats.
+ ///
+ /// See http://web.resource.org/rss/1.0/modules/content/
+ public var content: ContentNamespace?
+
+ /// iTunes Podcasting Tags are de facto standard for podcast syndication.
+ /// see https://help.apple.com/itc/podcasts_connect/#/itcb54353390
+ public var iTunes: ITunesNamespace?
+
+ /// Media RSS is a new RSS module that supplements the
+ /// capabilities of RSS 2.0.
+ public var media: MediaNamespace?
+}
+
+// MARK: - Equatable
+
+extension RSSFeedItem: Equatable {
+
+ public static func ==(lhs: RSSFeedItem, rhs: RSSFeedItem) -> Bool {
+ return
+ lhs.author == rhs.author &&
+ lhs.categories == rhs.categories &&
+ lhs.comments == rhs.comments &&
+ lhs.content == rhs.content &&
+ lhs.description == rhs.description &&
+ lhs.dublinCore == rhs.dublinCore &&
+ lhs.enclosure == rhs.enclosure &&
+ lhs.guid == rhs.guid &&
+ lhs.iTunes == rhs.iTunes &&
+ lhs.media == rhs.media &&
+ lhs.pubDate == rhs.pubDate &&
+ lhs.source == rhs.source &&
+ lhs.title == rhs.title
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedItemCategory.swift b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedItemCategory.swift
new file mode 100755
index 0000000..114625d
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedItemCategory.swift
@@ -0,0 +1,111 @@
+//
+// RSSFeedItemCategory.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Includes the item in one or more categories.
+///
+/// is an optional sub-element of
- .
+///
+/// It has one optional attribute, domain, a string that identifies a
+/// categorization taxonomy.
+///
+/// The value of the element is a forward-slash-separated string that
+/// identifies a hierarchic location in the indicated taxonomy. Processors
+/// may establish conventions for the interpretation of categories.
+///
+/// Two examples are provided below:
+///
+/// Grateful Dead
+/// MSFT
+///
+/// You may include as many category elements as you need to, for different
+/// domains, and to have an item cross-referenced in different parts of the
+/// same domain.
+public class RSSFeedItemCategory {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// A string that identifies a categorization taxonomy. It's an optional
+ /// attribute of ``.
+ ///
+ /// Example: http://www.fool.com/cusips
+ public var domain: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension RSSFeedItemCategory {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = RSSFeedItemCategory.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension RSSFeedItemCategory.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.domain = attributeDict["domain"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension RSSFeedItemCategory: Equatable {
+
+ public static func ==(lhs: RSSFeedItemCategory, rhs: RSSFeedItemCategory) -> Bool {
+ return lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension RSSFeedItemCategory.Attributes: Equatable {
+
+ public static func ==(lhs: RSSFeedItemCategory.Attributes, rhs: RSSFeedItemCategory.Attributes) -> Bool {
+ return lhs.domain == rhs.domain
+ }
+
+}
+
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedItemEnclosure.swift b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedItemEnclosure.swift
new file mode 100755
index 0000000..8125f1e
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedItemEnclosure.swift
@@ -0,0 +1,114 @@
+//
+// RSSFeedItemEnclosure.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Describes a media object that is attached to the item.
+///
+/// is an optional sub-element of
- .
+///
+/// It has three required attributes. url says where the enclosure is located,
+/// length says how big it is in bytes, and type says what its type is, a
+/// standard MIME type.
+///
+/// The url must be an http url.
+///
+///
+public class RSSFeedItemEnclosure {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// Where the enclosure is located.
+ ///
+ /// Example: http://www.scripting.com/mp3s/weatherReportSuite.mp3
+ public var url: String?
+
+ /// How big the media object is in bytes.
+ ///
+ /// Example: 12216320
+ public var length: Int64?
+
+ /// Standard MIME type.
+ ///
+ /// Example: audio/mpeg
+ public var type: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+}
+
+// MARK: - Initializers
+
+extension RSSFeedItemEnclosure {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = RSSFeedItemEnclosure.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension RSSFeedItemEnclosure.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.url = attributeDict["url"]
+ self.type = attributeDict["type"]
+ self.length = Int64(attributeDict["length"] ?? "")
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension RSSFeedItemEnclosure: Equatable {
+
+ public static func ==(lhs: RSSFeedItemEnclosure, rhs: RSSFeedItemEnclosure) -> Bool {
+ return lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension RSSFeedItemEnclosure.Attributes: Equatable {
+
+ public static func ==(lhs: RSSFeedItemEnclosure.Attributes, rhs: RSSFeedItemEnclosure.Attributes) -> Bool {
+ return
+ lhs.url == rhs.url &&
+ lhs.type == rhs.type &&
+ lhs.length == rhs.length
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedItemGUID.swift b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedItemGUID.swift
new file mode 100755
index 0000000..2a64aa5
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedItemGUID.swift
@@ -0,0 +1,125 @@
+//
+// RSSFeedItemGUID.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// A string that uniquely identifies the item.
+///
+/// Example: http://inessential.com/2002/09/01.php#a2
+///
+/// is an optional sub-element of
- .
+///
+/// guid stands for globally unique identifier. It's a string that uniquely
+/// identifies the item. When present, an aggregator may choose to use this
+/// string to determine if an item is new.
+///
+/// http://some.server.com/weblogItem3207
+///
+/// There are no rules for the syntax of a guid. Aggregators must view them
+/// as a string. It's up to the source of the feed to establish the
+/// uniqueness of the string.
+///
+/// If the guid element has an attribute named "isPermaLink" with a value of
+/// true, the reader may assume that it is a permalink to the item, that is,
+/// a url that can be opened in a Web browser, that points to the full item
+/// described by the
- element. An example:
+///
+/// http://inessential.com/2002/09/01.php#a2
+///
+/// isPermaLink is optional, its default value is true. If its value is false,
+/// the guid may not be assumed to be a url, or a url to anything in
+/// particular.
+public class RSSFeedItemGUID {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// If the guid element has an attribute named "isPermaLink" with a value of
+ /// true, the reader may assume that it is a permalink to the item, that is,
+ /// a url that can be opened in a Web browser, that points to the full item
+ /// described by the
- element. An example:
+ ///
+ /// http://inessential.com/2002/09/01.php#a2
+ ///
+ /// isPermaLink is optional, its default value is true. If its value is false,
+ /// the guid may not be assumed to be a url, or a url to anything in
+ /// particular.
+ public var isPermaLink: Bool?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension RSSFeedItemGUID {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = RSSFeedItemGUID.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension RSSFeedItemGUID.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.isPermaLink = attributeDict["isPermaLink"]?.toBool()
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension RSSFeedItemGUID: Equatable {
+
+ public static func ==(lhs: RSSFeedItemGUID, rhs: RSSFeedItemGUID) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension RSSFeedItemGUID.Attributes: Equatable {
+
+ public static func ==(lhs: RSSFeedItemGUID.Attributes, rhs: RSSFeedItemGUID.Attributes) -> Bool {
+ return lhs.isPermaLink == rhs.isPermaLink
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedItemSource.swift b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedItemSource.swift
new file mode 100755
index 0000000..8828f3f
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedItemSource.swift
@@ -0,0 +1,105 @@
+//
+// RSSFeedItemSource.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The RSS channel that the item came from.
+///
+/// is an optional sub-element of
- .
+///
+/// Its value is the name of the RSS channel that the item came from, derived
+/// from its . It has one required attribute, url, which links to the
+/// XMLization of the source.
+///
+/// Tomalak's Realm
+///
+/// The purpose of this element is to propagate credit for links, to
+/// publicize the sources of news items. It can be used in the Post command
+/// of an aggregator. It should be generated automatically when forwarding
+/// an item from an aggregator to a weblog authoring tool.
+public class RSSFeedItemSource {
+
+ /// The element's attributes.
+ public class Attributes {
+
+ /// Required attribute of the `Source` element, which links to the
+ /// XMLization of the source. e.g. "http://www.tomalak.org/links2.xml"
+ public var url: String?
+
+ }
+
+ /// The element's attributes.
+ public var attributes: Attributes?
+
+ /// The element's value.
+ public var value: String?
+
+}
+
+// MARK: - Initializers
+
+extension RSSFeedItemSource {
+
+ convenience init(attributes attributeDict: [String : String]) {
+ self.init()
+ self.attributes = RSSFeedItemSource.Attributes(attributes: attributeDict)
+ }
+
+}
+
+extension RSSFeedItemSource.Attributes {
+
+ convenience init?(attributes attributeDict: [String : String]) {
+
+ if attributeDict.isEmpty {
+ return nil
+ }
+
+ self.init()
+
+ self.url = attributeDict["url"]
+
+ }
+
+}
+
+// MARK: - Equatable
+
+extension RSSFeedItemSource: Equatable {
+
+ public static func ==(lhs: RSSFeedItemSource, rhs: RSSFeedItemSource) -> Bool {
+ return
+ lhs.value == rhs.value &&
+ lhs.attributes == rhs.attributes
+ }
+
+}
+
+extension RSSFeedItemSource.Attributes: Equatable {
+
+ public static func ==(lhs: RSSFeedItemSource.Attributes, rhs: RSSFeedItemSource.Attributes) -> Bool {
+ return lhs.url == rhs.url
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedSkipDay.swift b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedSkipDay.swift
new file mode 100755
index 0000000..ddf2b9d
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedSkipDay.swift
@@ -0,0 +1,70 @@
+//
+// RSSFeedSkipDay.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// A hint for aggregators telling them which days they can skip.
+///
+/// An XML element that contains up to seven sub-elements whose value
+/// is Monday, Tuesday, Wednesday, Thursday, Friday, Saturday or Sunday.
+/// Aggregators may not read the channel during days listed in the skipDays
+/// element.
+///
+/// - monday: Aggregator hint to skip parsing on `Monday`.
+/// - tuesday: Aggregator hint to skip parsing on `Tuesday`.
+/// - wednesday: Aggregator hint to skip parsing on `Wednesday`.
+/// - thursday: Aggregator hint to skip parsing on `Thursday`.
+/// - friday: Aggregator hint to skip parsing on `Friday`.
+/// - saturday: Aggregator hint to skip parsing on `Saturday`.
+/// - sunday: Aggregator hint to skip parsing on `Sunday`.
+public enum RSSFeedSkipDay: String {
+ case monday = "monday"
+ case tuesday = "tuesday"
+ case wednesday = "wednesday"
+ case thursday = "thursday"
+ case friday = "friday"
+ case saturday = "saturday"
+ case sunday = "sunday"
+}
+
+extension RSSFeedSkipDay {
+
+ /// Lowercase the incoming `rawValue` string to try and match the
+ /// `RSSFeedSkipDay`'s `rawValue`
+ ///
+ /// - Parameter rawValue: The raw value
+ public init?(rawValue: String) {
+ switch rawValue.lowercased() {
+ case "monday": self = .monday
+ case "tuesday": self = .tuesday
+ case "wednesday": self = .wednesday
+ case "thursday": self = .thursday
+ case "friday": self = .friday
+ case "saturday": self = .saturday
+ case "sunday": self = .sunday
+ default: return nil
+ }
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedSkipHour.swift b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedSkipHour.swift
new file mode 100755
index 0000000..3e41d45
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedSkipHour.swift
@@ -0,0 +1,35 @@
+//
+// RSSFeedSkipHour.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// A hint for aggregators telling them which hours they can skip.
+///
+/// An XML element that contains up to 24 sub-elements whose value is a
+/// number between 0 and 23, representing a time in GMT, when aggregators, if they
+/// support the feature, may not read the channel on hours listed in the skipHours
+/// element.
+///
+/// The hour beginning at midnight is hour zero.
+public typealias RSSFeedSkipHour = Int
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedTextInput.swift b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedTextInput.swift
new file mode 100755
index 0000000..924b79e
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSFeedTextInput.swift
@@ -0,0 +1,71 @@
+//
+// RSSFeedTextInput.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Specifies a text input box that can be displayed with the channel.
+///
+/// A channel may optionally contain a sub-element, which contains
+/// four required sub-elements.
+///
+/// -- The label of the Submit button in the text input area.
+///
+/// -- Explains the text input area.
+///
+/// -- The name of the text object in the text input area.
+///
+/// -- The URL of the CGI script that processes text input requests.
+///
+/// The purpose of the element is something of a mystery. You can
+/// use it to specify a search engine box. Or to allow a reader to provide
+/// feedback. Most aggregators ignore it.
+public class RSSFeedTextInput {
+
+ /// The label of the Submit button in the text input area.
+ public var title: String?
+
+ /// Explains the text input area.
+ public var description: String?
+
+ /// The name of the text object in the text input area.
+ public var name: String?
+
+ /// The URL of the CGI script that processes text input requests.
+ public var link: String?
+
+}
+
+// MARK: - Equatable
+
+extension RSSFeedTextInput: Equatable {
+
+ public static func ==(lhs: RSSFeedTextInput, rhs: RSSFeedTextInput) -> Bool {
+ return
+ lhs.title == rhs.title &&
+ lhs.description == rhs.description &&
+ lhs.name == rhs.name &&
+ lhs.link == lhs.link
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSPath.swift b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSPath.swift
new file mode 100755
index 0000000..a674587
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Models/RSS/RSSPath.swift
@@ -0,0 +1,184 @@
+//
+// RSSPath.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Describes the individual path for each XML DOM element of an RSS feed
+///
+/// See http://web.resource.org/rss/1.0/modules/content/
+enum RSSPath: String {
+
+ case rss = "/rss"
+ case rssChannel = "/rss/channel"
+ case rssChannelTitle = "/rss/channel/title"
+ case rssChannelLink = "/rss/channel/link"
+ case rssChannelDescription = "/rss/channel/description"
+ case rssChannelLanguage = "/rss/channel/language"
+ case rssChannelCopyright = "/rss/channel/copyright"
+ case rssChannelManagingEditor = "/rss/channel/managingEditor"
+ case rssChannelWebMaster = "/rss/channel/webMaster"
+ case rssChannelPubDate = "/rss/channel/pubDate"
+ case rssChannelLastBuildDate = "/rss/channel/lastBuildDate"
+ case rssChannelCategory = "/rss/channel/category"
+ case rssChannelGenerator = "/rss/channel/generator"
+ case rssChannelDocs = "/rss/channel/docs"
+ case rssChannelCloud = "/rss/channel/cloud"
+ case rssChannelRating = "/rss/channel/rating"
+ case rssChannelTTL = "/rss/channel/ttl"
+ case rssChannelImage = "/rss/channel/image"
+ case rssChannelImageURL = "/rss/channel/image/url"
+ case rssChannelImageTitle = "/rss/channel/image/title"
+ case rssChannelImageLink = "/rss/channel/image/link"
+ case rssChannelImageWidth = "/rss/channel/image/width"
+ case rssChannelImageHeight = "/rss/channel/image/height"
+ case rssChannelImageDescription = "/rss/channel/image/description"
+ case rssChannelTextInput = "/rss/channel/textInput"
+ case rssChannelTextInputTitle = "/rss/channel/textInput/title"
+ case rssChannelTextInputDescription = "/rss/channel/textInput/description"
+ case rssChannelTextInputName = "/rss/channel/textInput/name"
+ case rssChannelTextInputLink = "/rss/channel/textInput/link"
+ case rssChannelSkipHours = "/rss/channel/skipHours"
+ case rssChannelSkipHoursHour = "/rss/channel/skipHours/hour"
+ case rssChannelSkipDays = "/rss/channel/skipDays"
+ case rssChannelSkipDaysDay = "/rss/channel/skipDays/day"
+ case rssChannelItem = "/rss/channel/item"
+ case rssChannelItemTitle = "/rss/channel/item/title"
+ case rssChannelItemLink = "/rss/channel/item/link"
+ case rssChannelItemDescription = "/rss/channel/item/description"
+ case rssChannelItemAuthor = "/rss/channel/item/author"
+ case rssChannelItemCategory = "/rss/channel/item/category"
+ case rssChannelItemComments = "/rss/channel/item/comments"
+ case rssChannelItemEnclosure = "/rss/channel/item/enclosure"
+ case rssChannelItemGUID = "/rss/channel/item/guid"
+ case rssChannelItemPubDate = "/rss/channel/item/pubDate"
+ case rssChannelItemSource = "/rss/channel/item/source"
+
+ // Content
+
+ case rssChannelItemContentEncoded = "/rss/channel/item/content:encoded"
+
+ // Syndication
+
+ case rssChannelSyndicationUpdatePeriod = "/rss/channel/sy:updatePeriod"
+ case rssChannelSyndicationUpdateFrequency = "/rss/channel/sy:updateFrequency"
+ case rssChannelSyndicationUpdateBase = "/rss/channel/sy:updateBase"
+
+ // Dublin Core
+
+ case rssChannelDublinCoreTitle = "/rss/channel/dc:title"
+ case rssChannelDublinCoreCreator = "/rss/channel/dc:creator"
+ case rssChannelDublinCoreSubject = "/rss/channel/dc:subject"
+ case rssChannelDublinCoreDescription = "/rss/channel/dc:description"
+ case rssChannelDublinCorePublisher = "/rss/channel/dc:publisher"
+ case rssChannelDublinCoreContributor = "/rss/channel/dc:contributor"
+ case rssChannelDublinCoreDate = "/rss/channel/dc:date"
+ case rssChannelDublinCoreType = "/rss/channel/dc:type"
+ case rssChannelDublinCoreFormat = "/rss/channel/dc:format"
+ case rssChannelDublinCoreIdentifier = "/rss/channel/dc:identifier"
+ case rssChannelDublinCoreSource = "/rss/channel/dc:source"
+ case rssChannelDublinCoreLanguage = "/rss/channel/dc:language"
+ case rssChannelDublinCoreRelation = "/rss/channel/dc:relation"
+ case rssChannelDublinCoreCoverage = "/rss/channel/dc:coverage"
+ case rssChannelDublinCoreRights = "/rss/channel/dc:rights"
+ case rssChannelItemDublinCoreTitle = "/rss/channel/item/dc:title"
+ case rssChannelItemDublinCoreCreator = "/rss/channel/item/dc:creator"
+ case rssChannelItemDublinCoreSubject = "/rss/channel/item/dc:subject"
+ case rssChannelItemDublinCoreDescription = "/rss/channel/item/dc:description"
+ case rssChannelItemDublinCorePublisher = "/rss/channel/item/dc:publisher"
+ case rssChannelItemDublinCoreContributor = "/rss/channel/item/dc:contributor"
+ case rssChannelItemDublinCoreDate = "/rss/channel/item/dc:date"
+ case rssChannelItemDublinCoreType = "/rss/channel/item/dc:type"
+ case rssChannelItemDublinCoreFormat = "/rss/channel/item/dc:format"
+ case rssChannelItemDublinCoreIdentifier = "/rss/channel/item/dc:identifier"
+ case rssChannelItemDublinCoreSource = "/rss/channel/item/dc:source"
+ case rssChannelItemDublinCoreLanguage = "/rss/channel/item/dc:language"
+ case rssChannelItemDublinCoreRelation = "/rss/channel/item/dc:relation"
+ case rssChannelItemDublinCoreCoverage = "/rss/channel/item/dc:coverage"
+ case rssChannelItemDublinCoreRights = "/rss/channel/item/dc:rights"
+
+ // iTunes Podcasting Tags
+
+ case rssChannelItunesAuthor = "/rss/channel/itunes:author"
+ case rssChannelItunesBlock = "/rss/channel/itunes:block"
+ case rssChannelItunesCategory = "/rss/channel/itunes:category"
+ case rssChannelItunesSubcategory = "/rss/channel/itunes:category/itunes:category"
+ case rssChannelItunesImage = "/rss/channel/itunes:image"
+ case rssChannelItunesExplicit = "/rss/channel/itunes:explicit"
+ case rssChannelItunesComplete = "/rss/channel/itunes:complete"
+ case rssChannelItunesNewFeedURL = "/rss/channel/itunes:new-feed-url"
+ case rssChannelItunesOwner = "/rss/channel/itunes:owner"
+ case rssChannelItunesOwnerEmail = "/rss/channel/itunes:owner/itunes:email"
+ case rssChannelItunesOwnerName = "/rss/channel/itunes:owner/itunes:name"
+ case rssChannelItunesSubtitle = "/rss/channel/itunes:subtitle"
+ case rssChannelItunesSummary = "/rss/channel/itunes:summary"
+ case rssChannelItunesKeywords = "/rss/channel/itunes:keywords"
+
+ case rssChannelItemItunesAuthor = "/rss/channel/item/itunes:author"
+ case rssChannelItemItunesBlock = "/rss/channel/item/itunes:block"
+ case rssChannelItemItunesImage = "/rss/channel/item/itunes:image"
+ case rssChannelItemItunesDuration = "/rss/channel/item/itunes:duration"
+ case rssChannelItemItunesExplicit = "/rss/channel/item/itunes:explicit"
+ case rssChannelItemItunesIsClosedCaptioned = "/rss/channel/item/itunes:isClosedCaptioned"
+ case rssChannelItemItunesOrder = "/rss/channel/item/itunes:order"
+ case rssChannelItemItunesSubtitle = "/rss/channel/item/itunes:subtitle"
+ case rssChannelItemItunesSummary = "/rss/channel/item/itunes:summary"
+ case rssChannelItemItunesKeywords = "/rss/channel/item/itunes:keywords"
+
+ // MARK: Media
+
+ case rssChannelItemMediaThumbnail = "/rss/channel/item/media:thumbnail"
+ case rssChannelItemMediaContent = "/rss/channel/item/media:content"
+ case rssChannelItemMediaCommunity = "/rss/channel/item/media:community"
+ case rssChannelItemMediaCommunityMediaStarRating = "/rss/channel/item/media:community/media:starRating"
+ case rssChannelItemMediaCommunityMediaStatistics = "/rss/channel/item/media:community/media:statistics"
+ case rssChannelItemMediaCommunityMediaTags = "/rss/channel/item/media:community/media:tags"
+ case rssChannelItemMediaComments = "/rss/channel/item/media:comments"
+ case rssChannelItemMediaCommentsMediaComment = "/rss/channel/item/media:comments/media:comment"
+ case rssChannelItemMediaEmbed = "/rss/channel/item/media:embed"
+ case rssChannelItemMediaEmbedMediaParam = "/rss/channel/item/media:embed/media:param"
+ case rssChannelItemMediaResponses = "/rss/channel/item/media:responses"
+ case rssChannelItemMediaResponsesMediaResponse = "/rss/channel/item/media:responses/media:response"
+ case rssChannelItemMediaBackLinks = "/rss/channel/item/media:backLinks"
+ case rssChannelItemMediaBackLinksBackLink = "/rss/channel/item/media:backLinks/media:backLink"
+ case rssChannelItemMediaStatus = "/rss/channel/item/media:status"
+ case rssChannelItemMediaPrice = "/rss/channel/item/media:price"
+ case rssChannelItemMediaLicense = "/rss/channel/item/media:license"
+ case rssChannelItemMediaSubTitle = "/rss/channel/item/media:subTitle"
+ case rssChannelItemMediaPeerLink = "/rss/channel/item/media:peerLink"
+ case rssChannelItemMediaLocation = "/rss/channel/item/media:location"
+ case rssChannelItemMediaLocationPosition = "/rss/channel/item/media:location/georss:where/gml:Point/gml:pos"
+ case rssChannelItemMediaRestriction = "/rss/channel/item/media:restriction"
+ case rssChannelItemMediaScenes = "/rss/channel/item/media:scenes"
+ case rssChannelItemMediaScenesMediaScene = "/rss/channel/item/media:scenes/media:scene"
+ case rssChannelItemMediaScenesMediaSceneSceneTitle = "/rss/channel/item/media:scenes/media:scene/sceneTitle"
+ case rssChannelItemMediaScenesMediaSceneSceneDescription = "/rss/channel/item/media:scenes/media:scene/sceneDescription"
+ case rssChannelItemMediaScenesMediaSceneSceneStartTime = "/rss/channel/item/media:scenes/media:scene/sceneStartTime"
+ case rssChannelItemMediaScenesMediaSceneSceneEndTime = "/rss/channel/item/media:scenes/media:scene/sceneEndTime"
+ case rssChannelItemMediaGroup = "/rss/channel/item/media:group"
+ case rssChannelItemMediaGroupMediaCredit = "/rss/channel/item/media:group/media:credit"
+ case rssChannelItemMediaGroupMediaCategory = "/rss/channel/item/media:group/media:category"
+ case rssChannelItemMediaGroupMediaRating = "/rss/channel/item/media:group/media:rating"
+ case rssChannelItemMediaGroupMediaContent = "/rss/channel/item/media:group/media:content"
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Parser/FeedDataType.swift b/Pods/FeedKit/Sources/FeedKit/Parser/FeedDataType.swift
new file mode 100644
index 0000000..b86a94f
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Parser/FeedDataType.swift
@@ -0,0 +1,51 @@
+//
+// FeedDataType.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Types of data to determine how to parse a feed.
+///
+/// - xml: XML Data Type.
+/// - json: JSON Data Type.
+enum FeedDataType: String {
+ case xml
+ case json
+}
+
+extension FeedDataType {
+
+ /// A `FeedDataType` from the specified `Data` object
+ ///
+ /// - Parameter data: The `Data` object.
+ init?(data: Data) {
+ guard let string = String(data: data, encoding: .utf8) else { return nil }
+ guard let char = string.trimmingCharacters(in: .whitespacesAndNewlines).first else { return nil }
+ switch char {
+ case "<": self = .xml
+ case "{": self = .json
+ default: return nil
+ }
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Parser/FeedParser.swift b/Pods/FeedKit/Sources/FeedKit/Parser/FeedParser.swift
new file mode 100644
index 0000000..11203c8
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Parser/FeedParser.swift
@@ -0,0 +1,93 @@
+//
+// FeedParser.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+import Dispatch
+
+/// An RSS and Atom feed parser. `FeedParser` uses `Foundation`'s `XMLParser`.
+public class FeedParser {
+
+ /// A FeedParser handler provider.
+ let parser: FeedParserProtocol
+
+ /// Initializes the parser with the xml or json contents encapsulated in a
+ /// given data object.
+ ///
+ /// - Parameter data: An instance of `FeedParser`.
+ public init?(data: Data) {
+ guard let decoded = data.toUtf8() else { return nil }
+
+ guard let feedDataType = FeedDataType(data: decoded) else { return nil }
+ switch feedDataType {
+ case .json: self.parser = JSONFeedParser(data: decoded)
+ case .xml: self.parser = XMLFeedParser(data: decoded)
+ }
+ }
+
+ /// Initializes the parser with the XML content referenced by the given URL.
+ ///
+ /// - Parameter URL: An instance of `FeedParser`.
+ public convenience init?(URL: URL) {
+ guard let data = try? Data(contentsOf: URL) else {
+ return nil
+ }
+ self.init(data: data)
+ }
+
+ /// Starts parsing the feed.
+ ///
+ /// - Returns: The parsed `Result`.
+ public func parse() -> Result {
+ return self.parser.parse()
+ }
+
+ /// Starts parsing the feed asynchronously. Parsing runs by default on the
+ /// global queue. You are responsible to manually bring the result closure
+ /// to whichever queue is apropriate, if any.
+ ///
+ /// Usually to the Main queue if UI Updates are needed.
+ ///
+ /// DispatchQueue.main.async {
+ /// // UI Updates
+ /// }
+ ///
+ /// - Parameters:
+ /// - queue: The queue on which the completion handler is dispatched.
+ /// - result: The parsed `Result`.
+ public func parseAsync(
+ queue: DispatchQueue = DispatchQueue.global(),
+ result: @escaping (Result) -> Void)
+ {
+ queue.async {
+ result(self.parse())
+ }
+ }
+
+ /// Stops parsing XML feeds.
+ public func abortParsing() {
+ guard let xmlFeedParser = self.parser as? XMLFeedParser else { return }
+ xmlFeedParser.xmlParser.abortParsing()
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Parser/FeedParserProtocol.swift b/Pods/FeedKit/Sources/FeedKit/Parser/FeedParserProtocol.swift
new file mode 100644
index 0000000..f562da7
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Parser/FeedParserProtocol.swift
@@ -0,0 +1,31 @@
+//
+// FeedParserProtocol.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The protocol for Parsing handlers.
+protocol FeedParserProtocol {
+ init(data: Data)
+ func parse() -> Result
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Parser/JSONFeedParser.swift b/Pods/FeedKit/Sources/FeedKit/Parser/JSONFeedParser.swift
new file mode 100644
index 0000000..2c239c1
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Parser/JSONFeedParser.swift
@@ -0,0 +1,61 @@
+//
+// JSONFeedParser.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The actual engine behind the `FeedKit` framework. `JSONFeedParser` handles
+/// the parsing of JSON Feeds.
+///
+/// See: https://jsonfeed.org/version/1
+class JSONFeedParser: FeedParserProtocol {
+
+ let data: Data
+
+ required public init(data: Data) {
+ self.data = data
+ }
+
+ func parse() -> Result {
+
+ do {
+
+ let jsonObject = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
+
+ guard let jsonDictionary = jsonObject as? [String: Any?] else {
+ return Result.failure(ParserError.internalError(reason: "Unable to cast serialized json.").value)
+ }
+
+ guard let jsonFeed = JSONFeed(dictionary: jsonDictionary) else {
+ return Result.failure(ParserError.feedNotFound.value)
+ }
+
+ return Result.json(jsonFeed)
+
+ } catch {
+ return Result.failure(NSError(domain: error.localizedDescription, code: -1))
+ }
+
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Parser/ParserError.swift b/Pods/FeedKit/Sources/FeedKit/Parser/ParserError.swift
new file mode 100644
index 0000000..7c500db
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Parser/ParserError.swift
@@ -0,0 +1,82 @@
+//
+// ParserError.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+
+/// Error types with `NSError` codes and user info providers
+///
+/// - feedNotFound: Couldn't parse any known feed.
+/// - feedCDATABlockEncodingError: Unable to convert the bytes in `CDATABlock`
+/// to Unicode characters using the UTF-8 encoding.
+/// - internalError: An internal error from which the user cannot recover.
+public enum ParserError {
+
+ case feedNotFound
+ case feedCDATABlockEncodingError(path: String)
+ case internalError(reason: String)
+
+ /// An error's code for the specified case.
+ var code: Int {
+ switch self {
+ case .feedNotFound: return -1000
+ case .feedCDATABlockEncodingError: return -10001
+ case .internalError(_): return -90000
+ }
+ }
+
+ /// The error's userInfo dictionary for the specified case.
+ var userInfo: [String: String] {
+ switch self {
+ case .feedNotFound:
+ return [
+ NSLocalizedDescriptionKey: "Feed not found",
+ NSLocalizedFailureReasonErrorKey: "Couldn't parse any known feed",
+ NSLocalizedRecoverySuggestionErrorKey: "Provide a valid Atom/RSS/JSON feed "
+ ]
+
+ case .feedCDATABlockEncodingError(let path):
+ return [
+ NSLocalizedDescriptionKey: "`CDATAblock` encoding error",
+ NSLocalizedFailureReasonErrorKey: "Unable to convert the bytes in `CDATABlock` to Unicode characters using the UTF-8 encoding at current path: \(path)",
+ NSLocalizedRecoverySuggestionErrorKey: "Make sure the encoding provided in a `CDATABlock` is encoded as UTF-8"
+ ]
+
+ case .internalError(let reason):
+ return [
+ NSLocalizedDescriptionKey: "Internal unresolved error: \(reason)",
+ NSLocalizedFailureReasonErrorKey: "Unable to recover from an internal unresolved error: \(reason)",
+ NSLocalizedRecoverySuggestionErrorKey: "If you're seeing this error you probably should open an issue on github"
+ ]
+
+ }
+
+ }
+
+ /// The `NSError` from the specified case.
+ var value: NSError {
+ return NSError(domain:"com.feedkit.error", code: self.code, userInfo: self.userInfo)
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Parser/Result.swift b/Pods/FeedKit/Sources/FeedKit/Parser/Result.swift
new file mode 100644
index 0000000..ccc90d2
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Parser/Result.swift
@@ -0,0 +1,92 @@
+//
+// Result.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Used to provide the result of a parsed feed, whether the parsing was
+/// successfull or encountered an error.
+///
+/// - atom: The parsed `AtomFeed` model.
+/// - rss: The parsed `RSSFeed` model.
+/// - json: The parsed `JSONFeed` model.
+/// - failure: The failure `NSError` generated from parsing errors.
+public enum Result {
+
+ case atom(AtomFeed)
+ case rss(RSSFeed)
+ case json(JSONFeed)
+ case failure(NSError)
+
+ /// Returns `true` if the result is a success, `false` otherwise.
+ public var isSuccess: Bool {
+ switch self {
+ case .atom: return true
+ case .rss: return true
+ case .json: return true
+ case .failure: return false
+ }
+ }
+
+ /// Returns `true` if the result is a failure, `false` otherwise.
+ public var isFailure: Bool {
+ return !isSuccess
+ }
+
+ /// Returns the parsed rss feed value if the result is a success, `nil`
+ /// otherwise.
+ public var rssFeed: RSSFeed? {
+ switch self {
+ case .rss(let value): return value
+ default: return nil
+ }
+ }
+
+ /// Returns the parsed atom feed if the result is a success, `nil`
+ /// otherwise.
+ public var atomFeed: AtomFeed? {
+ switch self {
+ case .atom(let value): return value
+ default: return nil
+ }
+ }
+
+ /// Returns the parsed json feed if the result is a success, `nil`
+ /// otherwise.
+ public var jsonFeed: JSONFeed? {
+ switch self {
+ case .json(let value): return value
+ default: return nil
+ }
+ }
+
+ /// Returns the associated error value if the result is a failure, `nil`
+ /// otherwise.
+ public var error: NSError? {
+ switch self {
+ case .failure(let error): return error
+ default: return nil
+ }
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Parser/XMLFeedParser.swift b/Pods/FeedKit/Sources/FeedKit/Parser/XMLFeedParser.swift
new file mode 100644
index 0000000..e1adbdb
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Parser/XMLFeedParser.swift
@@ -0,0 +1,190 @@
+//
+// XMLFeedParser.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// The actual engine behind the `FeedKit` framework. `XMLFeedParser` handles
+/// the parsing of RSS and Atom feeds. It is an `XMLParserDelegate` of
+/// itself.
+class XMLFeedParser: NSObject, XMLParserDelegate, FeedParserProtocol {
+
+ /// The Feed Type currently being parsed. The Initial value of this variable
+ /// is unknown until a recognizable element that matches a feed type is
+ /// found.
+ var feedType: XMLFeedType?
+
+ /// The RSS feed model.
+ var rssFeed: RSSFeed?
+
+ /// The Atom feed model.
+ var atomFeed: AtomFeed?
+
+ /// The XML Parser.
+ let xmlParser: XMLParser
+
+ /// An XML Feed Parser, for rss and atom feeds.
+ ///
+ /// - Parameter data: A `Data` object containing an XML feed.
+ required init(data: Data) {
+ self.xmlParser = XMLParser(data: data)
+ super.init()
+ self.xmlParser.delegate = self
+ }
+
+ /// The current path along the XML's DOM elements. Path components are
+ /// updated to reflect the current XML element being parsed.
+ /// e.g. "/rss/channel/title" means it's currently parsing the channels
+ /// `` element.
+ fileprivate var currentXMLDOMPath: URL = URL(string: "/")!
+
+ /// A parsing error, if any.
+ var parsingError: NSError?
+
+ /// Starts parsing the feed.
+ func parse() -> Result {
+ let _ = self.xmlParser.parse()
+
+ if let error = parsingError {
+ return Result.failure(error)
+ }
+
+ guard let feedType = self.feedType else {
+ return Result.failure(ParserError.feedNotFound.value)
+ }
+
+ switch feedType {
+ case .atom: return Result.atom(self.atomFeed!)
+ case .rdf, .rss: return Result.rss(self.rssFeed!)
+ }
+
+ }
+
+ /// Redirects characters found between XML elements to their proper model
+ /// mappers based on the `currentXMLDOMPath`.
+ ///
+ /// - Parameter string: The characters to map.
+ fileprivate func map(_ string: String) {
+ guard let feedType = self.feedType else { return }
+
+ switch feedType {
+ case .atom:
+ if let path = AtomPath(rawValue: self.currentXMLDOMPath.absoluteString) {
+ self.atomFeed?.map(string, for: path)
+ }
+
+ case .rdf:
+ if let path = RDFPath(rawValue: self.currentXMLDOMPath.absoluteString) {
+ self.rssFeed?.map(string, for: path)
+ }
+
+ case .rss:
+ if let path = RSSPath(rawValue: self.currentXMLDOMPath.absoluteString) {
+ self.rssFeed?.map(string, for: path)
+ }
+
+ }
+
+ }
+
+}
+
+// MARK: - XMLParser delegate
+
+extension XMLFeedParser {
+
+ func parser(
+ _ parser: XMLParser,
+ didStartElement elementName: String,
+ namespaceURI: String?,
+ qualifiedName qName: String?,
+ attributes attributeDict: [String : String])
+ {
+
+ // Update the current path along the XML's DOM elements by appending the new component with `elementName`.
+ self.currentXMLDOMPath = self.currentXMLDOMPath.appendingPathComponent(elementName)
+
+ // Get the feed type from the element, if it hasn't been done yet.
+ guard let feedType = self.feedType else {
+ self.feedType = XMLFeedType(rawValue: elementName)
+ return
+ }
+
+ switch feedType {
+ case .atom:
+ if self.atomFeed == nil {
+ self.atomFeed = AtomFeed()
+ }
+ if let path = AtomPath(rawValue: self.currentXMLDOMPath.absoluteString) {
+ self.atomFeed?.map(attributeDict, for: path)
+ }
+
+ case .rdf:
+ if self.rssFeed == nil {
+ self.rssFeed = RSSFeed()
+ }
+ if let path = RDFPath(rawValue: self.currentXMLDOMPath.absoluteString) {
+ self.rssFeed?.map(attributeDict, for: path)
+ }
+
+ case .rss:
+ if self.rssFeed == nil {
+ self.rssFeed = RSSFeed()
+ }
+ if let path = RSSPath(rawValue: self.currentXMLDOMPath.absoluteString) {
+ self.rssFeed?.map(attributeDict, for: path)
+ }
+
+ }
+
+ }
+
+ func parser(
+ _ parser: XMLParser,
+ didEndElement elementName: String,
+ namespaceURI: String?,
+ qualifiedName qName: String?)
+ {
+ // Update the current path along the XML's DOM elements by deleting last component.
+ self.currentXMLDOMPath = self.currentXMLDOMPath.deletingLastPathComponent()
+ }
+
+ func parser(_ parser: XMLParser, foundCDATA CDATABlock: Data)
+ {
+ guard let string = String(data: CDATABlock, encoding: .utf8) else {
+ self.xmlParser.abortParsing()
+ self.parsingError = ParserError.feedCDATABlockEncodingError(path: self.currentXMLDOMPath.absoluteString).value
+ return
+ }
+ self.map(string)
+ }
+
+ func parser(_ parser: XMLParser, foundCharacters string: String) {
+ self.map(string)
+ }
+
+ func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
+ self.parsingError = NSError(domain: parseError.localizedDescription, code: -1)
+ }
+
+}
diff --git a/Pods/FeedKit/Sources/FeedKit/Parser/XMLFeedType.swift b/Pods/FeedKit/Sources/FeedKit/Parser/XMLFeedType.swift
new file mode 100755
index 0000000..ca3cd0d
--- /dev/null
+++ b/Pods/FeedKit/Sources/FeedKit/Parser/XMLFeedType.swift
@@ -0,0 +1,36 @@
+//
+// XMLFeedType.swift
+//
+// Copyright (c) 2017 Nuno Manuel Dias
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+import Foundation
+
+/// Types of feed. The `rawValue` matches the top-level XML element of a feed.
+///
+/// - atom: The `Atom Syndication Format feed type.
+/// - rdf: The Really Simple Syndication feed type version 0.90.
+/// - rss: The Really Simple Syndication feed type version 2.0.
+enum XMLFeedType: String {
+ case atom = "feed"
+ case rdf = "rdf:RDF"
+ case rss = "rss"
+}
diff --git a/Pods/GRDB.swift/GRDB/Core/Configuration.swift b/Pods/GRDB.swift/GRDB/Core/Configuration.swift
new file mode 100644
index 0000000..cc789ad
--- /dev/null
+++ b/Pods/GRDB.swift/GRDB/Core/Configuration.swift
@@ -0,0 +1,80 @@
+import Foundation
+#if SWIFT_PACKAGE
+ import CSQLite
+#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER
+ import SQLite3
+#endif
+
+/// Configuration for a DatabaseQueue or DatabasePool.
+public struct Configuration {
+
+ // MARK: - Misc options
+
+ /// If true, foreign key constraints are checked.
+ ///
+ /// Default: true
+ public var foreignKeysEnabled: Bool = true
+
+ /// If true, database modifications are disallowed.
+ ///
+ /// Default: false
+ public var readonly: Bool = false
+
+ /// A function that is called on every statement executed by the database.
+ ///
+ /// Default: nil
+ public var trace: TraceFunction?
+
+
+ // MARK: - Encryption
+
+ #if SQLITE_HAS_CODEC
+ /// The passphrase for encrypted database.
+ ///
+ /// Default: nil
+ public var passphrase: String?
+ #endif
+
+
+ // MARK: - Transactions
+
+ /// The default kind of transaction.
+ ///
+ /// Default: deferred
+ public var defaultTransactionKind: Database.TransactionKind = .deferred
+
+
+ // MARK: - Concurrency
+
+ /// The behavior in case of SQLITE_BUSY error. See https://www.sqlite.org/rescode.html#busy
+ ///
+ /// Default: immediateError
+ public var busyMode: Database.BusyMode = .immediateError
+
+ /// The maximum number of concurrent readers (applies to database
+ /// pools only).
+ ///
+ /// Default: 5
+ public var maximumReaderCount: Int = 5
+
+
+ // MARK: - Factory Configuration
+
+ /// Creates a factory configuration
+ public init() { }
+
+
+ // MARK: - Not Public
+
+ var threadingMode: Database.ThreadingMode = .`default`
+ var SQLiteConnectionDidOpen: (() -> ())?
+ var SQLiteConnectionWillClose: ((SQLiteConnection) -> ())?
+ var SQLiteConnectionDidClose: (() -> ())?
+ var SQLiteOpenFlags: Int32 {
+ let readWriteFlags = readonly ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE)
+ return threadingMode.SQLiteOpenFlags | readWriteFlags
+ }
+}
+
+/// A tracing function that takes an SQL string.
+public typealias TraceFunction = (String) -> Void
diff --git a/Pods/GRDB.swift/GRDB/Core/Cursor.swift b/Pods/GRDB.swift/GRDB/Core/Cursor.swift
new file mode 100644
index 0000000..525b9c3
--- /dev/null
+++ b/Pods/GRDB.swift/GRDB/Core/Cursor.swift
@@ -0,0 +1,344 @@
+extension Array {
+ /// Creates an array containing the elements of a cursor.
+ ///
+ /// let cursor = try String.fetchCursor(db, "SELECT 'foo' UNION ALL SELECT 'bar'")
+ /// let strings = try Array(cursor) // ["foo", "bar"]
+ public init(_ cursor: C) throws where C.Element == Element {
+ self.init()
+ while let element = try cursor.next() {
+ append(element)
+ }
+ }
+}
+
+extension Set {
+ /// Creates a set containing the elements of a cursor.
+ ///
+ /// let cursor = try String.fetchCursor(db, "SELECT 'foo' UNION ALL SELECT 'foo'")
+ /// let strings = try Set(cursor) // ["foo"]
+ public init(_ cursor: C) throws where C.Element == Element {
+ self.init()
+ while let element = try cursor.next() {
+ insert(element)
+ }
+ }
+}
+
+/// A type that supplies the values of some external resource, one at a time.
+///
+/// ## Overview
+///
+/// The most common way to iterate over the elements of a cursor is to use a
+/// `while` loop:
+///
+/// let cursor = ...
+/// while let element = try cursor.next() {
+/// ...
+/// }
+///
+/// ## Relationship with standard Sequence and IteratorProtocol
+///
+/// Cursors share traits with lazy sequences and iterators from the Swift
+/// standard library. Differences are:
+///
+/// - Cursor types are classes, and have a lifetime.
+/// - Cursor iteration may throw errors.
+/// - A cursor can not be repeated.
+///
+/// The protocol comes with default implementations for many operations similar
+/// to those defined by Swift's LazySequenceProtocol:
+///
+/// - `func contains(Self.Element)`
+/// - `func contains(where: (Self.Element) throws -> Bool)`
+/// - `func enumerated()`
+/// - `func filter((Self.Element) throws -> Bool)`
+/// - `func first(where: (Self.Element) throws -> Bool)`
+/// - `func flatMap((Self.Element) throws -> ElementOfResult?)`
+/// - `func flatMap((Self.Element) throws -> SegmentOfResult)`
+/// - `func forEach((Self.Element) throws -> Void)`
+/// - `func joined()`
+/// - `func map((Self.Element) throws -> T)`
+/// - `func reduce(Result, (Result, Self.Element) throws -> Result)`
+public protocol Cursor : class {
+ /// The type of element traversed by the cursor.
+ associatedtype Element
+
+ /// Advances to the next element and returns it, or nil if no next element
+ /// exists. Once nil has been returned, all subsequent calls return nil.
+ func next() throws -> Element?
+}
+
+/// A type-erased cursor of Element.
+///
+/// This cursor forwards its next() method to an arbitrary underlying cursor
+/// having the same Element type, hiding the specifics of the underlying
+/// cursor.
+public class AnyCursor : Cursor {
+ /// Creates a cursor that wraps a base cursor but whose type depends only on
+ /// the base cursor’s element type
+ public init(_ base: C) where C.Element == Element {
+ element = base.next
+ }
+
+ /// Creates a cursor that wraps the given closure in its next() method
+ public init(_ body: @escaping () throws -> Element?) {
+ element = body
+ }
+
+ /// Advances to the next element and returns it, or nil if no next
+ /// element exists.
+ public func next() throws -> Element? {
+ return try element()
+ }
+
+ private let element: () throws -> Element?
+}
+
+extension Cursor {
+ /// Returns a Boolean value indicating whether the cursor contains an
+ /// element that satisfies the given predicate.
+ ///
+ /// - parameter predicate: A closure that takes an element of the cursor as
+ /// its argument and returns a Boolean value that indicates whether the
+ /// passed element represents a match.
+ /// - returns: true if the cursor contains an element that satisfies
+ /// predicate; otherwise, false.
+ public func contains(where predicate: (Element) throws -> Bool) throws -> Bool {
+ while let element = try next() {
+ if try predicate(element) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /// Returns a cursor of pairs (n, x), where n represents a consecutive
+ /// integer starting at zero, and x represents an element of the cursor.
+ ///
+ /// let cursor = try String.fetchCursor(db, "SELECT 'foo' UNION ALL SELECT 'bar'")
+ /// let c = cursor.enumerated()
+ /// while let (n, x) = c.next() {
+ /// print("\(n): \(x)")
+ /// }
+ /// // Prints: "0: foo"
+ /// // Prints: "1: bar"
+ public func enumerated() -> EnumeratedCursor {
+ return EnumeratedCursor(self)
+ }
+
+ /// Returns the elements of the cursor that satisfy the given predicate.
+ public func filter(_ isIncluded: @escaping (Element) throws -> Bool) -> FilterCursor {
+ return FilterCursor(self, isIncluded)
+ }
+
+ /// Returns the first element of the cursor that satisfies the given
+ /// predicate or nil if no such element is found.
+ public func first(where predicate: (Element) throws -> Bool) throws -> Element? {
+ while let element = try next() {
+ if try predicate(element) {
+ return element
+ }
+ }
+ return nil
+ }
+
+ /// Returns a cursor over the concatenated non-nil results of mapping
+ /// transform over this cursor.
+ public func flatMap(_ transform: @escaping (Element) throws -> ElementOfResult?) -> MapCursor>, ElementOfResult> {
+ return map(transform).filter { $0 != nil }.map { $0! }
+ }
+
+ /// Returns a cursor over the concatenated results of mapping transform
+ /// over self.
+ public func flatMap(_ transform: @escaping (Element) throws -> SegmentOfResult) -> FlattenCursor>> {
+ return flatMap { try IteratorCursor(transform($0)) }
+ }
+
+ /// Returns a cursor over the concatenated results of mapping transform
+ /// over self.
+ public func flatMap(_ transform: @escaping (Element) throws -> SegmentOfResult) -> FlattenCursor> {
+ return map(transform).joined()
+ }
+
+ /// Calls the given closure on each element in the cursor.
+ public func forEach(_ body: (Element) throws -> Void) throws {
+ while let element = try next() {
+ try body(element)
+ }
+ }
+
+ /// Returns a cursor over the results of the transform function applied to
+ /// this cursor's elements.
+ public func map(_ transform: @escaping (Element) throws -> T) -> MapCursor {
+ return MapCursor(self, transform)
+ }
+
+ /// Returns the result of calling the given combining closure with each
+ /// element of this sequence and an accumulating value.
+ public func reduce(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) throws -> Result {
+ var result = initialResult
+ while let element = try next() {
+ result = try nextPartialResult(result, element)
+ }
+ return result
+ }
+}
+
+extension Cursor where Element: Equatable {
+ /// Returns a Boolean value indicating whether the cursor contains the
+ /// given element.
+ public func contains(_ element: Element) throws -> Bool {
+ while let e = try next() {
+ if e == element {
+ return true
+ }
+ }
+ return false
+ }
+}
+
+extension Cursor where Element: Cursor {
+ /// Returns the elements of this cursor of cursors, concatenated.
+ public func joined() -> FlattenCursor {
+ return FlattenCursor(self)
+ }
+}
+
+extension Cursor where Element: Sequence {
+ /// Returns the elements of this cursor of sequences, concatenated.
+ public func joined() -> FlattenCursor>> {
+ return flatMap { $0 }
+ }
+}
+
+/// An enumeration of the elements of a cursor.
+///
+/// To create an instance of `EnumeratedCursor`, call the `enumerated()` method
+/// on a cursor:
+///
+/// let cursor = try String.fetchCursor(db, "SELECT 'foo' UNION ALL SELECT 'bar'")
+/// let c = cursor.enumerated()
+/// while let (n, x) = c.next() {
+/// print("\(n): \(x)")
+/// }
+/// // Prints: "0: foo"
+/// // Prints: "1: bar"
+public final class EnumeratedCursor : Cursor {
+ init(_ base: Base) {
+ self.index = 0
+ self.base = base
+ }
+
+ /// Advances to the next element and returns it, or nil if no next
+ /// element exists.
+ public func next() throws -> (Int, Base.Element)? {
+ guard let element = try base.next() else { return nil }
+ defer { index += 1 }
+ return (index, element)
+ }
+
+ private var index: Int
+ private var base: Base
+}
+
+/// A cursor whose elements consist of the elements of some base cursor that
+/// also satisfy a given predicate.
+public final class FilterCursor : Cursor {
+ init(_ base: Base, _ isIncluded: @escaping (Base.Element) throws -> Bool) {
+ self.base = base
+ self.isIncluded = isIncluded
+ }
+
+ /// Advances to the next element and returns it, or nil if no next
+ /// element exists.
+ public func next() throws -> Base.Element? {
+ while let element = try base.next() {
+ if try isIncluded(element) {
+ return element
+ }
+ }
+ return nil
+ }
+
+ private let base: Base
+ private let isIncluded: (Base.Element) throws -> Bool
+}
+
+/// A cursor consisting of all the elements contained in each segment contained
+/// in some Base cursor.
+///
+/// See Cursor.joined(), Cursor.flatMap(_:), Sequence.flatMap(_:)
+public final class FlattenCursor : Cursor where Base.Element: Cursor {
+ init(_ base: Base) {
+ self.base = base
+ }
+
+ /// Advances to the next element and returns it, or nil if no next
+ /// element exists.
+ public func next() throws -> Base.Element.Element? {
+ while true {
+ if let element = try inner?.next() {
+ return element
+ }
+ guard let inner = try base.next() else {
+ return nil
+ }
+ self.inner = inner
+ }
+ }
+
+ private var inner: Base.Element?
+ private let base: Base
+}
+
+/// A Cursor whose elements consist of those in a Base Cursor passed through a
+/// transform function returning Element.
+///
+/// See Cursor.map(_:)
+public final class MapCursor : Cursor {
+ init(_ base: Base, _ transform: @escaping (Base.Element) throws -> Element) {
+ self.base = base
+ self.transform = transform
+ }
+
+ /// Advances to the next element and returns it, or nil if no next
+ /// element exists.
+ public func next() throws -> Element? {
+ guard let element = try base.next() else { return nil }
+ return try transform(element)
+ }
+
+ private let base: Base
+ private let transform: (Base.Element) throws -> Element
+}
+
+/// A Cursor whose elements are those of a sequence iterator.
+public final class IteratorCursor : Cursor {
+
+ /// Creates a cursor from a sequence iterator.
+ public init(_ base: Base) {
+ self.base = base
+ }
+
+ /// Creates a cursor from a sequence.
+ public init(_ s: S) where S.Iterator == Base {
+ self.base = s.makeIterator()
+ }
+
+ /// Advances to the next element and returns it, or nil if no next
+ /// element exists.
+ public func next() -> Base.Element? {
+ return base.next()
+ }
+
+ private var base: Base
+}
+
+extension Sequence {
+
+ /// Returns a cursor over the concatenated results of mapping transform
+ /// over self.
+ public func flatMap(_ transform: @escaping (Iterator.Element) throws -> SegmentOfResult) -> FlattenCursor, SegmentOfResult>> {
+ return IteratorCursor(self).flatMap(transform)
+ }
+}
diff --git a/Pods/GRDB.swift/GRDB/Core/Database.swift b/Pods/GRDB.swift/GRDB/Core/Database.swift
new file mode 100644
index 0000000..2588106
--- /dev/null
+++ b/Pods/GRDB.swift/GRDB/Core/Database.swift
@@ -0,0 +1,2758 @@
+import Foundation
+#if SWIFT_PACKAGE
+ import CSQLite
+#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER
+ import SQLite3
+#endif
+
+/// A raw SQLite connection, suitable for the SQLite C API.
+public typealias SQLiteConnection = OpaquePointer
+
+/// A raw SQLite function argument.
+typealias SQLiteValue = OpaquePointer
+
+let SQLITE_TRANSIENT = unsafeBitCast(OpaquePointer(bitPattern: -1), to: sqlite3_destructor_type.self)
+
+/// A Database connection.
+///
+/// You don't create a database directly. Instead, you use a DatabaseQueue, or
+/// a DatabasePool:
+///
+/// let dbQueue = DatabaseQueue(...)
+///
+/// // The Database is the `db` in the closure:
+/// try dbQueue.inDatabase { db in
+/// try db.execute(...)
+/// }
+public final class Database {
+ // The Database class is not thread-safe. An instance should always be
+ // used through a SerializedDatabase.
+
+ // MARK: - Database Types
+
+ /// See BusyMode and https://www.sqlite.org/c3ref/busy_handler.html
+ public typealias BusyCallback = (_ numberOfTries: Int) -> Bool
+
+ /// When there are several connections to a database, a connection may try
+ /// to access the database while it is locked by another connection.
+ ///
+ /// The BusyMode enum describes the behavior of GRDB when such a situation
+ /// occurs:
+ ///
+ /// - .immediateError: The SQLITE_BUSY error is immediately returned to the
+ /// connection that tries to access the locked database.
+ ///
+ /// - .timeout: The SQLITE_BUSY error will be returned only if the database
+ /// remains locked for more than the specified duration.
+ ///
+ /// - .callback: Perform your custom lock handling.
+ ///
+ /// To set the busy mode of a database, use Configuration:
+ ///
+ /// let configuration = Configuration(busyMode: .timeout(1))
+ /// let dbQueue = DatabaseQueue(path: "...", configuration: configuration)
+ ///
+ /// Relevant SQLite documentation:
+ ///
+ /// - https://www.sqlite.org/c3ref/busy_timeout.html
+ /// - https://www.sqlite.org/c3ref/busy_handler.html
+ /// - https://www.sqlite.org/lang_transaction.html
+ /// - https://www.sqlite.org/wal.html
+ public enum BusyMode {
+ /// The SQLITE_BUSY error is immediately returned to the connection that
+ /// tries to access the locked database.
+ case immediateError
+
+ /// The SQLITE_BUSY error will be returned only if the database remains
+ /// locked for more than the specified duration.
+ case timeout(TimeInterval)
+
+ /// A custom callback that is called when a database is locked.
+ /// See https://www.sqlite.org/c3ref/busy_handler.html
+ case callback(BusyCallback)
+ }
+
+ /// The available [checkpoint modes](https://www.sqlite.org/c3ref/wal_checkpoint_v2.html).
+ public enum CheckpointMode: Int32 {
+ case passive = 0 // SQLITE_CHECKPOINT_PASSIVE
+ case full = 1 // SQLITE_CHECKPOINT_FULL
+ case restart = 2 // SQLITE_CHECKPOINT_RESTART
+ case truncate = 3 // SQLITE_CHECKPOINT_TRUNCATE
+ }
+
+ /// A built-in SQLite collation.
+ ///
+ /// See https://www.sqlite.org/datatype3.html#collation
+ public struct CollationName : RawRepresentable, Hashable {
+ public let rawValue: String
+
+ public init(rawValue: String) {
+ self.rawValue = rawValue
+ }
+
+ public init(_ rawValue: String) {
+ self.rawValue = rawValue
+ }
+
+ /// The hash value
+ public var hashValue: Int {
+ return rawValue.hashValue
+ }
+
+ /// The `BINARY` built-in SQL collation
+ public static let binary = CollationName("BINARY")
+
+ /// The `NOCASE` built-in SQL collation
+ public static let nocase = CollationName("NOCASE")
+
+ /// The `RTRIM` built-in SQL collation
+ public static let rtrim = CollationName("RTRIM")
+ }
+
+ /// An SQLite conflict resolution.
+ ///
+ /// See https://www.sqlite.org/lang_conflict.html.
+ public enum ConflictResolution : String {
+ case rollback = "ROLLBACK"
+ case abort = "ABORT"
+ case fail = "FAIL"
+ case ignore = "IGNORE"
+ case replace = "REPLACE"
+ }
+
+ /// An SQL column type.
+ ///
+ /// try db.create(table: "players") { t in
+ /// t.column("id", .integer).primaryKey()
+ /// t.column("title", .text)
+ /// }
+ ///
+ /// See https://www.sqlite.org/datatype3.html
+ public struct ColumnType : RawRepresentable, Hashable {
+ public let rawValue: String
+
+ public init(rawValue: String) {
+ self.rawValue = rawValue
+ }
+
+ public init(_ rawValue: String) {
+ self.rawValue = rawValue
+ }
+
+ /// The hash value
+ public var hashValue: Int {
+ return rawValue.hashValue
+ }
+
+ /// The `TEXT` SQL column type
+ public static let text = ColumnType("TEXT")
+
+ /// The `INTEGER` SQL column type
+ public static let integer = ColumnType("INTEGER")
+
+ /// The `DOUBLE` SQL column type
+ public static let double = ColumnType("DOUBLE")
+
+ /// The `NUMERIC` SQL column type
+ public static let numeric = ColumnType("NUMERIC")
+
+ /// The `BOOLEAN` SQL column type
+ public static let boolean = ColumnType("BOOLEAN")
+
+ /// The `BLOB` SQL column type
+ public static let blob = ColumnType("BLOB")
+
+ /// The `DATE` SQL column type
+ public static let date = ColumnType("DATE")
+
+ /// The `DATETIME` SQL column type
+ public static let datetime = ColumnType("DATETIME")
+ }
+
+
+ /// A foreign key action.
+ ///
+ /// See https://www.sqlite.org/foreignkeys.html
+ public enum ForeignKeyAction : String {
+ case cascade = "CASCADE"
+ case restrict = "RESTRICT"
+ case setNull = "SET NULL"
+ case setDefault = "SET DEFAULT"
+ }
+
+ /// An SQLite threading mode. See https://www.sqlite.org/threadsafe.html.
+ enum ThreadingMode {
+ case `default`
+ case multiThread
+ case serialized
+
+ var SQLiteOpenFlags: Int32 {
+ switch self {
+ case .`default`:
+ return 0
+ case .multiThread:
+ return SQLITE_OPEN_NOMUTEX
+ case .serialized:
+ return SQLITE_OPEN_FULLMUTEX
+ }
+ }
+ }
+
+ /// An SQLite transaction kind. See https://www.sqlite.org/lang_transaction.html
+ public enum TransactionKind {
+ case deferred
+ case immediate
+ case exclusive
+ }
+
+ /// The end of a transaction: Commit, or Rollback
+ public enum TransactionCompletion {
+ case commit
+ case rollback
+ }
+
+ /// The states that keep track of transaction completions in order to notify
+ /// transaction observers.
+ private enum TransactionHookState {
+ case pending
+ case commit
+ case rollback
+ case cancelledCommit(Error)
+ }
+
+ // MARK: - Error Log
+
+ /// log function that takes an error message.
+ public typealias LogErrorFunction = (_ resultCode: ResultCode, _ message: String) -> Void
+
+ /// The error logging function.
+ ///
+ /// Quoting https://www.sqlite.org/errlog.html:
+ ///
+ /// > SQLite can be configured to invoke a callback function containing an
+ /// > error code and a terse error message whenever anomalies occur. This
+ /// > mechanism is very helpful in tracking obscure problems that occur
+ /// > rarely and in the field. Application developers are encouraged to take
+ /// > advantage of the error logging facility of SQLite in their products,
+ /// > as it is very low CPU and memory cost but can be a huge aid
+ /// > for debugging.
+ public static var logError: LogErrorFunction? = nil
+
+ private static func setupErrorLog() {
+ struct Impl {
+ static let setupErrorLog: () = {
+ registerErrorLogCallback { (_, code, message) in
+ guard let log = Database.logError else { return }
+ guard let message = message.map({ String(cString: $0) }) else { return }
+ let resultCode = ResultCode(rawValue: code)
+ log(resultCode, message)
+ }
+ }()
+ }
+ Impl.setupErrorLog
+ }
+
+
+ // MARK: - Database Information
+
+ /// The list of compile options used when building SQLite
+ static let sqliteCompileOptions: Set = DatabaseQueue().inDatabase { try! Set(String.fetchCursor($0, "PRAGMA COMPILE_OPTIONS")) }
+
+ /// The database configuration
+ public let configuration: Configuration
+
+ /// The raw SQLite connection, suitable for the SQLite C API.
+ public let sqliteConnection: SQLiteConnection
+
+ /// The rowID of the most recently inserted row.
+ ///
+ /// If no row has ever been inserted using this database connection,
+ /// returns zero.
+ ///
+ /// For more detailed information, see https://www.sqlite.org/c3ref/last_insert_rowid.html
+ public var lastInsertedRowID: Int64 {
+ SchedulingWatchdog.preconditionValidQueue(self)
+ return sqlite3_last_insert_rowid(sqliteConnection)
+ }
+
+ /// The number of rows modified, inserted or deleted by the most recent
+ /// successful INSERT, UPDATE or DELETE statement.
+ ///
+ /// For more detailed information, see https://www.sqlite.org/c3ref/changes.html
+ public var changesCount: Int {
+ SchedulingWatchdog.preconditionValidQueue(self)
+ return Int(sqlite3_changes(sqliteConnection))
+ }
+
+ /// The total number of rows modified, inserted or deleted by all successful
+ /// INSERT, UPDATE or DELETE statements since the database connection was
+ /// opened.
+ ///
+ /// For more detailed information, see https://www.sqlite.org/c3ref/total_changes.html
+ public var totalChangesCount: Int {
+ SchedulingWatchdog.preconditionValidQueue(self)
+ return Int(sqlite3_total_changes(sqliteConnection))
+ }
+
+ var lastErrorCode: ResultCode { return ResultCode(rawValue: sqlite3_errcode(sqliteConnection)) }
+ var lastErrorMessage: String? { return String(cString: sqlite3_errmsg(sqliteConnection)) }
+
+ /// True if the database connection is currently in a transaction.
+ public var isInsideTransaction: Bool {
+ // https://sqlite.org/c3ref/get_autocommit.html
+ //
+ // > The sqlite3_get_autocommit() interface returns non-zero or zero if
+ // > the given database connection is or is not in autocommit mode,
+ // > respectively.
+ //
+ // > Autocommit mode is on by default. Autocommit mode is disabled by a
+ // > BEGIN statement. Autocommit mode is re-enabled by a COMMIT
+ // > or ROLLBACK.
+ return sqlite3_get_autocommit(sqliteConnection) == 0
+ }
+
+ /// Set by SAVEPOINT/COMMIT/ROLLBACK/RELEASE savepoint statements.
+ private var savepointStack = SavepointStack()
+
+ /// Traces transaction hooks
+ private var transactionHookState: TransactionHookState = .pending
+
+ /// Transaction observers
+ private var transactionObservers = [ManagedTransactionObserver]()
+ private var activeTransactionObservers = [ManagedTransactionObserver]() // subset of transactionObservers, set in updateStatementWillExecute
+
+ /// See setupBusyMode()
+ private var busyCallback: BusyCallback?
+
+ /// Available functions
+ private var functions = Set()
+
+ /// Available collations
+ private var collations = Set()
+
+ /// Schema Cache
+ var schemaCache: DatabaseSchemaCache // internal so that it can be tested
+
+ /// Statement caches are not part of the schema cache because statements
+ /// belong to this connection, while schema cache can be shared with
+ /// other connections.
+ ///
+ /// There are two statement caches: one for statements generated by the
+ /// user, and one for the statements generated by GRDB. Those are separated
+ /// so that GRDB has no opportunity to inadvertently modify the arguments of
+ /// user statements.
+ enum StatementCacheName {
+ case grdb
+ case user
+ }
+ private lazy var grdbStatementCache: StatementCache = StatementCache(database: self)
+ private lazy var userStatementCache: StatementCache = StatementCache(database: self)
+
+ init(path: String, configuration: Configuration, schemaCache: DatabaseSchemaCache) throws {
+ // Error log setup must happen before any database connection
+ Database.setupErrorLog()
+
+ // See https://www.sqlite.org/c3ref/open.html
+ var sqliteConnection: SQLiteConnection? = nil
+ let code = sqlite3_open_v2(path, &sqliteConnection, configuration.SQLiteOpenFlags, nil)
+ guard code == SQLITE_OK else {
+ throw DatabaseError(resultCode: code, message: String(cString: sqlite3_errmsg(sqliteConnection)))
+ }
+
+ do {
+ // Use extended result codes
+ do {
+ let code = sqlite3_extended_result_codes(sqliteConnection!, 1)
+ guard code == SQLITE_OK else {
+ throw DatabaseError(resultCode: code, message: String(cString: sqlite3_errmsg(sqliteConnection)))
+ }
+ }
+
+ #if SQLITE_HAS_CODEC
+ // https://discuss.zetetic.net/t/important-advisory-sqlcipher-with-xcode-8-and-new-sdks/1688
+ //
+ // > In order to avoid situations where SQLite might be used
+ // > improperly at runtime, we strongly recommend that
+ // > applications institute a runtime test to ensure that the
+ // > application is actually using SQLCipher on the active
+ // > connection.
+
+ let isSQLCipherValid: Bool
+ do {
+ var sqliteStatement: SQLiteStatement? = nil
+ let code = sqlite3_prepare_v2(sqliteConnection, "PRAGMA cipher_version", -1, &sqliteStatement, nil)
+ guard code == SQLITE_OK else {
+ throw DatabaseError(resultCode: code, message: String(cString: sqlite3_errmsg(sqliteConnection)))
+ }
+ defer {
+ sqlite3_finalize(sqliteStatement)
+ }
+ switch sqlite3_step(sqliteStatement) {
+ case SQLITE_ROW:
+ isSQLCipherValid = (sqlite3_column_text(sqliteStatement, 0) != nil)
+ default:
+ isSQLCipherValid = false
+ }
+ }
+
+ guard isSQLCipherValid else {
+ throw DatabaseError(resultCode: .SQLITE_MISUSE, message: "GRDB is not linked against SQLCipher. Check https://discuss.zetetic.net/t/important-advisory-sqlcipher-with-xcode-8-and-new-sdks/1688")
+ }
+
+ if let passphrase = configuration.passphrase {
+ try Database.set(passphrase: passphrase, forConnection: sqliteConnection!)
+ }
+ #endif
+
+ // Users are surprised when they open a picture as a database and
+ // see no error (https://github.com/groue/GRDB.swift/issues/54).
+ //
+ // So let's fail early if file is not a database, or encrypted with
+ // another passphrase.
+ do {
+ let code = sqlite3_exec(sqliteConnection, "SELECT * FROM sqlite_master LIMIT 1", nil, nil, nil)
+ guard code == SQLITE_OK else {
+ throw DatabaseError(resultCode: code, message: String(cString: sqlite3_errmsg(sqliteConnection)))
+ }
+ }
+ } catch {
+ Database.close(connection: sqliteConnection!)
+ throw error
+ }
+
+ self.configuration = configuration
+ self.schemaCache = schemaCache
+ self.sqliteConnection = sqliteConnection!
+
+ configuration.SQLiteConnectionDidOpen?()
+ }
+
+ /// This method must be called after database initialization
+ func setup() throws {
+ // Setup trace first, so that setup queries are traced.
+ setupTrace()
+ try setupForeignKeys()
+ setupBusyMode()
+ setupDefaultFunctions()
+ setupDefaultCollations()
+ setupTransactionHooks()
+ installDefaultAuthorizer()
+ }
+
+ /// This method must be called before database deallocation
+ func close() {
+ SchedulingWatchdog.preconditionValidQueue(self)
+ assert(!isClosed)
+
+ configuration.SQLiteConnectionWillClose?(sqliteConnection)
+ grdbStatementCache.clear()
+ userStatementCache.clear()
+ Database.close(connection: sqliteConnection)
+ isClosed = true
+ configuration.SQLiteConnectionDidClose?()
+ }
+
+ private var isClosed: Bool = false
+ deinit {
+ assert(isClosed)
+ }
+
+ func releaseMemory() {
+ sqlite3_db_release_memory(sqliteConnection)
+ schemaCache.clear()
+ grdbStatementCache.clear()
+ userStatementCache.clear()
+ }
+
+ private func setupForeignKeys() throws {
+ // Foreign keys are disabled by default with SQLite3
+ if configuration.foreignKeysEnabled {
+ try execute("PRAGMA foreign_keys = ON")
+ }
+ }
+
+ private func setupTrace() {
+ guard configuration.trace != nil else {
+ return
+ }
+ let dbPointer = Unmanaged.passUnretained(self).toOpaque()
+ // sqlite3_trace_v2 and sqlite3_expanded_sql were introduced in SQLite 3.14.0 http://www.sqlite.org/changes.html#version_3_14
+ // It is available from iOS 10.0 and OS X 10.12 https://github.com/yapstudios/YapDatabase/wiki/SQLite-version-(bundled-with-OS)
+ #if GRDBCUSTOMSQLITE
+ sqlite3_trace_v2(sqliteConnection, UInt32(SQLITE_TRACE_STMT), { (mask, dbPointer, stmt, unexpandedSQL) -> Int32 in
+ guard let stmt = stmt else { return SQLITE_OK }
+ guard let expandedSQLCString = sqlite3_expanded_sql(OpaquePointer(stmt)) else { return SQLITE_OK }
+ let sql = String(cString: expandedSQLCString)
+ sqlite3_free(expandedSQLCString)
+ let db = Unmanaged.fromOpaque(dbPointer!).takeUnretainedValue()
+ db.configuration.trace!(sql)
+ return SQLITE_OK
+ }, dbPointer)
+ #elseif GRDBCIPHER
+ sqlite3_trace(sqliteConnection, { (dbPointer, sql) in
+ guard let sql = sql.map({ String(cString: $0) }) else { return }
+ let db = Unmanaged.fromOpaque(dbPointer!).takeUnretainedValue()
+ db.configuration.trace!(sql)
+ }, dbPointer)
+ #else
+ if #available(iOS 10.0, OSX 10.12, watchOS 3.0, *) {
+ sqlite3_trace_v2(sqliteConnection, UInt32(SQLITE_TRACE_STMT), { (mask, dbPointer, stmt, unexpandedSQL) -> Int32 in
+ guard let stmt = stmt else { return SQLITE_OK }
+ guard let expandedSQLCString = sqlite3_expanded_sql(OpaquePointer(stmt)) else { return SQLITE_OK }
+ let sql = String(cString: expandedSQLCString)
+ sqlite3_free(expandedSQLCString)
+ let db = Unmanaged.fromOpaque(dbPointer!).takeUnretainedValue()
+ db.configuration.trace!(sql)
+ return SQLITE_OK
+ }, dbPointer)
+ } else {
+ sqlite3_trace(sqliteConnection, { (dbPointer, sql) in
+ guard let sql = sql.map({ String(cString: $0) }) else { return }
+ let db = Unmanaged.fromOpaque(dbPointer!).takeUnretainedValue()
+ db.configuration.trace!(sql)
+ }, dbPointer)
+ }
+ #endif
+ }
+
+ private func setupBusyMode() {
+ switch configuration.busyMode {
+ case .immediateError:
+ break
+
+ case .timeout(let duration):
+ let milliseconds = Int32(duration * 1000)
+ sqlite3_busy_timeout(sqliteConnection, milliseconds)
+
+ case .callback(let callback):
+ busyCallback = callback
+ let dbPointer = Unmanaged.passUnretained(self).toOpaque()
+ sqlite3_busy_handler(
+ sqliteConnection,
+ { (dbPointer, numberOfTries) in
+ let db = Unmanaged.fromOpaque(dbPointer!).takeUnretainedValue()
+ let callback = db.busyCallback!
+ return callback(Int(numberOfTries)) ? 1 : 0
+ },
+ dbPointer)
+ }
+ }
+
+ private func setupDefaultFunctions() {
+ add(function: .capitalize)
+ add(function: .lowercase)
+ add(function: .uppercase)
+
+ if #available(iOS 9.0, OSX 10.11, watchOS 3.0, *) {
+ add(function: .localizedCapitalize)
+ add(function: .localizedLowercase)
+ add(function: .localizedUppercase)
+ }
+ }
+
+ private func setupDefaultCollations() {
+ add(collation: .unicodeCompare)
+ add(collation: .caseInsensitiveCompare)
+ add(collation: .localizedCaseInsensitiveCompare)
+ add(collation: .localizedCompare)
+ add(collation: .localizedStandardCompare)
+ }
+
+ private func setupTransactionHooks() {
+ let dbPointer = Unmanaged.passUnretained(self).toOpaque()
+
+ sqlite3_commit_hook(sqliteConnection, { dbPointer in
+ let db = Unmanaged.fromOpaque(dbPointer!).takeUnretainedValue()
+ do {
+ try db.willCommit()
+ db.transactionHookState = .commit
+ // Next step: updateStatementDidExecute()
+ return 0
+ } catch {
+ db.transactionHookState = .cancelledCommit(error)
+ // Next step: sqlite3_rollback_hook callback
+ return 1
+ }
+ }, dbPointer)
+
+
+ sqlite3_rollback_hook(sqliteConnection, { dbPointer in
+ let db = Unmanaged.fromOpaque(dbPointer!).takeUnretainedValue()
+ switch db.transactionHookState {
+ case .cancelledCommit:
+ // Next step: updateStatementDidFail()
+ break
+ default:
+ db.transactionHookState = .rollback
+ // Next step: updateStatementDidExecute()
+ }
+ }, dbPointer)
+ }
+
+ fileprivate func installDefaultAuthorizer() {
+ // https://www.sqlite.org/capi3ref.html#sqlite3_set_authorizer
+ // > When sqlite3_prepare_v2() is used to prepare a statement, the
+ // > statement might be re-prepared during sqlite3_step() due to a
+ // > schema change. Hence, the application should ensure that the
+ // > correct authorizer callback remains in place during the
+ // > sqlite3_step().
+ //
+ // As a matter of fact, without this default authorizer, the truncate
+ // optimization prevents transaction observers from observing
+ // individual deletions.
+ sqlite3_set_authorizer(sqliteConnection, { (_, actionCode, cString1, cString2, cString3, cString4) -> Int32 in
+ if actionCode == SQLITE_DELETE && String(cString: cString1!) != "sqlite_master" {
+ // Prevent [truncate optimization](https://www.sqlite.org/lang_delete.html#truncateopt)
+ // so that transaction observers can observe individual deletions.
+ return SQLITE_IGNORE
+ } else {
+ return SQLITE_OK
+ }
+ }, nil)
+ }
+
+ private static func close(connection sqliteConnection: SQLiteConnection) {
+ #if GRDBCUSTOMSQLITE || GRDBCIPHER
+ close_v2(connection: sqliteConnection)
+ #else
+ if #available(iOS 8.2, OSX 10.10, OSXApplicationExtension 10.10, *) {
+ close_v2(connection: sqliteConnection)
+ } else {
+ // https://www.sqlite.org/c3ref/close.html
+ // > If the database connection is associated with unfinalized prepared
+ // > statements or unfinished sqlite3_backup objects then
+ // > sqlite3_close() will leave the database connection open and
+ // > return SQLITE_BUSY.
+ let code = sqlite3_close(sqliteConnection)
+ if code != SQLITE_OK, let log = logError {
+ // A rare situation where GRDB doesn't fatalError on
+ // unprocessed errors.
+ let message = String(cString: sqlite3_errmsg(sqliteConnection))
+ log(ResultCode(rawValue: code), "could not close database: \(message)")
+ if code == SQLITE_BUSY {
+ // Let the user know about unfinalized statements that did
+ // prevent the connection from closing properly.
+ var stmt: SQLiteStatement? = sqlite3_next_stmt(sqliteConnection, nil)
+ while stmt != nil {
+ log(ResultCode(rawValue: code), "unfinalized statement: \(String(cString: sqlite3_sql(stmt)))")
+ stmt = sqlite3_next_stmt(sqliteConnection, stmt)
+ }
+ }
+ }
+ }
+ #endif
+ }
+
+ // sqlite3_close_v2 was added in SQLite 3.7.14 http://www.sqlite.org/changes.html#version_3_7_14
+ // It is available from iOS 8.2 and OS X 10.10 https://github.com/yapstudios/YapDatabase/wiki/SQLite-version-(bundled-with-OS)
+ #if GRDBCUSTOMSQLITE || GRDBCIPHER
+ private static func close_v2(connection sqliteConnection: SQLiteConnection) {
+ // https://www.sqlite.org/c3ref/close.html
+ // > If sqlite3_close_v2() is called with unfinalized prepared
+ // > statements and/or unfinished sqlite3_backups, then the database
+ // > connection becomes an unusable "zombie" which will automatically
+ // > be deallocated when the last prepared statement is finalized or the
+ // > last sqlite3_backup is finished.
+ let code = sqlite3_close_v2(sqliteConnection)
+ if code != SQLITE_OK, let log = logError {
+ // A rare situation where GRDB doesn't fatalError on
+ // unprocessed errors.
+ let message = String(cString: sqlite3_errmsg(sqliteConnection))
+ log(ResultCode(rawValue: code), "could not close database: \(message)")
+ }
+ }
+ #else
+ @available(iOS 8.2, OSX 10.10, OSXApplicationExtension 10.10, *)
+ private static func close_v2(connection sqliteConnection: SQLiteConnection) {
+ // https://www.sqlite.org/c3ref/close.html
+ // > If sqlite3_close_v2() is called with unfinalized prepared
+ // > statements and/or unfinished sqlite3_backups, then the database
+ // > connection becomes an unusable "zombie" which will automatically
+ // > be deallocated when the last prepared statement is finalized or the
+ // > last sqlite3_backup is finished.
+ let code = sqlite3_close_v2(sqliteConnection)
+ if code != SQLITE_OK, let log = logError {
+ // A rare situation where GRDB doesn't fatalError on
+ // unprocessed errors.
+ let message = String(cString: sqlite3_errmsg(sqliteConnection))
+ log(ResultCode(rawValue: code), "could not close database: \(message)")
+ }
+ }
+ #endif
+}
+
+
+// =========================================================================
+// MARK: - Statements
+
+extension Database {
+
+ /// Returns a new prepared statement that can be reused.
+ ///
+ /// let statement = try db.makeSelectStatement("SELECT COUNT(*) FROM players WHERE score > ?")
+ /// let moreThanTwentyCount = try Int.fetchOne(statement, arguments: [20])!
+ /// let moreThanThirtyCount = try Int.fetchOne(statement, arguments: [30])!
+ ///
+ /// - parameter sql: An SQL query.
+ /// - returns: A SelectStatement.
+ /// - throws: A DatabaseError whenever SQLite could not parse the sql query.
+ public func makeSelectStatement(_ sql: String) throws -> SelectStatement {
+ return try makeSelectStatement(sql, prepFlags: 0)
+ }
+
+ /// Returns a new prepared statement that can be reused.
+ ///
+ /// let statement = try db.makeSelectStatement("SELECT COUNT(*) FROM players WHERE score > ?", prepFlags: 0)
+ /// let moreThanTwentyCount = try Int.fetchOne(statement, arguments: [20])!
+ /// let moreThanThirtyCount = try Int.fetchOne(statement, arguments: [30])!
+ ///
+ /// - parameter sql: An SQL query.
+ /// - parameter prepFlags: Flags for sqlite3_prepare_v3 (available from
+ /// SQLite 3.20.0, see http://www.sqlite.org/c3ref/prepare.html)
+ /// - returns: A SelectStatement.
+ /// - throws: A DatabaseError whenever SQLite could not parse the sql query.
+ func makeSelectStatement(_ sql: String, prepFlags: Int32) throws -> SelectStatement {
+ return try SelectStatement(database: self, sql: sql, prepFlags: prepFlags)
+ }
+
+ /// Returns a prepared statement that can be reused.
+ ///
+ /// let statement = try db.cachedSelectStatement("SELECT COUNT(*) FROM players WHERE score > ?")
+ /// let moreThanTwentyCount = try Int.fetchOne(statement, arguments: [20])!
+ /// let moreThanThirtyCount = try Int.fetchOne(statement, arguments: [30])!
+ ///
+ /// The returned statement may have already been used: it may or may not
+ /// contain values for its eventual arguments.
+ ///
+ /// - parameter sql: An SQL query.
+ /// - returns: An UpdateStatement.
+ /// - throws: A DatabaseError whenever SQLite could not parse the sql query.
+ public func cachedSelectStatement(_ sql: String) throws -> SelectStatement {
+ return try selectStatement(sql, fromCache: .user)
+ }
+
+ /// Returns a prepared statement that can be reused.
+ func selectStatement(_ sql: String, fromCache cacheName: StatementCacheName) throws -> SelectStatement {
+ switch cacheName {
+ case .grdb: return try grdbStatementCache.selectStatement(sql)
+ case .user: return try userStatementCache.selectStatement(sql)
+ }
+ }
+
+ /// Returns a new prepared statement that can be reused.
+ ///
+ /// let statement = try db.makeUpdateStatement("INSERT INTO players (name) VALUES (?)")
+ /// try statement.execute(arguments: ["Arthur"])
+ /// try statement.execute(arguments: ["Barbara"])
+ ///
+ /// - parameter sql: An SQL query.
+ /// - returns: An UpdateStatement.
+ /// - throws: A DatabaseError whenever SQLite could not parse the sql query.
+ public func makeUpdateStatement(_ sql: String) throws -> UpdateStatement {
+ return try makeUpdateStatement(sql, prepFlags: 0)
+ }
+
+ /// Returns a new prepared statement that can be reused.
+ ///
+ /// let statement = try db.makeUpdateStatement("INSERT INTO players (name) VALUES (?)", prepFlags: 0)
+ /// try statement.execute(arguments: ["Arthur"])
+ /// try statement.execute(arguments: ["Barbara"])
+ ///
+ /// - parameter sql: An SQL query.
+ /// - parameter prepFlags: Flags for sqlite3_prepare_v3 (available from
+ /// SQLite 3.20.0, see http://www.sqlite.org/c3ref/prepare.html)
+ /// - returns: An UpdateStatement.
+ /// - throws: A DatabaseError whenever SQLite could not parse the sql query.
+ func makeUpdateStatement(_ sql: String, prepFlags: Int32) throws -> UpdateStatement {
+ return try UpdateStatement(database: self, sql: sql, prepFlags: prepFlags)
+ }
+
+ /// Returns a prepared statement that can be reused.
+ ///
+ /// let statement = try db.cachedUpdateStatement("INSERT INTO players (name) VALUES (?)")
+ /// try statement.execute(arguments: ["Arthur"])
+ /// try statement.execute(arguments: ["Barbara"])
+ ///
+ /// The returned statement may have already been used: it may or may not
+ /// contain values for its eventual arguments.
+ ///
+ /// - parameter sql: An SQL query.
+ /// - returns: An UpdateStatement.
+ /// - throws: A DatabaseError whenever SQLite could not parse the sql query.
+ public func cachedUpdateStatement(_ sql: String) throws -> UpdateStatement {
+ return try updateStatement(sql, fromCache: .grdb)
+ }
+
+ /// Returns a prepared statement that can be reused.
+ func updateStatement(_ sql: String, fromCache cacheName: StatementCacheName) throws -> UpdateStatement {
+ switch cacheName {
+ case .grdb: return try grdbStatementCache.updateStatement(sql)
+ case .user: return try userStatementCache.updateStatement(sql)
+ }
+ }
+
+ /// Executes one or several SQL statements, separated by semi-colons.
+ ///
+ /// try db.execute(
+ /// "INSERT INTO players (name) VALUES (:name)",
+ /// arguments: ["name": "Arthur"])
+ ///
+ /// try db.execute("""
+ /// INSERT INTO players (name) VALUES (?);
+ /// INSERT INTO players (name) VALUES (?);
+ /// INSERT INTO players (name) VALUES (?);
+ /// """, arguments; ['Arthur', 'Barbara', 'Craig'])
+ ///
+ /// This method may throw a DatabaseError.
+ ///
+ /// - parameters:
+ /// - sql: An SQL query.
+ /// - arguments: Optional statement arguments.
+ /// - throws: A DatabaseError whenever an SQLite error occurs.
+ public func execute(_ sql: String, arguments: StatementArguments? = nil) throws {
+ // This method is like sqlite3_exec (https://www.sqlite.org/c3ref/exec.html)
+ // It adds support for arguments.
+
+ SchedulingWatchdog.preconditionValidQueue(self)
+
+ // The tricky part is to consume arguments as statements are executed.
+ //
+ // Here we build two functions:
+ // - consumeArguments returns arguments for a statement
+ // - validateRemainingArguments validates the remaining arguments, after
+ // all statements have been executed, in the same way
+ // as Statement.validate(arguments:)
+
+ var arguments = arguments ?? StatementArguments()
+ let initialValuesCount = arguments.values.count
+ let consumeArguments = { (statement: UpdateStatement) throws -> StatementArguments in
+ let bindings = try arguments.consume(statement, allowingRemainingValues: true)
+ return StatementArguments(bindings)
+ }
+ let validateRemainingArguments = {
+ if !arguments.values.isEmpty {
+ throw DatabaseError(resultCode: .SQLITE_MISUSE, message: "wrong number of statement arguments: \(initialValuesCount)")
+ }
+ }
+
+
+ // Execute statements
+
+ let sqlCodeUnits = sql.utf8CString
+ var error: Error?
+
+ // During the execution of sqlite3_prepare_v2, the observer listens to
+ // authorization callbacks in order to recognize "interesting"
+ // statements. See updateStatementDidExecute().
+ let observer = StatementCompilationObserver(self)
+ observer.start()
+
+ sqlCodeUnits.withUnsafeBufferPointer { codeUnits in
+ let sqlStart = UnsafePointer(codeUnits.baseAddress)!
+ let sqlEnd = sqlStart + sqlCodeUnits.count
+ var statementStart = sqlStart
+ while statementStart < sqlEnd - 1 {
+ observer.reset()
+ var statementEnd: UnsafePointer? = nil
+ var sqliteStatement: SQLiteStatement? = nil
+ let code = sqlite3_prepare_v2(sqliteConnection, statementStart, -1, &sqliteStatement, &statementEnd)
+ guard code == SQLITE_OK else {
+ error = DatabaseError(resultCode: code, message: lastErrorMessage, sql: sql)
+ break
+ }
+
+ guard sqliteStatement != nil else {
+ // The remaining string contains only whitespace
+ assert(String(data: Data(bytesNoCopy: UnsafeMutableRawPointer(mutating: statementStart), count: statementEnd! - statementStart, deallocator: .none), encoding: .utf8)!.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
+ break
+ }
+
+ do {
+ let statement = UpdateStatement(
+ database: self,
+ sqliteStatement: sqliteStatement!,
+ invalidatesDatabaseSchemaCache: observer.invalidatesDatabaseSchemaCache,
+ transactionStatementInfo: observer.transactionStatementInfo,
+ databaseEventKinds: observer.databaseEventKinds)
+ let arguments = try consumeArguments(statement)
+ statement.unsafeSetArguments(arguments)
+ try statement.execute()
+ } catch let statementError {
+ error = statementError
+ break
+ }
+
+ statementStart = statementEnd!
+ }
+ }
+
+ observer.stop()
+
+ if let error = error {
+ throw error
+ }
+
+ // Force arguments validity: it is a programmer error to provide
+ // arguments that do not match the statement.
+ try! validateRemainingArguments() // throws if there are remaining arguments.
+ }
+}
+
+
+// =========================================================================
+// MARK: - Functions
+
+extension Database {
+
+ /// Add or redefine an SQL function.
+ ///
+ /// let fn = DatabaseFunction("succ", argumentCount: 1) { dbValues in
+ /// guard let int = Int.fromDatabaseValue(dbValues[0]) else {
+ /// return nil
+ /// }
+ /// return int + 1
+ /// }
+ /// db.add(function: fn)
+ /// try Int.fetchOne(db, "SELECT succ(1)")! // 2
+ public func add(function: DatabaseFunction) {
+ functions.update(with: function)
+ function.install(in: self)
+ }
+
+ /// Remove an SQL function.
+ public func remove(function: DatabaseFunction) {
+ functions.remove(function)
+ function.uninstall(in: self)
+ }
+}
+
+
+// =========================================================================
+// MARK: - Collations
+
+extension Database {
+
+ /// Add or redefine a collation.
+ ///
+ /// let collation = DatabaseCollation("localized_standard") { (string1, string2) in
+ /// return (string1 as NSString).localizedStandardCompare(string2)
+ /// }
+ /// db.add(collation: collation)
+ /// try db.execute("CREATE TABLE files (name TEXT COLLATE localized_standard")
+ public func add(collation: DatabaseCollation) {
+ collations.update(with: collation)
+ let collationPointer = Unmanaged.passUnretained(collation).toOpaque()
+ let code = sqlite3_create_collation_v2(
+ sqliteConnection,
+ collation.name,
+ SQLITE_UTF8,
+ collationPointer,
+ { (collationPointer, length1, buffer1, length2, buffer2) -> Int32 in
+ let collation = Unmanaged.fromOpaque(collationPointer!).takeUnretainedValue()
+ return Int32(collation.function(length1, buffer1, length2, buffer2).rawValue)
+ }, nil)
+ guard code == SQLITE_OK else {
+ // Assume a GRDB bug: there is no point throwing any error.
+ fatalError(DatabaseError(resultCode: code, message: lastErrorMessage).description)
+ }
+ }
+
+ /// Remove a collation.
+ public func remove(collation: DatabaseCollation) {
+ collations.remove(collation)
+ sqlite3_create_collation_v2(
+ sqliteConnection,
+ collation.name,
+ SQLITE_UTF8,
+ nil, nil, nil)
+ }
+}
+
+/// A Collation is a string comparison function used by SQLite.
+public final class DatabaseCollation {
+ public let name: String
+ let function: (Int32, UnsafeRawPointer?, Int32, UnsafeRawPointer?) -> ComparisonResult
+
+ /// Returns a collation.
+ ///
+ /// let collation = DatabaseCollation("localized_standard") { (string1, string2) in
+ /// return (string1 as NSString).localizedStandardCompare(string2)
+ /// }
+ /// db.add(collation: collation)
+ /// try db.execute("CREATE TABLE files (name TEXT COLLATE localized_standard")
+ ///
+ /// - parameters:
+ /// - name: The function name.
+ /// - function: A function that compares two strings.
+ public init(_ name: String, function: @escaping (String, String) -> ComparisonResult) {
+ self.name = name
+ self.function = { (length1, buffer1, length2, buffer2) in
+ // Buffers are not C strings: they do not end with \0.
+ let string1 = String(bytesNoCopy: UnsafeMutableRawPointer(mutating: buffer1.unsafelyUnwrapped), length: Int(length1), encoding: .utf8, freeWhenDone: false)!
+ let string2 = String(bytesNoCopy: UnsafeMutableRawPointer(mutating: buffer2.unsafelyUnwrapped), length: Int(length2), encoding: .utf8, freeWhenDone: false)!
+ return function(string1, string2)
+ }
+ }
+}
+
+extension DatabaseCollation : Hashable {
+ /// The hash value
+ public var hashValue: Int {
+ // We can't compute a hash since the equality is based on the opaque
+ // sqlite3_strnicmp SQLite function.
+ return 0
+ }
+
+ /// Two collations are equal if they share the same name (case insensitive)
+ public static func == (lhs: DatabaseCollation, rhs: DatabaseCollation) -> Bool {
+ // See https://www.sqlite.org/c3ref/create_collation.html
+ return sqlite3_stricmp(lhs.name, lhs.name) == 0
+ }
+}
+
+
+// =========================================================================
+// MARK: - Encryption
+
+#if SQLITE_HAS_CODEC
+extension Database {
+ private class func set(passphrase: String, forConnection sqliteConnection: SQLiteConnection) throws {
+ let data = passphrase.data(using: .utf8)!
+ let code = data.withUnsafeBytes { bytes in
+ sqlite3_key(sqliteConnection, bytes, Int32(data.count))
+ }
+ guard code == SQLITE_OK else {
+ throw DatabaseError(resultCode: code, message: String(cString: sqlite3_errmsg(sqliteConnection)))
+ }
+ }
+
+ func change(passphrase: String) throws {
+ // FIXME: sqlite3_rekey is discouraged.
+ //
+ // https://github.com/ccgus/fmdb/issues/547#issuecomment-259219320
+ //
+ // > We (Zetetic) have been discouraging the use of sqlite3_rekey in
+ // > favor of attaching a new database with the desired encryption
+ // > options and using sqlcipher_export() to migrate the contents and
+ // > schema of the original db into the new one:
+ // > https://discuss.zetetic.net/t/how-to-encrypt-a-plaintext-sqlite-database-to-use-sqlcipher-and-avoid-file-is-encrypted-or-is-not-a-database-errors/
+ let data = passphrase.data(using: .utf8)!
+ let code = data.withUnsafeBytes { bytes in
+ sqlite3_rekey(sqliteConnection, bytes, Int32(data.count))
+ }
+ guard code == SQLITE_OK else {
+ throw DatabaseError(resultCode: code, message: lastErrorMessage)
+ }
+ }
+}
+#endif
+
+
+// =========================================================================
+// MARK: - Database Schema
+
+extension Database {
+
+ /// Clears the database schema cache.
+ ///
+ /// You may need to clear the cache manually if the database schema is
+ /// modified by another connection.
+ public func clearSchemaCache() {
+ SchedulingWatchdog.preconditionValidQueue(self)
+ schemaCache.clear()
+
+ // We also clear updateStatementCache and selectStatementCache despite
+ // the automatic statement recompilation (see https://www.sqlite.org/c3ref/prepare.html)
+ // because the automatic statement recompilation only happens a
+ // limited number of times.
+ grdbStatementCache.clear()
+ userStatementCache.clear()
+ }
+
+ /// Returns whether a table exists.
+ public func tableExists(_ tableName: String) throws -> Bool {
+ // SQlite identifiers are case-insensitive, case-preserving (http://www.alberton.info/dbms_identifiers_and_case_sensitivity.html)
+ return try Row.fetchOne(self, "SELECT 1 FROM (SELECT sql, type, name FROM sqlite_master UNION SELECT sql, type, name FROM sqlite_temp_master) WHERE type = 'table' AND LOWER(name) = ?", arguments: [tableName.lowercased()]) != nil
+ }
+
+ /// The primary key for table named `tableName`.
+ ///
+ /// All tables have a primary key, even when it is not explicit. When a
+ /// table has no explicit primary key, the result is the hidden
+ /// "rowid" column.
+ ///
+ /// - throws: A DatabaseError if table does not exist.
+ public func primaryKey(_ tableName: String) throws -> PrimaryKeyInfo {
+ SchedulingWatchdog.preconditionValidQueue(self)
+
+ if let primaryKey = schemaCache.primaryKey(tableName) {
+ return primaryKey
+ }
+
+ // https://www.sqlite.org/pragma.html
+ //
+ // > PRAGMA database.table_info(table-name);
+ // >
+ // > This pragma returns one row for each column in the named table.
+ // > Columns in the result set include the column name, data type,
+ // > whether or not the column can be NULL, and the default value for
+ // > the column. The "pk" column in the result set is zero for columns
+ // > that are not part of the primary key, and is the index of the
+ // > column in the primary key for columns that are part of the primary
+ // > key.
+ //
+ // CREATE TABLE players (
+ // id INTEGER PRIMARY KEY,
+ // name TEXT,
+ // score INTEGER)
+ //
+ // PRAGMA table_info("players")
+ //
+ // cid | name | type | notnull | dflt_value | pk |
+ // 0 | id | INTEGER | 0 | NULL | 1 |
+ // 1 | name | TEXT | 0 | NULL | 0 |
+ // 2 | score | INTEGER | 0 | NULL | 0 |
+
+ let columns = try self.columns(in: tableName)
+
+ let primaryKey: PrimaryKeyInfo
+ let pkColumns = columns
+ .filter { $0.primaryKeyIndex > 0 }
+ .sorted { $0.primaryKeyIndex < $1.primaryKeyIndex }
+
+ switch pkColumns.count {
+ case 0:
+ // No explicit primary key => primary key is hidden rowID column
+ primaryKey = .hiddenRowID
+ case 1:
+ // Single column
+ let pkColumn = pkColumns.first!
+
+ // https://www.sqlite.org/lang_createtable.html:
+ //
+ // > With one exception noted below, if a rowid table has a primary
+ // > key that consists of a single column and the declared type of
+ // > that column is "INTEGER" in any mixture of upper and lower
+ // > case, then the column becomes an alias for the rowid. Such a
+ // > column is usually referred to as an "integer primary key".
+ // > A PRIMARY KEY column only becomes an integer primary key if the
+ // > declared type name is exactly "INTEGER". Other integer type
+ // > names like "INT" or "BIGINT" or "SHORT INTEGER" or "UNSIGNED
+ // > INTEGER" causes the primary key column to behave as an ordinary
+ // > table column with integer affinity and a unique index, not as
+ // > an alias for the rowid.
+ // >
+ // > The exception mentioned above is that if the declaration of a
+ // > column with declared type "INTEGER" includes an "PRIMARY KEY
+ // > DESC" clause, it does not become an alias for the rowid [...]
+ //
+ // FIXME: We ignore the exception, and consider all INTEGER primary
+ // keys as aliases for the rowid:
+ if pkColumn.type.uppercased() == "INTEGER" {
+ primaryKey = .rowID(pkColumn.name)
+ } else {
+ primaryKey = .regular([pkColumn.name])
+ }
+ default:
+ // Multi-columns primary key
+ primaryKey = .regular(pkColumns.map { $0.name })
+ }
+
+ schemaCache.set(primaryKey: primaryKey, forTable: tableName)
+ return primaryKey
+ }
+
+ /// The number of columns in the table named `tableName`.
+ ///
+ /// - throws: A DatabaseError if table does not exist.
+ public func columnCount(in tableName: String) throws -> Int {
+ return try columns(in: tableName).count
+ }
+
+ /// The columns in the table named `tableName`.
+ ///
+ /// - throws: A DatabaseError if table does not exist.
+ func columns(in tableName: String) throws -> [ColumnInfo] {
+ if let columns = schemaCache.columns(in: tableName) {
+ return columns
+ }
+
+ // https://www.sqlite.org/pragma.html
+ //
+ // > PRAGMA database.table_info(table-name);
+ // >
+ // > This pragma returns one row for each column in the named table.
+ // > Columns in the result set include the column name, data type,
+ // > whether or not the column can be NULL, and the default value for
+ // > the column. The "pk" column in the result set is zero for columns
+ // > that are not part of the primary key, and is the index of the
+ // > column in the primary key for columns that are part of the primary
+ // > key.
+ //
+ // CREATE TABLE players (
+ // id INTEGER PRIMARY KEY,
+ // firstName TEXT,
+ // lastName TEXT)
+ //
+ // PRAGMA table_info("players")
+ //
+ // cid | name | type | notnull | dflt_value | pk |
+ // 0 | id | INTEGER | 0 | NULL | 1 |
+ // 1 | name | TEXT | 0 | NULL | 0 |
+ // 2 | score | INTEGER | 0 | NULL | 0 |
+
+ if sqlite3_libversion_number() < 3008005 {
+ // Work around a bug in SQLite where PRAGMA table_info would
+ // return a result even after the table was deleted.
+ if try !tableExists(tableName) {
+ throw DatabaseError(message: "no such table: \(tableName)")
+ }
+ }
+ let columns = try ColumnInfo.fetchAll(self, "PRAGMA table_info(\(tableName.quotedDatabaseIdentifier))")
+ guard columns.count > 0 else {
+ throw DatabaseError(message: "no such table: \(tableName)")
+ }
+
+ schemaCache.set(columns: columns, forTable: tableName)
+ return columns
+ }
+
+ /// The indexes on table named `tableName`; returns the empty array if the
+ /// table does not exist.
+ ///
+ /// Note: SQLite does not define any index for INTEGER PRIMARY KEY columns:
+ /// this method does not return any index that represents this primary key.
+ ///
+ /// If you want to know if a set of columns uniquely identify a row, prefer
+ /// table(_:hasUniqueKey:) instead.
+ public func indexes(on tableName: String) throws -> [IndexInfo] {
+ if let indexes = schemaCache.indexes(on: tableName) {
+ return indexes
+ }
+
+ let indexes = try Row.fetchAll(self, "PRAGMA index_list(\(tableName.quotedDatabaseIdentifier))").map { row -> IndexInfo in
+ let indexName: String = row[1]
+ let unique: Bool = row[2]
+ let columns = try Row.fetchAll(self, "PRAGMA index_info(\(indexName.quotedDatabaseIdentifier))")
+ .map { ($0[0] as Int, $0[2] as String) }
+ .sorted { $0.0 < $1.0 }
+ .map { $0.1 }
+ return IndexInfo(name: indexName, columns: columns, unique: unique)
+ }
+
+ schemaCache.set(indexes: indexes, forTable: tableName)
+ return indexes
+ }
+
+ /// If there exists a unique key on columns, return the columns
+ /// ordered as the matching index (or primay key). Case of returned columns
+ /// is not guaranteed.
+ func columnsForUniqueKey(_ columns: T, in tableName: String) throws -> [String]? where T.Iterator.Element == String {
+ let primaryKey = try self.primaryKey(tableName) // first, so that we fail early and consistently should the table not exist
+ let lowercasedColumns = Set(columns.map { $0.lowercased() })
+ if Set(primaryKey.columns.map { $0.lowercased() }) == lowercasedColumns {
+ return primaryKey.columns
+ }
+ if let index = try indexes(on: tableName).first(where: { index in index.isUnique && Set(index.columns.map { $0.lowercased() }) == lowercasedColumns }) {
+ // There is an explicit unique index on the columns
+ return index.columns
+ }
+ return nil
+ }
+
+ /// True if a sequence of columns uniquely identifies a row, that is to say
+ /// if the columns are the primary key, or if there is a unique index on them.
+ public func table(_ tableName: String, hasUniqueKey columns: T) throws -> Bool where T.Iterator.Element == String {
+ return try columnsForUniqueKey(Array(columns), in: tableName) != nil
+ }
+
+ /// The foreign keys defined on table named `tableName`.
+ public func foreignKeys(on tableName: String) throws -> [ForeignKeyInfo] {
+ if let foreignKeys = schemaCache.foreignKeys(on: tableName) {
+ return foreignKeys
+ }
+
+ var rawForeignKeys: [(destinationTable: String, mapping: [(origin: String, destination: String?, seq: Int)])] = []
+ var previousId: Int? = nil
+ for row in try Row.fetchAll(self, "PRAGMA foreign_key_list(\(tableName.quotedDatabaseIdentifier))") {
+ // row =
+ let id: Int = row[0]
+ let seq: Int = row[1]
+ let table: String = row[2]
+ let origin: String = row[3]
+ let destination: String? = row[4]
+
+ if previousId == id {
+ rawForeignKeys[rawForeignKeys.count - 1].mapping.append((origin: origin, destination: destination, seq: seq))
+ } else {
+ rawForeignKeys.append((destinationTable: table, mapping: [(origin: origin, destination: destination, seq: seq)]))
+ previousId = id
+ }
+ }
+
+ let foreignKeys = try rawForeignKeys.map { (destinationTable, columnMapping) -> ForeignKeyInfo in
+ let orderedMapping = columnMapping
+ .sorted { $0.seq < $1.seq }
+ .map { (origin: $0.origin, destination: $0 .destination) }
+
+ let completeMapping: [(origin: String, destination: String)]
+ if orderedMapping.contains(where: { (_, destination) in destination == nil }) {
+ let pk = try primaryKey(destinationTable)
+ completeMapping = zip(pk.columns, orderedMapping).map { (pkColumn, arrow) in
+ (origin: arrow.origin, destination: pkColumn)
+ }
+ } else {
+ completeMapping = orderedMapping.map { (origin, destination) in
+ (origin: origin, destination: destination!)
+ }
+ }
+ return ForeignKeyInfo(destinationTable: destinationTable, mapping: completeMapping)
+ }
+
+ schemaCache.set(foreignKeys: foreignKeys, forTable: tableName)
+ return foreignKeys
+ }
+}
+
+/// A column of a table
+struct ColumnInfo : RowConvertible {
+ // CREATE TABLE players (
+ // id INTEGER PRIMARY KEY,
+ // firstName TEXT,
+ // lastName TEXT)
+ //
+ // PRAGMA table_info("players")
+ //
+ // cid | name | type | notnull | dflt_value | pk |
+ // 0 | id | INTEGER | 0 | NULL | 1 |
+ // 1 | name | TEXT | 0 | NULL | 0 |
+ // 2 | score | INTEGER | 0 | NULL | 0 |
+ let name: String
+ let type: String
+ let notNull: Bool
+ let defaultDatabaseValue: DatabaseValue
+ let primaryKeyIndex: Int
+
+ init(row: Row) {
+ name = row["name"]
+ type = row["type"]
+ notNull = row["notnull"]
+ defaultDatabaseValue = row["dflt_value"]
+ primaryKeyIndex = row["pk"]
+ }
+}
+
+/// An index on a database table.
+///
+/// See `Database.indexes(on:)`
+public struct IndexInfo {
+ /// The name of the index
+ public let name: String
+
+ /// The indexed columns
+ public let columns: [String]
+
+ /// True if the index is unique
+ public let isUnique: Bool
+
+ init(name: String, columns: [String], unique: Bool) {
+ self.name = name
+ self.columns = columns
+ self.isUnique = unique
+ }
+}
+
+/// Primary keys are returned from the Database.primaryKey(_:) method.
+///
+/// When the table's primary key is the rowid:
+///
+/// // CREATE TABLE items (name TEXT)
+/// let pk = try db.primaryKey("items")
+/// pk.columns // ["rowid"]
+/// pk.rowIDColumn // nil
+/// pk.isRowID // true
+///
+/// // CREATE TABLE citizens (
+/// // id INTEGER PRIMARY KEY,
+/// // name TEXT
+/// // )
+/// let pk = try db.primaryKey("citizens")!
+/// pk.columns // ["id"]
+/// pk.rowIDColumn // "id"
+/// pk.isRowID // true
+///
+/// When the table's primary key is not the rowid:
+///
+/// // CREATE TABLE countries (
+/// // isoCode TEXT NOT NULL PRIMARY KEY
+/// // name TEXT
+/// // )
+/// let pk = db.primaryKey("countries")!
+/// pk.columns // ["isoCode"]
+/// pk.rowIDColumn // nil
+/// pk.isRowID // false
+///
+/// // CREATE TABLE citizenships (
+/// // citizenID INTEGER NOT NULL REFERENCES citizens(id)
+/// // countryIsoCode TEXT NOT NULL REFERENCES countries(isoCode)
+/// // PRIMARY KEY (citizenID, countryIsoCode)
+/// // )
+/// let pk = db.primaryKey("citizenships")!
+/// pk.columns // ["citizenID", "countryIsoCode"]
+/// pk.rowIDColumn // nil
+/// pk.isRowID // false
+public struct PrimaryKeyInfo {
+ private enum Impl {
+ /// The hidden rowID.
+ case hiddenRowID
+
+ /// An INTEGER PRIMARY KEY column that aliases the Row ID.
+ /// Associated string is the column name.
+ case rowID(String)
+
+ /// Any primary key, but INTEGER PRIMARY KEY.
+ /// Associated strings are column names.
+ case regular([String])
+ }
+
+ private let impl: Impl
+
+ static func rowID(_ column: String) -> PrimaryKeyInfo {
+ return PrimaryKeyInfo(impl: .rowID(column))
+ }
+
+ static func regular(_ columns: [String]) -> PrimaryKeyInfo {
+ assert(!columns.isEmpty)
+ return PrimaryKeyInfo(impl: .regular(columns))
+ }
+
+ static let hiddenRowID = PrimaryKeyInfo(impl: .hiddenRowID)
+
+ /// The columns in the primary key; this array is never empty.
+ public var columns: [String] {
+ switch impl {
+ case .hiddenRowID:
+ return [Column.rowID.name]
+ case .rowID(let column):
+ return [column]
+ case .regular(let columns):
+ return columns
+ }
+ }
+
+ /// When not nil, the name of the column that contains the INTEGER PRIMARY KEY.
+ public var rowIDColumn: String? {
+ switch impl {
+ case .hiddenRowID:
+ return nil
+ case .rowID(let column):
+ return column
+ case .regular:
+ return nil
+ }
+ }
+
+ /// When true, the primary key is the rowid:
+ public var isRowID: Bool {
+ switch impl {
+ case .hiddenRowID:
+ return true
+ case .rowID:
+ return true
+ case .regular:
+ return false
+ }
+ }
+}
+
+/// You get foreign keys from table names, with the
+/// `foreignKeys(on:)` method.
+public struct ForeignKeyInfo {
+ /// The name of the destination table
+ public let destinationTable: String
+
+ /// The column to column mapping
+ public let mapping: [(origin: String, destination: String)]
+
+ /// The origin columns
+ public var originColumns: [String] {
+ return mapping.map { $0.origin }
+ }
+
+ /// The destination columns
+ public var destinationColumns: [String] {
+ return mapping.map { $0.destination }
+ }
+}
+
+
+// =========================================================================
+// MARK: - StatementCompilationObserver
+
+/// A class that gathers information about a statement during its compilation.
+final class StatementCompilationObserver {
+ let database: Database
+
+ /// A dictionary [tablename: Set] of accessed columns
+ var selectionInfo = SelectStatement.SelectionInfo()
+
+ /// What this statement does to the database
+ var databaseEventKinds: [DatabaseEventKind] = []
+
+ /// True if a statement alter the schema in a way that required schema cache
+ /// invalidation. Adding a column to a table does invalidate the schema
+ /// cache, but not adding a table.
+ var invalidatesDatabaseSchemaCache = false
+
+ /// Not nil if a statement is a BEGIN/COMMIT/ROLLBACK/RELEASE transaction/savepoint statement.
+ var transactionStatementInfo: UpdateStatement.TransactionStatementInfo?
+
+ var isDropTableStatement = false
+
+ init(_ database: Database) {
+ self.database = database
+ }
+
+ // Call this method before calling sqlite3_prepare_v2()
+ func start() {
+ let observerPointer = Unmanaged.passUnretained(self).toOpaque()
+ sqlite3_set_authorizer(database.sqliteConnection, { (observerPointer, actionCode, cString1, cString2, cString3, cString4) -> Int32 in
+ // print("\(actionCode) \([cString1, cString2, cString3, cString4].flatMap { $0.map({ String(cString: $0) }) })")
+
+ // https://www.sqlite.org/c3ref/set_authorizer.html:
+ //
+ // > Applications must always be prepared to encounter a NULL
+ // > pointer in any of the third through the sixth parameters of
+ // > the authorization callback.
+ switch actionCode {
+ case SQLITE_DROP_TABLE, SQLITE_DROP_TEMP_TABLE, SQLITE_DROP_TEMP_VIEW, SQLITE_DROP_VIEW, SQLITE_DETACH, SQLITE_ALTER_TABLE, SQLITE_DROP_VTABLE, SQLITE_CREATE_INDEX, SQLITE_CREATE_TEMP_INDEX, SQLITE_DROP_INDEX, SQLITE_DROP_TEMP_INDEX:
+ let observer = Unmanaged.fromOpaque(observerPointer!).takeUnretainedValue()
+ if actionCode == SQLITE_DROP_TABLE || actionCode == SQLITE_DROP_VTABLE {
+ observer.isDropTableStatement = true
+ }
+ observer.invalidatesDatabaseSchemaCache = true
+ case SQLITE_READ:
+ guard let tableName = cString1.map({ String(cString: $0) }) else { return SQLITE_OK }
+ guard let columnName = cString2.map({ String(cString: $0) }) else { return SQLITE_OK }
+ let observer = Unmanaged.fromOpaque(observerPointer!).takeUnretainedValue()
+ if columnName.isEmpty {
+ // SELECT COUNT(*) FROM table
+ observer.selectionInfo.insert(allColumnsOfTable: tableName)
+ } else {
+ // SELECT column FROM table
+ observer.selectionInfo.insert(column: columnName, ofTable: tableName)
+ }
+ case SQLITE_INSERT:
+ guard let tableName = cString1.map({ String(cString: $0) }) else { return SQLITE_OK }
+ let observer = Unmanaged.fromOpaque(observerPointer!).takeUnretainedValue()
+ observer.databaseEventKinds.append(.insert(tableName: tableName))
+ case SQLITE_DELETE:
+ guard let tableName = cString1.map({ String(cString: $0) }) else { return SQLITE_OK }
+ let observer = Unmanaged.fromOpaque(observerPointer!).takeUnretainedValue()
+ if tableName != "sqlite_master" && !observer.isDropTableStatement {
+ observer.databaseEventKinds.append(.delete(tableName: tableName))
+ // Prevent [truncate optimization](https://www.sqlite.org/lang_delete.html#truncateopt)
+ // so that transaction observers can observe individual deletions.
+ return SQLITE_IGNORE
+ }
+ case SQLITE_UPDATE:
+ guard let tableName = cString1.map({ String(cString: $0) }) else { return SQLITE_OK }
+ guard let columnName = cString2.map({ String(cString: $0) }) else { return SQLITE_OK }
+ let observer = Unmanaged.fromOpaque(observerPointer!).takeUnretainedValue()
+ observer.insertUpdateEventKind(tableName: tableName, columnName: columnName)
+ case SQLITE_TRANSACTION:
+ guard let rawAction = cString1.map({ String(cString: $0) }) else { return SQLITE_OK }
+ let observer = Unmanaged.fromOpaque(observerPointer!).takeUnretainedValue()
+ let action = UpdateStatement.TransactionStatementInfo.TransactionAction(rawValue: rawAction)!
+ observer.transactionStatementInfo = .transaction(action: action)
+ case SQLITE_SAVEPOINT:
+ guard let rawAction = cString1.map({ String(cString: $0) }) else { return SQLITE_OK }
+ guard let savepointName = cString2.map({ String(cString: $0) }) else { return SQLITE_OK }
+ let observer = Unmanaged.fromOpaque(observerPointer!).takeUnretainedValue()
+ let action = UpdateStatement.TransactionStatementInfo.SavepointAction(rawValue: rawAction)!
+ observer.transactionStatementInfo = .savepoint(name: savepointName, action: action)
+ case SQLITE_FUNCTION:
+ // Starting SQLite 3.19.0, `SELECT COUNT(*) FROM table` triggers
+ // an authorization callback for SQLITE_READ with an empty
+ // column: http://www.sqlite.org/changes.html#version_3_19_0
+ //
+ // Before SQLite 3.19.0, `SELECT COUNT(*) FROM table` does not
+ // trigger any authorization callback that tells about the
+ // counted table: any use of the COUNT function makes the
+ // selection undetermined.
+ guard sqlite3_libversion_number() < 3019000 else { return SQLITE_OK }
+ guard let functionName = cString2.map({ String(cString: $0) }) else { return SQLITE_OK }
+ if functionName.uppercased() == "COUNT" {
+ let observer = Unmanaged.fromOpaque(observerPointer!).takeUnretainedValue()
+ observer.selectionInfo = SelectStatement.SelectionInfo.unknown()
+ }
+ default:
+ break
+ }
+ return SQLITE_OK
+ }, observerPointer)
+ }
+
+ // Call this method between two calls to calling sqlite3_prepare_v2()
+ func reset() {
+ selectionInfo = SelectStatement.SelectionInfo()
+ databaseEventKinds = []
+ invalidatesDatabaseSchemaCache = false
+ transactionStatementInfo = nil
+ isDropTableStatement = false
+ }
+
+ func insertUpdateEventKind(tableName: String, columnName: String) {
+ for (index, eventKind) in databaseEventKinds.enumerated() {
+ if case .update(let t, let columnNames) = eventKind, t == tableName {
+ var columnNames = columnNames
+ columnNames.insert(columnName)
+ databaseEventKinds[index] = .update(tableName: tableName, columnNames: columnNames)
+ return
+ }
+ }
+ databaseEventKinds.append(.update(tableName: tableName, columnNames: [columnName]))
+ }
+
+ func stop() {
+ // Restore default authorizer
+ database.installDefaultAuthorizer()
+ }
+}
+
+
+// =========================================================================
+// MARK: - Transactions & Savepoint
+
+extension Database {
+
+ /// The extent of a transaction observation
+ ///
+ /// See Database.add(transactionObserver:extent:)
+ public enum TransactionObservationExtent {
+ /// Observation lasts until observer is deallocated
+ case observerLifetime
+ /// Observation lasts until the next transaction
+ case nextTransaction
+ /// Observation lasts until the database is closed
+ case databaseLifetime
+ }
+
+ /// Executes a block inside a database transaction.
+ ///
+ /// try dbQueue.inDatabase do {
+ /// try db.inTransaction {
+ /// try db.execute("INSERT ...")
+ /// return .commit
+ /// }
+ /// }
+ ///
+ /// If the block throws an error, the transaction is rollbacked and the
+ /// error is rethrown.
+ ///
+ /// This method is not reentrant: you can't nest transactions.
+ ///
+ /// - parameters:
+ /// - kind: The transaction type (default nil). If nil, the transaction
+ /// type is configuration.defaultTransactionKind, which itself
+ /// defaults to .immediate. See https://www.sqlite.org/lang_transaction.html
+ /// for more information.
+ /// - block: A block that executes SQL statements and return either
+ /// .commit or .rollback.
+ /// - throws: The error thrown by the block.
+ public func inTransaction(_ kind: TransactionKind? = nil, _ block: () throws -> TransactionCompletion) throws {
+ // Begin transaction
+ try beginTransaction(kind)
+
+ // Now that transaction has begun, we'll rollback in case of error.
+ // But we'll throw the first caught error, so that user knows
+ // what happened.
+ var firstError: Error? = nil
+ let needsRollback: Bool
+ do {
+ let completion = try block()
+ switch completion {
+ case .commit:
+ try commit()
+ needsRollback = false
+ case .rollback:
+ needsRollback = true
+ }
+ } catch {
+ firstError = error
+ needsRollback = true
+ }
+
+ if needsRollback {
+ do {
+ try rollback()
+ } catch {
+ if firstError == nil {
+ firstError = error
+ }
+ }
+ }
+
+ if let firstError = firstError {
+ throw firstError
+ }
+ }
+
+ /// Executes a block inside a savepoint.
+ ///
+ /// try dbQueue.inDatabase do {
+ /// try db.inSavepoint {
+ /// try db.execute("INSERT ...")
+ /// return .commit
+ /// }
+ /// }
+ ///
+ /// If the block throws an error, the savepoint is rollbacked and the
+ /// error is rethrown.
+ ///
+ /// This method is reentrant: you can nest savepoints.
+ ///
+ /// - parameter block: A block that executes SQL statements and return
+ /// either .commit or .rollback.
+ /// - throws: The error thrown by the block.
+ public func inSavepoint(_ block: () throws -> TransactionCompletion) throws {
+ // By default, top level SQLite savepoints open a deferred transaction.
+ //
+ // But GRDB database configuration mandates a default transaction kind
+ // that we have to honor.
+ //
+ // So when the default GRDB transaction kind is not deferred, we open a
+ // transaction instead
+ guard isInsideTransaction || configuration.defaultTransactionKind == .deferred else {
+ return try inTransaction(nil, block)
+ }
+
+ // If the savepoint is top-level, we'll use ROLLBACK TRANSACTION in
+ // order to perform the special error handling of rollbacks (see
+ // the rollback method).
+ let topLevelSavepoint = !isInsideTransaction
+
+ // Begin savepoint
+ //
+ // We use a single name for savepoints because there is no need
+ // using unique savepoint names. User could still mess with them
+ // with raw SQL queries, but let's assume that it is unlikely that
+ // the user uses "grdb" as a savepoint name.
+ try execute("SAVEPOINT grdb")
+
+ // Now that savepoint has begun, we'll rollback in case of error.
+ // But we'll throw the first caught error, so that user knows
+ // what happened.
+ var firstError: Error? = nil
+ let needsRollback: Bool
+ do {
+ let completion = try block()
+ switch completion {
+ case .commit:
+ try execute("RELEASE SAVEPOINT grdb")
+ needsRollback = false
+ case .rollback:
+ needsRollback = true
+ }
+ } catch {
+ firstError = error
+ needsRollback = true
+ }
+
+ if needsRollback {
+ do {
+ if topLevelSavepoint {
+ try rollback()
+ } else {
+ // Rollback, and release the savepoint.
+ // Rollback alone is not enough to clear the savepoint from
+ // the SQLite savepoint stack.
+ try execute("ROLLBACK TRANSACTION TO SAVEPOINT grdb")
+ try execute("RELEASE SAVEPOINT grdb")
+ }
+ } catch {
+ if firstError == nil {
+ firstError = error
+ }
+ }
+ }
+
+ if let firstError = firstError {
+ throw firstError
+ }
+ }
+
+ func beginTransaction(_ kind: TransactionKind? = nil) throws {
+ switch kind ?? configuration.defaultTransactionKind {
+ case .deferred:
+ try execute("BEGIN DEFERRED TRANSACTION")
+ case .immediate:
+ try execute("BEGIN IMMEDIATE TRANSACTION")
+ case .exclusive:
+ try execute("BEGIN EXCLUSIVE TRANSACTION")
+ }
+ }
+
+ private func rollback() throws {
+ // The SQLite documentation contains two related but distinct techniques
+ // to handle rollbacks and errors:
+ //
+ // https://www.sqlite.org/lang_transaction.html#immediate
+ //
+ // > Response To Errors Within A Transaction
+ // >
+ // > If certain kinds of errors occur within a transaction, the
+ // > transaction may or may not be rolled back automatically.
+ // > The errors that can cause an automatic rollback include:
+ // >
+ // > - SQLITE_FULL: database or disk full
+ // > - SQLITE_IOERR: disk I/O error
+ // > - SQLITE_BUSY: database in use by another process
+ // > - SQLITE_NOMEM: out or memory
+ // >
+ // > [...] It is recommended that applications respond to the
+ // > errors listed above by explicitly issuing a ROLLBACK
+ // > command. If the transaction has already been rolled back
+ // > automatically by the error response, then the ROLLBACK
+ // > command will fail with an error, but no harm is caused
+ // > by this.
+ //
+ // https://sqlite.org/c3ref/get_autocommit.html
+ //
+ // > The sqlite3_get_autocommit() interface returns non-zero or zero if
+ // > the given database connection is or is not in autocommit mode,
+ // > respectively.
+ // >
+ // > [...] If certain kinds of errors occur on a statement within a
+ // > multi-statement transaction (errors including SQLITE_FULL,
+ // > SQLITE_IOERR, SQLITE_NOMEM, SQLITE_BUSY, and SQLITE_INTERRUPT) then
+ // > the transaction might be rolled back automatically. The only way to
+ // > find out whether SQLite automatically rolled back the transaction
+ // > after an error is to use this function.
+ //
+ // The second technique is more robust, because we don't have to guess
+ // which rollback errors should be ignored, and which rollback errors
+ // should be exposed to the library user.
+ if sqlite3_get_autocommit(sqliteConnection) == 0 {
+ try execute("ROLLBACK TRANSACTION")
+ }
+ }
+
+ func commit() throws {
+ try execute("COMMIT TRANSACTION")
+ }
+
+ /// Add a transaction observer, so that it gets notified of
+ /// database changes.
+ ///
+ /// - parameter transactionObserver: A transaction observer.
+ /// - parameter extent: The duration of the observation. The default is
+ /// the observer lifetime (observation lasts until observer
+ /// is deallocated).
+ public func add(transactionObserver: TransactionObserver, extent: TransactionObservationExtent = .observerLifetime) {
+ SchedulingWatchdog.preconditionValidQueue(self)
+ transactionObservers.append(ManagedTransactionObserver(observer: transactionObserver, extent: extent))
+ if transactionObservers.count == 1 {
+ installUpdateHook()
+ }
+ }
+
+ /// Remove a transaction observer.
+ public func remove(transactionObserver: TransactionObserver) {
+ SchedulingWatchdog.preconditionValidQueue(self)
+ transactionObservers.removeFirst { $0.isWrapping(transactionObserver) }
+ if transactionObservers.isEmpty {
+ uninstallUpdateHook()
+ }
+ }
+
+ /// Registers a closure to be executed after the next or current
+ /// transaction completion.
+ ///
+ /// dbQueue.inTransaction { db in
+ /// db.afterNextTransactionCommit { _ in
+ /// print("commit did succeed")
+ /// }
+ /// ...
+ /// return .commit // prints "commit did succeed"
+ /// }
+ ///
+ /// If the transaction is rollbacked, the closure is not executed.
+ ///
+ /// If the transaction is committed, the closure is executed in a protected
+ /// dispatch queue, serialized will all database updates.
+ public func afterNextTransactionCommit(_ closure: @escaping (Database) -> ()) {
+ class CommitHandler : TransactionObserver {
+ let closure: (Database) -> ()
+
+ init(_ closure: @escaping (Database) -> ()) {
+ self.closure = closure
+ }
+
+ // Ignore individual changes and transaction rollbacks
+ func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { return false }
+ #if SQLITE_ENABLE_PREUPDATE_HOOK
+ func databaseWillChange(with event: DatabasePreUpdateEvent) { }
+ #endif
+ func databaseDidChange(with event: DatabaseEvent) { }
+ func databaseWillCommit() throws { }
+ func databaseDidRollback(_ db: Database) { }
+
+ // On commit, run closure
+ func databaseDidCommit(_ db: Database) {
+ closure(db)
+ }
+ }
+
+ add(transactionObserver: CommitHandler(closure), extent: .nextTransaction)
+ }
+
+ /// Remove transaction observers that have stopped observing transaction,
+ /// and uninstall SQLite update hooks if there is no remaining observers.
+ private func cleanupTransactionObservers() {
+ transactionObservers = transactionObservers.filter { $0.isObserving }
+ if transactionObservers.isEmpty {
+ uninstallUpdateHook()
+ }
+ }
+
+ /// Checks that a SQL query is valid for a select statement.
+ ///
+ /// Select statements do not call database.updateStatementDidExecute().
+ /// Here we make sure that the update statements we track are not hidden in
+ /// a select statement.
+ ///
+ /// An INSERT statement will pass, but not DROP TABLE (which invalidates the
+ /// database cache), or RELEASE SAVEPOINT (which alters the savepoint stack)
+ static func preconditionValidSelectStatement(sql: String, observer: StatementCompilationObserver) {
+ GRDBPrecondition(observer.invalidatesDatabaseSchemaCache == false, "Invalid statement type for query \(String(reflecting: sql)): use UpdateStatement instead.")
+ GRDBPrecondition(observer.transactionStatementInfo == nil, "Invalid statement type for query \(String(reflecting: sql)): use UpdateStatement instead.")
+
+ // Don't check for observer.databaseEventKinds.isEmpty
+ //
+ // When observer.databaseEventKinds.isEmpty is NOT empty, this means
+ // that the database is changed by the statement.
+ //
+ // It thus looks like the statement should be performed by an
+ // UpdateStatement, not a SelectStatement: transaction observers are not
+ // notified of database changes when they are executed by
+ // a SelectStatement.
+ //
+ // However https://github.com/groue/GRDB.swift/issues/80 and
+ // https://github.com/groue/GRDB.swift/issues/82 have shown that SELECT
+ // statements on virtual tables can generate database changes.
+ //
+ // :-(
+ //
+ // OK, this is getting very difficult to protect the user against
+ // himself: just give up, and allow SelectStatement to execute database
+ // changes. We'll cope with eventual troubles later, when they occur.
+ //
+ // GRDBPrecondition(observer.databaseEventKinds.isEmpty, "Invalid statement type for query \(String(reflecting: sql)): use UpdateStatement instead.")
+ }
+
+ func updateStatementWillExecute(_ statement: UpdateStatement) {
+ // Grab the transaction observers that are interested in the actions
+ // performed by the statement.
+ let databaseEventKinds = statement.databaseEventKinds
+ activeTransactionObservers = transactionObservers.filter { observer in
+ return databaseEventKinds.contains(where: observer.observes)
+ }
+ }
+
+ func selectStatementDidFail(_ statement: SelectStatement) {
+ // Failed statements can not be reused, because sqlite3_reset won't
+ // be able to restore the statement to its initial state:
+ // https://www.sqlite.org/c3ref/reset.html
+ //
+ // So make sure we clear this statement from the cache.
+ grdbStatementCache.remove(statement)
+ userStatementCache.remove(statement)
+ }
+
+ /// Some failed statements interest transaction observers.
+ func updateStatementDidFail(_ statement: UpdateStatement) throws {
+ // Wait for next statement
+ activeTransactionObservers = []
+
+ // Reset transactionHookState before didRollback eventually executes
+ // other statements.
+ let transactionHookState = self.transactionHookState
+ self.transactionHookState = .pending
+
+ // Failed statements can not be reused, because sqlite3_reset won't
+ // be able to restore the statement to its initial state:
+ // https://www.sqlite.org/c3ref/reset.html
+ //
+ // So make sure we clear this statement from the cache.
+ grdbStatementCache.remove(statement)
+ userStatementCache.remove(statement)
+
+ switch transactionHookState {
+ case .rollback:
+ // Don't notify observers because we're in a failed implicit
+ // transaction here (like an INSERT which fails with
+ // SQLITE_CONSTRAINT error)
+ didRollback(notifyTransactionObservers: false)
+ case .cancelledCommit(let error):
+ didRollback(notifyTransactionObservers: true)
+ throw error
+ default:
+ break
+ }
+ }
+
+ /// Some succeeded statements invalidate the database cache, others interest
+ /// transaction observers, and others modify the savepoint stack.
+ func updateStatementDidExecute(_ statement: UpdateStatement) {
+ // Wait for next statement
+ activeTransactionObservers = []
+
+ if statement.invalidatesDatabaseSchemaCache {
+ clearSchemaCache()
+ }
+
+ if let transactionStatementInfo = statement.transactionStatementInfo {
+ switch transactionStatementInfo {
+ case .transaction(action: let action):
+ switch action {
+ case .begin:
+ break
+ case .commit:
+ if case .pending = self.transactionHookState {
+ // A COMMIT statement has ended a deferred transaction
+ // that did not open, and sqlite_commit_hook was not
+ // called.
+ //
+ // BEGIN DEFERRED TRANSACTION
+ // COMMIT
+ self.transactionHookState = .commit
+ }
+ case .rollback:
+ break
+ }
+ case .savepoint(name: let name, action: let action):
+ switch action {
+ case .begin:
+ savepointStack.beginSavepoint(named: name)
+ case .release:
+ savepointStack.releaseSavepoint(named: name)
+ if savepointStack.isEmpty {
+ let eventsBuffer = savepointStack.eventsBuffer
+ savepointStack.clear()
+ for (event, observers) in eventsBuffer {
+ for observer in observers {
+ event.send(to: observer)
+ }
+ }
+ }
+ case .rollback:
+ savepointStack.rollbackSavepoint(named: name)
+ }
+ }
+ }
+
+ // Reset transactionHookState before didCommit or didRollback eventually
+ // execute other statements.
+ let transactionHookState = self.transactionHookState
+ self.transactionHookState = .pending
+
+ switch transactionHookState {
+ case .commit:
+ didCommit()
+ case .rollback:
+ didRollback(notifyTransactionObservers: true)
+ default:
+ break
+ }
+ }
+
+ /// See sqlite3_commit_hook
+ func willCommit() throws {
+ let eventsBuffer = savepointStack.eventsBuffer
+ savepointStack.clear()
+
+ for (event, observers) in eventsBuffer {
+ for observer in observers {
+ event.send(to: observer)
+ }
+ }
+ for observer in transactionObservers {
+ try observer.databaseWillCommit()
+ }
+ }
+
+#if SQLITE_ENABLE_PREUPDATE_HOOK
+ /// See sqlite3_preupdate_hook
+ private func willChange(with event: DatabasePreUpdateEvent) {
+ if savepointStack.isEmpty {
+ // Notify all interested transactionObservers.
+ for observer in activeTransactionObservers {
+ observer.databaseWillChange(with: event)
+ }
+ } else {
+ // Buffer both event and the observers that should be notified of the event.
+ savepointStack.eventsBuffer.append((event: event.copy(), observers: activeTransactionObservers))
+ }
+ }
+#endif
+
+ /// See sqlite3_update_hook
+ private func didChange(with event: DatabaseEvent) {
+ if savepointStack.isEmpty {
+ // Notify all interested transactionObservers.
+ for observer in activeTransactionObservers {
+ observer.databaseDidChange(with: event)
+ }
+ } else {
+ // Buffer both event and the observers that should be notified of the event.
+ savepointStack.eventsBuffer.append((event: event.copy(), observers: activeTransactionObservers))
+ }
+ }
+
+ private func didCommit() {
+ savepointStack.clear()
+
+ for observer in transactionObservers {
+ observer.databaseDidCommit(self)
+ }
+ cleanupTransactionObservers()
+ }
+
+ private func didRollback(notifyTransactionObservers: Bool) {
+ savepointStack.clear()
+
+ if notifyTransactionObservers {
+ for observer in transactionObservers {
+ observer.databaseDidRollback(self)
+ }
+ }
+ cleanupTransactionObservers()
+ }
+
+ private func installUpdateHook() {
+ let dbPointer = Unmanaged.passUnretained(self).toOpaque()
+ sqlite3_update_hook(sqliteConnection, { (dbPointer, updateKind, databaseNameCString, tableNameCString, rowID) in
+ let db = Unmanaged.fromOpaque(dbPointer!).takeUnretainedValue()
+ db.didChange(with: DatabaseEvent(
+ kind: DatabaseEvent.Kind(rawValue: updateKind)!,
+ rowID: rowID,
+ databaseNameCString: databaseNameCString,
+ tableNameCString: tableNameCString))
+ }, dbPointer)
+
+ #if SQLITE_ENABLE_PREUPDATE_HOOK
+ sqlite3_preupdate_hook(sqliteConnection, { (dbPointer, databaseConnection, updateKind, databaseNameCString, tableNameCString, initialRowID, finalRowID) in
+ let db = Unmanaged.fromOpaque(dbPointer!).takeUnretainedValue()
+ db.willChange(with: DatabasePreUpdateEvent(
+ connection: databaseConnection!,
+ kind: DatabasePreUpdateEvent.Kind(rawValue: updateKind)!,
+ initialRowID: initialRowID,
+ finalRowID: finalRowID,
+ databaseNameCString: databaseNameCString,
+ tableNameCString: tableNameCString))
+ }, dbPointer)
+ #endif
+ }
+
+ private func uninstallUpdateHook() {
+ sqlite3_update_hook(sqliteConnection, nil, nil)
+ #if SQLITE_ENABLE_PREUPDATE_HOOK
+ sqlite3_preupdate_hook(sqliteConnection, nil, nil)
+ #endif
+ }
+}
+
+
+/// A transaction observer is notified of all changes and transactions committed
+/// or rollbacked on a database.
+///
+/// Adopting types must be a class.
+public protocol TransactionObserver : class {
+
+ /// Filters database changes that should be notified the the
+ /// databaseDidChange(with:) method.
+ func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool
+
+ /// Notifies a database change (insert, update, or delete).
+ ///
+ /// The change is pending until the end of the current transaction, notified
+ /// to databaseWillCommit, databaseDidCommit and databaseDidRollback.
+ ///
+ /// This method is called on the database queue.
+ ///
+ /// The event is only valid for the duration of this method call. If you
+ /// need to keep it longer, store a copy of its properties.
+ ///
+ /// - warning: this method must not change the database.
+ func databaseDidChange(with event: DatabaseEvent)
+
+ /// When a transaction is about to be committed, the transaction observer
+ /// has an opportunity to rollback pending changes by throwing an error.
+ ///
+ /// This method is called on the database queue.
+ ///
+ /// - warning: this method must not change the database.
+ ///
+ /// - throws: An eventual error that rollbacks pending changes.
+ func databaseWillCommit() throws
+
+ /// Database changes have been committed.
+ ///
+ /// This method is called on the database queue. It can change the database.
+ func databaseDidCommit(_ db: Database)
+
+ /// Database changes have been rollbacked.
+ ///
+ /// This method is called on the database queue. It can change the database.
+ func databaseDidRollback(_ db: Database)
+
+ #if SQLITE_ENABLE_PREUPDATE_HOOK
+ /// Notifies before a database change (insert, update, or delete)
+ /// with change information (initial / final values for the row's
+ /// columns). (Called *before* databaseDidChangeWithEvent.)
+ ///
+ /// The change is pending until the end of the current transaction,
+ /// and you always get a second chance to get basic event information in
+ /// the databaseDidChangeWithEvent callback.
+ ///
+ /// This callback is mostly useful for calculating detailed change
+ /// information for a row, and provides the initial / final values.
+ ///
+ /// This method is called on the database queue.
+ ///
+ /// The event is only valid for the duration of this method call. If you
+ /// need to keep it longer, store a copy of its properties.
+ ///
+ /// - warning: this method must not change the database.
+ ///
+ /// Availability Info:
+ ///
+ /// Requires SQLite 3.13.0 +
+ /// Compiled with option SQLITE_ENABLE_PREUPDATE_HOOK
+ ///
+ /// As of OSX 10.11.5, and iOS 9.3.2, the built-in SQLite library
+ /// does not have this enabled, so you'll need to compile your own
+ /// copy using GRDBCustomSQLite. See the README.md in /SQLiteCustom/
+ ///
+ /// The databaseDidChangeWithEvent callback is always available,
+ /// and may provide most/all of what you need.
+ /// (For example, FetchedRecordsController is built without using
+ /// this functionality.)
+ ///
+ func databaseWillChange(with event: DatabasePreUpdateEvent)
+ #endif
+}
+
+/// This class manages the observation extent of a transaction observer
+private final class ManagedTransactionObserver : TransactionObserver {
+ let extent: Database.TransactionObservationExtent
+ private weak var weakObserver: TransactionObserver?
+ private var strongObserver: TransactionObserver?
+ private var observer: TransactionObserver? { return strongObserver ?? weakObserver }
+
+ fileprivate var isObserving: Bool {
+ return observer != nil
+ }
+
+ init(observer: TransactionObserver, extent: Database.TransactionObservationExtent) {
+ self.extent = extent
+ switch extent {
+ case .observerLifetime:
+ weakObserver = observer
+ case .nextTransaction:
+ // This strong reference will be released in databaseDidCommit() and databaseDidRollback()
+ strongObserver = observer
+ case .databaseLifetime:
+ strongObserver = observer
+ }
+ }
+
+ func isWrapping(_ observer: TransactionObserver) -> Bool {
+ return self.observer === observer
+ }
+
+ func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool {
+ return observer?.observes(eventsOfKind: eventKind) ?? false
+ }
+
+ func databaseDidChange(with event: DatabaseEvent) {
+ observer?.databaseDidChange(with: event)
+ }
+
+ func databaseWillCommit() throws {
+ try observer?.databaseWillCommit()
+ }
+
+ func databaseDidCommit(_ db: Database) {
+ switch extent {
+ case .observerLifetime, .databaseLifetime:
+ observer?.databaseDidCommit(db)
+ case .nextTransaction:
+ if let observer = self.observer {
+ // make sure observer is no longer notified
+ strongObserver = nil
+ observer.databaseDidCommit(db)
+ }
+ }
+ }
+
+ func databaseDidRollback(_ db: Database) {
+ switch extent {
+ case .observerLifetime, .databaseLifetime:
+ observer?.databaseDidRollback(db)
+ case .nextTransaction:
+ if let observer = self.observer {
+ // make sure observer is no longer notified
+ strongObserver = nil
+ observer.databaseDidRollback(db)
+ }
+ }
+ }
+
+ #if SQLITE_ENABLE_PREUPDATE_HOOK
+ func databaseWillChange(with event: DatabasePreUpdateEvent) {
+ observer?.databaseWillChange(with: event)
+ }
+ #endif
+}
+
+/// A kind of database event. See Database.add(transactionObserver:)
+/// and DatabaseWriter.add(transactionObserver:).
+public enum DatabaseEventKind {
+ /// The insertion of a row in a database table
+ case insert(tableName: String)
+
+ /// The deletion of a row in a database table
+ case delete(tableName: String)
+
+ /// The update of a set of columns in a database table
+ case update(tableName: String, columnNames: Set)
+
+ /// Returns whether event has any impact on tables and columns described
+ /// by selectionInfo.
+ public func impacts(_ selectionInfo: SelectStatement.SelectionInfo) -> Bool {
+ switch self {
+ case .delete(let tableName):
+ return selectionInfo.contains(anyColumnFrom: tableName)
+ case .insert(let tableName):
+ return selectionInfo.contains(anyColumnFrom: tableName)
+ case .update(let tableName, let updatedColumnNames):
+ return selectionInfo.contains(anyColumnIn: updatedColumnNames, from: tableName)
+ }
+ }
+
+}
+
+extension DatabaseEventKind {
+ /// The impacted database table
+ public var tableName: String {
+ switch self {
+ case .insert(tableName: let tableName): return tableName
+ case .delete(tableName: let tableName): return tableName
+ case .update(tableName: let tableName, columnNames: _): return tableName
+ }
+ }
+}
+
+protocol DatabaseEventProtocol {
+ func send(to observer: TransactionObserver)
+}
+
+/// A database event, notified to TransactionObserver.
+public struct DatabaseEvent {
+
+ /// An event kind
+ public enum Kind: Int32 {
+ /// SQLITE_INSERT
+ case insert = 18
+
+ /// SQLITE_DELETE
+ case delete = 9
+
+ /// SQLITE_UPDATE
+ case update = 23
+ }
+
+ /// The event kind
+ public let kind: Kind
+
+ /// The database name
+ public var databaseName: String { return impl.databaseName }
+
+ /// The table name
+ public var tableName: String { return impl.tableName }
+
+ /// The rowID of the changed row.
+ public let rowID: Int64
+
+ /// Returns an event that can be stored:
+ ///
+ /// class MyObserver: TransactionObserver {
+ /// var events: [DatabaseEvent]
+ /// func databaseDidChange(with event: DatabaseEvent) {
+ /// events.append(event.copy())
+ /// }
+ /// }
+ public func copy() -> DatabaseEvent {
+ return impl.copy(self)
+ }
+
+ fileprivate init(kind: Kind, rowID: Int64, impl: DatabaseEventImpl) {
+ self.kind = kind
+ self.rowID = rowID
+ self.impl = impl
+ }
+
+ init(kind: Kind, rowID: Int64, databaseNameCString: UnsafePointer?, tableNameCString: UnsafePointer?) {
+ self.init(kind: kind, rowID: rowID, impl: MetalDatabaseEventImpl(databaseNameCString: databaseNameCString, tableNameCString: tableNameCString))
+ }
+
+ private let impl: DatabaseEventImpl
+}
+
+extension DatabaseEvent : DatabaseEventProtocol {
+ func send(to observer: TransactionObserver) {
+ observer.databaseDidChange(with: self)
+ }
+}
+
+/// Protocol for internal implementation of DatabaseEvent
+private protocol DatabaseEventImpl {
+ var databaseName: String { get }
+ var tableName: String { get }
+ func copy(_ event: DatabaseEvent) -> DatabaseEvent
+}
+
+/// Optimization: MetalDatabaseEventImpl does not create Swift strings from raw
+/// SQLite char* until actually asked for databaseName or tableName.
+private struct MetalDatabaseEventImpl : DatabaseEventImpl {
+ let databaseNameCString: UnsafePointer?
+ let tableNameCString: UnsafePointer?
+
+ var databaseName: String { return String(cString: databaseNameCString!) }
+ var tableName: String { return String(cString: tableNameCString!) }
+ func copy(_ event: DatabaseEvent) -> DatabaseEvent {
+ return DatabaseEvent(kind: event.kind, rowID: event.rowID, impl: CopiedDatabaseEventImpl(databaseName: databaseName, tableName: tableName))
+ }
+}
+
+/// Impl for DatabaseEvent that contains copies of event strings.
+private struct CopiedDatabaseEventImpl : DatabaseEventImpl {
+ let databaseName: String
+ let tableName: String
+ func copy(_ event: DatabaseEvent) -> DatabaseEvent {
+ return event
+ }
+}
+
+#if SQLITE_ENABLE_PREUPDATE_HOOK
+
+ public struct DatabasePreUpdateEvent {
+
+ /// An event kind
+ public enum Kind: Int32 {
+ /// SQLITE_INSERT
+ case insert = 18
+
+ /// SQLITE_DELETE
+ case delete = 9
+
+ /// SQLITE_UPDATE
+ case update = 23
+ }
+
+ /// The event kind
+ public let kind: Kind
+
+ /// The database name
+ public var databaseName: String { return impl.databaseName }
+
+ /// The table name
+ public var tableName: String { return impl.tableName }
+
+ /// The number of columns in the row that is being inserted, updated, or deleted.
+ public var count: Int { return Int(impl.columnsCount) }
+
+ /// The triggering depth of the row update
+ /// Returns:
+ /// 0 if the preupdate callback was invoked as a result of a direct insert,
+ // update, or delete operation;
+ /// 1 for inserts, updates, or deletes invoked by top-level triggers;
+ /// 2 for changes resulting from triggers called by top-level triggers;
+ /// ... and so forth
+ public var depth: CInt { return impl.depth }
+
+ /// The initial rowID of the row being changed for .Update and .Delete changes,
+ /// and nil for .Insert changes.
+ public let initialRowID: Int64?
+
+ /// The final rowID of the row being changed for .Update and .Insert changes,
+ /// and nil for .Delete changes.
+ public let finalRowID: Int64?
+
+ /// The initial database values in the row.
+ ///
+ /// Values appear in the same order as the columns in the table.
+ ///
+ /// The result is nil if the event is an .Insert event.
+ public var initialDatabaseValues: [DatabaseValue]? {
+ guard (kind == .update || kind == .delete) else { return nil }
+ return impl.initialDatabaseValues
+ }
+
+ /// Returns the initial `DatabaseValue` at given index.
+ ///
+ /// Indexes span from 0 for the leftmost column to (row.count - 1) for the
+ /// righmost column.
+ ///
+ /// The result is nil if the event is an .Insert event.
+ public func initialDatabaseValue(atIndex index: Int) -> DatabaseValue? {
+ GRDBPrecondition(index >= 0 && index < count, "row index out of range")
+ guard (kind == .update || kind == .delete) else { return nil }
+ return impl.initialDatabaseValue(atIndex: index)
+ }
+
+ /// The final database values in the row.
+ ///
+ /// Values appear in the same order as the columns in the table.
+ ///
+ /// The result is nil if the event is a .Delete event.
+ public var finalDatabaseValues: [DatabaseValue]? {
+ guard (kind == .update || kind == .insert) else { return nil }
+ return impl.finalDatabaseValues
+ }
+
+ /// Returns the final `DatabaseValue` at given index.
+ ///
+ /// Indexes span from 0 for the leftmost column to (row.count - 1) for the
+ /// righmost column.
+ ///
+ /// The result is nil if the event is a .Delete event.
+ public func finalDatabaseValue(atIndex index: Int) -> DatabaseValue? {
+ GRDBPrecondition(index >= 0 && index < count, "row index out of range")
+ guard (kind == .update || kind == .insert) else { return nil }
+ return impl.finalDatabaseValue(atIndex: index)
+ }
+
+ /// Returns an event that can be stored:
+ ///
+ /// class MyObserver: TransactionObserver {
+ /// var events: [DatabasePreUpdateEvent]
+ /// func databaseWillChange(with event: DatabasePreUpdateEvent) {
+ /// events.append(event.copy())
+ /// }
+ /// }
+ public func copy() -> DatabasePreUpdateEvent {
+ return impl.copy(self)
+ }
+
+ fileprivate init(kind: Kind, initialRowID: Int64?, finalRowID: Int64?, impl: DatabasePreUpdateEventImpl) {
+ self.kind = kind
+ self.initialRowID = (kind == .update || kind == .delete ) ? initialRowID : nil
+ self.finalRowID = (kind == .update || kind == .insert ) ? finalRowID : nil
+ self.impl = impl
+ }
+
+ init(connection: SQLiteConnection, kind: Kind, initialRowID: Int64, finalRowID: Int64, databaseNameCString: UnsafePointer?, tableNameCString: UnsafePointer?) {
+ self.init(kind: kind,
+ initialRowID: (kind == .update || kind == .delete ) ? finalRowID : nil,
+ finalRowID: (kind == .update || kind == .insert ) ? finalRowID : nil,
+ impl: MetalDatabasePreUpdateEventImpl(connection: connection, kind: kind, databaseNameCString: databaseNameCString, tableNameCString: tableNameCString))
+ }
+
+ private let impl: DatabasePreUpdateEventImpl
+ }
+
+ extension DatabasePreUpdateEvent : DatabaseEventProtocol {
+ func send(to observer: TransactionObserver) {
+ observer.databaseWillChange(with: self)
+ }
+ }
+
+ /// Protocol for internal implementation of DatabaseEvent
+ private protocol DatabasePreUpdateEventImpl {
+ var databaseName: String { get }
+ var tableName: String { get }
+
+ var columnsCount: CInt { get }
+ var depth: CInt { get }
+ var initialDatabaseValues: [DatabaseValue]? { get }
+ var finalDatabaseValues: [DatabaseValue]? { get }
+
+ func initialDatabaseValue(atIndex index: Int) -> DatabaseValue?
+ func finalDatabaseValue(atIndex index: Int) -> DatabaseValue?
+
+ func copy(_ event: DatabasePreUpdateEvent) -> DatabasePreUpdateEvent
+ }
+
+ /// Optimization: MetalDatabasePreUpdateEventImpl does not create Swift strings from raw
+ /// SQLite char* until actually asked for databaseName or tableName,
+ /// nor does it request other data via the sqlite3_preupdate_* APIs
+ /// until asked.
+ private struct MetalDatabasePreUpdateEventImpl : DatabasePreUpdateEventImpl {
+ let connection: SQLiteConnection
+ let kind: DatabasePreUpdateEvent.Kind
+
+ let databaseNameCString: UnsafePointer?
+ let tableNameCString: UnsafePointer?
+
+ var databaseName: String { return String(cString: databaseNameCString!) }
+ var tableName: String { return String(cString: tableNameCString!) }
+
+ var columnsCount: CInt { return sqlite3_preupdate_count(connection) }
+ var depth: CInt { return sqlite3_preupdate_depth(connection) }
+ var initialDatabaseValues: [DatabaseValue]? {
+ guard (kind == .update || kind == .delete) else { return nil }
+ return preupdate_getValues_old(connection)
+ }
+
+ var finalDatabaseValues: [DatabaseValue]? {
+ guard (kind == .update || kind == .insert) else { return nil }
+ return preupdate_getValues_new(connection)
+ }
+
+ func initialDatabaseValue(atIndex index: Int) -> DatabaseValue? {
+ let columnCount = columnsCount
+ precondition(index >= 0 && index < Int(columnCount), "row index out of range")
+ return getValue(connection, column: CInt(index), sqlite_func: { (connection: SQLiteConnection, column: CInt, value: inout SQLiteValue? ) -> CInt in
+ return sqlite3_preupdate_old(connection, column, &value)
+ })
+ }
+
+ func finalDatabaseValue(atIndex index: Int) -> DatabaseValue? {
+ let columnCount = columnsCount
+ precondition(index >= 0 && index < Int(columnCount), "row index out of range")
+ return getValue(connection, column: CInt(index), sqlite_func: { (connection: SQLiteConnection, column: CInt, value: inout SQLiteValue? ) -> CInt in
+ return sqlite3_preupdate_new(connection, column, &value)
+ })
+ }
+
+ func copy(_ event: DatabasePreUpdateEvent) -> DatabasePreUpdateEvent {
+ return DatabasePreUpdateEvent(kind: event.kind, initialRowID: event.initialRowID, finalRowID: event.finalRowID, impl: CopiedDatabasePreUpdateEventImpl(
+ databaseName: databaseName,
+ tableName: tableName,
+ columnsCount: columnsCount,
+ depth: depth,
+ initialDatabaseValues: initialDatabaseValues,
+ finalDatabaseValues: finalDatabaseValues))
+ }
+
+ private func preupdate_getValues(_ connection: SQLiteConnection, sqlite_func: (_ connection: SQLiteConnection, _ column: CInt, _ value: inout SQLiteValue? ) -> CInt ) -> [DatabaseValue]? {
+ let columnCount = sqlite3_preupdate_count(connection)
+ guard columnCount > 0 else { return nil }
+
+ var columnValues = [DatabaseValue]()
+
+ for i in 0.. CInt ) -> DatabaseValue? {
+ var value : SQLiteValue? = nil
+ guard sqlite_func(connection, column, &value) == SQLITE_OK else { return nil }
+ if let value = value {
+ return DatabaseValue(sqliteValue: value)
+ }
+ return nil
+ }
+
+ private func preupdate_getValues_old(_ connection: SQLiteConnection) -> [DatabaseValue]? {
+ return preupdate_getValues(connection, sqlite_func: { (connection: SQLiteConnection, column: CInt, value: inout SQLiteValue? ) -> CInt in
+ return sqlite3_preupdate_old(connection, column, &value)
+ })
+ }
+
+ private func preupdate_getValues_new(_ connection: SQLiteConnection) -> [DatabaseValue]? {
+ return preupdate_getValues(connection, sqlite_func: { (connection: SQLiteConnection, column: CInt, value: inout SQLiteValue? ) -> CInt in
+ return sqlite3_preupdate_new(connection, column, &value)
+ })
+ }
+ }
+
+ /// Impl for DatabasePreUpdateEvent that contains copies of all event data.
+ private struct CopiedDatabasePreUpdateEventImpl : DatabasePreUpdateEventImpl {
+ let databaseName: String
+ let tableName: String
+ let columnsCount: CInt
+ let depth: CInt
+ let initialDatabaseValues: [DatabaseValue]?
+ let finalDatabaseValues: [DatabaseValue]?
+
+ func initialDatabaseValue(atIndex index: Int) -> DatabaseValue? { return initialDatabaseValues?[index] }
+ func finalDatabaseValue(atIndex index: Int) -> DatabaseValue? { return finalDatabaseValues?[index] }
+
+ func copy(_ event: DatabasePreUpdateEvent) -> DatabasePreUpdateEvent {
+ return event
+ }
+ }
+
+#endif
+
+/// The SQLite savepoint stack is described at
+/// https://www.sqlite.org/lang_savepoint.html
+///
+/// This class reimplements the SQLite stack, so that we can:
+///
+/// - know if there are currently active savepoints (isEmpty)
+/// - buffer database events when a savepoint is active, in order to avoid
+/// notifying transaction observers of database events that could be
+/// rollbacked.
+class SavepointStack {
+ /// The buffered events. See Database.didChange(with:)
+ fileprivate var eventsBuffer: [(event: DatabaseEventProtocol, observers: [TransactionObserver])] = []
+
+ /// The savepoint stack, as an array of tuples (savepointName, index in the eventsBuffer array).
+ /// Indexes let us drop rollbacked events from the event buffer.
+ private var savepoints: [(name: String, index: Int)] = []
+
+ var isEmpty: Bool { return savepoints.isEmpty }
+
+ func clear() {
+ eventsBuffer.removeAll()
+ savepoints.removeAll()
+ }
+
+ func beginSavepoint(named name: String) {
+ savepoints.append((name: name.lowercased(), index: eventsBuffer.count))
+ }
+
+ // https://www.sqlite.org/lang_savepoint.html
+ // > The ROLLBACK command with a TO clause rolls back transactions going
+ // > backwards in time back to the most recent SAVEPOINT with a matching
+ // > name. The SAVEPOINT with the matching name remains on the transaction
+ // > stack, but all database changes that occurred after that SAVEPOINT was
+ // > created are rolled back. If the savepoint-name in a ROLLBACK TO
+ // > command does not match any SAVEPOINT on the stack, then the ROLLBACK
+ // > command fails with an error and leaves the state of the
+ // > database unchanged.
+ func rollbackSavepoint(named name: String) {
+ let name = name.lowercased()
+ while let pair = savepoints.last, pair.name != name {
+ savepoints.removeLast()
+ }
+ if let savepoint = savepoints.last {
+ eventsBuffer.removeLast(eventsBuffer.count - savepoint.index)
+ }
+ assert(!savepoints.isEmpty || eventsBuffer.isEmpty)
+ }
+
+ // https://www.sqlite.org/lang_savepoint.html
+ // > The RELEASE command starts with the most recent addition to the
+ // > transaction stack and releases savepoints backwards in time until it
+ // > releases a savepoint with a matching savepoint-name. Prior savepoints,
+ // > even savepoints with matching savepoint-names, are unchanged.
+ func releaseSavepoint(named name: String) {
+ let name = name.lowercased()
+ while let pair = savepoints.last, pair.name != name {
+ savepoints.removeLast()
+ }
+ if !savepoints.isEmpty {
+ savepoints.removeLast()
+ }
+ }
+}
diff --git a/Pods/GRDB.swift/GRDB/Core/DatabaseError.swift b/Pods/GRDB.swift/GRDB/Core/DatabaseError.swift
new file mode 100644
index 0000000..24a0be2
--- /dev/null
+++ b/Pods/GRDB.swift/GRDB/Core/DatabaseError.swift
@@ -0,0 +1,256 @@
+import Foundation
+#if SWIFT_PACKAGE
+ import CSQLite
+#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER
+ import SQLite3
+#endif
+
+public struct ResultCode : RawRepresentable, Equatable, CustomStringConvertible {
+ public let rawValue: Int32
+
+ public init(rawValue: Int32) {
+ self.rawValue = rawValue
+ }
+
+ /// A result code limited to the least significant 8 bits of the receiver.
+ /// See https://www.sqlite.org/rescode.html for more information.
+ ///
+ /// let resultCode = .SQLITE_CONSTRAINT_FOREIGNKEY
+ /// resultCode.primaryResultCode == .SQLITE_CONSTRAINT // true
+ public var primaryResultCode: ResultCode {
+ return ResultCode(rawValue: rawValue & 0xFF)
+ }
+
+ var isPrimary: Bool {
+ return self == primaryResultCode
+ }
+
+ public var description: String {
+ // sqlite3_errstr was added in SQLite 3.7.15 http://www.sqlite.org/changes.html#version_3_7_15
+ // It is available from iOS 8.2 and OS X 10.10 https://github.com/yapstudios/YapDatabase/wiki/SQLite-version-(bundled-with-OS)
+ #if GRDBCUSTOMSQLITE || GRDBCIPHER
+ return "\(rawValue) (\(String(cString: sqlite3_errstr(rawValue))))"
+ #else
+ if #available(iOS 8.2, OSX 10.10, OSXApplicationExtension 10.10, iOSApplicationExtension 8.2, *) {
+ return "\(rawValue) (\(String(cString: sqlite3_errstr(rawValue))))"
+ } else {
+ return "\(rawValue)"
+ }
+ #endif
+ }
+
+ public static func == (_ lhs: ResultCode, _ rhs: ResultCode) -> Bool {
+ return lhs.rawValue == rhs.rawValue
+ }
+
+ /// Returns true if the code on the left matches the code on the right.
+ ///
+ /// Primary result codes match themselves and their extended result codes,
+ /// while extended result codes match only themselves:
+ ///
+ /// switch error.extendedResultCode {
+ /// case .SQLITE_CONSTRAINT_FOREIGNKEY: // foreign key constraint error
+ /// case .SQLITE_CONSTRAINT: // any other constraint error
+ /// default: // any other database error
+ /// }
+ public static func ~= (pattern: ResultCode, code: ResultCode) -> Bool {
+ if pattern.isPrimary {
+ return pattern == code.primaryResultCode
+ } else {
+ return pattern == code
+ }
+ }
+
+ // Primary Result codes
+ // https://www.sqlite.org/rescode.html#primary_result_code_list
+
+ public static let SQLITE_OK = ResultCode(rawValue: 0) // Successful result
+ public static let SQLITE_ERROR = ResultCode(rawValue: 1) // SQL error or missing database
+ public static let SQLITE_INTERNAL = ResultCode(rawValue: 2) // Internal logic error in SQLite
+ public static let SQLITE_PERM = ResultCode(rawValue: 3) // Access permission denied
+ public static let SQLITE_ABORT = ResultCode(rawValue: 4) // Callback routine requested an abort
+ public static let SQLITE_BUSY = ResultCode(rawValue: 5) // The database file is locked
+ public static let SQLITE_LOCKED = ResultCode(rawValue: 6) // A table in the database is locked
+ public static let SQLITE_NOMEM = ResultCode(rawValue: 7) // A malloc() failed
+ public static let SQLITE_READONLY = ResultCode(rawValue: 8) // Attempt to write a readonly database
+ public static let SQLITE_INTERRUPT = ResultCode(rawValue: 9) // Operation terminated by sqlite3_interrupt()
+ public static let SQLITE_IOERR = ResultCode(rawValue: 10) // Some kind of disk I/O error occurred
+ public static let SQLITE_CORRUPT = ResultCode(rawValue: 11) // The database disk image is malformed
+ public static let SQLITE_NOTFOUND = ResultCode(rawValue: 12) // Unknown opcode in sqlite3_file_control()
+ public static let SQLITE_FULL = ResultCode(rawValue: 13) // Insertion failed because database is full
+ public static let SQLITE_CANTOPEN = ResultCode(rawValue: 14) // Unable to open the database file
+ public static let SQLITE_PROTOCOL = ResultCode(rawValue: 15) // Database lock protocol error
+ public static let SQLITE_EMPTY = ResultCode(rawValue: 16) // Database is empty
+ public static let SQLITE_SCHEMA = ResultCode(rawValue: 17) // The database schema changed
+ public static let SQLITE_TOOBIG = ResultCode(rawValue: 18) // String or BLOB exceeds size limit
+ public static let SQLITE_CONSTRAINT = ResultCode(rawValue: 19) // Abort due to constraint violation
+ public static let SQLITE_MISMATCH = ResultCode(rawValue: 20) // Data type mismatch
+ public static let SQLITE_MISUSE = ResultCode(rawValue: 21) // Library used incorrectly
+ public static let SQLITE_NOLFS = ResultCode(rawValue: 22) // Uses OS features not supported on host
+ public static let SQLITE_AUTH = ResultCode(rawValue: 23) // Authorization denied
+ public static let SQLITE_FORMAT = ResultCode(rawValue: 24) // Auxiliary database format error
+ public static let SQLITE_RANGE = ResultCode(rawValue: 25) // 2nd parameter to sqlite3_bind out of range
+ public static let SQLITE_NOTADB = ResultCode(rawValue: 26) // File opened that is not a database file
+ public static let SQLITE_NOTICE = ResultCode(rawValue: 27) // Notifications from sqlite3_log()
+ public static let SQLITE_WARNING = ResultCode(rawValue: 28) // Warnings from sqlite3_log()
+ public static let SQLITE_ROW = ResultCode(rawValue: 100) // sqlite3_step() has another row ready
+ public static let SQLITE_DONE = ResultCode(rawValue: 101) // sqlite3_step() has finished executing
+
+ // Extended Result Code
+ // https://www.sqlite.org/rescode.html#extended_result_code_list
+
+ public static let SQLITE_IOERR_READ = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (1<<8)))
+ public static let SQLITE_IOERR_SHORT_READ = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (2<<8)))
+ public static let SQLITE_IOERR_WRITE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (3<<8)))
+ public static let SQLITE_IOERR_FSYNC = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (4<<8)))
+ public static let SQLITE_IOERR_DIR_FSYNC = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (5<<8)))
+ public static let SQLITE_IOERR_TRUNCATE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (6<<8)))
+ public static let SQLITE_IOERR_FSTAT = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (7<<8)))
+ public static let SQLITE_IOERR_UNLOCK = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (8<<8)))
+ public static let SQLITE_IOERR_RDLOCK = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (9<<8)))
+ public static let SQLITE_IOERR_DELETE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (10<<8)))
+ public static let SQLITE_IOERR_BLOCKED = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (11<<8)))
+ public static let SQLITE_IOERR_NOMEM = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (12<<8)))
+ public static let SQLITE_IOERR_ACCESS = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (13<<8)))
+ public static let SQLITE_IOERR_CHECKRESERVEDLOCK = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (14<<8)))
+ public static let SQLITE_IOERR_LOCK = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (15<<8)))
+ public static let SQLITE_IOERR_CLOSE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (16<<8)))
+ public static let SQLITE_IOERR_DIR_CLOSE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (17<<8)))
+ public static let SQLITE_IOERR_SHMOPEN = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (18<<8)))
+ public static let SQLITE_IOERR_SHMSIZE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (19<<8)))
+ public static let SQLITE_IOERR_SHMLOCK = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (20<<8)))
+ public static let SQLITE_IOERR_SHMMAP = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (21<<8)))
+ public static let SQLITE_IOERR_SEEK = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (22<<8)))
+ public static let SQLITE_IOERR_DELETE_NOENT = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (23<<8)))
+ public static let SQLITE_IOERR_MMAP = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (24<<8)))
+ public static let SQLITE_IOERR_GETTEMPPATH = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (25<<8)))
+ public static let SQLITE_IOERR_CONVPATH = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (26<<8)))
+ public static let SQLITE_IOERR_VNODE = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (27<<8)))
+ public static let SQLITE_IOERR_AUTH = ResultCode(rawValue: (SQLITE_IOERR.rawValue | (28<<8)))
+ public static let SQLITE_LOCKED_SHAREDCACHE = ResultCode(rawValue: (SQLITE_LOCKED.rawValue | (1<<8)))
+ public static let SQLITE_BUSY_RECOVERY = ResultCode(rawValue: (SQLITE_BUSY.rawValue | (1<<8)))
+ public static let SQLITE_BUSY_SNAPSHOT = ResultCode(rawValue: (SQLITE_BUSY.rawValue | (2<<8)))
+ public static let SQLITE_CANTOPEN_NOTEMPDIR = ResultCode(rawValue: (SQLITE_CANTOPEN.rawValue | (1<<8)))
+ public static let SQLITE_CANTOPEN_ISDIR = ResultCode(rawValue: (SQLITE_CANTOPEN.rawValue | (2<<8)))
+ public static let SQLITE_CANTOPEN_FULLPATH = ResultCode(rawValue: (SQLITE_CANTOPEN.rawValue | (3<<8)))
+ public static let SQLITE_CANTOPEN_CONVPATH = ResultCode(rawValue: (SQLITE_CANTOPEN.rawValue | (4<<8)))
+ public static let SQLITE_CORRUPT_VTAB = ResultCode(rawValue: (SQLITE_CORRUPT.rawValue | (1<<8)))
+ public static let SQLITE_READONLY_RECOVERY = ResultCode(rawValue: (SQLITE_READONLY.rawValue | (1<<8)))
+ public static let SQLITE_READONLY_CANTLOCK = ResultCode(rawValue: (SQLITE_READONLY.rawValue | (2<<8)))
+ public static let SQLITE_READONLY_ROLLBACK = ResultCode(rawValue: (SQLITE_READONLY.rawValue | (3<<8)))
+ public static let SQLITE_READONLY_DBMOVED = ResultCode(rawValue: (SQLITE_READONLY.rawValue | (4<<8)))
+ public static let SQLITE_ABORT_ROLLBACK = ResultCode(rawValue: (SQLITE_ABORT.rawValue | (2<<8)))
+ public static let SQLITE_CONSTRAINT_CHECK = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (1<<8)))
+ public static let SQLITE_CONSTRAINT_COMMITHOOK = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (2<<8)))
+ public static let SQLITE_CONSTRAINT_FOREIGNKEY = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (3<<8)))
+ public static let SQLITE_CONSTRAINT_FUNCTION = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (4<<8)))
+ public static let SQLITE_CONSTRAINT_NOTNULL = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (5<<8)))
+ public static let SQLITE_CONSTRAINT_PRIMARYKEY = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (6<<8)))
+ public static let SQLITE_CONSTRAINT_TRIGGER = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (7<<8)))
+ public static let SQLITE_CONSTRAINT_UNIQUE = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (8<<8)))
+ public static let SQLITE_CONSTRAINT_VTAB = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (9<<8)))
+ public static let SQLITE_CONSTRAINT_ROWID = ResultCode(rawValue: (SQLITE_CONSTRAINT.rawValue | (10<<8)))
+ public static let SQLITE_NOTICE_RECOVER_WAL = ResultCode(rawValue: (SQLITE_NOTICE.rawValue | (1<<8)))
+ public static let SQLITE_NOTICE_RECOVER_ROLLBACK = ResultCode(rawValue: (SQLITE_NOTICE.rawValue | (2<<8)))
+ public static let SQLITE_WARNING_AUTOINDEX = ResultCode(rawValue: (SQLITE_WARNING.rawValue | (1<<8)))
+ public static let SQLITE_AUTH_USER = ResultCode(rawValue: (SQLITE_AUTH.rawValue | (1<<8)))
+ public static let SQLITE_OK_LOAD_PERMANENTLY = ResultCode(rawValue: (SQLITE_OK.rawValue | (1<<8)))
+
+}
+
+/// DatabaseError wraps an SQLite error.
+public struct DatabaseError : Error {
+
+ /// The SQLite error code (see
+ /// https://www.sqlite.org/rescode.html#primary_result_code_list).
+ ///
+ /// do {
+ /// ...
+ /// } catch let error as DatabaseError where error.resultCode == .SQL_CONSTRAINT {
+ /// // A constraint error
+ /// }
+ ///
+ /// This property returns a "primary result code", that is to say the least
+ /// significant 8 bits of any SQLite result code. See
+ /// https://www.sqlite.org/rescode.html for more information.
+ ///
+ /// See also `extendedResultCode`.
+ public var resultCode: ResultCode {
+ return extendedResultCode.primaryResultCode
+ }
+
+ /// The SQLite extended error code (see
+ /// https://www.sqlite.org/rescode.html#extended_result_code_list).
+ ///
+ /// do {
+ /// ...
+ /// } catch let error as DatabaseError where error.extendedResultCode == .SQLITE_CONSTRAINT_FOREIGNKEY {
+ /// // A foreign key constraint error
+ /// }
+ ///
+ /// See also `resultCode`.
+ public let extendedResultCode: ResultCode
+
+ /// The SQLite error message.
+ public let message: String?
+
+ /// The SQL query that yielded the error (if relevant).
+ public let sql: String?
+
+ /// Creates a Database Error
+ public init(resultCode: ResultCode = .SQLITE_ERROR, message: String? = nil, sql: String? = nil, arguments: StatementArguments? = nil) {
+ self.extendedResultCode = resultCode
+ self.message = message
+ self.sql = sql
+ self.arguments = arguments
+ }
+
+ /// Creates a Database Error with a raw Int32 result code.
+ ///
+ /// This initializer is not public because library user is not supposed to
+ /// be exposed to raw result codes.
+ init(resultCode: Int32, message: String? = nil, sql: String? = nil, arguments: StatementArguments? = nil) {
+ self.init(resultCode: ResultCode(rawValue: resultCode), message: message, sql: sql, arguments: arguments)
+ }
+
+ // MARK: Not public
+
+ /// The query arguments that yielded the error (if relevant).
+ /// Not public because the StatementArguments class has no public method.
+ let arguments: StatementArguments?
+}
+
+extension DatabaseError: CustomStringConvertible {
+ /// A textual representation of `self`.
+ public var description: String {
+ var description = "SQLite error \(resultCode.rawValue)"
+ if let sql = sql {
+ description += " with statement `\(sql)`"
+ }
+ if let arguments = arguments, !arguments.isEmpty {
+ description += " arguments \(arguments)"
+ }
+ if let message = message {
+ description += ": \(message)"
+ }
+ return description
+ }
+}
+
+extension DatabaseError : CustomNSError {
+
+ /// NSError bridging: the domain of the error.
+ public static var errorDomain: String {
+ return "GRDB.DatabaseError"
+ }
+
+ /// NSError bridging: the error code within the given domain.
+ public var errorCode: Int {
+ return Int(extendedResultCode.rawValue)
+ }
+
+ /// NSError bridging: the user-info dictionary.
+ public var errorUserInfo: [String : Any] {
+ return [NSLocalizedDescriptionKey: description]
+ }
+}
diff --git a/Pods/GRDB.swift/GRDB/Core/DatabaseFunction.swift b/Pods/GRDB.swift/GRDB/Core/DatabaseFunction.swift
new file mode 100644
index 0000000..cecc315
--- /dev/null
+++ b/Pods/GRDB.swift/GRDB/Core/DatabaseFunction.swift
@@ -0,0 +1,371 @@
+#if SWIFT_PACKAGE
+ import CSQLite
+#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER
+ import SQLite3
+#endif
+
+/// An SQL function or aggregate.
+public final class DatabaseFunction {
+ public let name: String
+ let argumentCount: Int32?
+ let pure: Bool
+ private let kind: Kind
+ private var nArg: Int32 { return argumentCount ?? -1 }
+ private var eTextRep: Int32 { return (SQLITE_UTF8 | (pure ? SQLITE_DETERMINISTIC : 0)) }
+
+ /// Returns an SQL function.
+ ///
+ /// let fn = DatabaseFunction("succ", argumentCount: 1) { dbValues in
+ /// guard let int = Int.fromDatabaseValue(dbValues[0]) else {
+ /// return nil
+ /// }
+ /// return int + 1
+ /// }
+ /// db.add(function: fn)
+ /// try Int.fetchOne(db, "SELECT succ(1)")! // 2
+ ///
+ /// - parameters:
+ /// - name: The function name.
+ /// - argumentCount: The number of arguments of the function. If
+ /// omitted, or nil, the function accepts any number of arguments.
+ /// - pure: Whether the function is "pure", which means that its results
+ /// only depends on its inputs. When a function is pure, SQLite has
+ /// the opportunity to perform additional optimizations. Default value
+ /// is false.
+ /// - function: A function that takes an array of DatabaseValue
+ /// arguments, and returns an optional DatabaseValueConvertible such
+ /// as Int, String, NSDate, etc. The array is guaranteed to have
+ /// exactly *argumentCount* elements, provided *argumentCount* is
+ /// not nil.
+ public init(_ name: String, argumentCount: Int32? = nil, pure: Bool = false, function: @escaping ([DatabaseValue]) throws -> DatabaseValueConvertible?) {
+ self.name = name
+ self.argumentCount = argumentCount
+ self.pure = pure
+ self.kind = .function{ (argc, argv) in
+ let arguments = (0.. DatabaseValueConvertible? {
+ /// return sum
+ /// }
+ /// }
+ ///
+ /// let dbQueue = DatabaseQueue()
+ /// let fn = DatabaseFunction("mysum", argumentCount: 1, aggregate: MySum.self)
+ /// dbQueue.add(function: fn)
+ /// try dbQueue.inDatabase { db in
+ /// try db.execute("CREATE TABLE test(i)")
+ /// try db.execute("INSERT INTO test(i) VALUES (1)")
+ /// try db.execute("INSERT INTO test(i) VALUES (2)")
+ /// try Int.fetchOne(db, "SELECT mysum(i) FROM test")! // 3
+ /// }
+ ///
+ /// - parameters:
+ /// - name: The function name.
+ /// - argumentCount: The number of arguments of the aggregate. If
+ /// omitted, or nil, the aggregate accepts any number of arguments.
+ /// - pure: Whether the aggregate is "pure", which means that its
+ /// results only depends on its inputs. When an aggregate is pure,
+ /// SQLite has the opportunity to perform additional optimizations.
+ /// Default value is false.
+ /// - aggregate: A type that implements the DatabaseAggregate protocol.
+ /// For each step of the aggregation, its `step` method is called with
+ /// an array of DatabaseValue arguments. The array is guaranteed to
+ /// have exactly *argumentCount* elements, provided *argumentCount* is
+ /// not nil.
+ public init(_ name: String, argumentCount: Int32? = nil, pure: Bool = false, aggregate: Aggregate.Type) {
+ self.name = name
+ self.argumentCount = argumentCount
+ self.pure = pure
+ self.kind = .aggregate { return Aggregate() }
+ }
+
+ /// Calls sqlite3_create_function_v2
+ /// See https://sqlite.org/c3ref/create_function.html
+ func install(in db: Database) {
+ // Retain the function definition
+ let definition = kind.definition
+ let definitionP = Unmanaged.passRetained(definition).toOpaque()
+
+ let code = sqlite3_create_function_v2(
+ db.sqliteConnection,
+ name,
+ nArg,
+ eTextRep,
+ definitionP,
+ kind.xFunc,
+ kind.xStep,
+ kind.xFinal,
+ { definitionP in
+ // Release the function definition
+ Unmanaged.fromOpaque(definitionP!).release()
+ })
+
+ guard code == SQLITE_OK else {
+ // Assume a GRDB bug: there is no point throwing any error.
+ fatalError(DatabaseError(resultCode: code, message: db.lastErrorMessage).description)
+ }
+ }
+
+ /// Calls sqlite3_create_function_v2
+ /// See https://sqlite.org/c3ref/create_function.html
+ func uninstall(in db: Database) {
+ let code = sqlite3_create_function_v2(
+ db.sqliteConnection,
+ name,
+ nArg,
+ eTextRep,
+ nil, nil, nil, nil, nil)
+
+ guard code == SQLITE_OK else {
+ // Assume a GRDB bug: there is no point throwing any error.
+ fatalError(DatabaseError(resultCode: code, message: db.lastErrorMessage).description)
+ }
+ }
+
+ /// The way to compute the result of a function.
+ /// Feeds the `pApp` parameter of sqlite3_create_function_v2
+ /// http://sqlite.org/capi3ref.html#sqlite3_create_function
+ private class FunctionDefinition {
+ let compute: (Int32, UnsafeMutablePointer?) throws -> DatabaseValueConvertible?
+ init(compute: @escaping (Int32, UnsafeMutablePointer?) throws -> DatabaseValueConvertible?) {
+ self.compute = compute
+ }
+ }
+
+ /// The way to start an aggregate.
+ /// Feeds the `pApp` parameter of sqlite3_create_function_v2
+ /// http://sqlite.org/capi3ref.html#sqlite3_create_function
+ private class AggregateDefinition {
+ let makeAggregate: () -> DatabaseAggregate
+ init(makeAggregate: @escaping () -> DatabaseAggregate) {
+ self.makeAggregate = makeAggregate
+ }
+ }
+
+ /// The current state of an aggregate, storable in SQLite
+ private class AggregateContext {
+ var aggregate: DatabaseAggregate
+ var hasErrored = false
+ init(aggregate: DatabaseAggregate) {
+ self.aggregate = aggregate
+ }
+ }
+
+ /// A function kind: an "SQL function" or an "aggregate".
+ /// See http://sqlite.org/capi3ref.html#sqlite3_create_function
+ private enum Kind {
+ /// A regular function: SELECT f(1)
+ case function((Int32, UnsafeMutablePointer?) throws -> DatabaseValueConvertible?)
+
+ /// An aggregate: SELECT f(foo) FROM bar GROUP BY baz
+ case aggregate(() -> DatabaseAggregate)
+
+ /// Feeds the `pApp` parameter of sqlite3_create_function_v2
+ /// http://sqlite.org/capi3ref.html#sqlite3_create_function
+ var definition: AnyObject {
+ switch self {
+ case .function(let compute):
+ return FunctionDefinition(compute: compute)
+ case .aggregate(let makeAggregate):
+ return AggregateDefinition(makeAggregate: makeAggregate)
+ }
+ }
+
+ /// Feeds the `xFunc` parameter of sqlite3_create_function_v2
+ /// http://sqlite.org/capi3ref.html#sqlite3_create_function
+ var xFunc: (@convention(c) (OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void)? {
+ guard case .function = self else { return nil }
+ return { (sqliteContext, argc, argv) in
+ let definition = Unmanaged.fromOpaque(sqlite3_user_data(sqliteContext)).takeUnretainedValue()
+ do {
+ try DatabaseFunction.report(
+ result: definition.compute(argc, argv),
+ in: sqliteContext)
+ } catch {
+ DatabaseFunction.report(error: error, in: sqliteContext)
+ }
+ }
+ }
+
+ /// Feeds the `xStep` parameter of sqlite3_create_function_v2
+ /// http://sqlite.org/capi3ref.html#sqlite3_create_function
+ var xStep: (@convention(c) (OpaquePointer?, Int32, UnsafeMutablePointer?) -> Void)? {
+ guard case .aggregate = self else { return nil }
+ return { (sqliteContext, argc, argv) in
+ let aggregateContextU = DatabaseFunction.unmanagedAggregateContext(sqliteContext)
+ let aggregateContext = aggregateContextU.takeUnretainedValue()
+ assert(!aggregateContext.hasErrored)
+ do {
+ let arguments = (0.. Void)? {
+ guard case .aggregate = self else { return nil }
+ return { (sqliteContext) in
+ let aggregateContextU = DatabaseFunction.unmanagedAggregateContext(sqliteContext)
+ let aggregateContext = aggregateContextU.takeUnretainedValue()
+ aggregateContextU.release()
+
+ guard !aggregateContext.hasErrored else {
+ return
+ }
+
+ do {
+ try DatabaseFunction.report(
+ result: aggregateContext.aggregate.finalize(),
+ in: sqliteContext)
+ } catch {
+ DatabaseFunction.report(error: error, in: sqliteContext)
+ }
+ }
+ }
+ }
+
+ /// Helper function that extracts the current state of an aggregate from an
+ /// sqlite function execution context.
+ ///
+ /// The result must be released when the aggregate concludes.
+ ///
+ /// See https://sqlite.org/c3ref/context.html
+ /// See https://sqlite.org/c3ref/aggregate_context.html
+ private static func unmanagedAggregateContext(_ sqliteContext: OpaquePointer?) -> Unmanaged