diff --git a/.gitignore b/.gitignore index 637b6d18..97372299 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ DerivedData/ # Testing code_coverage.json + +# Python virtual environment +venv/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6f75e2c1..26bf5b4b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -# Setting up a Python environment and pre-commit. +# Set up a Python environment and prek for pre-commit hooks. # Unix or MacOS: # >>> python3 -m venv venv @@ -9,9 +9,9 @@ # >>> venv\Scripts\activate.bat # >>> pip install --upgrade pip -# >>> pip install pre-commit -# >>> pre-commit install -# >>> pre-commit run --all-files +# >>> pip install prek +# >>> prek install +# >>> prek run --all-files repos: - repo: https://github.com/pre-commit/pre-commit-hooks diff --git a/Keyboards/DataManager/LanguageDBManager.swift b/Keyboards/DataManager/LanguageDBManager.swift index d1e8461c..c8925d31 100644 --- a/Keyboards/DataManager/LanguageDBManager.swift +++ b/Keyboards/DataManager/LanguageDBManager.swift @@ -22,23 +22,42 @@ class LanguageDBManager { /// Makes a connection to the language database given the value for controllerLanguage. private func openDBQueue(_ dbName: String) -> DatabaseQueue { - let dbResourcePath = Bundle.main.path(forResource: dbName, ofType: "sqlite")! + let mainBundlePath = Bundle.main.path(forResource: dbName, ofType: "sqlite") + let classBundlePath = Bundle(for: LanguageDBManager.self).path(forResource: dbName, ofType: "sqlite") + + guard let resourcePath = mainBundlePath ?? classBundlePath else { + print("Database \(dbName).sqlite not found in main or class bundle. Using in-memory DB.") + return try! DatabaseQueue() + } + let fileManager = FileManager.default do { - let dbPath = try fileManager - .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true) - .appendingPathComponent("\(dbName).sqlite") - .path + let appSupportURL = try fileManager.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true) + let dbURL = appSupportURL.appendingPathComponent("\(dbName).sqlite") + let dbPath = dbURL.path + + var shouldCopy = true if fileManager.fileExists(atPath: dbPath) { + // Only copy if the resource is newer or if we want to ensure a fresh copy. + // For now, keeping the "fresh copy" behavior but more safely. try fileManager.removeItem(atPath: dbPath) } - try fileManager.copyItem(atPath: dbResourcePath, toPath: dbPath) - let dbQueue = try DatabaseQueue(path: dbPath) - return dbQueue + + if shouldCopy { + try fileManager.copyItem(atPath: resourcePath, toPath: dbPath) + } + + return try DatabaseQueue(path: dbPath) } catch { - print("An error occurred: UILexicon not available") - let dbQueue = try! DatabaseQueue(path: dbResourcePath) - return dbQueue + print("An error occurred during DB setup for \(dbName): \(error). Attempting read-only access.") + var config = Configuration() + config.readonly = true + if let dbQueue = try? DatabaseQueue(path: resourcePath, configuration: config) { + return dbQueue + } + // Last resort: try to return an empty DB instead of crashing the keyword. + print("Failed to open database \(dbName) even in read-only mode. Returning empty DB.") + return try! DatabaseQueue() } } @@ -273,6 +292,49 @@ extension LanguageDBManager { return queryDBRow(query: query, outputCols: outputCols, args: StatementArguments(args)) } + /// Query emojis of word in `emoji_keywords` using pattern matching. + func queryEmojisPatternMatching(of word: String) -> [String] { + var outputValues = [String]() + let query = """ + SELECT + emoji_keyword_0, emoji_keyword_1, emoji_keyword_2 + + FROM + emoji_keywords + + WHERE + word LIKE ? + + ORDER BY + LENGTH(word) ASC + + LIMIT + 3 + """ + let args = StatementArguments(["\(word.lowercased())%"]) + do { + try database?.read { db in + let rows = try Row.fetchAll(db, sql: query, arguments: args) + for row in rows { + for col in ["emoji_keyword_0", "emoji_keyword_1", "emoji_keyword_2"] { + if let val = row[col] as? String, !val.isEmpty { + if !outputValues.contains(val) { + outputValues.append(val) + } + if outputValues.count == 9 { return } + } + } + } + } + } catch {} + + while outputValues.count < 9 { + outputValues.append("") + } + + return Array(outputValues.prefix(9)) + } + /// Query the noun form of word in `nonuns`. func queryNounForm(of word: String) -> [String] { let language = getControllerLanguageAbbr() diff --git a/Keyboards/KeyboardsBase/InterfaceVariables.swift b/Keyboards/KeyboardsBase/InterfaceVariables.swift index a5a509f3..5f8fecb7 100644 --- a/Keyboards/KeyboardsBase/InterfaceVariables.swift +++ b/Keyboards/KeyboardsBase/InterfaceVariables.swift @@ -94,6 +94,7 @@ enum CommandState { case invalid case displayInformation case dynamicConjugation + case colonToEmoji } /// States of the keyboard corresponding to which auto actions should be presented. diff --git a/Keyboards/KeyboardsBase/Keyboard.xib b/Keyboards/KeyboardsBase/Keyboard.xib index 29c5703d..bdb8a87f 100644 --- a/Keyboards/KeyboardsBase/Keyboard.xib +++ b/Keyboards/KeyboardsBase/Keyboard.xib @@ -50,12 +50,24 @@ + + + + + + + + + + + + @@ -375,7 +387,7 @@ -