Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/build_and_test_packages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: Swift Package Manager

on:
pull_request:
branches:
- main
- develop

jobs:
use-reusable:
uses: EasyPackages/.github/.github/workflows/build_and_test_packages.yml@main
84 changes: 84 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/EasyCore.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "EasyCore"
BuildableName = "EasyCore"
BlueprintName = "EasyCore"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans>
<TestPlanReference
reference = "container:EasyCore.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "EasyCoreTests"
BuildableName = "EasyCoreTests"
BlueprintName = "EasyCoreTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "EasyCore"
BuildableName = "EasyCore"
BlueprintName = "EasyCore"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
33 changes: 33 additions & 0 deletions EasyCore.xctestplan
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"configurations" : [
{
"id" : "CF935032-BC1F-44BE-B770-A175A1872A51",
"name" : "Test Scheme Action",
"options" : {

}
}
],
"defaultOptions" : {
"codeCoverage" : {
"targets" : [
{
"containerPath" : "container:",
"identifier" : "EasyCore",
"name" : "EasyCore"
}
]
},
"testExecutionOrdering" : "random"
},
"testTargets" : [
{
"target" : {
"containerPath" : "container:",
"identifier" : "EasyCoreTests",
"name" : "EasyCoreTests"
}
}
],
"version" : 1
}
58 changes: 58 additions & 0 deletions Sources/EasyCore/Collection/ArrayExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import Foundation

public extension Array {

///
/// Safely accesses an element at the specified index.
///
/// Returns the element at the given index, or `nil` if the index is out of bounds.
///
/// Use this subscript to avoid runtime crashes caused by out-of-range index access, especially when working with dynamic indices.
///
/// - Parameter index: The index of the desired element.
/// - Returns: The element at the given index, or `nil` if the index is invalid.
///
/// ### Example:
/// ```swift
/// let names = ["Anna", "Brian", "Carlos"]
/// names[safe: 1] // "Brian"
/// names[safe: 5] // nil
/// ```
///
subscript(safe index: Int) -> Element? {
indices.contains(index) ? self[index] : nil
}

///
/// Splits the array into multiple subarrays ("chunks") with a maximum specified size.
///
/// Each subarray will contain at most `size` elements. The last chunk may contain fewer elements if the total count is not a multiple of `size`.
///
/// - Parameter size: The maximum size of each chunk. Must be greater than zero.
/// - Returns: An array of subarrays containing the original elements in order.
///
/// ### Example:
/// ```swift
/// let numbers = [1, 2, 3, 4, 5, 6, 7]
/// let chunks = numbers.chunked(by: 3)
/// // Result: [[1, 2, 3], [4, 5, 6], [7]]
/// ```
///
/// - Note: If `size <= 0`, the result will be an empty array (`[]`).
///
func chunked(by size: Int) -> [[Element]] {
guard size > 0 else { return [] }

var chunks: [[Element]] = []
var currentIndex = 0

while currentIndex < count {
let endIndex = Swift.min(currentIndex + size, count)
let chunk = Array(self[currentIndex..<endIndex])
chunks.append(chunk)
currentIndex += size
}

return chunks
}
}
32 changes: 32 additions & 0 deletions Sources/EasyCore/Collection/ArrayHashableExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Foundation

///
/// Utility extensions for arrays whose elements conform to `Hashable`, enabling operations like duplicate removal.
///
public extension Array where Element: Hashable {

///
/// Returns a new array containing only the unique elements from the original collection, preserving their original order.
///
/// This computed property creates a new array by eliminating duplicate elements, while keeping the first occurrence of each.
///
/// Any repeated elements are ignored after their first appearance.
///
/// ### Complexity:
/// - Time: O(n), where n is the number of elements in the array.
/// - Space: O(n), to store previously seen elements in a `Set`.
///
/// ### Example:
/// ```swift
/// let values = [1, 2, 2, 3, 1, 4]
/// let uniqueValues = values.uniqued
/// // Result: [1, 2, 3, 4]
/// ```
///
/// - Returns: A new array with duplicates removed, preserving the original order.
///
var uniqued: [Element] {
var seen: Set<Element> = []
return filter { seen.insert($0).inserted }
}
}
28 changes: 28 additions & 0 deletions Sources/EasyCore/Collection/CollectionExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Foundation

