Skip to content

Commit

Permalink
Add module FileCache and append CocoaLumberjack dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
tzopiz committed Jul 8, 2024
1 parent f0c7fac commit 08f1cb3
Show file tree
Hide file tree
Showing 10 changed files with 409 additions and 4 deletions.
24 changes: 24 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"originHash" : "90eb498ae5930a04b7630d92315dd6af3c8b8f2d886297aed55966f330f14687",
"pins" : [
{
"identity" : "cocoalumberjack",
"kind" : "remoteSourceControl",
"location" : "https://github.com/CocoaLumberjack/CocoaLumberjack.git",
"state" : {
"revision" : "4b8714a7fb84d42393314ce897127b3939885ec3",
"version" : "3.8.5"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log",
"state" : {
"revision" : "9cb486020ebf03bfa5b5df985387a14a98744537",
"version" : "1.6.1"
}
}
],
"version" : 3
}
24 changes: 21 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 5.10
// swift-tools-version:5.10
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand All @@ -11,9 +11,27 @@ let package = Package(
products: [
.library(
name: "UIComponents",
targets: ["UIComponents"]),
targets: ["UIComponents"]
),
.library(
name: "FileCache",
targets: ["FileCache"]
)
],
dependencies: [
.package(url: "https://github.com/CocoaLumberjack/CocoaLumberjack.git", from: "3.7.0")
],
targets: [
.target(name: "UIComponents"),
.target(
name: "UIComponents",
dependencies: []
),
.target(
name: "FileCache",
dependencies: [
.product(name: "CocoaLumberjack", package: "CocoaLumberjack"),
.product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack")
]
)
]
)
35 changes: 35 additions & 0 deletions Sources/FileCache/CSVParsing/CSVBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// JSONBuilder 2.swift
// DailyDeeds
//
// Created by Дмитрий Корчагин on 6/17/24.
//

import Foundation

@resultBuilder
public struct CSVBuilder {
public static func buildBlock(_ components: String...) -> String {
return components.joined(separator: ",")
}

public static func buildExpression(_ expression: String) -> String {
return expression.escapeSpecialCharacters(",")
}

public static func buildExpression(_ expression: String?) -> String {
if let str = expression {
return str.escapeSpecialCharacters(",")
} else {
return ""
}
}

public static func buildExpression(_ expression: Date?) -> String {
return expression.toString()
}

public static func buildExpression(_ expression: Bool) -> String {
return expression.description
}
}
13 changes: 13 additions & 0 deletions Sources/FileCache/CSVParsing/CSVParsable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// CSVParsable.swift
// DailyDeeds
//
// Created by Дмитрий Корчагин on 7/8/24.
//

import Foundation

public protocol CSVParsable {
var csv: String { get }
static func parse(csv: String) -> Self?
}
53 changes: 53 additions & 0 deletions Sources/FileCache/Extensions/Date.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// File.swift
//
//
// Created by Дмитрий Корчагин on 7/8/24.
//

import Foundation

extension Date {
enum StripTimeType: CaseIterable {
case days
case minutes
}

static var calendar: Calendar = {
var calendar = Calendar(identifier: .gregorian)
calendar.firstWeekday = 2
return calendar
}()

var tomorrow: Date {
return Calendar.current.date(byAdding: .day, value: 1, to: self) ?? self
}

func toString(format: String = "yyyy-MM-dd HH:mm:ss") -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
return dateFormatter.string(from: self)
}

func strip(to stripTimeType: Date.StripTimeType) -> Date {
switch stripTimeType {
case .days:
let components = Date.calendar.dateComponents([.year, .month, .day], from: self)
return Date.calendar.date(from: components) ?? self
case .minutes:
let components = Date.calendar.dateComponents([.year, .month, .day, .hour, .minute], from: self)
return Date.calendar.date(from: components) ?? self
}
}
}

extension Optional where Wrapped == Date {
func toString(format: String = "yyyy-MM-dd HH:mm:ss") -> String {
switch self {
case .none:
return ""
case .some(let wrapped):
return wrapped.toString()
}
}
}
53 changes: 53 additions & 0 deletions Sources/FileCache/Extensions/String.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// File.swift
//
//
// Created by Дмитрий Корчагин on 7/8/24.
//

import Foundation

public extension String {
func toDate(format: String = "yyyy-MM-dd HH:mm:ss") -> Date? {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
return dateFormatter.date(from: self)
}

func escapeSpecialCharacters(_ chars: Character...) -> String {
return chars.reduce(into: self) { partialResult, char in
partialResult = partialResult.replacingOccurrences(of: String(char), with: "\\\(char)")
}
}

func unescapeSpecialCharacters(_ chars: Character...) -> String {
return chars.reduce(into: self) { partialResult, char in
partialResult = partialResult.replacingOccurrences(of: "\\\(char)", with: String(char))
}
}

func splitByUnescaped(separator: Character) -> [String] {
// Создаем регулярное выражение для поиска разделителя, не предшествующего обратному слэшу
let escapedSeparator = NSRegularExpression.escapedPattern(for: String(separator))
let pattern = "(?<!\\\\)" + escapedSeparator
guard let regex = try? NSRegularExpression(pattern: pattern) else { return [] }

let range = NSRange(self.startIndex..<self.endIndex, in: self)
let matches = regex.matches(in: self, range: range)

var results: [String] = []
var lastIndex = self.startIndex

for match in matches {
guard let matchRange = Range(match.range, in: self) else { continue }
let substring = self[lastIndex..<matchRange.lowerBound]
results.append(String(substring))
lastIndex = matchRange.upperBound
}

results.append(String(self[lastIndex...]))
results = results.map { $0.unescapeSpecialCharacters(separator) }

return results
}
}
116 changes: 116 additions & 0 deletions Sources/FileCache/FileCache.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//
// FileCache.swift
// DailyDeeds
//
// Created by Дмитрий Корчагин on 7/8/24.
//

import Foundation
import CocoaLumberjackSwift

public class FileCache<T: JSONParsable & CSVParsable> {
public enum FileFormat {
case json
case csv
}

public enum FileError: Error {
case fileNotFound
case dataCorrupted
case parseFailed
case writeToFileFailed
case incorrectFileName
case directoryNotFound
case loadFromJSONFileFailed
case loadFromCSVFileFailed
case fileAlreadyExists
case unknown
}

public func loadFromFile(named fileName: String, format: FileFormat) -> Result<[T], FileError> {
do {
let url = try getDocumentsDirectory().appendingPathComponent(fileName)
guard FileManager.default.fileExists(atPath: url.path) else { return .failure(.fileNotFound) }

let result: Result<[T], FileError>
switch format {
case .json: result = loadFromJSONFile(with: url)
case .csv: result = loadFromCSVFile(with: url)
}
return result
} catch let error as FileError {
return .failure(error)
} catch {
return .failure(.unknown)
}
}

private func loadFromJSONFile(with url: URL) -> Result<[T], FileError> {
do {
let data = try Data(contentsOf: url)
guard let jsonArray = try JSONSerialization.jsonObject(with: data) as? [JSONDictionary]
else { return .failure(.dataCorrupted) }

let items = jsonArray.compactMap { T.parse(json: $0) }
return .success(items)
} catch {
DDLogError("Failed to load from JSON file: \(error.localizedDescription)")
return .failure(.loadFromJSONFileFailed)
}
}

private func loadFromCSVFile(with url: URL) -> Result<[T], FileError> {
do {
let csvString = try String(contentsOf: url)
let items = csvString.split(separator: "\n").compactMap { T.parse(csv: String($0)) }
return .success(items)
} catch {
DDLogError("Failed to load from CSV file: \(error.localizedDescription)")
return .failure(.loadFromCSVFileFailed)
}
}

@discardableResult
public func saveToFile(named fileName: String, items: [T], format: FileFormat = .json) -> FileError? {
do {
let url = try getDocumentsDirectory().appendingPathComponent(fileName)
switch format {
case .json: return saveToJSONFile(with: url, items: items)
case .csv: return saveToCSVFile(with: url, items: items)
}
} catch let error as FileError {
return error
} catch {
return .unknown
}
}

private func saveToJSONFile(with url: URL, items: [T]) -> FileError? {
do {
let jsonArray = items.map { $0.json }
let jsonData = try JSONSerialization.data(withJSONObject: jsonArray, options: .prettyPrinted)
try jsonData.write(to: url)
return nil
} catch {
DDLogError("Failed to save to JSON file: \(error.localizedDescription)")
return .writeToFileFailed
}
}

private func saveToCSVFile(with url: URL, items: [T]) -> FileError? {
do {
let csvString = items.map { $0.csv }.joined(separator: "\n")
try csvString.write(to: url, atomically: true, encoding: .utf8)
return nil
} catch {
DDLogError("Failed to save to CSV file: \(error.localizedDescription)")
return .writeToFileFailed
}
}

private func getDocumentsDirectory() throws -> URL {
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
guard let first = urls.first else { throw FileError.directoryNotFound }
return first
}
}
39 changes: 39 additions & 0 deletions Sources/FileCache/JSONParsing/JSONBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// JSONBuilder.swift
// DailyDeeds
//
// Created by Дмитрий Корчагин on 6/17/24.
//

import Foundation

@resultBuilder
public struct JSONBuilder {
public static func buildBlock(_ components: JSONDictionary...) -> JSONDictionary {
components.reduce(into: JSONDictionary()) { result, dictionary in
for (key, value) in dictionary {
result[key] = value
}
}
}

public static func buildExpression(_ expression: (key: String, value: String)) -> JSONDictionary {
return [expression.key: expression.value]
}

public static func buildExpression(_ expression: (key: String, value: String?)) -> JSONDictionary {
if let str = expression.value {
return [expression.key: str]
} else {
return [:]
}
}

public static func buildExpression(_ expression: (key: String, value: Date?)) -> JSONDictionary {
return [expression.key: expression.value.toString()]
}

public static func buildExpression(_ expression: (key: String, value: Bool)) -> JSONDictionary {
return [expression.key: expression.value]
}
}
Loading

0 comments on commit 08f1cb3

Please sign in to comment.