Wire Strong's dictionary concordance UI
This commit is contained in:
+214
-19
@@ -49,6 +49,8 @@ struct Verse {
|
||||
#[derive(Serialize)]
|
||||
struct StrongLink {
|
||||
strong: String,
|
||||
surface: String,
|
||||
position: i64,
|
||||
lemma: String,
|
||||
transliteration: String,
|
||||
definition: String,
|
||||
@@ -104,6 +106,7 @@ struct LibreBibleResourceCatalog {
|
||||
struct LibreBiblePackageFiles {
|
||||
verses_jsonl: Option<LibreBiblePackageFile>,
|
||||
strongs_jsonl: Option<LibreBiblePackageFile>,
|
||||
entries_jsonl: Option<LibreBiblePackageFile>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -117,6 +120,18 @@ struct LibreBiblePackageSource {
|
||||
package_dir: PathBuf,
|
||||
verses_path: PathBuf,
|
||||
strongs_path: Option<PathBuf>,
|
||||
lexicon_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ImportedStrongEntry {
|
||||
strongs_number: String,
|
||||
language: String,
|
||||
lemma: Option<String>,
|
||||
transliteration: Option<String>,
|
||||
pronunciation: Option<String>,
|
||||
strongs_definition: Option<String>,
|
||||
kjv_definition: Option<String>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -251,7 +266,17 @@ fn migrate(conn: &Connection) -> Result<(), String> {
|
||||
);
|
||||
",
|
||||
)
|
||||
.map_err(|err| err.to_string())
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
add_column_if_missing(conn, "verse_strongs", "surface", "TEXT NOT NULL DEFAULT ''")?;
|
||||
add_column_if_missing(conn, "lexicon", "pronunciation", "TEXT NOT NULL DEFAULT ''")?;
|
||||
add_column_if_missing(
|
||||
conn,
|
||||
"lexicon",
|
||||
"kjv_definition",
|
||||
"TEXT NOT NULL DEFAULT ''",
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn seed(conn: &mut Connection) -> Result<(), String> {
|
||||
@@ -263,7 +288,7 @@ fn seed(conn: &mut Connection) -> Result<(), String> {
|
||||
let source_count: i64 = conn
|
||||
.query_row("SELECT COUNT(*) FROM library_sources", [], |row| row.get(0))
|
||||
.map_err(|err| err.to_string())?;
|
||||
if source_count == 0 {
|
||||
if source_count == 0 || needs_resource_refresh(conn)? {
|
||||
if let Some(package_source) = find_default_package_source() {
|
||||
reset_resource_cache(conn)?;
|
||||
import_package(conn, package_source)?;
|
||||
@@ -397,12 +422,14 @@ fn load_package_source_from_catalog(
|
||||
|
||||
if let Some(package_path) = &resource.package_path {
|
||||
let resource_catalog_path = resolve_catalog_relative(catalog_dir, package_path);
|
||||
return load_package_source_from_resource_catalog(resource_catalog_path);
|
||||
let mut source = load_package_source_from_resource_catalog(resource_catalog_path)?;
|
||||
source.lexicon_path = find_catalog_lexicon_path(catalog_dir, &index);
|
||||
return Ok(source);
|
||||
}
|
||||
|
||||
if let Some(files) = &resource.files {
|
||||
let package_dir = catalog_dir.join(&resource.id);
|
||||
return package_source_from_files(
|
||||
let mut source = package_source_from_files(
|
||||
resource.id.clone(),
|
||||
resource
|
||||
.title
|
||||
@@ -410,7 +437,9 @@ fn load_package_source_from_catalog(
|
||||
.unwrap_or_else(|| resource.id.clone()),
|
||||
package_dir,
|
||||
files,
|
||||
);
|
||||
)?;
|
||||
source.lexicon_path = find_catalog_lexicon_path(catalog_dir, &index);
|
||||
return Ok(source);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,6 +497,7 @@ fn load_package_source_from_package_dir(
|
||||
strongs_jsonl: Some(LibreBiblePackageFile {
|
||||
path: "strongs-links.jsonl".to_string(),
|
||||
}),
|
||||
entries_jsonl: None,
|
||||
};
|
||||
|
||||
package_source_from_files(
|
||||
@@ -504,6 +534,11 @@ fn package_source_from_files(
|
||||
.as_ref()
|
||||
.map(|file| package_dir.join(&file.path))
|
||||
.filter(|path| path.exists());
|
||||
let lexicon_path = files
|
||||
.entries_jsonl
|
||||
.as_ref()
|
||||
.map(|file| package_dir.join(&file.path))
|
||||
.filter(|path| path.exists());
|
||||
|
||||
Ok(LibreBiblePackageSource {
|
||||
resource_id,
|
||||
@@ -511,6 +546,7 @@ fn package_source_from_files(
|
||||
package_dir,
|
||||
verses_path,
|
||||
strongs_path,
|
||||
lexicon_path,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -575,6 +611,47 @@ fn import_package(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(lexicon_path) = package_source.lexicon_path {
|
||||
let file = File::open(&lexicon_path)
|
||||
.map_err(|err| format!("Unable to open {}: {err}", lexicon_path.display()))?;
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
for line in reader.lines() {
|
||||
let line = line.map_err(|err| err.to_string())?;
|
||||
if line.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
let imported: ImportedStrongEntry =
|
||||
serde_json::from_str(&line).map_err(|err| err.to_string())?;
|
||||
let language = match imported.language.as_str() {
|
||||
"hebrew" => "Hebrew",
|
||||
"greek" => "Greek",
|
||||
other => other,
|
||||
};
|
||||
let definition = imported
|
||||
.strongs_definition
|
||||
.clone()
|
||||
.or(imported.kjv_definition.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
tx.execute(
|
||||
"INSERT OR REPLACE INTO lexicon
|
||||
(strong, lemma, transliteration, pronunciation, definition, kjv_definition, language)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
||||
params![
|
||||
imported.strongs_number,
|
||||
imported.lemma.unwrap_or_default(),
|
||||
imported.transliteration.unwrap_or_default(),
|
||||
imported.pronunciation.unwrap_or_default(),
|
||||
definition,
|
||||
imported.kjv_definition.unwrap_or_default(),
|
||||
language
|
||||
],
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(strongs_path) = package_source.strongs_path {
|
||||
let file = File::open(&strongs_path)
|
||||
.map_err(|err| format!("Unable to open {}: {err}", strongs_path.display()))?;
|
||||
@@ -597,16 +674,23 @@ fn import_package(
|
||||
};
|
||||
|
||||
tx.execute(
|
||||
"INSERT OR IGNORE INTO lexicon (strong, lemma, transliteration, definition, language)
|
||||
VALUES (?1, ?2, '', 'Linked Strong''s number from the source USFM package.', ?3)",
|
||||
params![imported.strong, imported.surface, language],
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
"INSERT OR IGNORE INTO lexicon
|
||||
(strong, lemma, transliteration, definition, language)
|
||||
VALUES (?1, ?2, '', 'Linked Strong''s number from the source USFM package.', ?3)",
|
||||
params![imported.strong, imported.surface, language],
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
tx.execute(
|
||||
"INSERT OR IGNORE INTO verse_strongs (verse_id, strong, position) VALUES (?1, ?2, ?3)",
|
||||
params![verse_id, imported.strong, imported.position],
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
"INSERT OR IGNORE INTO verse_strongs (verse_id, strong, position, surface)
|
||||
VALUES (?1, ?2, ?3, ?4)",
|
||||
params![
|
||||
verse_id,
|
||||
imported.strong,
|
||||
imported.position,
|
||||
imported.surface
|
||||
],
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -735,7 +819,7 @@ fn load_strongs(conn: &Connection, verse_id: i64) -> Result<Vec<StrongLink>, Str
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"
|
||||
SELECT l.strong, l.lemma, l.transliteration, l.definition, l.language
|
||||
SELECT l.strong, vs.surface, vs.position, l.lemma, l.transliteration, l.definition, l.language
|
||||
FROM verse_strongs vs
|
||||
JOIN lexicon l ON l.strong = vs.strong
|
||||
WHERE vs.verse_id = ?1
|
||||
@@ -748,10 +832,12 @@ fn load_strongs(conn: &Connection, verse_id: i64) -> Result<Vec<StrongLink>, Str
|
||||
.query_map(params![verse_id], |row| {
|
||||
Ok(StrongLink {
|
||||
strong: row.get(0)?,
|
||||
lemma: row.get(1)?,
|
||||
transliteration: row.get(2)?,
|
||||
definition: row.get(3)?,
|
||||
language: row.get(4)?,
|
||||
surface: row.get(1)?,
|
||||
position: row.get(2)?,
|
||||
lemma: row.get(3)?,
|
||||
transliteration: row.get(4)?,
|
||||
definition: row.get(5)?,
|
||||
language: row.get(6)?,
|
||||
})
|
||||
})
|
||||
.map_err(|err| err.to_string())?;
|
||||
@@ -763,6 +849,103 @@ fn load_strongs(conn: &Connection, verse_id: i64) -> Result<Vec<StrongLink>, Str
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
fn add_column_if_missing(
|
||||
conn: &Connection,
|
||||
table: &str,
|
||||
column: &str,
|
||||
definition: &str,
|
||||
) -> Result<(), String> {
|
||||
let mut stmt = conn
|
||||
.prepare(&format!("PRAGMA table_info({table})"))
|
||||
.map_err(|err| err.to_string())?;
|
||||
let columns = stmt
|
||||
.query_map([], |row| row.get::<_, String>(1))
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
for existing in columns {
|
||||
if existing.map_err(|err| err.to_string())? == column {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
conn.execute(
|
||||
&format!("ALTER TABLE {table} ADD COLUMN {column} {definition}"),
|
||||
[],
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn needs_resource_refresh(conn: &Connection) -> Result<bool, String> {
|
||||
let lexicon_count: i64 = conn
|
||||
.query_row("SELECT COUNT(*) FROM lexicon", [], |row| row.get(0))
|
||||
.map_err(|err| err.to_string())?;
|
||||
let real_definition_count: i64 = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM lexicon WHERE definition <> 'Linked Strong''s number from the source USFM package.'",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
Ok(lexicon_count < 10_000 || real_definition_count < 10_000)
|
||||
}
|
||||
|
||||
fn find_catalog_lexicon_path(
|
||||
catalog_dir: &Path,
|
||||
index: &LibreBibleIndexCatalog,
|
||||
) -> Option<PathBuf> {
|
||||
for resource in &index.resources {
|
||||
if resource.resource_type.as_deref() != Some("lexicon") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(package_path) = &resource.package_path {
|
||||
if let Ok(catalog) =
|
||||
load_resource_catalog_for_files(resolve_catalog_relative(catalog_dir, package_path))
|
||||
{
|
||||
let package_dir = resolve_catalog_relative(catalog_dir, package_path)
|
||||
.parent()
|
||||
.map(Path::to_path_buf)?;
|
||||
if let Some(file) = catalog.files.entries_jsonl {
|
||||
let path = package_dir.join(file.path);
|
||||
if path.exists() {
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(files) = &resource.files {
|
||||
if let Some(file) = &files.entries_jsonl {
|
||||
let path = catalog_dir.join(&resource.id).join(&file.path);
|
||||
if path.exists() {
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn load_resource_catalog_for_files(
|
||||
catalog_path: PathBuf,
|
||||
) -> Result<LibreBibleResourceCatalog, String> {
|
||||
let catalog_text = fs::read_to_string(&catalog_path).map_err(|err| {
|
||||
format!(
|
||||
"Unable to read LibreBible resource catalog {}: {err}",
|
||||
catalog_path.display()
|
||||
)
|
||||
})?;
|
||||
serde_json::from_str(&catalog_text).map_err(|err| {
|
||||
format!(
|
||||
"Invalid LibreBible resource catalog {}: {err}",
|
||||
catalog_path.display()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn load_tags(conn: &Connection, verse_id: i64) -> Result<Vec<Tag>, String> {
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
@@ -851,9 +1034,21 @@ mod tests {
|
||||
let source_count: i64 = conn
|
||||
.query_row("SELECT COUNT(*) FROM library_sources", [], |row| row.get(0))
|
||||
.map_err(|err| err.to_string())?;
|
||||
let lexicon_count: i64 = conn
|
||||
.query_row("SELECT COUNT(*) FROM lexicon", [], |row| row.get(0))
|
||||
.map_err(|err| err.to_string())?;
|
||||
let g25_definition: String = conn
|
||||
.query_row(
|
||||
"SELECT definition FROM lexicon WHERE strong = 'G25'",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
assert_eq!(verse_count, 31_102);
|
||||
assert_eq!(strongs_count, 348_884);
|
||||
assert_eq!(lexicon_count, 14_197);
|
||||
assert!(g25_definition.contains("to love"));
|
||||
assert_eq!(source_count, 1);
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user