Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
f5bca61839
|
|||
|
056aaaa0ce
|
|||
|
a696ed9733
|
|||
|
00b963bfed
|
|||
|
4376012f18
|
|||
|
8ae1d882a0
|
|||
|
81db60ccf7
|
|||
|
f57cc68ef3
|
|||
|
48f50628a1
|
|||
|
1783338b2a
|
|||
|
e92e99922b
|
|||
|
05b56466e7
|
|||
|
33016ca751
|
|||
|
98d92d370d
|
|||
|
5252936bdc
|
@@ -0,0 +1,31 @@
|
|||||||
|
name: "Run tests"
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
jobs:
|
||||||
|
evals:
|
||||||
|
runs-on: debian-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Install sudo
|
||||||
|
run: apt-get update && apt-get -y install sudo
|
||||||
|
|
||||||
|
- name: Install nix
|
||||||
|
uses: https://github.com/cachix/install-nix-action@v31
|
||||||
|
|
||||||
|
- name: Configure nix
|
||||||
|
run: echo -e "show-trace = true\nmax-jobs = auto\ntrusted-users = root\nexperimental-features = nix-command flakes\nbuild-users-group =" > /etc/nix/nix.conf
|
||||||
|
|
||||||
|
- name: Update database inputs
|
||||||
|
run: |
|
||||||
|
nix flake update jmdict-src
|
||||||
|
nix flake update jmdict-with-examples-src
|
||||||
|
nix flake update radkfile-src
|
||||||
|
nix flake update kanjidic2-src
|
||||||
|
|
||||||
|
- name: Build database
|
||||||
|
run: nix build .#database -L
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: nix develop .# --command dart test
|
||||||
Generated
+6
-6
@@ -3,7 +3,7 @@
|
|||||||
"jmdict-src": {
|
"jmdict-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"narHash": "sha256-coRi0AIx02GIrVms4C1GMCjgtdIVRpS7WEpN2UdUX1E=",
|
"narHash": "sha256-lh46uougUzBrRhhwa7cOb32j5Jt9/RjBUhlVjwVzsII=",
|
||||||
"type": "file",
|
"type": "file",
|
||||||
"url": "http://ftp.edrdg.org/pub/Nihongo/JMdict_e.gz"
|
"url": "http://ftp.edrdg.org/pub/Nihongo/JMdict_e.gz"
|
||||||
},
|
},
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"jmdict-with-examples-src": {
|
"jmdict-with-examples-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"narHash": "sha256-UD9QVy9dtPeExN/yvvR8Mi4r+3PvxlbGJA+oRNIGUGk=",
|
"narHash": "sha256-5oS2xDyetbuSM6ax3LUjYA3N60x+D3Hg41HEXGFMqLQ=",
|
||||||
"type": "file",
|
"type": "file",
|
||||||
"url": "http://ftp.edrdg.org/pub/Nihongo/JMdict_e_examp.gz"
|
"url": "http://ftp.edrdg.org/pub/Nihongo/JMdict_e_examp.gz"
|
||||||
},
|
},
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"kanjidic2-src": {
|
"kanjidic2-src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"narHash": "sha256-NwQ9SmlwxyXXxSS8cVh1PL17m/LKXdIhyyitTIbB2DI=",
|
"narHash": "sha256-orSeQqSxhn9TtX3anYtbiMEm7nFkuomGnIKoVIUR2CM=",
|
||||||
"type": "file",
|
"type": "file",
|
||||||
"url": "https://www.edrdg.org/kanjidic/kanjidic2.xml.gz"
|
"url": "https://www.edrdg.org/kanjidic/kanjidic2.xml.gz"
|
||||||
},
|
},
|
||||||
@@ -38,11 +38,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1765779637,
|
"lastModified": 1771848320,
|
||||||
"narHash": "sha256-KJ2wa/BLSrTqDjbfyNx70ov/HdgNBCBBSQP3BIzKnv4=",
|
"narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "1306659b587dc277866c7b69eb97e5f07864d8c4",
|
"rev": "2fc6539b481e1d2569f25f8799236694180c0993",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
sqlite-interactive
|
sqlite-interactive
|
||||||
sqlite-analyzer
|
sqlite-analyzer
|
||||||
sqlite-web
|
sqlite-web
|
||||||
sqlint
|
# sqlint
|
||||||
sqlfluff
|
sqlfluff
|
||||||
];
|
];
|
||||||
env = {
|
env = {
|
||||||
@@ -109,10 +109,14 @@
|
|||||||
in !(lib.any (b: b) [
|
in !(lib.any (b: b) [
|
||||||
(!(lib.cleanSourceFilter path type))
|
(!(lib.cleanSourceFilter path type))
|
||||||
(baseName == ".github" && type == "directory")
|
(baseName == ".github" && type == "directory")
|
||||||
|
(baseName == ".gitea" && type == "directory")
|
||||||
|
|
||||||
(baseName == "nix" && type == "directory")
|
(baseName == "nix" && type == "directory")
|
||||||
(baseName == ".envrc" && type == "regular")
|
(baseName == ".envrc" && type == "regular")
|
||||||
(baseName == "flake.lock" && type == "regular")
|
(baseName == "flake.lock" && type == "regular")
|
||||||
(baseName == "flake.nix" && type == "regular")
|
(baseName == "flake.nix" && type == "regular")
|
||||||
|
|
||||||
|
(baseName == ".sqlfluff" && type == "regular")
|
||||||
])) ./.;
|
])) ./.;
|
||||||
|
|
||||||
in forAllSystems (system: pkgs: {
|
in forAllSystems (system: pkgs: {
|
||||||
@@ -123,7 +127,7 @@
|
|||||||
'';
|
'';
|
||||||
|
|
||||||
jmdict = pkgs.callPackage ./nix/jmdict.nix {
|
jmdict = pkgs.callPackage ./nix/jmdict.nix {
|
||||||
inherit jmdict-src jmdict-with-examples-src edrdgMetadata;
|
inherit jmdict-src jmdict-with-examples-src edrdgMetadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
radkfile = pkgs.callPackage ./nix/radkfile.nix {
|
radkfile = pkgs.callPackage ./nix/radkfile.nix {
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import 'dart:ffi';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:jadb/search.dart';
|
import 'package:jadb/search.dart';
|
||||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||||
import 'package:sqlite3/open.dart';
|
|
||||||
|
|
||||||
Future<Database> openLocalDb({
|
Future<Database> openLocalDb({
|
||||||
String? libsqlitePath,
|
String? libsqlitePath,
|
||||||
@@ -12,32 +10,17 @@ Future<Database> openLocalDb({
|
|||||||
bool verifyTablesExist = true,
|
bool verifyTablesExist = true,
|
||||||
bool walMode = false,
|
bool walMode = false,
|
||||||
}) async {
|
}) async {
|
||||||
libsqlitePath ??= Platform.environment['LIBSQLITE_PATH'];
|
|
||||||
jadbPath ??= Platform.environment['JADB_PATH'];
|
jadbPath ??= Platform.environment['JADB_PATH'];
|
||||||
jadbPath ??= Directory.current.uri.resolve('jadb.sqlite').path;
|
jadbPath ??= Directory.current.uri.resolve('jadb.sqlite').path;
|
||||||
|
|
||||||
libsqlitePath = (libsqlitePath == null)
|
|
||||||
? null
|
|
||||||
: File(libsqlitePath).resolveSymbolicLinksSync();
|
|
||||||
jadbPath = File(jadbPath).resolveSymbolicLinksSync();
|
jadbPath = File(jadbPath).resolveSymbolicLinksSync();
|
||||||
|
|
||||||
if (libsqlitePath == null) {
|
|
||||||
throw Exception('LIBSQLITE_PATH is not set');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!File(libsqlitePath).existsSync()) {
|
|
||||||
throw Exception('LIBSQLITE_PATH does not exist: $libsqlitePath');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!File(jadbPath).existsSync()) {
|
if (!File(jadbPath).existsSync()) {
|
||||||
throw Exception('JADB_PATH does not exist: $jadbPath');
|
throw Exception('JADB_PATH does not exist: $jadbPath');
|
||||||
}
|
}
|
||||||
|
|
||||||
final db =
|
final db =
|
||||||
await createDatabaseFactoryFfi(
|
await createDatabaseFactoryFfi().openDatabase(
|
||||||
ffiInit: () =>
|
|
||||||
open.overrideForAll(() => DynamicLibrary.open(libsqlitePath!)),
|
|
||||||
).openDatabase(
|
|
||||||
jadbPath,
|
jadbPath,
|
||||||
options: OpenDatabaseOptions(
|
options: OpenDatabaseOptions(
|
||||||
onConfigure: (db) async {
|
onConfigure: (db) async {
|
||||||
|
|||||||
@@ -3,12 +3,20 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:csv/csv.dart';
|
import 'package:csv/csv.dart';
|
||||||
import 'package:jadb/_data_ingestion/tanos-jlpt/objects.dart';
|
import 'package:jadb/_data_ingestion/tanos-jlpt/objects.dart';
|
||||||
|
import 'package:xml/xml_events.dart';
|
||||||
|
|
||||||
Future<List<JLPTRankedWord>> parseJLPTRankedWords(
|
Future<List<JLPTRankedWord>> parseJLPTRankedWords(
|
||||||
Map<String, File> files,
|
Map<String, File> files,
|
||||||
) async {
|
) async {
|
||||||
final List<JLPTRankedWord> result = [];
|
final List<JLPTRankedWord> result = [];
|
||||||
|
|
||||||
|
final codec = CsvCodec(
|
||||||
|
fieldDelimiter: ',',
|
||||||
|
lineDelimiter: '\n',
|
||||||
|
quoteMode: QuoteMode.strings,
|
||||||
|
escapeCharacter: '\\',
|
||||||
|
);
|
||||||
|
|
||||||
for (final entry in files.entries) {
|
for (final entry in files.entries) {
|
||||||
final jlptLevel = entry.key;
|
final jlptLevel = entry.key;
|
||||||
final file = entry.value;
|
final file = entry.value;
|
||||||
@@ -17,42 +25,42 @@ Future<List<JLPTRankedWord>> parseJLPTRankedWords(
|
|||||||
throw Exception('File $jlptLevel does not exist');
|
throw Exception('File $jlptLevel does not exist');
|
||||||
}
|
}
|
||||||
|
|
||||||
final rows = await file
|
final words = await file
|
||||||
.openRead()
|
.openRead()
|
||||||
.transform(utf8.decoder)
|
.transform(utf8.decoder)
|
||||||
.transform(CsvToListConverter())
|
.transform(codec.decoder)
|
||||||
|
.flatten()
|
||||||
|
.map((row) {
|
||||||
|
if (row.length != 3) {
|
||||||
|
throw Exception('Invalid line in $jlptLevel: $row');
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
})
|
||||||
|
.map((row) => row.map((e) => e as String).toList())
|
||||||
|
.map((row) {
|
||||||
|
final kanji = row[0].isEmpty
|
||||||
|
? null
|
||||||
|
: row[0]
|
||||||
|
.replaceFirst(RegExp('^お・'), '')
|
||||||
|
.replaceAll(RegExp(r'(.*)'), '');
|
||||||
|
|
||||||
|
final readings = row[1]
|
||||||
|
.split(RegExp('[・/、(:?s+)]'))
|
||||||
|
.map((e) => e.trim())
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final meanings = row[2].split(',').expand(cleanMeaning).toList();
|
||||||
|
|
||||||
|
return JLPTRankedWord(
|
||||||
|
readings: readings,
|
||||||
|
kanji: kanji,
|
||||||
|
jlptLevel: jlptLevel,
|
||||||
|
meanings: meanings,
|
||||||
|
);
|
||||||
|
})
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
for (final row in rows) {
|
result.addAll(words);
|
||||||
if (row.length != 3) {
|
|
||||||
throw Exception('Invalid line in $jlptLevel: $row');
|
|
||||||
}
|
|
||||||
|
|
||||||
final kanji = (row[0] as String).isEmpty
|
|
||||||
? null
|
|
||||||
: (row[0] as String)
|
|
||||||
.replaceFirst(RegExp('^お・'), '')
|
|
||||||
.replaceAll(RegExp(r'(.*)'), '');
|
|
||||||
|
|
||||||
final readings = (row[1] as String)
|
|
||||||
.split(RegExp('[・/、(:?s+)]'))
|
|
||||||
.map((e) => e.trim())
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
final meanings = (row[2] as String)
|
|
||||||
.split(',')
|
|
||||||
.expand(cleanMeaning)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
result.add(
|
|
||||||
JLPTRankedWord(
|
|
||||||
readings: readings,
|
|
||||||
kanji: kanji,
|
|
||||||
jlptLevel: jlptLevel,
|
|
||||||
meanings: meanings,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
enum WordSearchMatchSpanType { kanji, kana, sense }
|
||||||
|
|
||||||
|
/// A span of a word search result that corresponds to a match for a kanji, kana, or sense.
|
||||||
|
class WordSearchMatchSpan {
|
||||||
|
/// Which subtype of the word search result this span corresponds to - either a kanji, a kana, or a sense.
|
||||||
|
final WordSearchMatchSpanType spanType;
|
||||||
|
|
||||||
|
/// The index of the kanji/kana/sense in the word search result that this span corresponds to.
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
/// When matching a 'sense', this is the index of the English definition in that sense that this span corresponds to. Otherwise, this is always 0.
|
||||||
|
final int subIndex;
|
||||||
|
|
||||||
|
/// The start of the span (inclusive)
|
||||||
|
final int start;
|
||||||
|
|
||||||
|
/// The end of the span (inclusive)
|
||||||
|
final int end;
|
||||||
|
|
||||||
|
WordSearchMatchSpan({
|
||||||
|
required this.spanType,
|
||||||
|
required this.index,
|
||||||
|
required this.start,
|
||||||
|
required this.end,
|
||||||
|
this.subIndex = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'WordSearchMatchSpan(spanType: $spanType, index: $index, start: $start, end: $end)';
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object?> toJson() => {
|
||||||
|
'spanType': spanType.toString().split('.').last,
|
||||||
|
'index': index,
|
||||||
|
'start': start,
|
||||||
|
'end': end,
|
||||||
|
};
|
||||||
|
|
||||||
|
factory WordSearchMatchSpan.fromJson(Map<String, dynamic> json) =>
|
||||||
|
WordSearchMatchSpan(
|
||||||
|
spanType: WordSearchMatchSpanType.values.firstWhere(
|
||||||
|
(e) => e.toString().split('.').last == json['spanType'],
|
||||||
|
),
|
||||||
|
index: json['index'] as int,
|
||||||
|
start: json['start'] as int,
|
||||||
|
end: json['end'] as int,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(spanType, index, start, end);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
return other is WordSearchMatchSpan &&
|
||||||
|
other.spanType == spanType &&
|
||||||
|
other.index == index &&
|
||||||
|
other.start == start &&
|
||||||
|
other.end == end;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import 'package:jadb/models/common/jlpt_level.dart';
|
import 'package:jadb/models/common/jlpt_level.dart';
|
||||||
import 'package:jadb/models/jmdict/jmdict_kanji_info.dart';
|
import 'package:jadb/models/jmdict/jmdict_kanji_info.dart';
|
||||||
import 'package:jadb/models/jmdict/jmdict_reading_info.dart';
|
import 'package:jadb/models/jmdict/jmdict_reading_info.dart';
|
||||||
|
import 'package:jadb/models/word_search/word_search_match_span.dart';
|
||||||
import 'package:jadb/models/word_search/word_search_ruby.dart';
|
import 'package:jadb/models/word_search/word_search_ruby.dart';
|
||||||
import 'package:jadb/models/word_search/word_search_sense.dart';
|
import 'package:jadb/models/word_search/word_search_sense.dart';
|
||||||
import 'package:jadb/models/word_search/word_search_sources.dart';
|
import 'package:jadb/models/word_search/word_search_sources.dart';
|
||||||
|
import 'package:jadb/search/word_search/word_search.dart';
|
||||||
|
|
||||||
/// A class representing a single dictionary entry from a word search.
|
/// A class representing a single dictionary entry from a word search.
|
||||||
class WordSearchResult {
|
class WordSearchResult {
|
||||||
@@ -34,7 +36,15 @@ class WordSearchResult {
|
|||||||
/// A class listing the sources used to make up the data for this word search result.
|
/// A class listing the sources used to make up the data for this word search result.
|
||||||
final WordSearchSources sources;
|
final WordSearchSources sources;
|
||||||
|
|
||||||
const WordSearchResult({
|
/// A list of spans, specifying which part of this word result matched the search keyword.
|
||||||
|
///
|
||||||
|
/// Note that this is considered ephemeral data - it does not originate from the dictionary,
|
||||||
|
/// and unlike the rest of the class it varies based on external information (the searchword).
|
||||||
|
/// It will *NOT* be exported to JSON, but can be reinferred by invoking [inferMatchSpans] with
|
||||||
|
/// the original searchword.
|
||||||
|
List<WordSearchMatchSpan>? matchSpans;
|
||||||
|
|
||||||
|
WordSearchResult({
|
||||||
required this.score,
|
required this.score,
|
||||||
required this.entryId,
|
required this.entryId,
|
||||||
required this.isCommon,
|
required this.isCommon,
|
||||||
@@ -44,6 +54,7 @@ class WordSearchResult {
|
|||||||
required this.senses,
|
required this.senses,
|
||||||
required this.jlptLevel,
|
required this.jlptLevel,
|
||||||
required this.sources,
|
required this.sources,
|
||||||
|
this.matchSpans,
|
||||||
});
|
});
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
@@ -81,6 +92,77 @@ class WordSearchResult {
|
|||||||
sources: WordSearchSources.fromJson(json['sources']),
|
sources: WordSearchSources.fromJson(json['sources']),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
factory WordSearchResult.empty() => WordSearchResult(
|
||||||
|
score: 0,
|
||||||
|
entryId: 0,
|
||||||
|
isCommon: false,
|
||||||
|
japanese: [],
|
||||||
|
kanjiInfo: {},
|
||||||
|
readingInfo: {},
|
||||||
|
senses: [],
|
||||||
|
jlptLevel: JlptLevel.none,
|
||||||
|
sources: WordSearchSources.empty(),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Infers which part(s) of this word search result matched the search keyword, and populates [matchSpans] accordingly.
|
||||||
|
void inferMatchSpans(
|
||||||
|
String searchword, {
|
||||||
|
SearchMode searchMode = SearchMode.Auto,
|
||||||
|
}) {
|
||||||
|
// TODO: handle wildcards like '?' and '*' when that becomes supported in the search.
|
||||||
|
// TODO: If the searchMode is provided, we can use that to narrow down which part of the word search results to look at.
|
||||||
|
|
||||||
|
final regex = RegExp(RegExp.escape(searchword));
|
||||||
|
final matchSpans = <WordSearchMatchSpan>[];
|
||||||
|
|
||||||
|
for (final (i, japanese) in japanese.indexed) {
|
||||||
|
final baseMatches = regex.allMatches(japanese.base);
|
||||||
|
matchSpans.addAll(
|
||||||
|
baseMatches.map(
|
||||||
|
(match) => WordSearchMatchSpan(
|
||||||
|
spanType: WordSearchMatchSpanType.kanji,
|
||||||
|
index: i,
|
||||||
|
start: match.start,
|
||||||
|
end: match.end,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (japanese.furigana != null) {
|
||||||
|
final furiganaMatches = regex.allMatches(japanese.furigana!);
|
||||||
|
matchSpans.addAll(
|
||||||
|
furiganaMatches.map(
|
||||||
|
(match) => WordSearchMatchSpan(
|
||||||
|
spanType: WordSearchMatchSpanType.kana,
|
||||||
|
index: i,
|
||||||
|
start: match.start,
|
||||||
|
end: match.end,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final (i, sense) in senses.indexed) {
|
||||||
|
for (final (k, definition) in sense.englishDefinitions.indexed) {
|
||||||
|
final definitionMatches = regex.allMatches(definition);
|
||||||
|
matchSpans.addAll(
|
||||||
|
definitionMatches.map(
|
||||||
|
(match) => WordSearchMatchSpan(
|
||||||
|
spanType: WordSearchMatchSpanType.sense,
|
||||||
|
index: i,
|
||||||
|
subIndex: k,
|
||||||
|
start: match.start,
|
||||||
|
end: match.end,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.matchSpans = matchSpans;
|
||||||
|
}
|
||||||
|
|
||||||
String _formatJapaneseWord(WordSearchRuby word) =>
|
String _formatJapaneseWord(WordSearchRuby word) =>
|
||||||
word.furigana == null ? word.base : '${word.base} (${word.furigana})';
|
word.furigana == null ? word.base : '${word.base} (${word.furigana})';
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ class WordSearchSources {
|
|||||||
|
|
||||||
const WordSearchSources({this.jmdict = true, this.jmnedict = false});
|
const WordSearchSources({this.jmdict = true, this.jmnedict = false});
|
||||||
|
|
||||||
|
factory WordSearchSources.empty() => const WordSearchSources();
|
||||||
|
|
||||||
Map<String, Object?> get sqlValue => {'jmdict': jmdict, 'jmnedict': jmnedict};
|
Map<String, Object?> get sqlValue => {'jmdict': jmdict, 'jmnedict': jmnedict};
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {'jmdict': jmdict, 'jmnedict': jmnedict};
|
Map<String, dynamic> toJson() => {'jmdict': jmdict, 'jmnedict': jmnedict};
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import 'package:jadb/table_names/kanjidic.dart';
|
import 'package:jadb/table_names/kanjidic.dart';
|
||||||
import 'package:sqflite_common/sqflite.dart';
|
import 'package:sqflite_common/sqflite.dart';
|
||||||
|
|
||||||
|
/// Filters a list of kanji characters, returning only those that exist in the database.
|
||||||
|
///
|
||||||
|
/// If [deduplicate] is true, the returned list will deduplicate the input kanji list before returning the filtered results.
|
||||||
Future<List<String>> filterKanjiWithDbConnection(
|
Future<List<String>> filterKanjiWithDbConnection(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
List<String> kanji,
|
List<String> kanji,
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import 'package:jadb/models/kanji_search/kanji_search_radical.dart';
|
|||||||
import 'package:jadb/models/kanji_search/kanji_search_result.dart';
|
import 'package:jadb/models/kanji_search/kanji_search_result.dart';
|
||||||
import 'package:jadb/table_names/kanjidic.dart';
|
import 'package:jadb/table_names/kanjidic.dart';
|
||||||
import 'package:jadb/table_names/radkfile.dart';
|
import 'package:jadb/table_names/radkfile.dart';
|
||||||
import 'package:jadb/util/romaji_transliteration.dart';
|
|
||||||
import 'package:sqflite_common/sqflite.dart';
|
import 'package:sqflite_common/sqflite.dart';
|
||||||
|
|
||||||
|
/// Searches for a kanji character and returns its details, or null if the kanji is not found in the database.
|
||||||
Future<KanjiSearchResult?> searchKanjiWithDbConnection(
|
Future<KanjiSearchResult?> searchKanjiWithDbConnection(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
String kanji,
|
String kanji,
|
||||||
@@ -214,6 +214,7 @@ Future<KanjiSearchResult?> searchKanjiWithDbConnection(
|
|||||||
|
|
||||||
// TODO: Use fewer queries with `IN` clauses to reduce the number of queries
|
// TODO: Use fewer queries with `IN` clauses to reduce the number of queries
|
||||||
|
|
||||||
|
/// Searches for multiple kanji at once, returning a map of kanji to their search results.
|
||||||
Future<Map<String, KanjiSearchResult>> searchManyKanjiWithDbConnection(
|
Future<Map<String, KanjiSearchResult>> searchManyKanjiWithDbConnection(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
Set<String> kanji,
|
Set<String> kanji,
|
||||||
|
|||||||
@@ -3,10 +3,16 @@ import 'package:sqflite_common/sqlite_api.dart';
|
|||||||
|
|
||||||
// TODO: validate that the list of radicals all are valid radicals
|
// TODO: validate that the list of radicals all are valid radicals
|
||||||
|
|
||||||
|
/// Returns a list of radicals that are part of any kanji that contains all of the input radicals.
|
||||||
|
///
|
||||||
|
/// This can be used to limit the choices of additional radicals provided to a user,
|
||||||
|
/// so that any choice they make will still yield at least one kanji.
|
||||||
Future<List<String>> searchRemainingRadicalsWithDbConnection(
|
Future<List<String>> searchRemainingRadicalsWithDbConnection(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
List<String> radicals,
|
List<String> radicals,
|
||||||
) async {
|
) async {
|
||||||
|
final distinctRadicals = radicals.toSet();
|
||||||
|
|
||||||
final queryResult = await connection.rawQuery(
|
final queryResult = await connection.rawQuery(
|
||||||
'''
|
'''
|
||||||
SELECT DISTINCT "radical"
|
SELECT DISTINCT "radical"
|
||||||
@@ -14,12 +20,12 @@ Future<List<String>> searchRemainingRadicalsWithDbConnection(
|
|||||||
WHERE "kanji" IN (
|
WHERE "kanji" IN (
|
||||||
SELECT "kanji"
|
SELECT "kanji"
|
||||||
FROM "${RADKFILETableNames.radkfile}"
|
FROM "${RADKFILETableNames.radkfile}"
|
||||||
WHERE "radical" IN (${List.filled(radicals.length, '?').join(',')})
|
WHERE "radical" IN (${List.filled(distinctRadicals.length, '?').join(',')})
|
||||||
GROUP BY "kanji"
|
GROUP BY "kanji"
|
||||||
HAVING COUNT(DISTINCT "radical") = ?
|
HAVING COUNT(DISTINCT "radical") = ?
|
||||||
)
|
)
|
||||||
''',
|
''',
|
||||||
[...radicals, radicals.length],
|
[...distinctRadicals, distinctRadicals.length],
|
||||||
);
|
);
|
||||||
|
|
||||||
final remainingRadicals = queryResult
|
final remainingRadicals = queryResult
|
||||||
@@ -29,19 +35,22 @@ Future<List<String>> searchRemainingRadicalsWithDbConnection(
|
|||||||
return remainingRadicals;
|
return remainingRadicals;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a list of kanji that contain all of the input radicals.
|
||||||
Future<List<String>> searchKanjiByRadicalsWithDbConnection(
|
Future<List<String>> searchKanjiByRadicalsWithDbConnection(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
List<String> radicals,
|
List<String> radicals,
|
||||||
) async {
|
) async {
|
||||||
|
final distinctRadicals = radicals.toSet();
|
||||||
|
|
||||||
final queryResult = await connection.rawQuery(
|
final queryResult = await connection.rawQuery(
|
||||||
'''
|
'''
|
||||||
SELECT "kanji"
|
SELECT "kanji"
|
||||||
FROM "${RADKFILETableNames.radkfile}"
|
FROM "${RADKFILETableNames.radkfile}"
|
||||||
WHERE "radical" IN (${List.filled(radicals.length, '?').join(',')})
|
WHERE "radical" IN (${List.filled(distinctRadicals.length, '?').join(',')})
|
||||||
GROUP BY "kanji"
|
GROUP BY "kanji"
|
||||||
HAVING COUNT(DISTINCT "radical") = ?
|
HAVING COUNT(DISTINCT "radical") = ?
|
||||||
''',
|
''',
|
||||||
[...radicals, radicals.length],
|
[...distinctRadicals, distinctRadicals.length],
|
||||||
);
|
);
|
||||||
|
|
||||||
final kanji = queryResult.map((row) => row['kanji'] as String).toList();
|
final kanji = queryResult.map((row) => row['kanji'] as String).toList();
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import 'package:sqflite_common/sqlite_api.dart';
|
|||||||
|
|
||||||
enum SearchMode { Auto, English, Kanji, MixedKanji, Kana, MixedKana }
|
enum SearchMode { Auto, English, Kanji, MixedKanji, Kana, MixedKana }
|
||||||
|
|
||||||
|
/// Searches for an input string, returning a list of results with their details. Returns null if the input string is empty.
|
||||||
Future<List<WordSearchResult>?> searchWordWithDbConnection(
|
Future<List<WordSearchResult>?> searchWordWithDbConnection(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
String word, {
|
String word, {
|
||||||
@@ -51,9 +52,14 @@ Future<List<WordSearchResult>?> searchWordWithDbConnection(
|
|||||||
linearWordQueryData: linearWordQueryData,
|
linearWordQueryData: linearWordQueryData,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for (final resultEntry in result) {
|
||||||
|
resultEntry.inferMatchSpans(word, searchMode: searchMode);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Searches for an input string, returning the amount of results that the search would yield without pagination.
|
||||||
Future<int?> searchWordCountWithDbConnection(
|
Future<int?> searchWordCountWithDbConnection(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
String word, {
|
String word, {
|
||||||
@@ -72,6 +78,7 @@ Future<int?> searchWordCountWithDbConnection(
|
|||||||
return entryIdCount;
|
return entryIdCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches a single word by its entry ID, returning null if not found.
|
||||||
Future<WordSearchResult?> getWordByIdWithDbConnection(
|
Future<WordSearchResult?> getWordByIdWithDbConnection(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
int id,
|
int id,
|
||||||
@@ -107,6 +114,7 @@ Future<WordSearchResult?> getWordByIdWithDbConnection(
|
|||||||
return result.firstOrNull;
|
return result.firstOrNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetches multiple words by their entry IDs, returning a map from entry ID to result.
|
||||||
Future<Map<int, WordSearchResult>> getWordsByIdsWithDbConnection(
|
Future<Map<int, WordSearchResult>> getWordsByIdsWithDbConnection(
|
||||||
DatabaseExecutor connection,
|
DatabaseExecutor connection,
|
||||||
Set<int> ids,
|
Set<int> ids,
|
||||||
|
|||||||
@@ -7,6 +7,29 @@ buildDartApplication {
|
|||||||
version = "1.0.0";
|
version = "1.0.0";
|
||||||
inherit src;
|
inherit src;
|
||||||
|
|
||||||
|
dartEntryPoints."bin/jadb" = "bin/jadb.dart";
|
||||||
|
|
||||||
|
# NOTE: the default dart hooks are using `dart compile`, which is not able to call the
|
||||||
|
# new dart build hooks required to use package:sqlite3 >= 3.0.0. So we override
|
||||||
|
# these phases to use `dart build` instead.
|
||||||
|
buildPhase = ''
|
||||||
|
runHook preBuild
|
||||||
|
|
||||||
|
mkdir -p "$out/bin"
|
||||||
|
dart build cli --target "bin/jadb.dart"
|
||||||
|
|
||||||
|
runHook postBuild
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
mkdir -p "$out"
|
||||||
|
mv build/cli/*/bundle/* "$out/"
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
autoPubspecLock = ../pubspec.lock;
|
autoPubspecLock = ../pubspec.lock;
|
||||||
|
|
||||||
meta.mainProgram = "jadb";
|
meta.mainProgram = "jadb";
|
||||||
|
|||||||
+55
-31
@@ -5,18 +5,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "5b7468c326d2f8a4f630056404ca0d291ade42918f4a3c6233618e724f39da8e"
|
sha256: "3b19a47f6ea7c2632760777c78174f47f6aec1e05f0cd611380d4593b8af1dbc"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "92.0.0"
|
version: "96.0.0"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: "70e4b1ef8003c64793a9e268a551a82869a8a96f39deb73dea28084b0e8bf75e"
|
sha256: "0c516bc4ad36a1a75759e54d5047cb9d15cded4459df01aa35a0b5ec7db2c2a0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.0.0"
|
version: "10.2.0"
|
||||||
args:
|
args:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -49,6 +49,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0"
|
version: "0.2.0"
|
||||||
|
code_assets:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: code_assets
|
||||||
|
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -85,26 +93,26 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: csv
|
name: csv
|
||||||
sha256: c6aa2679b2a18cb57652920f674488d89712efaf4d3fdf2e537215b35fc19d6c
|
sha256: bef2950f7a753eb82f894a2eabc3072e73cf21c17096296a5a992797e50b1d0d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "7.1.0"
|
||||||
equatable:
|
equatable:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: equatable
|
name: equatable
|
||||||
sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
|
sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.7"
|
version: "2.0.8"
|
||||||
ffi:
|
ffi:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: ffi
|
name: ffi
|
||||||
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.2.0"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -129,6 +137,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.3"
|
||||||
|
hooks:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hooks
|
||||||
|
sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -157,10 +173,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: lints
|
name: lints
|
||||||
sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0
|
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.1.0"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -181,10 +197,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
sha256: "9f29b9bcc8ee287b1a31e0d01be0eae99a930dbffdaecf04b3f3d82a969f296f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.0"
|
version: "1.18.1"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -193,6 +209,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
|
native_toolchain_c:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: native_toolchain_c
|
||||||
|
sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.17.4"
|
||||||
node_preamble:
|
node_preamble:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -221,10 +245,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: petitparser
|
name: petitparser
|
||||||
sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
|
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
version: "7.0.2"
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -293,10 +317,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_span
|
name: source_span
|
||||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.1"
|
version: "1.10.2"
|
||||||
sqflite_common:
|
sqflite_common:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -309,18 +333,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqflite_common_ffi
|
name: sqflite_common_ffi
|
||||||
sha256: "9faa2fedc5385ef238ce772589f7718c24cdddd27419b609bb9c6f703ea27988"
|
sha256: c59fcdc143839a77581f7a7c4de018e53682408903a0a0800b95ef2dc4033eff
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.6"
|
version: "2.4.0+2"
|
||||||
sqlite3:
|
sqlite3:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqlite3
|
name: sqlite3
|
||||||
sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2"
|
sha256: b7cf6b37667f6a921281797d2499ffc60fb878b161058d422064f0ddc78f6aa6
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.9.4"
|
version: "3.1.6"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -365,26 +389,26 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: test
|
name: test
|
||||||
sha256: "77cc98ea27006c84e71a7356cf3daf9ddbde2d91d84f77dbfe64cf0e4d9611ae"
|
sha256: "54c516bbb7cee2754d327ad4fca637f78abfc3cbcc5ace83b3eda117e42cd71a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.28.0"
|
version: "1.29.0"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "19a78f63e83d3a61f00826d09bc2f60e191bf3504183c001262be6ac75589fb8"
|
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.8"
|
version: "0.7.9"
|
||||||
test_core:
|
test_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_core
|
name: test_core
|
||||||
sha256: f1072617a6657e5fc09662e721307f7fb009b4ed89b19f47175d11d5254a62d4
|
sha256: "394f07d21f0f2255ec9e3989f21e54d3c7dc0e6e9dbce160e5a9c1a6be0e2943"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.14"
|
version: "0.6.15"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -405,10 +429,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: watcher
|
name: watcher
|
||||||
sha256: f52385d4f73589977c80797e60fe51014f7f2b957b5e9a62c3f6ada439889249
|
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.1"
|
||||||
web:
|
web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -458,4 +482,4 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.9.0 <4.0.0"
|
dart: ">=3.10.1 <4.0.0"
|
||||||
|
|||||||
+7
-2
@@ -9,12 +9,12 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
args: ^2.7.0
|
args: ^2.7.0
|
||||||
collection: ^1.19.0
|
collection: ^1.19.0
|
||||||
csv: ^6.0.0
|
csv: ^7.1.0
|
||||||
equatable: ^2.0.0
|
equatable: ^2.0.0
|
||||||
path: ^1.9.1
|
path: ^1.9.1
|
||||||
sqflite_common: ^2.5.0
|
sqflite_common: ^2.5.0
|
||||||
sqflite_common_ffi: ^2.3.0
|
sqflite_common_ffi: ^2.3.0
|
||||||
sqlite3: ^2.9.4
|
sqlite3: ^3.1.6
|
||||||
xml: ^6.5.0
|
xml: ^6.5.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
@@ -24,6 +24,11 @@ dev_dependencies:
|
|||||||
executables:
|
executables:
|
||||||
jadb: jadb
|
jadb: jadb
|
||||||
|
|
||||||
|
hooks:
|
||||||
|
user_defines:
|
||||||
|
sqlite3:
|
||||||
|
source: system
|
||||||
|
|
||||||
topics:
|
topics:
|
||||||
- database
|
- database
|
||||||
- dictionary
|
- dictionary
|
||||||
|
|||||||
@@ -4,20 +4,11 @@ import 'dart:io';
|
|||||||
import 'package:jadb/models/create_empty_db.dart';
|
import 'package:jadb/models/create_empty_db.dart';
|
||||||
import 'package:jadb/search.dart';
|
import 'package:jadb/search.dart';
|
||||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||||
import 'package:sqlite3/open.dart';
|
// import 'package:sqlite3/open.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
Future<DatabaseExecutor> setup_inmemory_database() async {
|
Future<DatabaseExecutor> setup_inmemory_database() async {
|
||||||
final libsqlitePath = Platform.environment['LIBSQLITE_PATH'];
|
final dbConnection = await createDatabaseFactoryFfi().openDatabase(':memory:');
|
||||||
|
|
||||||
if (libsqlitePath == null) {
|
|
||||||
throw Exception('LIBSQLITE_PATH is not set');
|
|
||||||
}
|
|
||||||
|
|
||||||
final dbConnection = await createDatabaseFactoryFfi(
|
|
||||||
ffiInit: () =>
|
|
||||||
open.overrideForAll(() => DynamicLibrary.open(libsqlitePath)),
|
|
||||||
).openDatabase(':memory:');
|
|
||||||
|
|
||||||
return dbConnection;
|
return dbConnection;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,257 @@
|
|||||||
|
import 'package:jadb/models/common/jlpt_level.dart';
|
||||||
|
import 'package:jadb/models/word_search/word_search_match_span.dart';
|
||||||
|
import 'package:jadb/models/word_search/word_search_result.dart';
|
||||||
|
import 'package:jadb/models/word_search/word_search_ruby.dart';
|
||||||
|
import 'package:jadb/models/word_search/word_search_sense.dart';
|
||||||
|
import 'package:jadb/models/word_search/word_search_sources.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('Infer match whole word', () {
|
||||||
|
final wordSearchResult = WordSearchResult(
|
||||||
|
entryId: 0,
|
||||||
|
score: 0,
|
||||||
|
isCommon: false,
|
||||||
|
jlptLevel: JlptLevel.none,
|
||||||
|
kanjiInfo: {},
|
||||||
|
readingInfo: {},
|
||||||
|
japanese: [WordSearchRuby(base: '仮名')],
|
||||||
|
senses: [],
|
||||||
|
sources: WordSearchSources.empty(),
|
||||||
|
);
|
||||||
|
|
||||||
|
wordSearchResult.inferMatchSpans('仮名');
|
||||||
|
|
||||||
|
expect(wordSearchResult.matchSpans, [
|
||||||
|
WordSearchMatchSpan(
|
||||||
|
spanType: WordSearchMatchSpanType.kanji,
|
||||||
|
start: 0,
|
||||||
|
end: 2,
|
||||||
|
index: 0,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Infer match part of word', () {
|
||||||
|
final wordSearchResult = WordSearchResult(
|
||||||
|
entryId: 0,
|
||||||
|
score: 0,
|
||||||
|
isCommon: false,
|
||||||
|
jlptLevel: JlptLevel.none,
|
||||||
|
kanjiInfo: {},
|
||||||
|
readingInfo: {},
|
||||||
|
japanese: [WordSearchRuby(base: '仮名')],
|
||||||
|
senses: [],
|
||||||
|
sources: WordSearchSources.empty(),
|
||||||
|
);
|
||||||
|
|
||||||
|
wordSearchResult.inferMatchSpans('仮');
|
||||||
|
|
||||||
|
expect(wordSearchResult.matchSpans, [
|
||||||
|
WordSearchMatchSpan(
|
||||||
|
spanType: WordSearchMatchSpanType.kanji,
|
||||||
|
start: 0,
|
||||||
|
end: 1,
|
||||||
|
index: 0,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Infer match in middle of word', () {
|
||||||
|
final wordSearchResult = WordSearchResult(
|
||||||
|
entryId: 0,
|
||||||
|
score: 0,
|
||||||
|
isCommon: false,
|
||||||
|
jlptLevel: JlptLevel.none,
|
||||||
|
kanjiInfo: {},
|
||||||
|
readingInfo: {},
|
||||||
|
japanese: [WordSearchRuby(base: 'ありがとう')],
|
||||||
|
senses: [],
|
||||||
|
sources: WordSearchSources.empty(),
|
||||||
|
);
|
||||||
|
|
||||||
|
wordSearchResult.inferMatchSpans('りがと');
|
||||||
|
|
||||||
|
expect(wordSearchResult.matchSpans, [
|
||||||
|
WordSearchMatchSpan(
|
||||||
|
spanType: WordSearchMatchSpanType.kanji,
|
||||||
|
start: 1,
|
||||||
|
end: 4,
|
||||||
|
index: 0,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Infer match in furigana', () {
|
||||||
|
final wordSearchResult = WordSearchResult(
|
||||||
|
entryId: 0,
|
||||||
|
score: 0,
|
||||||
|
isCommon: false,
|
||||||
|
jlptLevel: JlptLevel.none,
|
||||||
|
kanjiInfo: {},
|
||||||
|
readingInfo: {},
|
||||||
|
japanese: [WordSearchRuby(base: '仮名', furigana: 'かな')],
|
||||||
|
senses: [],
|
||||||
|
sources: WordSearchSources.empty(),
|
||||||
|
);
|
||||||
|
|
||||||
|
wordSearchResult.inferMatchSpans('かな');
|
||||||
|
|
||||||
|
expect(wordSearchResult.matchSpans, [
|
||||||
|
WordSearchMatchSpan(
|
||||||
|
spanType: WordSearchMatchSpanType.kana,
|
||||||
|
start: 0,
|
||||||
|
end: 2,
|
||||||
|
index: 0,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Infer match in sense', () {
|
||||||
|
final wordSearchResult = WordSearchResult(
|
||||||
|
entryId: 0,
|
||||||
|
score: 0,
|
||||||
|
isCommon: false,
|
||||||
|
jlptLevel: JlptLevel.none,
|
||||||
|
kanjiInfo: {},
|
||||||
|
readingInfo: {},
|
||||||
|
japanese: [WordSearchRuby(base: '仮名')],
|
||||||
|
senses: [
|
||||||
|
WordSearchSense(
|
||||||
|
antonyms: [],
|
||||||
|
dialects: [],
|
||||||
|
englishDefinitions: ['kana'],
|
||||||
|
fields: [],
|
||||||
|
info: [],
|
||||||
|
languageSource: [],
|
||||||
|
misc: [],
|
||||||
|
partsOfSpeech: [],
|
||||||
|
restrictedToKanji: [],
|
||||||
|
restrictedToReading: [],
|
||||||
|
seeAlso: [],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
sources: WordSearchSources.empty(),
|
||||||
|
);
|
||||||
|
|
||||||
|
wordSearchResult.inferMatchSpans('kana');
|
||||||
|
|
||||||
|
expect(wordSearchResult.matchSpans, [
|
||||||
|
WordSearchMatchSpan(
|
||||||
|
spanType: WordSearchMatchSpanType.sense,
|
||||||
|
start: 0,
|
||||||
|
end: 4,
|
||||||
|
index: 0,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Infer multiple matches', () {
|
||||||
|
final wordSearchResult = WordSearchResult(
|
||||||
|
entryId: 0,
|
||||||
|
score: 0,
|
||||||
|
isCommon: false,
|
||||||
|
jlptLevel: JlptLevel.none,
|
||||||
|
kanjiInfo: {},
|
||||||
|
readingInfo: {},
|
||||||
|
japanese: [WordSearchRuby(base: '仮名', furigana: 'かな')],
|
||||||
|
senses: [
|
||||||
|
WordSearchSense(
|
||||||
|
antonyms: [],
|
||||||
|
dialects: [],
|
||||||
|
englishDefinitions: ['kana', 'the kana'],
|
||||||
|
fields: [],
|
||||||
|
info: [],
|
||||||
|
languageSource: [],
|
||||||
|
misc: [],
|
||||||
|
partsOfSpeech: [],
|
||||||
|
restrictedToKanji: [],
|
||||||
|
restrictedToReading: [],
|
||||||
|
seeAlso: [],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
sources: WordSearchSources.empty(),
|
||||||
|
);
|
||||||
|
|
||||||
|
wordSearchResult.inferMatchSpans('kana');
|
||||||
|
|
||||||
|
expect(wordSearchResult.matchSpans, [
|
||||||
|
WordSearchMatchSpan(
|
||||||
|
spanType: WordSearchMatchSpanType.sense,
|
||||||
|
start: 0,
|
||||||
|
end: 4,
|
||||||
|
index: 0,
|
||||||
|
),
|
||||||
|
WordSearchMatchSpan(
|
||||||
|
spanType: WordSearchMatchSpanType.sense,
|
||||||
|
start: 4,
|
||||||
|
end: 8,
|
||||||
|
index: 0,
|
||||||
|
subIndex: 1,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Infer match with no matches', () {
|
||||||
|
final wordSearchResult = WordSearchResult(
|
||||||
|
entryId: 0,
|
||||||
|
score: 0,
|
||||||
|
isCommon: false,
|
||||||
|
jlptLevel: JlptLevel.none,
|
||||||
|
kanjiInfo: {},
|
||||||
|
readingInfo: {},
|
||||||
|
japanese: [WordSearchRuby(base: '仮名', furigana: 'かな')],
|
||||||
|
senses: [
|
||||||
|
WordSearchSense(
|
||||||
|
antonyms: [],
|
||||||
|
dialects: [],
|
||||||
|
englishDefinitions: ['kana'],
|
||||||
|
fields: [],
|
||||||
|
info: [],
|
||||||
|
languageSource: [],
|
||||||
|
misc: [],
|
||||||
|
partsOfSpeech: [],
|
||||||
|
restrictedToKanji: [],
|
||||||
|
restrictedToReading: [],
|
||||||
|
seeAlso: [],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
sources: WordSearchSources.empty(),
|
||||||
|
);
|
||||||
|
|
||||||
|
wordSearchResult.inferMatchSpans('xyz');
|
||||||
|
|
||||||
|
expect(wordSearchResult.matchSpans, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Infer multiple matches of same substring', () {
|
||||||
|
final wordSearchResult = WordSearchResult(
|
||||||
|
entryId: 0,
|
||||||
|
score: 0,
|
||||||
|
isCommon: false,
|
||||||
|
jlptLevel: JlptLevel.none,
|
||||||
|
kanjiInfo: {},
|
||||||
|
readingInfo: {},
|
||||||
|
japanese: [WordSearchRuby(base: 'ああ')],
|
||||||
|
senses: [],
|
||||||
|
sources: WordSearchSources.empty(),
|
||||||
|
);
|
||||||
|
|
||||||
|
wordSearchResult.inferMatchSpans('あ');
|
||||||
|
|
||||||
|
expect(wordSearchResult.matchSpans, [
|
||||||
|
WordSearchMatchSpan(
|
||||||
|
spanType: WordSearchMatchSpanType.kanji,
|
||||||
|
start: 0,
|
||||||
|
end: 1,
|
||||||
|
index: 0,
|
||||||
|
),
|
||||||
|
WordSearchMatchSpan(
|
||||||
|
spanType: WordSearchMatchSpanType.kanji,
|
||||||
|
start: 1,
|
||||||
|
end: 2,
|
||||||
|
index: 0,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user