cjk_index 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +29 -0
- data/lib/cjk_index/bridgetown.rb +2 -2
- data/lib/cjk_index/builder.rb +6 -4
- data/lib/cjk_index/cli.rb +2 -2
- data/lib/cjk_index/jekyll.rb +2 -2
- data/lib/cjk_index/normalizer.rb +44 -7
- data/lib/cjk_index/searcher.rb +1 -1
- data/lib/cjk_index/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b8780b3265cb3e02580edd66360721d6a9ea245c3b8465c49e499e0b1bb6d420
|
|
4
|
+
data.tar.gz: a9c1233d9e57d170ba90c25e7e0ecf9af6b10e89640584855dbe6fae5b358418
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 205fff662f662ae191a3bb437351531473bcbb8943758d13b7b6593aeabb425814fb8a4cc779f5a028b3d0ff2c6c895ab90efb49ed6d9a6423d31f63f62bb8ed
|
|
7
|
+
data.tar.gz: 4a82cf20daf35f34bb5596645e373e4c1902722f5fd91c6541a77c8039f6d97a97d8421c68d20761c29914140d4e8201aa3d5409b883458ff2417deb633dde6b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0 (2026-10-28)
|
|
4
|
+
|
|
5
|
+
- `variants` also takes table files of your own: one group per line,
|
|
6
|
+
tab-separated, the form to fold to first (the format of the itaiji lists
|
|
7
|
+
that collection databases keep). Paths are relative to the site's folder
|
|
8
|
+
(Jekyll, Bridgetown) or the current folder; the index records the path as
|
|
9
|
+
written and a digest of the contents. `Builder.load` and `Builder.read`
|
|
10
|
+
take `normalizer:` to read such an index from another folder.
|
|
11
|
+
- `Searcher.new(builder)` folds with the builder's normalizer.
|
|
12
|
+
|
|
3
13
|
## 1.0.0 (2026-10-28)
|
|
4
14
|
|
|
5
15
|
Indexes built with 0.x should be rebuilt: the `ja` table changed, and the
|
data/README.md
CHANGED
|
@@ -366,6 +366,35 @@ by Unicode normalization. `ko` is for old Hangul stored in the Private Use
|
|
|
366
366
|
Area by HWP and the Hanyang fonts; a query typed in standard jamo then finds
|
|
367
367
|
and highlights it.
|
|
368
368
|
|
|
369
|
+
### A table of your own
|
|
370
|
+
|
|
371
|
+
Any entry of `variants` that is not one of the names above is a table file
|
|
372
|
+
of your own, relative to the site's folder (Jekyll, Bridgetown) or the
|
|
373
|
+
current folder (CLI, Rake, Ruby). One group per line, tab-separated: the
|
|
374
|
+
form to fold to, then its variants, one character each. This is the format
|
|
375
|
+
of the itaiji lists that collection databases often keep already.
|
|
376
|
+
|
|
377
|
+
```text
|
|
378
|
+
# itaiji.tsv
|
|
379
|
+
与 與
|
|
380
|
+
宴 醼 讌
|
|
381
|
+
嶋 嶌
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
```yaml
|
|
385
|
+
cjk_index:
|
|
386
|
+
variants: [ja, _data/itaiji.tsv]
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Each cell is folded first (NFKC, lowercase, katakana → hiragana), and the
|
|
390
|
+
table listed first still picks the form a group folds to. The index records
|
|
391
|
+
the entry as written (`_data/itaiji.tsv`), not the full path, and its digest
|
|
392
|
+
covers the file's contents, so editing the file means rebuilding the index.
|
|
393
|
+
To read such an index from another folder in Ruby, pass the normalizer:
|
|
394
|
+
`Builder.load(json, normalizer: CJKIndex::Normalizer.new(tables: [...], base: dir))`.
|
|
395
|
+
Wide lists merge more than spelling: a list that puts 燕 with 宴 makes
|
|
396
|
+
a search for 燕 (swallow) find 宴 (banquet). Check a list against your own material first.
|
|
397
|
+
|
|
369
398
|
Browser script size, gzipped: no table 6.3 KB, `ja` 9.0 KB, `ja, zh-safe`
|
|
370
399
|
24.1 KB, `ja, zh` 25.9 KB, `ja, ko` 36.1 KB.
|
|
371
400
|
|
data/lib/cjk_index/bridgetown.rb
CHANGED
|
@@ -11,7 +11,7 @@ module CJKIndex
|
|
|
11
11
|
# Bridgetown.configure do |config|
|
|
12
12
|
# init :cjk_index,
|
|
13
13
|
# output: "cjk-index", # folder inside the output (default)
|
|
14
|
-
# variants: ["ja"], # variant tables: ja (default), zh
|
|
14
|
+
# variants: ["ja"], # variant tables: ja (default), zh-safe, zh, ko, or a file
|
|
15
15
|
# indexes: [
|
|
16
16
|
# { name: "posts", # -> output/cjk-index/posts.json
|
|
17
17
|
# collection: "posts", # a collection (or a list of them), or...
|
|
@@ -37,7 +37,7 @@ module CJKIndex
|
|
|
37
37
|
def write(site, config)
|
|
38
38
|
config = stringify(config)
|
|
39
39
|
output = config.fetch("output", DEFAULT_OUTPUT).to_s.gsub(%r{\A/+|/+\z}, "")
|
|
40
|
-
normalizer = Normalizer.new(tables: Array(config.fetch("variants", Normalizer::DEFAULT_TABLES)))
|
|
40
|
+
normalizer = Normalizer.new(tables: Array(config.fetch("variants", Normalizer::DEFAULT_TABLES)), base: site.root_dir)
|
|
41
41
|
put(site, output, RUNTIME_NAME, Runtime.source(normalizer: normalizer))
|
|
42
42
|
Array(config["indexes"]).each do |spec|
|
|
43
43
|
builder = build(site, stringify(spec), normalizer)
|
data/lib/cjk_index/builder.rb
CHANGED
|
@@ -129,7 +129,9 @@ module CJKIndex
|
|
|
129
129
|
# Reads an index written by to_json (String) or to_h back, to update it.
|
|
130
130
|
# Reads formats 2, 3 and 4; a split index is read with read.
|
|
131
131
|
# A value filter keeps the form of each value that the index stored.
|
|
132
|
-
|
|
132
|
+
# normalizer: needed when the index uses a table file of your own and
|
|
133
|
+
# the current folder is not the one it was built from.
|
|
134
|
+
def self.load(index, normalizer: nil)
|
|
133
135
|
data = index.is_a?(String) ? JSON.parse(index) : index
|
|
134
136
|
unless data.is_a?(Hash) && FORMATS.include?(data["format"])
|
|
135
137
|
raise ArgumentError, "cjk_index: unsupported index format #{data.is_a?(Hash) ? data['format'].inspect : data.class}"
|
|
@@ -137,7 +139,7 @@ module CJKIndex
|
|
|
137
139
|
raise ArgumentError, "cjk_index: a split index is read with Builder.read(directory)" unless data["tokens"]
|
|
138
140
|
|
|
139
141
|
filters = (data["filters"] || {}).transform_values { |f| f.slice("kind", "separator") }
|
|
140
|
-
normalizer
|
|
142
|
+
normalizer ||= Normalizer.new(tables: data["variants"])
|
|
141
143
|
normalizer.check!(data)
|
|
142
144
|
builder = new(fields: data["fields"], boosts: data["fields"].zip(data["boosts"]).to_h,
|
|
143
145
|
normalizer: normalizer, filters: filters)
|
|
@@ -147,8 +149,8 @@ module CJKIndex
|
|
|
147
149
|
|
|
148
150
|
# Reads an index from a file (written by write or to_json) or from the
|
|
149
151
|
# directory of a split index (written by write_split), to update it.
|
|
150
|
-
def self.read(path)
|
|
151
|
-
load(Builder.read_data(path))
|
|
152
|
+
def self.read(path, normalizer: nil)
|
|
153
|
+
load(Builder.read_data(path), normalizer: normalizer)
|
|
152
154
|
end
|
|
153
155
|
|
|
154
156
|
# The index at path as one Hash with "tokens", for a file or a split
|
data/lib/cjk_index/cli.rb
CHANGED
|
@@ -73,7 +73,7 @@ module CJKIndex
|
|
|
73
73
|
name, spec = parse_filter(v)
|
|
74
74
|
opts[:filters][name] = spec
|
|
75
75
|
end
|
|
76
|
-
o.on("--variants LIST", Array, "variant tables: ja (default), zh-safe, zh, ko") { |v| opts[:variants] = v }
|
|
76
|
+
o.on("--variants LIST", Array, "variant tables: ja (default), zh-safe, zh, ko, or table files") { |v| opts[:variants] = v }
|
|
77
77
|
o.on("-o", "--output PATH", "index file (default: standard output)") { |v| output = v }
|
|
78
78
|
o.on("--runtime PATH", "also write the browser script for these variants") { |v| runtime = v }
|
|
79
79
|
end
|
|
@@ -196,7 +196,7 @@ module CJKIndex
|
|
|
196
196
|
output = nil
|
|
197
197
|
OptionParser.new do |o|
|
|
198
198
|
o.banner = "Usage: cjk_index runtime [options]"
|
|
199
|
-
o.on("--variants LIST", Array, "variant tables: ja (default), zh-safe, zh, ko; must match the index") { |v| variants = v }
|
|
199
|
+
o.on("--variants LIST", Array, "variant tables: ja (default), zh-safe, zh, ko, or table files; must match the index") { |v| variants = v }
|
|
200
200
|
o.on("-o", "--output PATH", "script file (default: standard output)") { |v| output = v }
|
|
201
201
|
end.parse!(argv)
|
|
202
202
|
raise Error, "too many arguments: #{argv.join(' ')}" unless argv.empty?
|
data/lib/cjk_index/jekyll.rb
CHANGED
|
@@ -9,7 +9,7 @@ module CJKIndex
|
|
|
9
9
|
#
|
|
10
10
|
# cjk_index:
|
|
11
11
|
# output: assets/cjk-index # where files are written (default)
|
|
12
|
-
# variants: [ja] # variant tables: ja (default), zh
|
|
12
|
+
# variants: [ja] # variant tables: ja (default), zh-safe, zh, ko, or a file of your own
|
|
13
13
|
# indexes:
|
|
14
14
|
# - name: posts # -> assets/cjk-index/posts.json
|
|
15
15
|
# collection: posts # a Jekyll collection, or...
|
|
@@ -64,7 +64,7 @@ module CJKIndex
|
|
|
64
64
|
return unless config.is_a?(Hash)
|
|
65
65
|
|
|
66
66
|
output = config.fetch("output", DEFAULT_OUTPUT).sub(%r{\A/+}, "").sub(%r{/+\z}, "")
|
|
67
|
-
normalizer = Normalizer.new(tables: Array(config.fetch("variants", Normalizer::DEFAULT_TABLES)))
|
|
67
|
+
normalizer = Normalizer.new(tables: Array(config.fetch("variants", Normalizer::DEFAULT_TABLES)), base: site.source)
|
|
68
68
|
site.static_files << GeneratedFile.new(site, output, RUNTIME_NAME, Runtime.source(normalizer: normalizer))
|
|
69
69
|
Array(config["indexes"]).each do |spec|
|
|
70
70
|
builder = build(site, spec, normalizer)
|
data/lib/cjk_index/normalizer.rb
CHANGED
|
@@ -24,10 +24,19 @@ module CJKIndex
|
|
|
24
24
|
# With several tables, characters linked by any table fold to one form,
|
|
25
25
|
# preferring the target of the table listed first: with [ja, zh],
|
|
26
26
|
# 圖, 図 and 图 all become 図.
|
|
27
|
+
# Any other entry names a table file of your own, relative to base (the
|
|
28
|
+
# site's folder for Jekyll and Bridgetown, the current folder otherwise).
|
|
29
|
+
# The index records the entry as written, not the resolved path.
|
|
27
30
|
#
|
|
28
31
|
# Table format: UTF-8 lines "variant<TAB>folded", optionally followed by
|
|
29
32
|
# another tab and a comment column; lines starting with "#" are comments.
|
|
30
33
|
# "folded" may be several characters.
|
|
34
|
+
#
|
|
35
|
+
# Your own table is one group per line, tab-separated: the form to fold to,
|
|
36
|
+
# then its variants, one character each (the format of the itaiji lists
|
|
37
|
+
# that collection databases keep). Each cell is folded first (NFKC,
|
|
38
|
+
# lowercase, katakana -> hiragana), so a compatibility ideograph such as
|
|
39
|
+
# U+F900 joins the group of the character NFKC makes of it.
|
|
31
40
|
class Normalizer
|
|
32
41
|
DATA_DIR = File.expand_path("../../data", __dir__)
|
|
33
42
|
TABLES = {
|
|
@@ -48,11 +57,38 @@ module CJKIndex
|
|
|
48
57
|
@default ||= new
|
|
49
58
|
end
|
|
50
59
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
# Pairs [variant, folded] of a built-in table, or of a table file of your
|
|
61
|
+
# own (a name that is not built in, resolved against base).
|
|
62
|
+
def self.read_table(name, base: Dir.pwd)
|
|
63
|
+
return read_groups(name, File.expand_path(name, base)) unless TABLES.key?(name)
|
|
64
|
+
|
|
65
|
+
lines(File.join(DATA_DIR, TABLES[name])).map { |_, line| line.split("\t")[0, 2] }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.lines(path) # :nodoc:
|
|
69
|
+
File.readlines(path, chomp: true, encoding: "UTF-8").each_with_index
|
|
70
|
+
.reject { |line, _| line.strip.empty? || line.start_with?("#") }
|
|
71
|
+
.map { |line, i| [i + 1, line.delete_prefix("\uFEFF")] }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.read_groups(name, path) # :nodoc:
|
|
75
|
+
unless File.file?(path)
|
|
76
|
+
raise ArgumentError, "unknown variant table #{name.inspect}: not one of #{TABLES.keys.join(', ')}, " \
|
|
77
|
+
"and no file at #{path}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
plain = new(tables: [])
|
|
81
|
+
lines(path).flat_map do |number, line|
|
|
82
|
+
target, *variants = line.split("\t").map(&:strip).reject(&:empty?).map { |cell| plain.normalize(cell) }
|
|
83
|
+
variants = variants.uniq.reject { |v| v == target } # NFKC may already fold a cell (㍿)
|
|
84
|
+
variants.each do |v|
|
|
85
|
+
next if v.length == 1
|
|
86
|
+
|
|
87
|
+
raise ArgumentError, "#{name}:#{number}: #{v.inspect} is not one character; " \
|
|
88
|
+
"only the first cell (the form to fold to) may be longer"
|
|
89
|
+
end
|
|
90
|
+
variants.map { |v| [v, target] }
|
|
91
|
+
end
|
|
56
92
|
end
|
|
57
93
|
|
|
58
94
|
attr_reader :tables, :variants, :digest
|
|
@@ -60,8 +96,9 @@ module CJKIndex
|
|
|
60
96
|
KATAKANA = "#{KATAKANA_FIRST.chr(Encoding::UTF_8)}-#{KATAKANA_LAST.chr(Encoding::UTF_8)}"
|
|
61
97
|
HIRAGANA = "#{(KATAKANA_FIRST - KANA_OFFSET).chr(Encoding::UTF_8)}-#{(KATAKANA_LAST - KANA_OFFSET).chr(Encoding::UTF_8)}"
|
|
62
98
|
|
|
63
|
-
def initialize(tables: DEFAULT_TABLES)
|
|
99
|
+
def initialize(tables: DEFAULT_TABLES, base: Dir.pwd)
|
|
64
100
|
@tables = tables.map(&:to_s).uniq.freeze
|
|
101
|
+
@base = base
|
|
65
102
|
@variants = build_variants.freeze
|
|
66
103
|
keys = @variants.keys.select { |k| k.length == 1 }
|
|
67
104
|
@variant_chars = keys.empty? ? nil : Regexp.new("[#{keys.map { |k| format('\\u{%x}', k.ord) }.join}]")
|
|
@@ -122,7 +159,7 @@ module CJKIndex
|
|
|
122
159
|
end
|
|
123
160
|
rank = {}
|
|
124
161
|
@tables.each_with_index do |name, priority|
|
|
125
|
-
self.class.read_table(name).each do |from, to|
|
|
162
|
+
self.class.read_table(name, base: @base).each do |from, to|
|
|
126
163
|
parent[find.call(from)] = find.call(to)
|
|
127
164
|
rank[to] = [rank.fetch(to, [priority, to.ord]), [priority, to.ord]].min
|
|
128
165
|
end
|
data/lib/cjk_index/searcher.rb
CHANGED
|
@@ -57,7 +57,7 @@ module CJKIndex
|
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
@normalizer = normalizer || Normalizer.new(tables: data["variants"])
|
|
60
|
+
@normalizer = normalizer || @builder&.normalizer || Normalizer.new(tables: data["variants"])
|
|
61
61
|
@normalizer.check!(data)
|
|
62
62
|
|
|
63
63
|
if data["shards"] && !@load_shard
|
data/lib/cjk_index/version.rb
CHANGED