Skip to content

Commit

Permalink
Added documentation.
Browse files Browse the repository at this point in the history
  • Loading branch information
will-lumley committed Sep 30, 2024
1 parent 5899070 commit d37d6e3
Show file tree
Hide file tree
Showing 23 changed files with 761 additions and 127 deletions.
50 changes: 44 additions & 6 deletions Sources/FaviconFinder/FaviconFinder+Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,61 @@ import SwiftSoup

public extension FaviconFinder {

/// The `Configuration` struct contains settings and preferences for the `FaviconFinder`.
/// It allows users to customize how the favicon is retrieved, including preferences for specific
/// sources, custom HTTP headers, and more.
///
struct Configuration: @unchecked Sendable {

// MARK: - Properties

/// Which download type our user would prefer to use
/// The user's preferred source type for fetching the favicon.
///
/// This can be set to prioritize fetching from a specific source
/// such as `.html`, `.ico`, or `.webApplicationManifestFile`.
/// Defaults to `.html`.
///
public let preferredSource: FaviconSourceType

/// Which preferences the user has for each source type
/// Preferences for specific favicon source types.
///
/// This allows users to specify preferences for each source type.
/// For example, a preference could define the desired file or link type for each source.
public let preferences: [FaviconSourceType: String]

/// Indicates if we should check for a meta-refresh-redirect tag in the HTML header
/// Determines whether or not the HTML should be checked for a `meta-refresh-redirect` tag.
///
/// When this is enabled, `FaviconFinder` will inspect the HTML header for meta-refresh redirects
/// and follow the redirect if found.
/// Defaults to `false`.
public let checkForMetaRefreshRedirect: Bool

/// The HTTP headers we'll pass along to our HTTP request
/// HTTP headers to pass along with the HTTP request when fetching the favicon.
///
/// This allows users to specify custom HTTP headers, such as user-agent or authorization tokens.
public let httpHeaders: [String: String?]?

/// An optional prefetched HTML document that you can pass if you'd rather not FaviconFinder
/// do the HTML document downloading, or you have a local document.
/// A prefetched HTML document that can be passed in if the user already has the HTML.
///
/// If set, `FaviconFinder` will use this document instead of downloading the HTML
/// from the specified URL.
/// Useful when working with local HTML documents or when the HTML is already available in memory.
public let prefetchedHTML: Document?

// MARK: - Lifecycle

/// Initializes a new configuration object.
///
/// - Parameters:
/// - preferredSource: The preferred source for fetching the favicon. Defaults to `.html`.
/// - preferences: A dictionary mapping each source type to a specific preference.
/// Defaults to an empty dictionary.
/// - checkForMetaRefreshRedirect: A boolean indicating whether to check for
/// meta-refresh-redirect tags. Defaults to `false`.
/// - prefetchedHTML: A pre-downloaded HTML document to use instead of fetching HTML from the
/// network. Defaults to `nil`.
/// - httpHeaders: HTTP headers to use when making requests. Defaults to `nil`.
///
public init(
preferredSource: FaviconSourceType = .html,
preferences: [FaviconSourceType: String] = [:],
Expand All @@ -51,6 +84,11 @@ public extension FaviconFinder {

public extension FaviconFinder.Configuration {

/// Provides the default configuration for `FaviconFinder`.
///
/// This configuration uses `.html` as the preferred source type, with no additional preferences,
/// meta-refresh checking disabled, and no pre-downloaded HTML.
///
static var defaultConfiguration: FaviconFinder.Configuration {
.init()
}
Expand Down
32 changes: 22 additions & 10 deletions Sources/FaviconFinder/FaviconFinder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,31 @@

import Foundation

/// `FaviconFinder` is responsible for locating favicons from a given website URL.
///
/// It manages the process of searching for favicons across different sources such as HTML, raw files,
/// and Web Application Manifest files.
public final class FaviconFinder: @unchecked Sendable {

// MARK: - Properties

/// The URL of the site we're trying to extract the Favicon from
/// The URL of the site we're trying to extract the Favicon from.
private let url: URL

/// Our configuration object
/// Configuration object containing preferences and settings for the favicon search.
private let configuration: FaviconFinder.Configuration

/// The current task for fetching favicon URLs
/// The current task used to fetch favicon URLs. Can be cancelled if needed.
private var currentTask: Task<[FaviconURL], Error>?

// MARK: - Lifecycle

/// Initializes a new instance of `FaviconFinder`.
///
/// - Parameters:
/// - url: The URL of the website where the favicon will be searched for.
/// - configuration: The configuration settings for the search. Defaults to `.defaultConfiguration`.
///
public init(
url: URL,
configuration: FaviconFinder.Configuration = .defaultConfiguration
Expand All @@ -37,13 +47,14 @@ public final class FaviconFinder: @unchecked Sendable {

public extension FaviconFinder {

/// Will iterate through each of the source types we have available to us, and
/// search for the websites favicon through there.
/// Initiates the search for favicon URLs using available sources.
///
/// - Important: If the user has set a source preference in the configuration, that source will be prioritized first.
///
/// - Returns: An array of `FaviconURL` instances representing the locations of the favicons found.
///
/// - important: If the user has set a source preference in the Configuration, then
/// that source will be moved to the front of the queue in the hopes the preference can be
/// made.
/// - returns: An array of FaviconURLs that contain the location of all the users favicons
/// - Throws: Throws a `FaviconError.failedToFindFavicon` if no favicons could be found.
/// - Throws: A `CancellationError` if the task is cancelled by the user.
///
func fetchFaviconURLs() async throws -> [FaviconURL] {
// Cancel any previous task
Expand Down Expand Up @@ -101,7 +112,7 @@ public extension FaviconFinder {
return try await currentTask.value
}

/// Cancels the ongoing favicon fetch task, if any.
/// Cancels the current favicon fetching task if one is active.
func cancel() {
self.currentTask?.cancel()
}
Expand All @@ -112,6 +123,7 @@ public extension FaviconFinder {

private extension NSError {

/// Checks if the error represents a cancellation.
var isCancelled: Bool {
self.code == NSURLErrorCancelled
}
Expand Down
38 changes: 32 additions & 6 deletions Sources/FaviconFinder/FaviconFinderProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,48 @@

import Foundation

/// `FaviconFinderProtocol` defines the blueprint for any class responsible for
/// finding favicons from a given website. Classes conforming to this protocol must
/// implement the logic to extract favicon URLs and support various configurations
/// and preferences for fetching favicons.
///
protocol FaviconFinderProtocol {

/// The URL of the website we're querying the Favicon for
/// The URL of the website from which the favicon is being queried.
///
/// This URL represents the base location of the website that we are attempting to retrieve
/// the favicon from.
var url: URL { get set }

/// The object that contains all our configuration data
/// The configuration object containing the user's preferences for finding favicons.
///
/// The `configuration` object encapsulates user preferences, such as which sources
/// to prioritize, custom headers, and meta-refresh handling.
var configuration: FaviconFinder.Configuration { get set }

/// The preferred type of Favicon. This is dependant on type.
/// For example, in`ICOFaviconFinder` the `preferredType` is a filename,
/// for `WebApplicationManifestFaviconFinder` the `preferredType`
/// is the desired key in the JSON file, etc.
/// The preferred type of favicon to search for, based on the specific finder type.
///
/// This value varies depending on the implementation. For instance, in `ICOFaviconFinder`,
/// the preferred type might be a specific file name, such
/// as `favicon.ico`. In `WebApplicationManifestFaviconFinder`, the `preferredType` could
/// be the key in a JSON manifest file.
var preferredType: String { get }

/// Initializes a new instance of the favicon finder.
///
/// - Parameters:
/// - url: The URL of the website from which to query the favicon.
/// - configuration: The configuration object containing user preferences for fetching favicons.
init(url: URL, configuration: FaviconFinder.Configuration)

/// Attempts to find all available favicons for the website.
///
/// The function will attempt to fetch the favicon URLs based on the finder type's specific logic
/// and return an array of `FaviconURL`s representing the found favicons.
///
/// - Returns: An array of `FaviconURL`s that represent the favicons available for the website.
/// - Throws: An error if the favicons could not be found.
///
func find() async throws -> [FaviconURL]

}
67 changes: 58 additions & 9 deletions Sources/FaviconFinder/Finders/HTMLFaviconFinder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,22 @@
import Foundation
import SwiftSoup

class HTMLFaviconFinder: FaviconFinderProtocol {
/// `HTMLFaviconFinder` is responsible for extracting favicons from an HTML document.
/// It conforms to the `FaviconFinderProtocol` and is responsible for parsing both
/// `<link>` and `<meta>` tags in the HTML head to retrieve relevant favicon data.
///
/// This class supports fetching favicons via both standard HTML `<link>` elements
/// (e.g., `<link rel="icon" href="...">`) and OpenGraph meta tags (e.g., `<meta property="og:image" content="...">`).
///
/// Use the `find()` method to start searching for favicons in an HTML document.
///
/// - Note: This class also supports handling meta-refresh redirects if enabled in the configuration.
///
final class HTMLFaviconFinder: FaviconFinderProtocol {

// MARK: - Types

/// A structure representing a reference to a favicon found in an HTML `<link>` tag.
struct HtmlReference {
let rel: String
let href: String
Expand All @@ -21,6 +33,7 @@ class HTMLFaviconFinder: FaviconFinderProtocol {
let format: FaviconFormatType
}

/// A structure representing a reference to a favicon found in an OpenGraph `<meta>` tag.
struct OpenGraphicReference {
let type: String
let content: String
Expand All @@ -32,7 +45,11 @@ class HTMLFaviconFinder: FaviconFinderProtocol {

// MARK: - Properties

/// The `URL` of the website for which the favicons are being searched.
var url: URL

/// Configuration options for customizing the favicon search, including preferences
/// for which favicon types to search for and whether meta-refresh redirects should be handled.
var configuration: FaviconFinder.Configuration

var preferredType: String {
Expand All @@ -41,11 +58,30 @@ class HTMLFaviconFinder: FaviconFinderProtocol {

// MARK: - FaviconFinder

/// Initialises a `HTMLFaviconFinder` instance.
///
/// - Parameters:
/// - url: The URL of the website to search for favicons.
/// - configuration: A configuration object that contains user preferences and options.
///
/// - Returns: A new `HTMLFaviconFinder` instance.
///
required init(url: URL, configuration: FaviconFinder.Configuration) {
self.url = url
self.configuration = configuration
}

/// Finds favicons in the HTML document. This method looks for both
/// `<link>` and `<meta>` tags to identify any favicons.
///
/// The method checks for a prefetched HTML document in the configuration;
/// if one isn't found, it fetches the HTML from the `url`.
///
/// - Throws: `FaviconError.failedToParseHTML` if the HTML cannot be parsed.
/// - Throws: `FaviconError.failedToFindFavicon` if no favicons are found.
///
/// - Returns: An array of `FaviconURL` instances representing the found favicons.
///
func find() async throws -> [FaviconURL] {
let html: Document

Expand Down Expand Up @@ -111,12 +147,18 @@ class HTMLFaviconFinder: FaviconFinderProtocol {

private extension HTMLFaviconFinder {

/// Will extrapolate all the "link" elements from the provided HTML header element, and
/// return the ones that correlate to favicon imgaes
/// Parses the `<link>` elements in the provided HTML head element
/// and returns references to any favicons found.
///
/// This method identifies favicon-related `<link>` tags, such as
/// those with `rel` attributes like `icon`, `apple-touch-icon`, etc.
///
/// - Parameter htmlHead: The HTML head element to parse.
///
/// - parameter htmlHead: Our HTML header elelment
/// - returns: An array of "link" elements, formatted in our internal `Reference` struct
/// - Throws: Throws an error if the parsing fails.
///
/// - Returns: An array of `HtmlReference` objects that correspond to favicons found in `<link>`
/// elements.
func links(from htmlHead: Element) throws -> [HtmlReference] {
// Where we're going to store our HTML favicons
var links = [HtmlReference]()
Expand Down Expand Up @@ -158,11 +200,18 @@ private extension HTMLFaviconFinder {
return links
}

/// Will extrapolate all the "meta" elements from the provided HTML header element, and
/// return the ones that correlate to favicon imgaes
/// Parses the `<meta>` elements in the provided HTML head element
/// and returns references to any OpenGraph favicons found.
///
/// This method identifies OpenGraph-related `<meta>` tags, such as
/// those with `property` attributes like `og:image`.
///
/// - Parameter htmlHead: The HTML head element to parse.
///
/// - Throws: Throws an error if the parsing fails.
///
/// - parameter htmlHead: Our HTML header elelment
/// - returns: An array of "link" elements, formatted in our internal `Reference` struct
/// - Returns: An array of `OpenGraphicReference` objects that correspond to favicons found in
/// `<meta>` elements.
///
func metas(from htmlHead: Element) throws -> [OpenGraphicReference] {
// Where we're going to store our HTML favicons
Expand Down
38 changes: 37 additions & 1 deletion Sources/FaviconFinder/Finders/ICOFaviconFinder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,60 @@

import Foundation

class ICOFaviconFinder: FaviconFinderProtocol {
/// `ICOFaviconFinder` is a class that finds `.ico` favicons in a given website.
/// It conforms to the `FaviconFinderProtocol` and focuses on detecting and retrieving
/// favicons that are either located in the root of the domain or in subdomains.
///
/// This class first tries to find the favicon at the URL provided. If it cannot find it,
/// it will attempt to resolve the favicon by removing subdomains from the URL and
/// searching at the root domain (e.g., searching `google.com` if it failed on `subdomain.google.com`).
///
/// Use the `find()` method to start searching for `.ico` favicons.
///
/// - Note: This class handles meta-refresh redirects if enabled in the configuration.
///
final class ICOFaviconFinder: FaviconFinderProtocol {

// MARK: - Properties

/// The URL of the website to query for the `.ico` favicon.
var url: URL

/// Configuration options that allow users to customize the favicon search,
/// such as specifying a preferred filename for `.ico` files.
var configuration: FaviconFinder.Configuration

/// The preferred filename for the `.ico` favicon.
/// If no preference is provided in the configuration, defaults to `"favicon.ico"`.
var preferredType: String {
self.configuration.preferences[.ico] ?? "favicon.ico"
}

// MARK: - FaviconFinder

/// Initializes an `ICOFaviconFinder` instance.
///
/// - Parameters:
/// - url: The URL of the website to search for favicons.
/// - configuration: A configuration object that contains user preferences and options.
///
/// - Returns: A new `ICOFaviconFinder` instance.
///
required init(url: URL, configuration: FaviconFinder.Configuration) {
self.url = url
self.configuration = configuration
}

/// Finds the `.ico` favicon at the provided URL.
///
/// This method attempts to retrieve the favicon in two steps:
/// 1. It first tries to fetch the favicon at the provided URL.
/// 2. If it fails, it removes subdomains from the URL and tries to find the favicon at the root domain.
///
/// - Throws: `FaviconError.failedToFindFavicon` if no favicon can be found.
///
/// - Returns: An array containing a single `FaviconURL` if the favicon is found, or throws an error
/// otherwise.
func find() async throws -> [FaviconURL] {

// Get our URL without any appendages and add our favicon filename to it
Expand Down
Loading

0 comments on commit d37d6e3

Please sign in to comment.