public extension Collection {

///
/// Checks whether a given optional collection is either `nil` or empty.
///
/// This static method is a convenient utility for validating optional collections in a safe and concise way.
///
/// - Parameter collection: An optional collection of the same type as `Self`.
/// - Returns: `true` if the collection is `nil` or contains no elements; otherwise, `false`.
///
/// ### Example:
/// ```swift
/// let items: [String]? = nil
/// Collection.isNilOrEmpty(items) // true
///
/// let emptyList: [Int]? = []
/// Collection.isNilOrEmpty(emptyList) // true
///
/// let numbers: [Int]? = [1, 2, 3]
/// Collection.isNilOrEmpty(numbers) // false
/// ```
///
static func isNilOrEmpty(_ collection: Self?) -> Bool {
collection?.isEmpty ?? true
}
}
58 changes: 58 additions & 0 deletions Sources/EasyCore/Debouncer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import Foundation

///
/// A utility class that delays the execution of a block of code until a specified time interval has passed since the last call.
///
/// Useful for scenarios where you want to limit the number of times a function is executed,
/// such as reacting to user input, search queries, or other high-frequency events.
///
/// Each call to `call(_:)` resets the timer. The block will only be executed if no further calls are made within the delay interval.
///
public final class Debouncer {

///
/// The delay interval before the block is executed.
///
private let delay: TimeInterval

///
/// The currently scheduled work item, if any.
///
private var workItem: DispatchWorkItem?

///
/// Initializes a new `Debouncer` with the specified delay interval.
///
/// - Parameter delay: The time interval (in seconds) to wait after the last call before executing the block.
///
/// ### Example:
/// ```swift
/// let debouncer = Debouncer(delay: 0.3)
/// ```
///
public init(delay: TimeInterval) {
self.delay = delay
}

///
/// Schedules a block to be executed after the specified delay.
///
/// If this method is called again before the delay elapses, the previous block is cancelled and the timer resets.
///
/// - Parameter block: The closure to execute after the delay.
///
/// ### Example:
/// ```swift
/// debouncer.call {
/// print("This will only print if no other call occurs in the next 0.3 seconds.")
/// }
/// ```
///
public func call(_ block: @escaping () -> Void) {
workItem?.cancel()
workItem = DispatchWorkItem(block: block)
if let workItem = workItem {
DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: workItem)
}
}
}
36 changes: 36 additions & 0 deletions Sources/EasyCore/Decoder/DecodableDataExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Foundation

public extension Decodable where Self == Data {

///
/// Decodes the current `Data` instance into a specified `Decodable` type.
///
/// This method provides a convenient way to decode JSON data directly into a model object using a provided or default `JSONDecoder`.
///
/// - Parameters:
/// - type: The `Decodable` type to decode from the data.
/// - decoder: An optional `JSONDecoder` to use. Defaults to `.standard`.
/// - Returns: An instance of the decoded type `T` if decoding is successful; otherwise, `nil`.
///
/// ### Example:
/// ```swift
/// struct User: Decodable {
/// let name: String
/// let age: Int
/// }
///
/// let jsonData = """
/// {
/// "name": "Letícia",
/// "age": 28
/// }
/// """.data(using: .utf8)!
///
/// let user = jsonData.decode(User.self)
/// // user is of type `User?`
/// ```
///
func decode<T: Decodable>(_ type: T.Type, decoder: JSONDecoder = .standard) -> T? {
try? decoder.decode(T.self, from: self)
}
}
39 changes: 39 additions & 0 deletions Sources/EasyCore/Decoder/DecodableStringExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Foundation

public extension Decodable where Self == String {

///
/// Decodes the current `String` instance, assumed to be a JSON string, into a specified `Decodable` type.
///
/// This method first attempts to convert the string to UTF-8 encoded `Data`, then decodes it into the desired type using the provided or default `JSONDecoder`.
///
/// - Parameters:
/// - type: The `Decodable` type you want to decode from the JSON string.
/// - decoder: An optional `JSONDecoder` to use for decoding. Defaults to `.standard`.
/// - Returns: An instance of the decoded type `T` if decoding is successful; otherwise, `nil`.
///
/// ### Example:
/// ```swift
/// struct User: Decodable {
/// let name: String
/// let age: Int
/// }
///
/// let json = """
/// {
/// "name": "Letícia",
/// "age": 28
/// }
/// """
///
/// let user = json.decode(User.self)
/// // user is of type `User?`
/// ```
///
/// - Note: This method fails silently and returns `nil` if the string cannot be converted to UTF-8 `Data` or if decoding fails.
///
func decode<T: Decodable>(_ type: T.Type, decoder: JSONDecoder = .standard) -> T? {
guard let data = self.data(using: .utf8) else { return nil }
return data.decode(T.self, decoder: decoder)
}
}
Loading