-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Typesafe sorting and filtering (#37)
- Typesafe sorting and filtering - Improved how page bundles are sorted, filtered
- Loading branch information
Showing
8 changed files
with
267 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// | ||
// File.swift | ||
// toucan | ||
// | ||
// Created by Viasz-Kádi Ferenc on 2024. 10. 17.. | ||
// | ||
|
||
import Foundation | ||
import Yams | ||
|
||
extension [PageBundle] { | ||
|
||
/// Sorts an array of PageBundle objects based on the given key and order. | ||
/// | ||
/// - Parameters: | ||
/// - key: The key used for sorting the PageBundle objects. | ||
/// - order: The order in which the sorting should be done (e.g., ascending or descending). | ||
/// | ||
/// - Returns: A sorted array of PageBundle objects, or the original array if key or order is nil. | ||
func sorted( | ||
frontMatterKey: String?, | ||
order: ContentType.Order? | ||
) -> [PageBundle] { | ||
guard | ||
let frontMatterKey, | ||
let order | ||
else { | ||
return self | ||
} | ||
return sorted { lhs, rhs in | ||
lhs.compareForSorting( | ||
for: rhs, | ||
frontMatterKey: frontMatterKey, | ||
order: order | ||
) | ||
} | ||
} | ||
|
||
/// Limits the number of elements in the collection to the specified value if provided. | ||
/// If no value is provided, returns the entire collection. | ||
/// | ||
/// - Parameters: | ||
/// - value: An optional integer specifying the maximum number of elements to return. | ||
/// | ||
/// - Returns: An array containing up to the specified number of elements, or the entire collection if no value is provided. | ||
func limited(_ value: Int?) -> [PageBundle] { | ||
guard let value else { | ||
return self | ||
} | ||
return Array(prefix(value)) | ||
} | ||
|
||
/// Filters an array of `PageBundle` objects based on the provided filter and date formatter. | ||
/// | ||
/// - Parameters: | ||
/// - filter: An optional `ContentType.Filter` to apply. If `nil`, the original array is returned. | ||
/// - dateFormatter: A `DateFormatter` used for date-based filtering. | ||
/// | ||
/// - Returns: A filtered array of `PageBundle` objects. If no filter is provided, the original array is returned. | ||
func filtered( | ||
_ filter: ContentType.Filter?, | ||
dateFormatter: DateFormatter | ||
) -> [PageBundle] { | ||
guard let filter else { | ||
return self | ||
} | ||
|
||
let result = self.filter { | ||
$0.checkFilter(filter, dateFormatter: dateFormatter) | ||
} | ||
return result | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// | ||
// File.swift | ||
// toucan | ||
// | ||
// Created by Viasz-Kádi Ferenc on 2024. 10. 17.. | ||
// | ||
|
||
import Foundation | ||
|
||
extension String { | ||
|
||
/// Converts the string into a value of the specified data type. | ||
/// | ||
/// - Parameters: | ||
/// - dataType: The target data type to which the string should be converted. | ||
/// - dateFormatter: The date formatter used to convert the string to a date if the data type is `date`. | ||
/// - Returns: The converted value of the specified type, or nil if the conversion fails. | ||
func value<T>( | ||
for dataType: ContentType.Property.DataType, | ||
dateFormatter: DateFormatter | ||
) -> T? { | ||
switch dataType { | ||
case .bool: | ||
return Bool(self) as? T | ||
case .int: | ||
return Int(self) as? T | ||
case .double: | ||
return Double(self) as? T | ||
case .string: | ||
return self as? T | ||
case .date: | ||
return dateFormatter.date(from: self) as? T | ||
} | ||
} | ||
} | ||
|
||
extension PageBundle { | ||
|
||
/// Checks if a filter is satisfied based on the current page's front matter. | ||
/// | ||
/// - Parameters: | ||
/// - filter: The filter to check. | ||
/// - dateFormatter: The date formatter used to parse date values. | ||
/// - Returns: A boolean indicating whether the filter is satisfied. | ||
func checkFilter( | ||
_ filter: ContentType.Filter, | ||
dateFormatter: DateFormatter | ||
) -> Bool { | ||
guard | ||
let field = frontMatter[filter.field], | ||
let dataType = contentType.properties?[filter.field]?.type, | ||
let filterValue: Any = filter.value.value( | ||
for: dataType, | ||
dateFormatter: dateFormatter | ||
) | ||
else { | ||
return false | ||
} | ||
|
||
switch filter.method { | ||
case .equals: | ||
return areValuesEqual(field, filterValue) | ||
} | ||
} | ||
} | ||
|
||
/// Compares two values of any type for equality. | ||
/// | ||
/// - Parameters: | ||
/// - lhs: The first value to compare. | ||
/// - rhs: The second value to compare. | ||
/// - Returns: A boolean indicating whether the two values are equal. | ||
func areValuesEqual(_ lhs: Any, _ rhs: Any) -> Bool { | ||
switch (lhs, rhs) { | ||
case (let lhs as Bool, let rhs as Bool): | ||
return lhs == rhs | ||
case (let lhs as Int, let rhs as Int): | ||
return lhs == rhs | ||
case (let lhs as Double, let rhs as Double): | ||
return lhs == rhs | ||
case (let lhs as String, let rhs as String): | ||
return lhs == rhs | ||
case (let lhs as Date, let rhs as Date): | ||
return lhs == rhs | ||
case (let lhs as [Any], let rhs as [Any]): | ||
return lhs.elementsEqual(rhs, by: { areValuesEqual($0, $1) }) | ||
case (let lhs as [String: Any], let rhs as [String: Any]): | ||
return lhs.isEqualTo(rhs) | ||
default: | ||
return false | ||
} | ||
} | ||
|
||
extension Dictionary where Key == String, Value == Any { | ||
|
||
/// Compares two dictionaries for equality by checking their keys and values. | ||
/// | ||
/// - Parameters: | ||
/// - rhs: The dictionary to compare against. | ||
/// - Returns: A boolean indicating whether the two dictionaries are equal. | ||
func isEqualTo(_ rhs: [Key: Value]) -> Bool { | ||
guard self.count == rhs.count else { return false } | ||
for (key, value) in self { | ||
guard let rhsValue = rhs[key] else { | ||
return false | ||
} | ||
return areValuesEqual(value, rhsValue) | ||
} | ||
return true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// | ||
// File.swift | ||
// toucan | ||
// | ||
// Created by Viasz-Kádi Ferenc on 2024. 10. 17.. | ||
// | ||
|
||
import Foundation | ||
|
||
extension PageBundle { | ||
|
||
/// Compares two `PageBundle` instances for sorting based on a specified front matter key and order. | ||
/// | ||
/// - Parameters: | ||
/// - rhs: The `PageBundle` instance to compare with. | ||
/// - frontMatterKey: The key in the front matter to use for comparison. | ||
/// - order: The order (`asc` or `desc`) in which to perform the comparison. | ||
/// - Returns: A Boolean value indicating whether the current `PageBundle` instance should be ordered before (`true`) or after (`false`) the rhs instance. | ||
func compareForSorting( | ||
for rhs: PageBundle, | ||
frontMatterKey: String, | ||
order: ContentType.Order | ||
) -> Bool { | ||
guard | ||
let lhsField = frontMatter[frontMatterKey], | ||
let rhsField = rhs.frontMatter[frontMatterKey] | ||
else { | ||
return false | ||
} | ||
|
||
switch order { | ||
case .asc: | ||
return compareValuesAscending(lhsField, rhsField) | ||
case .desc: | ||
return !compareValuesAscending(lhsField, rhsField) | ||
} | ||
} | ||
} | ||
|
||
/// Compares two values of any type in ascending order. | ||
/// | ||
/// - Parameters: | ||
/// - lhs: The first value to compare. | ||
/// - rhs: The second value to compare. | ||
/// - Returns: A Boolean value indicating whether the first value is less than the second value. | ||
func compareValuesAscending(_ lhs: Any, _ rhs: Any) -> Bool { | ||
switch (lhs, rhs) { | ||
case let (lhs as Bool, rhs as Bool): | ||
return !lhs && rhs | ||
case let (lhs as Int, rhs as Int): | ||
return lhs < rhs | ||
case let (lhs as Double, rhs as Double): | ||
return lhs < rhs | ||
case let (lhs as String, rhs as String): | ||
return lhs.caseInsensitiveCompare(rhs) == .orderedAscending | ||
case let (lhs as Date, rhs as Date): | ||
return lhs < rhs | ||
default: | ||
return String(describing: lhs) < String(describing: rhs) | ||
} | ||
} |
Oops, something went wrong.