Create ArticleExtractor module.

This commit is contained in:
Brent Simmons 2024-04-07 15:05:38 -07:00
parent c35187900a
commit 300ffbb711
12 changed files with 96 additions and 52 deletions

8
ArticleExtractor/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@ -0,0 +1,30 @@
// swift-tools-version: 5.10
import PackageDescription
let package = Package(
name: "ArticleExtractor",
platforms: [.macOS(.v14), .iOS(.v17)],
products: [
.library(
name: "ArticleExtractor",
targets: ["ArticleExtractor"]),
],
dependencies: [
.package(path: "../FoundationExtras")
],
targets: [
.target(
name: "ArticleExtractor",
dependencies: [
"FoundationExtras",
],
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency")
]
),
.testTarget(
name: "ArticleExtractorTests",
dependencies: ["ArticleExtractor"]),
]
)

View File

@ -9,7 +9,7 @@
import Foundation
import FoundationExtras
public enum ArticleExtractorState {
public enum ArticleExtractorState: Sendable {
case ready
case processing
case failedToParse
@ -17,22 +17,21 @@ public enum ArticleExtractorState {
case cancelled
}
protocol ArticleExtractorDelegate {
public protocol ArticleExtractorDelegate {
@MainActor func articleExtractionDidFail(with: Error)
@MainActor func articleExtractionDidComplete(extractedArticle: ExtractedArticle)
}
@MainActor final class ArticleExtractor {
@MainActor public final class ArticleExtractor {
public var state: ArticleExtractorState!
public var article: ExtractedArticle?
public var delegate: ArticleExtractorDelegate?
public let articleLink: String?
private var dataTask: URLSessionDataTask? = nil
var state: ArticleExtractorState!
var article: ExtractedArticle?
var delegate: ArticleExtractorDelegate?
let articleLink: String?
private let url: URL!
private let url: URL!
public init?(_ articleLink: String, clientID: String, clientSecret: String) {
self.articleLink = articleLink

View File

@ -8,23 +8,23 @@
import Foundation
struct ExtractedArticle: Codable, Equatable {
public struct ExtractedArticle: Codable, Equatable, Sendable {
public let title: String?
public let author: String?
public let datePublished: String?
public let dek: String?
public let leadImageURL: String?
public let content: String?
public let nextPageURL: String?
public let url: String?
public let domain: String?
public let excerpt: String?
public let wordCount: Int?
public let direction: String?
public let totalPages: Int?
public let renderedPages: Int?
let title: String?
let author: String?
let datePublished: String?
let dek: String?
let leadImageURL: String?
let content: String?
let nextPageURL: String?
let url: String?
let domain: String?
let excerpt: String?
let wordCount: Int?
let direction: String?
let totalPages: Int?
let renderedPages: Int?
enum CodingKeys: String, CodingKey {
case title = "title"
case author = "author"
@ -41,5 +41,4 @@ struct ExtractedArticle: Codable, Equatable {
case totalPages = "total_pages"
case renderedPages = "rendered_pages"
}
}

View File

@ -0,0 +1,12 @@
import XCTest
@testable import ArticleExtractor
final class ArticleExtractorTests: XCTestCase {
func testExample() throws {
// XCTest Documentation
// https://developer.apple.com/documentation/xctest
// Defining Test Cases and Test Methods
// https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
}
}

View File

@ -16,7 +16,6 @@ let package = Package(
.package(path: "../FoundationExtras"),
.package(path: "../CommonErrors"),
],
targets: [
.target(
name: "FeedFinder",

View File

@ -10,6 +10,7 @@ import Foundation
import WebKit
import Articles
import Web
import ArticleExtractor
enum DetailState: Equatable {
case noSelection

View File

@ -12,6 +12,7 @@ import Articles
import Account
import Core
import AppKitExtras
import ArticleExtractor
enum TimelineSourceMode {
case regular, search

View File

@ -354,10 +354,6 @@
51F9F3F723DF6DB200A314FD /* ArticleIconSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F9F3F623DF6DB200A314FD /* ArticleIconSchemeHandler.swift */; };
51F9F3F923DFB16300A314FD /* UITableView-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F9F3F823DFB16300A314FD /* UITableView-Extensions.swift */; };
51F9F3FB23DFB25700A314FD /* Animations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F9F3FA23DFB25700A314FD /* Animations.swift */; };
51FA73A42332BE110090D516 /* ArticleExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A32332BE110090D516 /* ArticleExtractor.swift */; };
51FA73A52332BE110090D516 /* ArticleExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A32332BE110090D516 /* ArticleExtractor.swift */; };
51FA73A72332BE880090D516 /* ExtractedArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A62332BE880090D516 /* ExtractedArticle.swift */; };
51FA73A82332BE880090D516 /* ExtractedArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A62332BE880090D516 /* ExtractedArticle.swift */; };
51FA73B72332D5F70090D516 /* LegacyArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73B62332D5F70090D516 /* LegacyArticleExtractorButton.swift */; };
51FD413B2342BD0500880194 /* TimelineUnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FD413A2342BD0500880194 /* TimelineUnreadCountView.swift */; };
51FE10032345529D0056195D /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; };
@ -510,7 +506,6 @@
65ED4025235DEF6C0081F399 /* DetailWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8E0EA202F693600562D8F /* DetailWebView.swift */; };
65ED4026235DEF6C0081F399 /* TimelineTableRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97691ED9EBC8007D329B /* TimelineTableRowView.swift */; };
65ED4027235DEF6C0081F399 /* UnreadIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */; };
65ED4028235DEF6C0081F399 /* ExtractedArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A62332BE880090D516 /* ExtractedArticle.swift */; };
65ED4029235DEF6C0081F399 /* DeleteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9C1FAE83C600ECDEDB /* DeleteCommand.swift */; };
65ED402A235DEF6C0081F399 /* AddFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97521ED9EAC0007D329B /* AddFeedWindowController.swift */; };
65ED402B235DEF6C0081F399 /* ImportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */; };
@ -519,7 +514,6 @@
65ED402E235DEF6C0081F399 /* MainWindowController+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E4CC63202C1AC1009B4FFC /* MainWindowController+Scriptability.swift */; };
65ED402F235DEF6C0081F399 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC6E22629E1200D921D6 /* PreferencesWindowController.swift */; };
65ED4030235DEF6C0081F399 /* SmallIconProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */; };
65ED4031235DEF6C0081F399 /* ArticleExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A32332BE110090D516 /* ArticleExtractor.swift */; };
65ED4032235DEF6C0081F399 /* FetchRequestQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAFCA322BC8C08007694F0 /* FetchRequestQueue.swift */; };
65ED4033235DEF6C0081F399 /* SidebarKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B5B581FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift */; };
65ED4034235DEF6C0081F399 /* AccountsPreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC7222629E1200D921D6 /* AccountsPreferencesViewController.swift */; };
@ -691,6 +685,8 @@
84A37CB5201ECD610087C5AF /* RenameWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A37CB4201ECD610087C5AF /* RenameWindowController.swift */; };
84A3EE5F223B667F00557320 /* DefaultFeeds.opml in Resources */ = {isa = PBXBuildFile; fileRef = 84A3EE52223B667F00557320 /* DefaultFeeds.opml */; };
84A3EE61223B667F00557320 /* DefaultFeeds.opml in Resources */ = {isa = PBXBuildFile; fileRef = 84A3EE52223B667F00557320 /* DefaultFeeds.opml */; };
84A699152BC34F3D00605AB8 /* ArticleExtractor in Frameworks */ = {isa = PBXBuildFile; productRef = 84A699142BC34F3D00605AB8 /* ArticleExtractor */; };
84A699172BC34F4400605AB8 /* ArticleExtractor in Frameworks */ = {isa = PBXBuildFile; productRef = 84A699162BC34F4400605AB8 /* ArticleExtractor */; };
84AD1EAA2031617300BC20B7 /* PasteboardFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EA92031617300BC20B7 /* PasteboardFolder.swift */; };
84AD1EBA2031649C00BC20B7 /* SmartFeedPasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EB92031649C00BC20B7 /* SmartFeedPasteboardWriter.swift */; };
84AD1EBC2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EBB2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift */; };
@ -1266,8 +1262,6 @@
51F9F3F623DF6DB200A314FD /* ArticleIconSchemeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleIconSchemeHandler.swift; sourceTree = "<group>"; };
51F9F3F823DFB16300A314FD /* UITableView-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView-Extensions.swift"; sourceTree = "<group>"; };
51F9F3FA23DFB25700A314FD /* Animations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Animations.swift; sourceTree = "<group>"; };
51FA73A32332BE110090D516 /* ArticleExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractor.swift; sourceTree = "<group>"; };
51FA73A62332BE880090D516 /* ExtractedArticle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtractedArticle.swift; sourceTree = "<group>"; };
51FA73B62332D5F70090D516 /* LegacyArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyArticleExtractorButton.swift; sourceTree = "<group>"; };
51FD413A2342BD0500880194 /* TimelineUnreadCountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineUnreadCountView.swift; sourceTree = "<group>"; };
51FE10022345529D0056195D /* UserNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationManager.swift; sourceTree = "<group>"; };
@ -1396,6 +1390,7 @@
84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToMarsEditCommand.swift; sourceTree = "<group>"; };
84A37CB4201ECD610087C5AF /* RenameWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RenameWindowController.swift; sourceTree = "<group>"; };
84A3EE52223B667F00557320 /* DefaultFeeds.opml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = DefaultFeeds.opml; sourceTree = "<group>"; };
84A699132BC34E8500605AB8 /* ArticleExtractor */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = ArticleExtractor; sourceTree = "<group>"; };
84AD1EA92031617300BC20B7 /* PasteboardFolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteboardFolder.swift; sourceTree = "<group>"; };
84AD1EB92031649C00BC20B7 /* SmartFeedPasteboardWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartFeedPasteboardWriter.swift; sourceTree = "<group>"; };
84AD1EBB2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarOutlineDataSource.swift; sourceTree = "<group>"; };
@ -1598,6 +1593,7 @@
8410C4A52BC1E28200D4F799 /* ReaderAPI in Frameworks */,
84C1A8582BBBA5BD006E3E96 /* Web in Frameworks */,
516B695F24D2F33B00B5702F /* Account in Frameworks */,
84A699152BC34F3D00605AB8 /* ArticleExtractor in Frameworks */,
845611742BBD145D00507B73 /* ParserObjC in Frameworks */,
845611712BBD145D00507B73 /* Parser in Frameworks */,
513F32712593EE6F0003048F /* Articles in Frameworks */,
@ -1619,6 +1615,7 @@
513277642590FC640064F1E7 /* SyncDatabase in Frameworks */,
17192ADA2567B3D500AAEACA /* RSSparkle in Frameworks */,
8438C2DB2BABE0B00040C9EE /* CoreResources in Frameworks */,
84A699172BC34F4400605AB8 /* ArticleExtractor in Frameworks */,
5132775E2590FC640064F1E7 /* Articles in Frameworks */,
84DCA5252BABBB5A00792720 /* Core in Frameworks */,
8479ABE32B9E906E00F84C4D /* Database in Frameworks */,
@ -2059,15 +2056,6 @@
name = Frameworks;
sourceTree = "<group>";
};
51FA739A2332BDE70090D516 /* Article Extractor */ = {
isa = PBXGroup;
children = (
51FA73A32332BE110090D516 /* ArticleExtractor.swift */,
51FA73A62332BE880090D516 /* ExtractedArticle.swift */,
);
path = "Article Extractor";
sourceTree = "<group>";
};
51FE0FF9234552490056195D /* UserNotifications */ = {
isa = PBXGroup;
children = (
@ -2359,6 +2347,7 @@
D5907CDA2002F084005947E5 /* xcconfig */,
849C64611ED37A5D003D8FC0 /* Products */,
51C452B22265141B00C03939 /* Frameworks */,
84A699132BC34E8500605AB8 /* ArticleExtractor */,
51CD32C624D2DEF9009ABAEF /* Account */,
84FB9FAE2BC3494B00B7AFC3 /* FeedFinder */,
84FB9FAD2BC344F800B7AFC3 /* Feedbin */,
@ -2461,7 +2450,6 @@
51C452AD2265102800C03939 /* Timeline */,
84702AB31FA27AE8006B8943 /* Commands */,
51934CCC231078DC006127BE /* Activity */,
51FA739A2332BDE70090D516 /* Article Extractor */,
51C452A822650DA100C03939 /* Article Rendering */,
849A97861ED9ECEF007D329B /* Article Styles */,
84DAEE201F86CAE00058304B /* Importers */,
@ -2985,6 +2973,7 @@
845611702BBD145D00507B73 /* Parser */,
845611732BBD145D00507B73 /* ParserObjC */,
8410C4A42BC1E28200D4F799 /* ReaderAPI */,
84A699142BC34F3D00605AB8 /* ArticleExtractor */,
);
productName = "NetNewsWire-iOS";
productReference = 840D617C2029031C009BC708 /* NetNewsWire.app */;
@ -3032,6 +3021,7 @@
8456116A2BBD145200507B73 /* Parser */,
8456116D2BBD145200507B73 /* ParserObjC */,
8410C4A22BC1E27A00D4F799 /* ReaderAPI */,
84A699162BC34F4400605AB8 /* ArticleExtractor */,
);
productName = NetNewsWire;
productReference = 849C64601ED37A5D003D8FC0 /* NetNewsWire.app */;
@ -3862,7 +3852,6 @@
65ED4026235DEF6C0081F399 /* TimelineTableRowView.swift in Sources */,
65ED4027235DEF6C0081F399 /* UnreadIndicatorView.swift in Sources */,
51A9A5F22380DE520033AADF /* AddFeedDefaultContainer.swift in Sources */,
65ED4028235DEF6C0081F399 /* ExtractedArticle.swift in Sources */,
65ED4029235DEF6C0081F399 /* DeleteCommand.swift in Sources */,
65ED402A235DEF6C0081F399 /* AddFeedWindowController.swift in Sources */,
65ED402B235DEF6C0081F399 /* ImportOPMLWindowController.swift in Sources */,
@ -3875,7 +3864,6 @@
653813192680E15B007A082C /* CacheCleaner.swift in Sources */,
847120D82B8AE6AF00BBFC34 /* UTType+Extensions.swift in Sources */,
510C417E24E5D1AE008226FD /* ExtensionFeedAddRequest.swift in Sources */,
65ED4031235DEF6C0081F399 /* ArticleExtractor.swift in Sources */,
51868BF2254386630011A17B /* SidebarDeleteItemsAlert.swift in Sources */,
6538131B2680E176007A082C /* IconImage.swift in Sources */,
65ED4032235DEF6C0081F399 /* FetchRequestQueue.swift in Sources */,
@ -3955,7 +3943,6 @@
176813D02564BA5900D98635 /* WidgetData.swift in Sources */,
510289CD24519A1D00426DDF /* SelectComboTableViewCell.swift in Sources */,
514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */,
51FA73A52332BE110090D516 /* ArticleExtractor.swift in Sources */,
51314704235C41FC00387FDC /* Intents.intentdefinition in Sources */,
FF3ABF162325AF5D0074C542 /* ArticleSorter.swift in Sources */,
51C4525C226508DF00C03939 /* String-Extensions.swift in Sources */,
@ -3993,7 +3980,6 @@
5142192A23522B5500E07E2C /* ImageViewController.swift in Sources */,
516244E3241E19F000B61C47 /* ColorPaletteTableViewController.swift in Sources */,
51C45258226508CF00C03939 /* AppAssets.swift in Sources */,
51FA73A82332BE880090D516 /* ExtractedArticle.swift in Sources */,
51C4527C2265091600C03939 /* TimelineDefaultCellLayout.swift in Sources */,
51E4398023805EBC00015C31 /* AddComboTableViewCell.swift in Sources */,
51C4529A22650A0400C03939 /* ArticleTheme.swift in Sources */,
@ -4219,7 +4205,6 @@
84E8E0EB202F693600562D8F /* DetailWebView.swift in Sources */,
849A976C1ED9EBC8007D329B /* TimelineTableRowView.swift in Sources */,
849A977B1ED9EC04007D329B /* UnreadIndicatorView.swift in Sources */,
51FA73A72332BE880090D516 /* ExtractedArticle.swift in Sources */,
84B99C9D1FAE83C600ECDEDB /* DeleteCommand.swift in Sources */,
849A97541ED9EAC0007D329B /* AddFeedWindowController.swift in Sources */,
5144EA40227A37EC00D19003 /* ImportOPMLWindowController.swift in Sources */,
@ -4231,7 +4216,6 @@
84C9FC7922629E1200D921D6 /* PreferencesWindowController.swift in Sources */,
84411E711FE5FBFA004B527F /* SmallIconProvider.swift in Sources */,
847120D72B8AE6AF00BBFC34 /* UTType+Extensions.swift in Sources */,
51FA73A42332BE110090D516 /* ArticleExtractor.swift in Sources */,
84CAFCA422BC8C08007694F0 /* FetchRequestQueue.swift in Sources */,
844B5B591FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift in Sources */,
84C9FC7C22629E1200D921D6 /* AccountsPreferencesViewController.swift in Sources */,
@ -4945,6 +4929,14 @@
isa = XCSwiftPackageProductDependency;
productName = Web;
};
84A699142BC34F3D00605AB8 /* ArticleExtractor */ = {
isa = XCSwiftPackageProductDependency;
productName = ArticleExtractor;
};
84A699162BC34F4400605AB8 /* ArticleExtractor */ = {
isa = XCSwiftPackageProductDependency;
productName = ArticleExtractor;
};
84C1A8572BBBA5BD006E3E96 /* Web */ = {
isa = XCSwiftPackageProductDependency;
productName = Web;

View File

@ -13,6 +13,7 @@ import UIKit
import Articles
import Account
import Core
import ArticleExtractor
@MainActor struct ArticleRenderer {

View File

@ -11,6 +11,7 @@ import WebKit
import Account
import Articles
import SafariServices
import ArticleExtractor
class ArticleViewController: UIViewController {

View File

@ -13,6 +13,7 @@ import Articles
import SafariServices
import MessageUI
import Core
import ArticleExtractor
protocol WebViewControllerDelegate: AnyObject {
func webViewController(_: WebViewController, articleExtractorButtonStateDidUpdate: ArticleExtractorButtonState)