mirror of
https://github.com/keymanapp/keyman.git
synced 2026-10-18 11:57:38 +00:00
Merge pull request #3205 from keymanapp/refactor/ios/engine/kmp-parsing-and-installation
refactor(ios/engine): KMP installation now relies on KeymanPackage class, parses
This commit is contained in:
commit
9395cc2f75
20 changed files with 333 additions and 391 deletions
|
|
@ -179,6 +179,7 @@
|
|||
CE867FEF2485E97A00B2EAED /* KMPMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE867FEE2485E97A00B2EAED /* KMPMetadata.swift */; };
|
||||
CE867FF12485EE1500B2EAED /* KMPInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE867FF02485EE1500B2EAED /* KMPInfo.swift */; };
|
||||
CE88042F247F4B97009BCA1A /* ExpectationDownloaderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88042E247F4B97009BCA1A /* ExpectationDownloaderDelegate.swift */; };
|
||||
CE8B0BBD248734240045EB2E /* KeymanPackageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8B0BBC248734240045EB2E /* KeymanPackageTests.swift */; };
|
||||
CE8EDEB123F53D1A009E1FF6 /* FileManagementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A079DD1223194B100581263 /* FileManagementTests.swift */; };
|
||||
CE8EDEB323F53F96009E1FF6 /* VersionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8EDEB223F53F96009E1FF6 /* VersionTests.swift */; };
|
||||
CE973D812484A56500F66045 /* PackageJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE973D802484A56500F66045 /* PackageJSON.swift */; };
|
||||
|
|
@ -460,6 +461,7 @@
|
|||
CE867FEE2485E97A00B2EAED /* KMPMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMPMetadata.swift; sourceTree = "<group>"; };
|
||||
CE867FF02485EE1500B2EAED /* KMPInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMPInfo.swift; sourceTree = "<group>"; };
|
||||
CE88042E247F4B97009BCA1A /* ExpectationDownloaderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpectationDownloaderDelegate.swift; sourceTree = "<group>"; };
|
||||
CE8B0BBC248734240045EB2E /* KeymanPackageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeymanPackageTests.swift; sourceTree = "<group>"; };
|
||||
CE8EDEB223F53F96009E1FF6 /* VersionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionTests.swift; sourceTree = "<group>"; };
|
||||
CE973D802484A56500F66045 /* PackageJSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageJSON.swift; sourceTree = "<group>"; };
|
||||
CE973D822484A5DB00F66045 /* PackageJSON.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = PackageJSON.bundle; sourceTree = "<group>"; };
|
||||
|
|
@ -708,6 +710,7 @@
|
|||
CE9CD88123FCC4C7002BF2F8 /* TestUtils */,
|
||||
CE37C38923FCC9AF007031C6 /* resources */,
|
||||
9A079DD1223194B100581263 /* FileManagementTests.swift */,
|
||||
CE8B0BBC248734240045EB2E /* KeymanPackageTests.swift */,
|
||||
CE07E3D1247E3FB100DFA9D2 /* HTTPDownloaderTests.swift */,
|
||||
9A079DD3223194B100581263 /* Info.plist */,
|
||||
CE8EDEB223F53F96009E1FF6 /* VersionTests.swift */,
|
||||
|
|
@ -760,9 +763,6 @@
|
|||
C042ED5C1FC6A65A001D82F4 /* Version.swift */,
|
||||
C08E69901FDA6F6F0026056B /* FullKeyboardID.swift */,
|
||||
9A079E42223B61AE00581263 /* FullLexicalModelID.swift */,
|
||||
1645D5942036C6FF0076C51B /* KeymanPackage.swift */,
|
||||
9A079E39223AFF3C00581263 /* KeyboardKeymanPackage.swift */,
|
||||
9A079DC9222E050E00581263 /* LexicalModelKeymanPackage.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -878,6 +878,9 @@
|
|||
CE973D7F2484A07300F66045 /* KMP Packaging */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1645D5942036C6FF0076C51B /* KeymanPackage.swift */,
|
||||
9A079E39223AFF3C00581263 /* KeyboardKeymanPackage.swift */,
|
||||
9A079DC9222E050E00581263 /* LexicalModelKeymanPackage.swift */,
|
||||
CE867FF22485F38300B2EAED /* kmp.json */,
|
||||
);
|
||||
name = "KMP Packaging";
|
||||
|
|
@ -1390,6 +1393,7 @@
|
|||
CE37C39123FCD617007031C6 /* LexicalModels.swift in Sources */,
|
||||
CE37C38D23FCD41E007031C6 /* Keyboards.swift in Sources */,
|
||||
CE9CD88323FCC4E3002BF2F8 /* UserDefaults.swift in Sources */,
|
||||
CE8B0BBD248734240045EB2E /* KeymanPackageTests.swift in Sources */,
|
||||
CE973D852484A71700F66045 /* KMPJSONTests.swift in Sources */,
|
||||
CEF888D523FE786C00667693 /* KeyboardScaleTests.swift in Sources */,
|
||||
CE07E3D6247E41DB00DFA9D2 /* URLSessionMock.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// KeyboardKeymanPackage.swift
|
||||
// KeymanEngine
|
||||
//
|
||||
// Created by Randy Boring on 3/14/19.
|
||||
// Copyright © 2019 SIL International. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class KeyboardKeymanPackage : KeymanPackage {
|
||||
public var keyboards: [KMPKeyboard]!
|
||||
|
||||
override init(metadata: KMPMetadata, folder: URL) {
|
||||
super.init(metadata: metadata, folder: folder)
|
||||
self.keyboards = []
|
||||
|
||||
if let packagedKeyboards = metadata.keyboards {
|
||||
for keyboard in packagedKeyboards {
|
||||
if(keyboard.isValid && FileManager.default.fileExists(atPath: self.sourceFolder.appendingPathComponent("\(keyboard.keyboardId).js").path)) {
|
||||
keyboards.append(keyboard)
|
||||
} else {
|
||||
log.debug("\(keyboard.name) not valid / corresponding file not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override func defaultInfoHtml() -> String {
|
||||
var str = "Found Keyboards in package:<br/>"
|
||||
for keyboard in keyboards {
|
||||
str += keyboard.keyboardId + "<br/>"
|
||||
}
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -10,30 +10,27 @@ import Foundation
|
|||
import Zip
|
||||
|
||||
public enum KMPError : String, Error {
|
||||
case noMetadata = "Could not find kmp.json for package."
|
||||
case invalidPackage = "Invalid Keyman Package."
|
||||
case fileSystem = "Unable to create directory structure in file system."
|
||||
case copyFiles = "Unable to copy keyboard files to file system."
|
||||
case wrongPackageType = "Package provided does not match the type expected by this method"
|
||||
}
|
||||
|
||||
public class KeymanPackage
|
||||
{
|
||||
public class KeymanPackage {
|
||||
static private let kmpFile = "kmp.json"
|
||||
public var sourceFolder: URL
|
||||
public let sourceFolder: URL
|
||||
public let metadata: KMPMetadata
|
||||
|
||||
init(folder: URL) {
|
||||
init(metadata: KMPMetadata, folder: URL) {
|
||||
sourceFolder = folder
|
||||
}
|
||||
|
||||
// to be overridden by subclasses
|
||||
public func isKeyboard() -> Bool {
|
||||
return false
|
||||
self.metadata = metadata
|
||||
}
|
||||
|
||||
// to be overridden by subclasses
|
||||
public func parse(json: [String:AnyObject], version: String) {
|
||||
return
|
||||
public func isKeyboard() -> Bool {
|
||||
return metadata.packageType == .Keyboard
|
||||
}
|
||||
|
||||
|
||||
// to be overridden by subclasses
|
||||
public func defaultInfoHtml() -> String {
|
||||
return "base class!"
|
||||
|
|
@ -50,6 +47,10 @@ public class KeymanPackage
|
|||
|
||||
return defaultInfoHtml()
|
||||
}
|
||||
|
||||
public var version: String? {
|
||||
return metadata.version
|
||||
}
|
||||
|
||||
static public func parse(_ folder: URL) -> KeymanPackage? {
|
||||
do {
|
||||
|
|
@ -57,32 +58,23 @@ public class KeymanPackage
|
|||
path.appendPathComponent(kmpFile)
|
||||
if FileManager.default.fileExists(atPath: path.path) {
|
||||
let data = try Data(contentsOf: path, options: .mappedIfSafe)
|
||||
let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
|
||||
if let jsonResult = jsonResult as? [String:AnyObject] {
|
||||
// The 'version' info has moved, and is no longer available in the original
|
||||
// location for lexical models. Still present for keyboards, though!
|
||||
var version: String = ""
|
||||
if let info = jsonResult["info"] as? [String:AnyObject] {
|
||||
if let versionBlock = info["version"] as? [String:AnyObject] {
|
||||
version = versionBlock["description"] as? String ?? ""
|
||||
}
|
||||
}
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
if let _ = jsonResult["keyboards"] as? [[String:AnyObject]] {
|
||||
if let _ = jsonResult["lexicalModels"] as? [[String:AnyObject]] {
|
||||
//TODO: rrb show error to user, for now, just log
|
||||
log.error("error parsing keyman package: packages MUST NOT have both keyboards and lexical models")
|
||||
return nil
|
||||
}
|
||||
let kmp = KeyboardKeymanPackage.init(folder: folder)
|
||||
kmp.parse(json: jsonResult, version: version)
|
||||
return kmp
|
||||
}
|
||||
else if let _ = jsonResult["lexicalModels"] as? [[String:AnyObject]] {
|
||||
let kmm = LexicalModelKeymanPackage.init(folder: folder)
|
||||
kmm.parse(json: jsonResult, version: version)
|
||||
return kmm
|
||||
}
|
||||
var metadata: KMPMetadata
|
||||
|
||||
do {
|
||||
metadata = try decoder.decode(KMPMetadata.self, from: data)
|
||||
} catch {
|
||||
throw KMPError.noMetadata
|
||||
}
|
||||
|
||||
switch metadata.packageType {
|
||||
case .Keyboard:
|
||||
return KeyboardKeymanPackage(metadata: metadata, folder: folder)
|
||||
case .LexicalModel:
|
||||
return LexicalModelKeymanPackage(metadata: metadata, folder: folder)
|
||||
default:
|
||||
throw KMPError.invalidPackage
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// LexicalModelKeymanPackage.swift
|
||||
// KeymanEngine
|
||||
//
|
||||
// Created by Randy Boring on 3/4/19.
|
||||
// Copyright © 2019 SIL International. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class LexicalModelKeymanPackage : KeymanPackage {
|
||||
public var models : [KMPLexicalModel]!
|
||||
|
||||
override init(metadata: KMPMetadata, folder: URL) {
|
||||
super.init(metadata: metadata, folder: folder)
|
||||
self.models = []
|
||||
|
||||
if let packagedModels = metadata.lexicalModels {
|
||||
for model in packagedModels {
|
||||
// If completely missing, we set the version to 1.0. Legacy decision from 2005.
|
||||
model.setNilVersion(to: metadata.info?.version?.description ?? "1.0")
|
||||
if(model.isValid && FileManager.default.fileExists(atPath: self.sourceFolder.appendingPathComponent("\(model.lexicalModelId).model.js").path)) {
|
||||
models.append(model)
|
||||
} else {
|
||||
log.debug("\(model.name) not valid / corresponding file not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override func defaultInfoHtml() -> String {
|
||||
let count = models.count
|
||||
var str = "Found "+(count > 1 ? "\(count) dictionaries" : "dictionary")+" in package:<br/>"
|
||||
for model in models {
|
||||
str += model.lexicalModelId + "<br/>"
|
||||
}
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -613,191 +613,97 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
|
|||
|
||||
// MARK: - Adhoc keyboards
|
||||
public func parseKbdKMP(_ folder: URL, isCustom: Bool) throws -> Void {
|
||||
do {
|
||||
var path = folder
|
||||
path.appendPathComponent("kmp.json")
|
||||
let data = try Data(contentsOf: path, options: .mappedIfSafe)
|
||||
let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
|
||||
if let jsonResult = jsonResult as? [String:AnyObject] {
|
||||
if let keyboards = jsonResult["keyboards"] as? [[String:AnyObject]] {
|
||||
for k in keyboards {
|
||||
let name = k["name"] as! String
|
||||
let keyboardID = k["id"] as! String
|
||||
let version = k["version"] as! String
|
||||
//true if the keyboard targets a right-to-left script. false if absent.
|
||||
let isrtl: Bool = k["rtl"] as? Bool ?? false
|
||||
guard let kmp = KeymanPackage.parse(folder) as? KeyboardKeymanPackage else {
|
||||
throw KMPError.wrongPackageType
|
||||
}
|
||||
|
||||
var oskFont: Font?
|
||||
let osk = k["oskFont"] as? String
|
||||
if let _ = osk {
|
||||
oskFont = Font(filename: osk!)
|
||||
}
|
||||
var displayFont: Font?
|
||||
let font = k["displayFont"] as? String
|
||||
if let _ = font {
|
||||
displayFont = Font(filename: font!)
|
||||
}
|
||||
|
||||
//TODO: handle errors if languages do not exist
|
||||
var languageName = ""
|
||||
var languageId = ""
|
||||
|
||||
var installableKeyboards : [InstallableKeyboard] = []
|
||||
if let langs = k["languages"] as? [[String:String]] {
|
||||
for l in langs {
|
||||
languageName = l["name"]!
|
||||
languageId = l["id"]!
|
||||
|
||||
installableKeyboards.append( InstallableKeyboard(
|
||||
id: keyboardID,
|
||||
name: name,
|
||||
languageID: languageId,
|
||||
languageName: languageName,
|
||||
version: version,
|
||||
isRTL: isrtl,
|
||||
font: displayFont,
|
||||
oskFont: oskFont,
|
||||
isCustom: isCustom))
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
try FileManager.default.createDirectory(at: Storage.active.keyboardDir(forID: keyboardID),
|
||||
withIntermediateDirectories: true)
|
||||
} catch {
|
||||
log.error("Could not create dir for download: \(error)")
|
||||
throw KMPError.fileSystem
|
||||
}
|
||||
|
||||
var haveInstalledOne = false
|
||||
for keyboard in installableKeyboards {
|
||||
let storedPath = Storage.active.keyboardURL(for: keyboard)
|
||||
|
||||
var installableFiles: [[Any]] = [["\(keyboardID).js", storedPath]]
|
||||
if let osk = osk {
|
||||
let oskPath = Storage.active.fontURL(forKeyboardID: keyboardID, filename: osk)
|
||||
installableFiles.append([osk, oskPath])
|
||||
}
|
||||
|
||||
if let font = font {
|
||||
let displayPath = Storage.active.fontURL(forKeyboardID: keyboardID, filename: font)
|
||||
installableFiles.append([font, displayPath])
|
||||
}
|
||||
do {
|
||||
for item in installableFiles {
|
||||
var filePath = folder
|
||||
if(FileManager.default.fileExists(atPath: (item[1] as! URL).path)) {
|
||||
try FileManager.default.removeItem(at: item[1] as! URL)
|
||||
}
|
||||
filePath.appendPathComponent(item[0] as! String)
|
||||
try FileManager.default.copyItem(at: filePath,
|
||||
to: item[1] as! URL)
|
||||
|
||||
}
|
||||
} catch {
|
||||
log.error("Error saving the download: \(error)")
|
||||
throw KMPError.copyFiles
|
||||
}
|
||||
if !haveInstalledOne {
|
||||
Manager.shared.addKeyboard(keyboard)
|
||||
haveInstalledOne = true
|
||||
}
|
||||
for k in kmp.keyboards {
|
||||
let installableKeyboards : [InstallableKeyboard] = k.installableKeyboards
|
||||
let keyboardID = k.keyboardId
|
||||
|
||||
do {
|
||||
try FileManager.default.createDirectory(at: Storage.active.keyboardDir(forID: keyboardID),
|
||||
withIntermediateDirectories: true)
|
||||
} catch {
|
||||
log.error("Could not create dir for download: \(error)")
|
||||
throw KMPError.fileSystem
|
||||
}
|
||||
|
||||
var haveInstalledOne = false
|
||||
for keyboard in installableKeyboards {
|
||||
let storedPath = Storage.active.keyboardURL(for: keyboard)
|
||||
|
||||
var installableFiles: [[Any]] = [["\(keyboardID).js", storedPath]]
|
||||
if let osk = k.osk {
|
||||
let oskPath = Storage.active.fontURL(forKeyboardID: keyboardID, filename: osk)
|
||||
installableFiles.append([osk, oskPath])
|
||||
}
|
||||
|
||||
if let font = k.font {
|
||||
let displayPath = Storage.active.fontURL(forKeyboardID: keyboardID, filename: font)
|
||||
installableFiles.append([font, displayPath])
|
||||
}
|
||||
do {
|
||||
for item in installableFiles {
|
||||
var filePath = folder
|
||||
if(FileManager.default.fileExists(atPath: (item[1] as! URL).path)) {
|
||||
try FileManager.default.removeItem(at: item[1] as! URL)
|
||||
}
|
||||
filePath.appendPathComponent(item[0] as! String)
|
||||
try FileManager.default.copyItem(at: filePath,
|
||||
to: item[1] as! URL)
|
||||
|
||||
}
|
||||
} catch {
|
||||
log.error("Error saving the download: \(error)")
|
||||
throw KMPError.copyFiles
|
||||
}
|
||||
if !haveInstalledOne {
|
||||
Manager.shared.addKeyboard(keyboard)
|
||||
haveInstalledOne = true
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
log.error("error parsing keyboard kmp: \(error)")
|
||||
throw KMPError.invalidPackage
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Adhoc lexical models
|
||||
static public func parseLMKMP(_ folder: URL, isCustom: Bool) throws -> Void {
|
||||
do {
|
||||
var path = folder
|
||||
path.appendPathComponent("kmp.json")
|
||||
let data = try Data(contentsOf: path, options: .mappedIfSafe)
|
||||
let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
|
||||
if let jsonResult = jsonResult as? [String:AnyObject] {
|
||||
var version: String = "1.0"
|
||||
guard let kmp = KeymanPackage.parse(folder) as? LexicalModelKeymanPackage else {
|
||||
throw KMPError.wrongPackageType
|
||||
}
|
||||
|
||||
if let info = jsonResult["info"] as? [String:AnyObject] {
|
||||
if let versionEntry = info["version"] as? [String:AnyObject] {
|
||||
if let description = versionEntry["description"] as? String {
|
||||
version = description;
|
||||
}
|
||||
}
|
||||
}
|
||||
for m in kmp.models {
|
||||
let installableLexicalModels : [InstallableLexicalModel] = m.installableLexicalModels
|
||||
let lexicalModelID = m.lexicalModelId
|
||||
|
||||
// Version uses a 'conditional initializer'. If it fails, the version info is invalid.
|
||||
if let _ = Version(version) {
|
||||
// No problem
|
||||
} else {
|
||||
// Lazy-handle the error and replace version with 1.0. Legacy decision from 2005.
|
||||
version = "1.0"
|
||||
}
|
||||
|
||||
if let lexicalModels = jsonResult["lexicalModels"] as? [[String:AnyObject]] {
|
||||
for k in lexicalModels {
|
||||
let name = k["name"] as! String
|
||||
let lexicalModelID = k["id"] as! String
|
||||
|
||||
//TODO: handle errors if languages do not exist
|
||||
//var languageName = ""
|
||||
var languageId = ""
|
||||
|
||||
var installableLexicalModels : [InstallableLexicalModel] = []
|
||||
if let langs = k["languages"] as? [[String:String]] {
|
||||
for l in langs {
|
||||
//languageName = l["name"]!
|
||||
languageId = l["id"]!
|
||||
|
||||
installableLexicalModels.append( InstallableLexicalModel(
|
||||
id: lexicalModelID,
|
||||
name: name,
|
||||
languageID: languageId,
|
||||
// languageName: languageName,
|
||||
version: version,
|
||||
isCustom: isCustom))
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
try FileManager.default.createDirectory(at: Storage.active.lexicalModelDir(forID: lexicalModelID),
|
||||
withIntermediateDirectories: true)
|
||||
} catch {
|
||||
log.error("Could not create dir for download: \(error)")
|
||||
throw KMPError.fileSystem
|
||||
}
|
||||
|
||||
for lexicalModel in installableLexicalModels {
|
||||
let storedPath = Storage.active.lexicalModelURL(for: lexicalModel)
|
||||
|
||||
let installableFiles: [[Any]] = [["\(lexicalModelID).model.js", storedPath]]
|
||||
do {
|
||||
for item in installableFiles {
|
||||
var filePath = folder
|
||||
if(FileManager.default.fileExists(atPath: (item[1] as! URL).path)) {
|
||||
try FileManager.default.removeItem(at: item[1] as! URL)
|
||||
}
|
||||
filePath.appendPathComponent(item[0] as! String)
|
||||
try FileManager.default.copyItem(at: filePath,
|
||||
to: item[1] as! URL)
|
||||
|
||||
}
|
||||
} catch {
|
||||
log.error("Error saving the lexical model download: \(error)")
|
||||
throw KMPError.copyFiles
|
||||
}
|
||||
Manager.addLexicalModel(lexicalModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
do {
|
||||
try FileManager.default.createDirectory(at: Storage.active.lexicalModelDir(forID: lexicalModelID),
|
||||
withIntermediateDirectories: true)
|
||||
} catch {
|
||||
log.error("Could not create dir for download: \(error)")
|
||||
throw KMPError.fileSystem
|
||||
}
|
||||
|
||||
for lexicalModel in installableLexicalModels {
|
||||
let storedPath = Storage.active.lexicalModelURL(for: lexicalModel)
|
||||
|
||||
let installableFiles: [[Any]] = [["\(lexicalModelID).model.js", storedPath]]
|
||||
do {
|
||||
for item in installableFiles {
|
||||
var filePath = folder
|
||||
if(FileManager.default.fileExists(atPath: (item[1] as! URL).path)) {
|
||||
try FileManager.default.removeItem(at: item[1] as! URL)
|
||||
}
|
||||
filePath.appendPathComponent(item[0] as! String)
|
||||
try FileManager.default.copyItem(at: filePath,
|
||||
to: item[1] as! URL)
|
||||
}
|
||||
} catch {
|
||||
log.error("Error saving the lexical model download: \(error)")
|
||||
throw KMPError.copyFiles
|
||||
}
|
||||
Manager.addLexicalModel(lexicalModel)
|
||||
}
|
||||
} catch {
|
||||
log.error("error parsing lexical model kmp: \(error)")
|
||||
throw KMPError.invalidPackage
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
//
|
||||
// KeyboardKeymanPackage.swift
|
||||
// KeymanEngine
|
||||
//
|
||||
// Created by Randy Boring on 3/14/19.
|
||||
// Copyright © 2019 SIL International. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class KeyboardKeymanPackage : KeymanPackage
|
||||
{
|
||||
private var keyboards: [KMPKeyboard]!
|
||||
|
||||
public override func parse(json: [String:AnyObject], version: String) {
|
||||
self.keyboards = []
|
||||
|
||||
if let packagedKeyboards = json["keyboards"] as? [[String:AnyObject]] {
|
||||
for keyboardJson in packagedKeyboards {
|
||||
do {
|
||||
// A temporary hybrid state; we now transition to using a Decoder-based strategy.
|
||||
let jsonData = try JSONSerialization.data(withJSONObject: keyboardJson, options: .prettyPrinted)
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
let keyboard = try decoder.decode(KMPKeyboard.self, from: jsonData)
|
||||
if(keyboard.isValid && FileManager.default.fileExists(atPath: self.sourceFolder.appendingPathComponent("\(keyboard.keyboardId).js").path)) {
|
||||
keyboards.append(keyboard)
|
||||
} else {
|
||||
log.debug("\(keyboard.name) not valid / corresponding file not found")
|
||||
}
|
||||
} catch {
|
||||
// Simply... don't append a keyboard, like with the .isValid check above.
|
||||
// Denotes original behavior of this method; isn't exactly optimal.
|
||||
log.debug("Error occurred when processing keyboards: \(String(describing: error))")
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override func defaultInfoHtml() -> String {
|
||||
var str = "Found Keyboards in package:<br/>"
|
||||
for keyboard in keyboards {
|
||||
str += keyboard.keyboardId + "<br/>"
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
public override func isKeyboard() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
//
|
||||
// LexicalModelKeymanPackage.swift
|
||||
// KeymanEngine
|
||||
//
|
||||
// Created by Randy Boring on 3/4/19.
|
||||
// Copyright © 2019 SIL International. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class LexicalModelKeymanPackage : KeymanPackage {
|
||||
public var models : [KMPLexicalModel]!
|
||||
|
||||
public override func defaultInfoHtml() -> String {
|
||||
let count = models.count
|
||||
var str = "Found "+(count > 1 ? "\(count) dictionaries" : "dictionary")+" in package:<br/>"
|
||||
for model in models {
|
||||
str += model.lexicalModelId + "<br/>"
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// may be called parse()
|
||||
// returns a dictionary mapping language ID to lexical model ID
|
||||
override public func parse(json: [String:AnyObject], version: String) {
|
||||
self.models = []
|
||||
|
||||
if let packagedModels = json["lexicalModels"] as? [[String:AnyObject]] {
|
||||
for modelJson in packagedModels {
|
||||
// A temporary hybrid state; we now transition to using a Decoder-based strategy.
|
||||
do {
|
||||
let jsonData = try JSONSerialization.data(withJSONObject: modelJson, options: .prettyPrinted)
|
||||
let decoder = JSONDecoder()
|
||||
|
||||
let model = try decoder.decode(KMPLexicalModel.self, from: jsonData)
|
||||
if(model.isValid && FileManager.default.fileExists(atPath: self.sourceFolder.appendingPathComponent("\(model.lexicalModelId).model.js").path)) {
|
||||
models.append(model)
|
||||
} else {
|
||||
log.debug("\(model.name) not valid / corresponding file not found")
|
||||
}
|
||||
} catch {
|
||||
// Append no models. Not the greatest strategy, but it's the one that had always
|
||||
// been taken here.
|
||||
log.debug("Error occurred when processing lexical models: \(String(describing: error))")
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -414,8 +414,6 @@ class ResourceDownloadQueue: HTTPDownloadDelegate {
|
|||
// The request has succeeded.
|
||||
if downloader!.requestsCount == 0 { // Download queue finished.
|
||||
let keyboards = batch.resources as! [InstallableKeyboard]
|
||||
|
||||
FontManager.shared.registerCustomFonts()
|
||||
log.info("Downloaded keyboard: \(keyboards[0].id).")
|
||||
|
||||
if(!isUpdate) {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ public class KMPKeyboard: Codable {
|
|||
// Technically, the schema says that this doesn't have to be specified.
|
||||
// However, it's a requirement for KeymanEngine for iOS.
|
||||
public var languages: [KMPLanguage]
|
||||
public var installableKeyboards: [InstallableKeyboard]! = []
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
|
|
@ -40,8 +39,9 @@ public class KMPKeyboard: Codable {
|
|||
font = try values.decodeIfPresent(String.self, forKey: .font)
|
||||
isRTL = try values.decodeIfPresent(Bool.self, forKey: .isRTL) ?? false
|
||||
languages = try values.decode([KMPLanguage].self, forKey: .languages)
|
||||
}
|
||||
|
||||
// Now to build the InstallableKeyboard list.
|
||||
public var installableKeyboards: [InstallableKeyboard] {
|
||||
var installableKeyboards : [InstallableKeyboard] = []
|
||||
|
||||
for language in self.languages {
|
||||
|
|
@ -57,7 +57,7 @@ public class KMPKeyboard: Codable {
|
|||
installableKeyboards.append( keyboard )
|
||||
}
|
||||
|
||||
self.installableKeyboards = installableKeyboards
|
||||
return installableKeyboards
|
||||
}
|
||||
|
||||
public var displayFont: Font? {
|
||||
|
|
|
|||
|
|
@ -16,4 +16,11 @@ public struct KMPLanguage: Codable {
|
|||
case name
|
||||
case languageId = "id"
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
name = try values.decode(String.self, forKey: .name)
|
||||
languageId = try values.decode(String.self, forKey: .languageId).lowercased()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ public class KMPLexicalModel: Codable {
|
|||
public var version: String?
|
||||
public var isRTL: Bool = false
|
||||
public var languages: [KMPLanguage]
|
||||
public var installableLexicalModels: [InstallableLexicalModel]! = []
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
|
|
@ -35,8 +34,15 @@ public class KMPLexicalModel: Codable {
|
|||
version = try values.decodeIfPresent(String.self, forKey: .version)
|
||||
isRTL = try values.decodeIfPresent(Bool.self, forKey: .isRTL) ?? false
|
||||
languages = try values.decode([KMPLanguage].self, forKey: .languages)
|
||||
}
|
||||
|
||||
// Now to build the InstallableKeyboard list.
|
||||
/** This function will set the value for version only when a lexical model doesn't have a value for it specified directly in its definition.
|
||||
*/
|
||||
public func setNilVersion(to version: String) {
|
||||
self.version = self.version ?? version
|
||||
}
|
||||
|
||||
public var installableLexicalModels: [InstallableLexicalModel] {
|
||||
var installableLexicalModels : [InstallableLexicalModel] = []
|
||||
|
||||
for language in self.languages {
|
||||
|
|
@ -48,7 +54,7 @@ public class KMPLexicalModel: Codable {
|
|||
installableLexicalModels.append( model )
|
||||
}
|
||||
|
||||
self.installableLexicalModels = installableLexicalModels
|
||||
return installableLexicalModels
|
||||
}
|
||||
|
||||
public var isValid: Bool {
|
||||
|
|
|
|||
|
|
@ -65,4 +65,8 @@ public class KMPMetadata: Codable {
|
|||
return .Unsupported
|
||||
}
|
||||
}
|
||||
|
||||
var version: String? {
|
||||
return self.info?.version?.description
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,4 +11,9 @@ import Foundation
|
|||
public struct KMPSystem: Codable {
|
||||
public var keymanDeveloperVersion: String?
|
||||
public var fileVersion: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case keymanDeveloperVersion
|
||||
case fileVersion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,61 +21,11 @@ class FileManagementTests: XCTestCase {
|
|||
TestUtils.standardTearDown()
|
||||
}
|
||||
|
||||
func testKeyboardPackageExtraction() throws {
|
||||
let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||
let khmerPackageZip = cacheDirectory.appendingPathComponent("khmer_angkor.zip")
|
||||
try FileManager.default.copyItem(at: TestUtils.Keyboards.khmerAngkorKMP, to: khmerPackageZip)
|
||||
|
||||
let destinationFolderURL = cacheDirectory.appendingPathComponent("khmer_angkor")
|
||||
|
||||
// Requires that the source file is already .zip, not .kmp. It's a ZipUtils limitation.
|
||||
do {
|
||||
try KeymanPackage.extract(fileUrl: khmerPackageZip, destination: destinationFolderURL, complete: { kmp in
|
||||
if let kmp = kmp {
|
||||
XCTAssertTrue(kmp.isKeyboard(), "Keyboard KMP test extraction did not yield a keyboard package!")
|
||||
|
||||
// extracted ok, test kmp
|
||||
XCTAssert(kmp.sourceFolder == destinationFolderURL,
|
||||
"The KMP's reported 'source folder' should match the specified destination folder")
|
||||
} else {
|
||||
XCTAssert(false, "KeymanPackage.extract failed")
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
XCTFail("KeymanPackage.extract failed with error \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testLexicalModelPackageExtraction() throws {
|
||||
let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||
let mtntZip = cacheDirectory.appendingPathComponent("mtnt.zip")
|
||||
try FileManager.default.copyItem(at: TestUtils.LexicalModels.mtntKMP, to: mtntZip)
|
||||
|
||||
let destinationFolderURL = cacheDirectory.appendingPathComponent("mtnt.model")
|
||||
|
||||
// Requires that the source file is already .zip, not .kmp. It's a ZipUtils limitation.
|
||||
do {
|
||||
try KeymanPackage.extract(fileUrl: mtntZip, destination: destinationFolderURL, complete: { kmp in
|
||||
if let kmp = kmp {
|
||||
XCTAssertTrue(!kmp.isKeyboard(), "Lexical model KMP test extraction yielded a keyboard package!")
|
||||
|
||||
// extracted ok, test kmp
|
||||
XCTAssert(kmp.sourceFolder == destinationFolderURL,
|
||||
"The KMP's reported 'source folder' should match the specified destination folder")
|
||||
} else {
|
||||
XCTAssert(false, "KeymanPackage.extract failed")
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
XCTFail("KeymanPackage.extract failed with error \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testKeyboardInstallation() {
|
||||
ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.khmerAngkorKMP) { kmp, error in
|
||||
XCTAssertNotNil(kmp, "Failed to prepare KMP for installation")
|
||||
XCTAssertNil(error, "Error occurred while preparing KMP for installation")
|
||||
XCTAssertTrue(kmp!.isKeyboard(), "KMP resource type improperly recognized - expected a keyboard package!")
|
||||
XCTAssertNotNil(kmp as? KeyboardKeymanPackage, "KMP resource type improperly recognized - expected a keyboard package!")
|
||||
|
||||
ResourceFileManager.shared.finalizePackageInstall(kmp!, isCustom: true) { innerError in
|
||||
XCTAssertNil(innerError, "Error occurred while finalizing KMP installation")
|
||||
|
|
@ -97,7 +47,7 @@ class FileManagementTests: XCTestCase {
|
|||
ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.LexicalModels.mtntKMP) { kmp, error in
|
||||
XCTAssertNotNil(kmp, "Failed to prepare KMP for installation")
|
||||
XCTAssertNil(error, "Error occurred while preparing KMP for installation")
|
||||
XCTAssertFalse(kmp!.isKeyboard(), "KMP resource type improperly recognized - expected a lexical model package!")
|
||||
XCTAssertNotNil(kmp as? LexicalModelKeymanPackage, "KMP resource type improperly recognized - expected a lexical model package!")
|
||||
|
||||
ResourceFileManager.shared.finalizePackageInstall(kmp!, isCustom: true) { innerError in
|
||||
XCTAssertNil(innerError, "Error occurred while finalizing KMP installation")
|
||||
|
|
|
|||
|
|
@ -27,6 +27,13 @@ class KMPJSONTests: XCTestCase {
|
|||
|
||||
XCTAssertEqual(lang_km.name, "Central Khmer (Khmer, Cambodia)")
|
||||
XCTAssertEqual(lang_km.languageId, "km")
|
||||
|
||||
let lang_str_latn: KMPLanguage = try loadObjectFromJSON(at: TestUtils.PackageJSON.language_str_latn)
|
||||
|
||||
XCTAssertEqual(lang_str_latn.name, "SENĆOŦEN")
|
||||
// Is upper-cased in the .json, but should be lower-cased on deserialization.
|
||||
// Our language-tag matching is case-insensitive.
|
||||
XCTAssertEqual(lang_str_latn.languageId, "str-latn")
|
||||
}
|
||||
|
||||
// A helper method, since these are useful in two separate tests.
|
||||
|
|
@ -49,12 +56,12 @@ class KMPJSONTests: XCTestCase {
|
|||
}
|
||||
|
||||
// A helper method, since these are useful in two separate tests.
|
||||
func lexical_model_nrc_en_mtnt_assertions(_ nrc_en_mtnt: KMPLexicalModel) {
|
||||
func lexical_model_nrc_en_mtnt_assertions(_ nrc_en_mtnt: KMPLexicalModel, version: String? = nil) {
|
||||
XCTAssertEqual(nrc_en_mtnt.name, "English dictionary (MTNT)")
|
||||
XCTAssertEqual(nrc_en_mtnt.lexicalModelId, "nrc.en.mtnt")
|
||||
// Our example case does not define either of these two values.
|
||||
// One (isRTL), we assume a default value for.
|
||||
XCTAssertEqual(nrc_en_mtnt.version, nil)
|
||||
XCTAssertEqual(nrc_en_mtnt.version, version)
|
||||
XCTAssertEqual(nrc_en_mtnt.isRTL, false)
|
||||
|
||||
XCTAssertEqual(nrc_en_mtnt.languages.count, 3)
|
||||
|
|
@ -68,9 +75,7 @@ class KMPJSONTests: XCTestCase {
|
|||
lexical_model_nrc_en_mtnt_assertions(nrc_en_mtnt)
|
||||
}
|
||||
|
||||
func testKeyboardPackageInfoDecoding() throws {
|
||||
let khmer_angkor: KMPMetadata = try loadObjectFromJSON(at: TestUtils.PackageJSON.kmp_json_khmer_angkor)
|
||||
|
||||
func kmp_info_khmer_angkor_assertions(_ khmer_angkor: KMPMetadata) {
|
||||
XCTAssertTrue(khmer_angkor.isValid)
|
||||
XCTAssertEqual(khmer_angkor.packageType, KMPMetadata.PackageType.Keyboard)
|
||||
|
||||
|
|
@ -95,15 +100,18 @@ class KMPJSONTests: XCTestCase {
|
|||
XCTAssertNil(khmer_angkor.options.readmeFile)
|
||||
}
|
||||
|
||||
func testLexicalModelPackageInfoDecoding() throws {
|
||||
let nrc_en_mtnt: KMPMetadata = try loadObjectFromJSON(at: TestUtils.PackageJSON.kmp_json_nrc_en_mtnt)
|
||||
func testKeyboardPackageInfoDecoding() throws {
|
||||
let khmer_angkor: KMPMetadata = try loadObjectFromJSON(at: TestUtils.PackageJSON.kmp_json_khmer_angkor)
|
||||
kmp_info_khmer_angkor_assertions(khmer_angkor)
|
||||
}
|
||||
|
||||
func kmp_info_nrc_en_mtnt_assertions(_ nrc_en_mtnt: KMPMetadata, version: String? = nil) {
|
||||
XCTAssertTrue(nrc_en_mtnt.isValid)
|
||||
XCTAssertEqual(nrc_en_mtnt.packageType, KMPMetadata.PackageType.LexicalModel)
|
||||
|
||||
XCTAssertNotNil(nrc_en_mtnt.lexicalModels)
|
||||
XCTAssertEqual(nrc_en_mtnt.lexicalModels!.count, 1)
|
||||
lexical_model_nrc_en_mtnt_assertions(nrc_en_mtnt.lexicalModels![0])
|
||||
lexical_model_nrc_en_mtnt_assertions(nrc_en_mtnt.lexicalModels![0], version: version)
|
||||
|
||||
XCTAssertNil(nrc_en_mtnt.keyboards)
|
||||
|
||||
|
|
@ -111,7 +119,7 @@ class KMPJSONTests: XCTestCase {
|
|||
XCTAssertEqual(nrc_en_mtnt.files!.count, 1)
|
||||
|
||||
XCTAssertNotNil(nrc_en_mtnt.info)
|
||||
XCTAssertEqual(nrc_en_mtnt.info!.version!.description, "0.1.3")
|
||||
XCTAssertEqual(nrc_en_mtnt.info!.version!.description, "0.1.4")
|
||||
XCTAssertNil(nrc_en_mtnt.info!.version!.url)
|
||||
XCTAssertEqual(nrc_en_mtnt.info!.author!.description, "Eddie Antonio Santos")
|
||||
XCTAssertEqual(nrc_en_mtnt.info!.author!.url, "mailto:easantos@ualberta.ca")
|
||||
|
|
@ -121,4 +129,9 @@ class KMPJSONTests: XCTestCase {
|
|||
XCTAssertNil(nrc_en_mtnt.options.graphicFile)
|
||||
XCTAssertNil(nrc_en_mtnt.options.readmeFile)
|
||||
}
|
||||
|
||||
func testLexicalModelPackageInfoDecoding() throws {
|
||||
let nrc_en_mtnt: KMPMetadata = try loadObjectFromJSON(at: TestUtils.PackageJSON.kmp_json_nrc_en_mtnt)
|
||||
kmp_info_nrc_en_mtnt_assertions(nrc_en_mtnt)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
77
ios/engine/KMEI/KeymanEngineTests/KeymanPackageTests.swift
Normal file
77
ios/engine/KMEI/KeymanEngineTests/KeymanPackageTests.swift
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
//
|
||||
// KeymanPackageTests.swift
|
||||
// KeymanEngineTests
|
||||
//
|
||||
// Created by Joshua Horton on 6/3/20.
|
||||
// Copyright © 2020 SIL International. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import KeymanEngine
|
||||
|
||||
class KeymanPackageTests: XCTestCase {
|
||||
func testKeyboardPackageExtraction() throws {
|
||||
let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||
let khmerPackageZip = cacheDirectory.appendingPathComponent("khmer_angkor.zip")
|
||||
try FileManager.default.copyItem(at: TestUtils.Keyboards.khmerAngkorKMP, to: khmerPackageZip)
|
||||
|
||||
let destinationFolderURL = cacheDirectory.appendingPathComponent("khmer_angkor")
|
||||
|
||||
// Requires that the source file is already .zip, not .kmp. It's a ZipUtils limitation.
|
||||
do {
|
||||
try KeymanPackage.extract(fileUrl: khmerPackageZip, destination: destinationFolderURL, complete: { kmp in
|
||||
if let kmp = kmp {
|
||||
// Run assertions on the package's kmp.info.
|
||||
// Assumes the KMP used for testing here has the same kmp.info used for those tests.
|
||||
let kmp_json_testcase = KMPJSONTests()
|
||||
kmp_json_testcase.kmp_info_khmer_angkor_assertions(kmp.metadata)
|
||||
|
||||
XCTAssertNotNil(kmp as? KeyboardKeymanPackage, "Keyboard KMP test extraction did not yield a keyboard package!")
|
||||
XCTAssertTrue(kmp.isKeyboard(), "Keyboard KMP test extraction did not yield a keyboard package!")
|
||||
|
||||
// extracted ok, test kmp
|
||||
XCTAssert(kmp.sourceFolder == destinationFolderURL,
|
||||
"The KMP's reported 'source folder' should match the specified destination folder")
|
||||
} else {
|
||||
XCTAssert(false, "KeymanPackage.extract failed")
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
XCTFail("KeymanPackage.extract failed with error \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func testLexicalModelPackageExtraction() throws {
|
||||
let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||
let mtntZip = cacheDirectory.appendingPathComponent("mtnt.zip")
|
||||
try FileManager.default.copyItem(at: TestUtils.LexicalModels.mtntKMP, to: mtntZip)
|
||||
|
||||
let destinationFolderURL = cacheDirectory.appendingPathComponent("mtnt.model")
|
||||
|
||||
// Requires that the source file is already .zip, not .kmp. It's a ZipUtils limitation.
|
||||
do {
|
||||
try KeymanPackage.extract(fileUrl: mtntZip, destination: destinationFolderURL, complete: { kmp in
|
||||
if let kmp = kmp {
|
||||
// Run assertions on the package's kmp.info.
|
||||
// Assumes the KMP used for testing here has the same kmp.info used for those tests.
|
||||
let kmp_json_testcase = KMPJSONTests()
|
||||
|
||||
// As this test takes place after construction of the LexicalModelPackage,
|
||||
// the version will be set accordingly, unlike in the other JSON-related tests.
|
||||
kmp_json_testcase.kmp_info_nrc_en_mtnt_assertions(kmp.metadata, version: "0.1.4")
|
||||
|
||||
XCTAssertNotNil(kmp as? LexicalModelKeymanPackage, "Lexical model KMP test extraction yielded a keyboard package!")
|
||||
XCTAssertTrue(!kmp.isKeyboard(), "Lexical model KMP test extraction yielded a keyboard package!")
|
||||
|
||||
// extracted ok, test kmp
|
||||
XCTAssert(kmp.sourceFolder == destinationFolderURL,
|
||||
"The KMP's reported 'source folder' should match the specified destination folder")
|
||||
} else {
|
||||
XCTAssert(false, "KeymanPackage.extract failed")
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
XCTFail("KeymanPackage.extract failed with error \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,10 @@ import Foundation
|
|||
extension TestUtils {
|
||||
enum LexicalModels {
|
||||
static let mtntKMP = TestUtils.lexicalModelsBundle.url(forResource: "nrc.en.mtnt.model", withExtension: "kmp")!
|
||||
static let mtnt = Defaults.lexicalModel
|
||||
static let mtnt = InstallableLexicalModel(id: "nrc.en.mtnt",
|
||||
name: "English dictionary (MTNT)",
|
||||
languageID: "en",
|
||||
version: "0.1.4",
|
||||
isCustom: false)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ extension TestUtils {
|
|||
// place entries here.
|
||||
static let language_en = jsonBundle.url(forResource: "language-en", withExtension: "json")!
|
||||
static let language_km = jsonBundle.url(forResource: "language-km", withExtension: "json")!
|
||||
static let language_str_latn = jsonBundle.url(forResource: "language-str-Latn", withExtension: "json")!
|
||||
|
||||
static let keyboard_khmer_angkor = jsonBundle.url(forResource: "keyboard-khmer_angkor", withExtension: "json")!
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"fileVersion": "12.0"
|
||||
},
|
||||
"options": {
|
||||
"followKeyboardVersion": true
|
||||
"followKeyboardVersion": false
|
||||
},
|
||||
"info": {
|
||||
"author": {
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
"description": "English language model mined from MTNT"
|
||||
},
|
||||
"version": {
|
||||
"description": "0.1.3"
|
||||
"description": "0.1.4"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "SENĆOŦEN",
|
||||
"id": "str-Latn"
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue