Phase 4 consume LibreBible catalogs
This commit is contained in:
@@ -0,0 +1,10 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 0.1.1 - 2026-07-12
|
||||||
|
|
||||||
|
- Phase 4 Libre Study consumption: the app can discover the KJV package from a LibreBible `packages/json/catalog.json` catalog, import files declared by the resource catalog, and retain a local record of the imported LibreBible resource.
|
||||||
|
- Adopted the convention that every named implementation phase bumps the app version and is committed locally and remotely.
|
||||||
|
|
||||||
|
## 0.1.0 - 2026-07-12
|
||||||
|
|
||||||
|
- Initial Windows-first Tauri/React/SQLite scaffold with KJV/Strong's seed data, browser dev mode, native SQLite commands, search, and tags.
|
||||||
@@ -14,6 +14,8 @@ Libre Study is the desktop-first Bible study application. Its job is to provide
|
|||||||
- Local development happens in the workspace repo on drive `W:`.
|
- Local development happens in the workspace repo on drive `W:`.
|
||||||
- The Christ Unscripted Gitea remote is the remote backup, collaboration, and publishing copy.
|
- The Christ Unscripted Gitea remote is the remote backup, collaboration, and publishing copy.
|
||||||
- Do not treat work as durable until it is committed locally and pushed to Gitea.
|
- Do not treat work as durable until it is committed locally and pushed to Gitea.
|
||||||
|
- Every named implementation phase should bump the app version and be recorded in Git both locally and remotely.
|
||||||
|
- Phase completion means the version bump, code/docs changes, and validation result are committed locally and pushed to Gitea.
|
||||||
- The normal flow is local edit, local verify, local commit, push to Gitea, then publish or release from the pushed state.
|
- The normal flow is local edit, local verify, local commit, push to Gitea, then publish or release from the pushed state.
|
||||||
- Do not publish release artifacts from a dirty worktree unless Jason explicitly asks for that exact operation.
|
- Do not publish release artifacts from a dirty worktree unless Jason explicitly asks for that exact operation.
|
||||||
- Keep source-control bookkeeping quiet unless Jason asks about it or a source-control problem affects the work.
|
- Keep source-control bookkeeping quiet unless Jason asks about it or a source-control problem affects the work.
|
||||||
@@ -69,6 +71,7 @@ git@git.christit.com:libre-study/libre-study.git
|
|||||||
## Data Boundary
|
## Data Boundary
|
||||||
|
|
||||||
- The app should import from `libre-bible-data` packages rather than scraping or downloading Bible sources directly.
|
- The app should import from `libre-bible-data` packages rather than scraping or downloading Bible sources directly.
|
||||||
|
- The app should prefer LibreBible machine catalogs over direct file guesses. Catalog paths may come from `LIBREBIBLE_CATALOG_PATH`, a sibling `libre-bible-data` checkout, or later a public ChristIT.com catalog.
|
||||||
- The local SQLite database is an app cache and user workspace, not the canonical source of public Bible data.
|
- The local SQLite database is an app cache and user workspace, not the canonical source of public Bible data.
|
||||||
- User-created tags, highlights, notes, saved searches, layouts, and reading state belong to Libre Study.
|
- User-created tags, highlights, notes, saved searches, layouts, and reading state belong to Libre Study.
|
||||||
- Translation text, Strong's links, study notes, commentaries, maps, and source metadata belong to `libre-bible-data`.
|
- Translation text, Strong's links, study notes, commentaries, maps, and source metadata belong to `libre-bible-data`.
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "libre-study",
|
"name": "libre-study",
|
||||||
"version": "0.0.0",
|
"version": "0.1.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "libre-study",
|
"name": "libre-study",
|
||||||
"version": "0.0.0",
|
"version": "0.1.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^2.11.1",
|
"@tauri-apps/api": "^2.11.1",
|
||||||
"react": "^19.2.7",
|
"react": "^19.2.7",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "libre-study",
|
"name": "libre-study",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.1.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host 127.0.0.1",
|
"dev": "vite --host 127.0.0.1",
|
||||||
|
|||||||
Generated
+1
-1
@@ -77,7 +77,7 @@ checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "app"
|
name = "app"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "app"
|
name = "app"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
description = "A Tauri App"
|
description = "A Tauri App"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
license = ""
|
license = ""
|
||||||
|
|||||||
+315
-17
@@ -26,7 +26,7 @@ use std::collections::HashMap;
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::{BufRead, BufReader};
|
use std::io::{BufRead, BufReader};
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
use tauri::{AppHandle, Manager};
|
use tauri::{AppHandle, Manager};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@@ -79,6 +79,46 @@ struct ImportedStrongLink {
|
|||||||
strong: String,
|
strong: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct LibreBibleIndexCatalog {
|
||||||
|
resources: Vec<LibreBibleResourceSummary>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct LibreBibleResourceSummary {
|
||||||
|
id: String,
|
||||||
|
resource_type: Option<String>,
|
||||||
|
title: Option<String>,
|
||||||
|
package_path: Option<String>,
|
||||||
|
files: Option<LibreBiblePackageFiles>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct LibreBibleResourceCatalog {
|
||||||
|
id: String,
|
||||||
|
title: Option<String>,
|
||||||
|
files: LibreBiblePackageFiles,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct LibreBiblePackageFiles {
|
||||||
|
verses_jsonl: Option<LibreBiblePackageFile>,
|
||||||
|
strongs_jsonl: Option<LibreBiblePackageFile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct LibreBiblePackageFile {
|
||||||
|
path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LibreBiblePackageSource {
|
||||||
|
resource_id: String,
|
||||||
|
title: String,
|
||||||
|
package_dir: PathBuf,
|
||||||
|
verses_path: PathBuf,
|
||||||
|
strongs_path: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn get_library_state(app: AppHandle) -> Result<LibraryState, String> {
|
fn get_library_state(app: AppHandle) -> Result<LibraryState, String> {
|
||||||
let conn = open_db(&app)?;
|
let conn = open_db(&app)?;
|
||||||
@@ -183,7 +223,7 @@ fn migrate(conn: &Connection) -> Result<(), String> {
|
|||||||
verse_id INTEGER NOT NULL,
|
verse_id INTEGER NOT NULL,
|
||||||
strong TEXT NOT NULL,
|
strong TEXT NOT NULL,
|
||||||
position INTEGER NOT NULL,
|
position INTEGER NOT NULL,
|
||||||
PRIMARY KEY (verse_id, strong),
|
PRIMARY KEY (verse_id, strong, position),
|
||||||
FOREIGN KEY (verse_id) REFERENCES verses(id),
|
FOREIGN KEY (verse_id) REFERENCES verses(id),
|
||||||
FOREIGN KEY (strong) REFERENCES lexicon(strong)
|
FOREIGN KEY (strong) REFERENCES lexicon(strong)
|
||||||
);
|
);
|
||||||
@@ -194,6 +234,14 @@ fn migrate(conn: &Connection) -> Result<(), String> {
|
|||||||
color TEXT NOT NULL
|
color TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS library_sources (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
resource_id TEXT NOT NULL UNIQUE,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
package_dir TEXT NOT NULL,
|
||||||
|
imported_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS verse_tags (
|
CREATE TABLE IF NOT EXISTS verse_tags (
|
||||||
verse_id INTEGER NOT NULL,
|
verse_id INTEGER NOT NULL,
|
||||||
tag_id INTEGER NOT NULL,
|
tag_id INTEGER NOT NULL,
|
||||||
@@ -212,11 +260,20 @@ fn seed(conn: &mut Connection) -> Result<(), String> {
|
|||||||
.map_err(|err| err.to_string())?;
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
if verse_count > 0 {
|
if verse_count > 0 {
|
||||||
|
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 let Some(package_source) = find_default_package_source() {
|
||||||
|
reset_resource_cache(conn)?;
|
||||||
|
import_package(conn, package_source)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(package_dir) = find_default_package_dir() {
|
if let Some(package_source) = find_default_package_source() {
|
||||||
import_package(conn, package_dir)?;
|
import_package(conn, package_source)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,12 +307,29 @@ fn seed(conn: &mut Connection) -> Result<(), String> {
|
|||||||
conn.execute_batch(seed_sql).map_err(|err| err.to_string())
|
conn.execute_batch(seed_sql).map_err(|err| err.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_default_package_dir() -> Option<PathBuf> {
|
fn reset_resource_cache(conn: &Connection) -> Result<(), String> {
|
||||||
|
conn.execute_batch(
|
||||||
|
"
|
||||||
|
DELETE FROM verse_tags;
|
||||||
|
DELETE FROM verse_strongs;
|
||||||
|
DELETE FROM lexicon;
|
||||||
|
DELETE FROM verses;
|
||||||
|
DELETE FROM library_sources;
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.map_err(|err| err.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_default_package_source() -> Option<LibreBiblePackageSource> {
|
||||||
|
if let Ok(path) = env::var("LIBREBIBLE_CATALOG_PATH") {
|
||||||
|
if let Ok(source) = load_package_source_from_catalog(PathBuf::from(path)) {
|
||||||
|
return Some(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Ok(path) = env::var("LIBRE_BIBLE_DATA_PACKAGE") {
|
if let Ok(path) = env::var("LIBRE_BIBLE_DATA_PACKAGE") {
|
||||||
let candidate = PathBuf::from(path);
|
if let Ok(source) = load_package_source_from_package_dir(PathBuf::from(path)) {
|
||||||
if candidate.join("verses.jsonl").exists() && candidate.join("strongs-links.jsonl").exists()
|
return Some(source);
|
||||||
{
|
|
||||||
return Some(candidate);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +340,7 @@ fn find_default_package_dir() -> Option<PathBuf> {
|
|||||||
.join("libre-bible-data")
|
.join("libre-bible-data")
|
||||||
.join("packages")
|
.join("packages")
|
||||||
.join("json")
|
.join("json")
|
||||||
.join("kjv-eng-kjv2006"),
|
.join("catalog.json"),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(parent) = cwd.parent() {
|
if let Some(parent) = cwd.parent() {
|
||||||
@@ -275,21 +349,204 @@ fn find_default_package_dir() -> Option<PathBuf> {
|
|||||||
.join("libre-bible-data")
|
.join("libre-bible-data")
|
||||||
.join("packages")
|
.join("packages")
|
||||||
.join("json")
|
.join("json")
|
||||||
.join("kjv-eng-kjv2006"),
|
.join("catalog.json"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
candidates.into_iter().find(|path| {
|
for candidate in candidates {
|
||||||
path.join("verses.jsonl").exists() && path.join("strongs-links.jsonl").exists()
|
if let Ok(source) = load_package_source_from_catalog(candidate) {
|
||||||
|
return Some(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_package_source_from_catalog(
|
||||||
|
catalog_path: PathBuf,
|
||||||
|
) -> Result<LibreBiblePackageSource, String> {
|
||||||
|
let catalog_text = fs::read_to_string(&catalog_path).map_err(|err| {
|
||||||
|
format!(
|
||||||
|
"Unable to read LibreBible catalog {}: {err}",
|
||||||
|
catalog_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let catalog_dir = catalog_path
|
||||||
|
.parent()
|
||||||
|
.ok_or_else(|| format!("Catalog path has no parent: {}", catalog_path.display()))?;
|
||||||
|
|
||||||
|
if let Ok(index) = serde_json::from_str::<LibreBibleIndexCatalog>(&catalog_text) {
|
||||||
|
let selected_id =
|
||||||
|
env::var("LIBREBIBLE_RESOURCE_ID").unwrap_or_else(|_| "kjv-eng-kjv2006".to_string());
|
||||||
|
let resource = index
|
||||||
|
.resources
|
||||||
|
.iter()
|
||||||
|
.find(|resource| resource.id == selected_id)
|
||||||
|
.or_else(|| {
|
||||||
|
index
|
||||||
|
.resources
|
||||||
|
.iter()
|
||||||
|
.find(|resource| resource.resource_type.as_deref() == Some("translation"))
|
||||||
|
})
|
||||||
|
.ok_or_else(|| {
|
||||||
|
format!(
|
||||||
|
"LibreBible catalog {} does not contain an importable translation.",
|
||||||
|
catalog_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(files) = &resource.files {
|
||||||
|
let package_dir = catalog_dir.join(&resource.id);
|
||||||
|
return package_source_from_files(
|
||||||
|
resource.id.clone(),
|
||||||
|
resource
|
||||||
|
.title
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| resource.id.clone()),
|
||||||
|
package_dir,
|
||||||
|
files,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
load_package_source_from_resource_catalog(catalog_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_package_source_from_resource_catalog(
|
||||||
|
catalog_path: PathBuf,
|
||||||
|
) -> Result<LibreBiblePackageSource, String> {
|
||||||
|
let catalog_text = fs::read_to_string(&catalog_path).map_err(|err| {
|
||||||
|
format!(
|
||||||
|
"Unable to read LibreBible resource catalog {}: {err}",
|
||||||
|
catalog_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let catalog: LibreBibleResourceCatalog =
|
||||||
|
serde_json::from_str(&catalog_text).map_err(|err| {
|
||||||
|
format!(
|
||||||
|
"Invalid LibreBible resource catalog {}: {err}",
|
||||||
|
catalog_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let package_dir = catalog_path
|
||||||
|
.parent()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
format!(
|
||||||
|
"Resource catalog path has no parent: {}",
|
||||||
|
catalog_path.display()
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.to_path_buf();
|
||||||
|
|
||||||
|
package_source_from_files(
|
||||||
|
catalog.id,
|
||||||
|
catalog
|
||||||
|
.title
|
||||||
|
.unwrap_or_else(|| "LibreBible resource".to_string()),
|
||||||
|
package_dir,
|
||||||
|
&catalog.files,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_package_source_from_package_dir(
|
||||||
|
package_dir: PathBuf,
|
||||||
|
) -> Result<LibreBiblePackageSource, String> {
|
||||||
|
let catalog_path = package_dir.join("catalog.json");
|
||||||
|
if catalog_path.exists() {
|
||||||
|
return load_package_source_from_resource_catalog(catalog_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let files = LibreBiblePackageFiles {
|
||||||
|
verses_jsonl: Some(LibreBiblePackageFile {
|
||||||
|
path: "verses.jsonl".to_string(),
|
||||||
|
}),
|
||||||
|
strongs_jsonl: Some(LibreBiblePackageFile {
|
||||||
|
path: "strongs-links.jsonl".to_string(),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
package_source_from_files(
|
||||||
|
package_dir
|
||||||
|
.file_name()
|
||||||
|
.and_then(|name| name.to_str())
|
||||||
|
.unwrap_or("legacy-package")
|
||||||
|
.to_string(),
|
||||||
|
"Legacy LibreBible package".to_string(),
|
||||||
|
package_dir,
|
||||||
|
&files,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn package_source_from_files(
|
||||||
|
resource_id: String,
|
||||||
|
title: String,
|
||||||
|
package_dir: PathBuf,
|
||||||
|
files: &LibreBiblePackageFiles,
|
||||||
|
) -> Result<LibreBiblePackageSource, String> {
|
||||||
|
let verses_file = files.verses_jsonl.as_ref().ok_or_else(|| {
|
||||||
|
format!("LibreBible resource {resource_id} does not define verses_jsonl.")
|
||||||
|
})?;
|
||||||
|
let verses_path = package_dir.join(&verses_file.path);
|
||||||
|
if !verses_path.exists() {
|
||||||
|
return Err(format!(
|
||||||
|
"LibreBible verses file is missing: {}",
|
||||||
|
verses_path.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let strongs_path = files
|
||||||
|
.strongs_jsonl
|
||||||
|
.as_ref()
|
||||||
|
.map(|file| package_dir.join(&file.path))
|
||||||
|
.filter(|path| path.exists());
|
||||||
|
|
||||||
|
Ok(LibreBiblePackageSource {
|
||||||
|
resource_id,
|
||||||
|
title,
|
||||||
|
package_dir,
|
||||||
|
verses_path,
|
||||||
|
strongs_path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn import_package(conn: &mut Connection, package_dir: PathBuf) -> Result<(), String> {
|
fn resolve_catalog_relative(catalog_dir: &Path, package_path: &str) -> PathBuf {
|
||||||
let verses_path = package_dir.join("verses.jsonl");
|
let candidate = PathBuf::from(package_path);
|
||||||
let strongs_path = package_dir.join("strongs-links.jsonl");
|
if candidate.is_absolute() {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
let from_repo_root = catalog_dir.join("..").join("..").join(package_path);
|
||||||
|
if from_repo_root.exists() {
|
||||||
|
return from_repo_root;
|
||||||
|
}
|
||||||
|
|
||||||
|
catalog_dir.join(package_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_package(
|
||||||
|
conn: &mut Connection,
|
||||||
|
package_source: LibreBiblePackageSource,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let verses_path = package_source.verses_path.clone();
|
||||||
let tx = conn.transaction().map_err(|err| err.to_string())?;
|
let tx = conn.transaction().map_err(|err| err.to_string())?;
|
||||||
let mut verse_ids = HashMap::new();
|
let mut verse_ids = HashMap::new();
|
||||||
|
|
||||||
|
tx.execute(
|
||||||
|
"INSERT OR REPLACE INTO library_sources (resource_id, title, package_dir, imported_at)
|
||||||
|
VALUES (?1, ?2, ?3, CURRENT_TIMESTAMP)",
|
||||||
|
params![
|
||||||
|
package_source.resource_id,
|
||||||
|
package_source.title,
|
||||||
|
package_source.package_dir.to_string_lossy().to_string()
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
{
|
{
|
||||||
let file = File::open(&verses_path)
|
let file = File::open(&verses_path)
|
||||||
.map_err(|err| format!("Unable to open {}: {err}", verses_path.display()))?;
|
.map_err(|err| format!("Unable to open {}: {err}", verses_path.display()))?;
|
||||||
@@ -318,7 +575,7 @@ fn import_package(conn: &mut Connection, package_dir: PathBuf) -> Result<(), Str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
if let Some(strongs_path) = package_source.strongs_path {
|
||||||
let file = File::open(&strongs_path)
|
let file = File::open(&strongs_path)
|
||||||
.map_err(|err| format!("Unable to open {}: {err}", strongs_path.display()))?;
|
.map_err(|err| format!("Unable to open {}: {err}", strongs_path.display()))?;
|
||||||
let reader = BufReader::new(file);
|
let reader = BufReader::new(file);
|
||||||
@@ -561,3 +818,44 @@ fn looks_like_strongs(query: &str) -> bool {
|
|||||||
let mut chars = query.chars();
|
let mut chars = query.chars();
|
||||||
matches!(chars.next(), Some('G' | 'g' | 'H' | 'h')) && chars.all(|char| char.is_ascii_digit())
|
matches!(chars.next(), Some('G' | 'g' | 'H' | 'h')) && chars.all(|char| char.is_ascii_digit())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn imports_sibling_librebible_catalog_when_available() -> Result<(), String> {
|
||||||
|
let catalog_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("..")
|
||||||
|
.join("..")
|
||||||
|
.join("libre-bible-data")
|
||||||
|
.join("packages")
|
||||||
|
.join("json")
|
||||||
|
.join("catalog.json");
|
||||||
|
|
||||||
|
if !catalog_path.exists() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let source = load_package_source_from_catalog(catalog_path)?;
|
||||||
|
let mut conn = Connection::open_in_memory().map_err(|err| err.to_string())?;
|
||||||
|
migrate(&conn)?;
|
||||||
|
import_package(&mut conn, source)?;
|
||||||
|
|
||||||
|
let verse_count: i64 = conn
|
||||||
|
.query_row("SELECT COUNT(*) FROM verses", [], |row| row.get(0))
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
let strongs_count: i64 = conn
|
||||||
|
.query_row("SELECT COUNT(*) FROM verse_strongs", [], |row| row.get(0))
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
let source_count: i64 = conn
|
||||||
|
.query_row("SELECT COUNT(*) FROM library_sources", [], |row| row.get(0))
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
assert_eq!(verse_count, 31_102);
|
||||||
|
assert_eq!(strongs_count, 348_884);
|
||||||
|
assert_eq!(source_count, 1);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"productName": "Libre Study",
|
"productName": "Libre Study",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"identifier": "org.librestudy.desktop",
|
"identifier": "org.librestudy.desktop",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../dist",
|
"frontendDist": "../dist",
|
||||||
|
|||||||
Reference in New Issue
Block a user