diff --git a/.periphery.yml b/.periphery.yml index 9259eceb..d95b2dba 100644 --- a/.periphery.yml +++ b/.periphery.yml @@ -1,3 +1,3 @@ retain_public: true targets: -- ContributeWordpress +- ContributeWordPress diff --git a/Sources/ContributeWordPress/Decoder/PostsExportSynDecoder.swift b/Sources/ContributeWordPress/Decoder/PostsExportSynDecoder.swift index 6fc74c95..68c91848 100644 --- a/Sources/ContributeWordPress/Decoder/PostsExportSynDecoder.swift +++ b/Sources/ContributeWordPress/Decoder/PostsExportSynDecoder.swift @@ -12,8 +12,8 @@ public struct PostsExportSynDecoder: PostsExportDecoder { private let decoder: WordPressDecoder = SynDecoder() /// Returns an array of URLs for all of the files in the given directory. - // swiftlint:disable:next line_length - private let fileURLsFromDirectory: (_ atDirectory: URL) throws -> [URL] = Self.defaultFileURLs(atDirectory:) + private let fileURLsFromDirectory: (URL) throws -> [URL] = + Self.defaultFileURLs(atDirectory:) /// Returns the last path component of the given URL without its extension. /// This will be the section id for all posts found in the export file at given URL. @@ -63,14 +63,15 @@ extension PostsExportSynDecoder { /// - Parameter directoryURL: The directory URL. /// - Returns: An array of export file URLs that can be found in the given directory. /// - Throws: An error if the directory could not be enumerated. - private static func defaultFileURLs(atDirectory directoryURL: URL) throws -> [URL] { + private static func defaultFileURLs(atDirectory directoryURL: URL) -> [URL] { let enumerator = FileManager.default.enumerator( at: directoryURL, includingPropertiesForKeys: nil ) guard let enumerator = enumerator else { - throw ImportError.directory(directoryURL) + assertionFailure("\(directoryURL) returned empty enumerator.") + return [] } return enumerator.compactMap { $0 as? URL } diff --git a/Sources/ContributeWordPress/Images/AssetDownloader.swift b/Sources/ContributeWordPress/Images/AssetDownloader.swift index b2dae6c1..66252b20 100644 --- a/Sources/ContributeWordPress/Images/AssetDownloader.swift +++ b/Sources/ContributeWordPress/Images/AssetDownloader.swift @@ -1,4 +1,3 @@ -// swiftlint:disable function_body_length import Contribute import Foundation import SyndiKit @@ -7,6 +6,10 @@ import SyndiKit import FoundationNetworking #endif +enum WordPressError: Error { + case assetDownloadErrors([URL: Error]) +} + /// A type that downloads assets required by WordPress posts. public struct AssetDownloader: Downloader { private let urlDownloader: URLDownloader @@ -55,7 +58,7 @@ public struct AssetDownloader: Downloader { group.wait() guard errors.isEmpty else { - throw ImportError.assetDownloads(errors) + throw WordPressError.assetDownloadErrors(errors) } } } diff --git a/Sources/ContributeWordPress/Images/WordPressAssetImport.swift b/Sources/ContributeWordPress/Images/WordPressAssetImport.swift index 389037a8..0c119fcf 100644 --- a/Sources/ContributeWordPress/Images/WordPressAssetImport.swift +++ b/Sources/ContributeWordPress/Images/WordPressAssetImport.swift @@ -62,3 +62,25 @@ public struct WordPressAssetImport: Hashable { ) } } + +extension WordPressAssetImport { + public static func extractAssetImports( + from posts: [WordPressPost], + using importSettings: WordPressMarkdownProcessorSettings + ) -> [WordPressAssetImport] { + importSettings.assetsImagesRegex + .matchUrls(in: posts) + .compactMap { match in + WordPressAssetImport( + forPost: match.post, + sourceURL: match.sourceURL, + assetRoot: importSettings.assetDirectoryPath, + resourcePathURL: importSettings.resourcesPathURL, + importPathURL: importSettings.importAssetPathURL + ) + } + } +} + +public typealias AssetImportFactory = + ([WordPressPost], WordPressMarkdownProcessorSettings) -> [WordPressAssetImport] diff --git a/Sources/ContributeWordPress/WordPressMarkdownProcessor.swift b/Sources/ContributeWordPress/WordPressMarkdownProcessor.swift index 8375eb14..bd70ed68 100644 --- a/Sources/ContributeWordPress/WordPressMarkdownProcessor.swift +++ b/Sources/ContributeWordPress/WordPressMarkdownProcessor.swift @@ -14,11 +14,12 @@ public struct WordPressMarkdownProcessor< > where ContentURLGeneratorType.SourceType == WordPressSource, MarkdownContentBuilderType.SourceType == WordPressSource { private let exportDecoder: PostsExportDecoder - private var assetDownloader: Downloader + private let assetDownloader: Downloader private let redirectWriter: RedirectFileWriter private let destinationURLGenerator: ContentURLGeneratorType private let contentBuilder: MarkdownContentBuilderType private let postFilters: [PostFilter] + private let assetImportFactory: AssetImportFactory /// Initializes a new `WordPressMarkdownProcessor` instance. /// @@ -33,7 +34,8 @@ public struct WordPressMarkdownProcessor< assetDownloader: Downloader, destinationURLGenerator: ContentURLGeneratorType, contentBuilder: MarkdownContentBuilderType, - postFilters: [PostFilter] + postFilters: [PostFilter], + assetImportFactory: @escaping AssetImportFactory ) { self.exportDecoder = exportDecoder self.redirectWriter = redirectWriter @@ -41,6 +43,7 @@ public struct WordPressMarkdownProcessor< self.destinationURLGenerator = destinationURLGenerator self.contentBuilder = contentBuilder self.postFilters = postFilters + self.assetImportFactory = assetImportFactory } /// Writes all posts to the given content path URL. @@ -96,23 +99,11 @@ public struct WordPressMarkdownProcessor< inDirectory: settings.resourcesPathURL ) - // 3. Calculate assets root for assets under resources directory. - let assetRelative = settings.resourceAssetPathURL.relativePath( - from: settings.resourcesPathURL - ) ?? settings.resourcesPathURL.path - - let directoryPrefix = settings.assetSiteURL.host? - .components(separatedBy: ".") - .first ?? "default" - let assetRoot = ["", assetRelative, directoryPrefix].joined(separator: "/") + #warning("Should this just use `.filter(self.postFilters.postSatisfiesAll)`?") + let importPosts = allPosts.flatMap(\.value).filter { $0.type == "post" } // 4. Build asset imports from all posts - let assetsImports: [WordPressAssetImport] = extractAssetImports( - from: allPosts.flatMap(\.value).filter { $0.type == "post" }, - with: "\(settings.assetSiteURL)/wp-content/uploads([^\"]+)", - assetRoot: assetRoot, - settings: settings - ) + let assetsImports: [WordPressAssetImport] = assetImportFactory(importPosts, settings) // 5. Download all assets try assetDownloader.download( @@ -121,46 +112,15 @@ public struct WordPressMarkdownProcessor< allowsOverwrites: settings.overwriteAssets ) - // 6. - let htmlFromPost: (WordPressPost) -> String = { post in - post.body.replacingOccurrences( - of: "\(settings.assetSiteURL)/wp-content/uploads", - with: assetRoot - ) - } - // 7. Starts writing the markdown files for all WordPress post, try writeAllPosts( allPosts, withAssets: assetsImports, to: settings.contentPathURL, using: type(of: settings).markdownFrom(html:), - htmlFromPost: htmlFromPost + htmlFromPost: settings.htmlFromPost ) } - - private func extractAssetImports( - from allPosts: [WordPressPost], - with pattern: String, - assetRoot: String, - settings: WordPressMarkdownProcessorSettings - ) -> [WordPressAssetImport] { - guard let regex = try? NSRegularExpression(pattern: pattern) else { - return [] - } - - return regex - .matchUrls(in: allPosts) - .compactMap { match in - WordPressAssetImport( - forPost: match.post, - sourceURL: match.sourceURL, - assetRoot: assetRoot, - resourcePathURL: settings.resourcesPathURL, - importPathURL: settings.importAssetPathURL - ) - } - } } extension WordPressMarkdownProcessor { @@ -181,6 +141,8 @@ extension WordPressMarkdownProcessor { redirectFromatter: RedirectFormatter, postFilters: [PostFilter], exportDecoder: PostsExportDecoder = PostsExportSynDecoder(), + assetImportFactory: @escaping AssetImportFactory = + WordPressAssetImport.extractAssetImports(from:using:), assetDownloader: Downloader = AssetDownloader(), destinationURLGenerator: ContentURLGeneratorType = .init(), contentBuilder: MarkdownContentBuilderType = .init( @@ -199,7 +161,8 @@ extension WordPressMarkdownProcessor { assetDownloader: assetDownloader, destinationURLGenerator: destinationURLGenerator, contentBuilder: contentBuilder, - postFilters: postFilters + postFilters: postFilters, + assetImportFactory: assetImportFactory ) } } diff --git a/Sources/ContributeWordPress/WordPressMarkdownProcessorSettings.swift b/Sources/ContributeWordPress/WordPressMarkdownProcessorSettings.swift index b49a43c0..599ead7b 100644 --- a/Sources/ContributeWordPress/WordPressMarkdownProcessorSettings.swift +++ b/Sources/ContributeWordPress/WordPressMarkdownProcessorSettings.swift @@ -1,4 +1,5 @@ import Foundation +import SyndiKit /// A protocol that defines the settings for the `WordPressMarkdownProcessor`. public protocol WordPressMarkdownProcessorSettings { @@ -38,9 +39,54 @@ public protocol WordPressMarkdownProcessorSettings { /// Whether to skip downloading assets. var skipDownload: Bool { get } + /// Name of directory to store assets relative to `ResourceAssetPathURL` + var assetDirectoryName: String { get } + + @available(*, deprecated, message: "Use imports rather then prefix.") + var assetPostURLSearchPrefix: String { get } + + var assetsImagesRegex: NSRegularExpression { get } + /// Converts the given HTML to Markdown. /// /// - Parameter html: The HTML string to convert. /// - Returns: The Markdown representation of the HTML. static func markdownFrom(html: String) throws -> String + + /// Converts the given HTML to Markdown. + /// + /// - Parameter html: The HTML string to convert. + /// - Returns: The Markdown representation of the HTML. + func htmlFromPost(_ post: WordPressPost) -> String +} + +extension WordPressMarkdownProcessorSettings { + public var assetDirectoryName: String { + assetSiteURL.host? + .components(separatedBy: ".") + .first ?? "default" + } + + public var assetDirectoryPath: String { + let assetRelative = resourceAssetPathURL.relativePath( + from: resourcesPathURL + ) ?? resourcesPathURL.path + + return ["", assetRelative, assetDirectoryName].joined(separator: "/") + } + + public func defaultAssetsImagesRegex() throws -> NSRegularExpression { + try NSRegularExpression(pattern: "\(assetSiteURL)/wp-content/uploads([^\"]+)") + } + + public var assetPostURLSearchPrefix: String { + "\(assetSiteURL)/wp-content/uploads" + } + + public func htmlFromPost(_ post: WordPressPost) -> String { + post.body.replacingOccurrences( + of: assetPostURLSearchPrefix, + with: assetDirectoryPath + ) + } }