ucode 0.1.0 → 0.2.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 +72 -0
- data/TODO.full/00-README.md +116 -0
- data/TODO.full/01-panglyph-vision.md +112 -0
- data/TODO.full/02-panglyph-repo-bootstrap.md +184 -0
- data/TODO.full/03-panglyph-font-builder.md +201 -0
- data/TODO.full/04-panglyph-publish-pipeline.md +126 -0
- data/TODO.full/05-ucode-0-1-1-release.md +139 -0
- data/TODO.full/06-fontisan-remove-audit.md +142 -0
- data/TODO.full/07-fontisan-remove-ucd.md +125 -0
- data/TODO.full/08-archive-private-bin-build.md +143 -0
- data/TODO.full/09-archive-public-structure.md +164 -0
- data/TODO.full/10-fontist-org-woff-glyphs.md +131 -0
- data/TODO.full/11-fontist-org-audit-coverage.md +140 -0
- data/TODO.full/12-implementation-order.md +216 -0
- data/TODO.full/13-fontisan-font-writer-api.md +189 -0
- data/TODO.full/14-fontisan-table-writers.md +66 -0
- data/TODO.full/15-panglyph-builder-real.md +82 -0
- data/TODO.full/16-archive-public-sync-workflows.md +167 -0
- data/TODO.full/17-fontist-org-font-picker.md +73 -0
- data/TODO.full/18-comprehensive-spec-coverage.md +64 -0
- data/TODO.full/19-ucode-0-1-2-patch.md +32 -0
- data/TODO.full/20-fontisan-0-2-23-release.md +52 -0
- data/TODO.new/00-README.md +30 -0
- data/TODO.new/23-universal-glyph-set-source-map.md +312 -0
- data/TODO.new/24-universal-glyph-set-build.md +189 -0
- data/TODO.new/25-font-audit-against-universal-set.md +195 -0
- data/TODO.new/26-missing-glyph-reporter.md +189 -0
- data/TODO.new/27-fontist-org-consumer-integration.md +200 -0
- data/TODO.new/28-implementation-order-update.md +187 -0
- data/TODO.new/29-universal-set-curation-uc17.md +312 -0
- data/TODO.new/30-tier1-font-acquisition.md +241 -0
- data/TODO.new/31-universal-set-production-build.md +205 -0
- data/TODO.new/32-uc17-coverage-matrix.md +165 -0
- data/TODO.new/33-specialist-font-acquisition-refresh.md +138 -0
- data/TODO.new/34-pillar2-content-stream-correlator.md +147 -0
- data/TODO.new/35-universal-set-production-run.md +160 -0
- data/TODO.new/36-per-font-coverage-audit.md +145 -0
- data/TODO.new/37-coverage-highlight-reporter.md +125 -0
- data/TODO.new/38-fontist-org-glyph-consumer.md +141 -0
- data/TODO.new/39-implementation-order-update-32-38.md +258 -0
- data/TODO.new/40-archive-private-uses-ucode-audit.md +124 -0
- data/TODO.new/41-ucode-unicode-archive-bridge.md +160 -0
- data/config/specialist_fonts.yml +102 -0
- data/config/unicode17_tier1_fonts.yml +42 -0
- data/config/unicode17_universal_glyph_set.yml +293 -0
- data/lib/ucode/audit/block_aggregator.rb +57 -29
- data/lib/ucode/audit/browser/face_page.rb +128 -0
- data/lib/ucode/audit/browser/glyph_panel.rb +124 -0
- data/lib/ucode/audit/browser/library_page.rb +74 -0
- data/lib/ucode/audit/browser/missing_glyph_page.rb +87 -0
- data/lib/ucode/audit/browser/template.rb +47 -0
- data/lib/ucode/audit/browser/templates/face.css +200 -0
- data/lib/ucode/audit/browser/templates/face.html.erb +41 -0
- data/lib/ucode/audit/browser/templates/face.js +298 -0
- data/lib/ucode/audit/browser/templates/library.css +119 -0
- data/lib/ucode/audit/browser/templates/library.html.erb +42 -0
- data/lib/ucode/audit/browser/templates/library.js +99 -0
- data/lib/ucode/audit/browser/templates/missing_glyph_page.css +119 -0
- data/lib/ucode/audit/browser/templates/missing_glyph_page.html.erb +58 -0
- data/lib/ucode/audit/browser/templates/missing_glyph_page.js +2 -0
- data/lib/ucode/audit/browser.rb +32 -0
- data/lib/ucode/audit/context.rb +27 -1
- data/lib/ucode/audit/coverage_reference.rb +103 -0
- data/lib/ucode/audit/differ.rb +121 -0
- data/lib/ucode/audit/emitter/block_emitter.rb +52 -0
- data/lib/ucode/audit/emitter/codepoint_emitter.rb +87 -0
- data/lib/ucode/audit/emitter/collection_emitter.rb +80 -0
- data/lib/ucode/audit/emitter/face_directory.rb +212 -0
- data/lib/ucode/audit/emitter/glyph_emitter.rb +48 -0
- data/lib/ucode/audit/emitter/index_emitter.rb +149 -0
- data/lib/ucode/audit/emitter/library_emitter.rb +96 -0
- data/lib/ucode/audit/emitter/paths.rb +312 -0
- data/lib/ucode/audit/emitter/plane_emitter.rb +29 -0
- data/lib/ucode/audit/emitter/script_emitter.rb +29 -0
- data/lib/ucode/audit/emitter.rb +29 -0
- data/lib/ucode/audit/extractors/aggregations.rb +31 -2
- data/lib/ucode/audit/face_auditor.rb +86 -0
- data/lib/ucode/audit/formatters/audit_diff_text.rb +112 -0
- data/lib/ucode/audit/formatters/audit_text.rb +411 -0
- data/lib/ucode/audit/formatters/color.rb +48 -0
- data/lib/ucode/audit/formatters/library_summary_text.rb +98 -0
- data/lib/ucode/audit/formatters/text_formatter.rb +83 -0
- data/lib/ucode/audit/formatters.rb +23 -0
- data/lib/ucode/audit/library_aggregator.rb +86 -0
- data/lib/ucode/audit/library_auditor.rb +105 -0
- data/lib/ucode/audit/reference_factory.rb +66 -0
- data/lib/ucode/audit/release/emitter.rb +152 -0
- data/lib/ucode/audit/release/face_card.rb +93 -0
- data/lib/ucode/audit/release/formula_audits.rb +50 -0
- data/lib/ucode/audit/release/library_index_builder.rb +78 -0
- data/lib/ucode/audit/release/manifest_builder.rb +127 -0
- data/lib/ucode/audit/release.rb +42 -0
- data/lib/ucode/audit/ucd_only_reference.rb +81 -0
- data/lib/ucode/audit/universal_set_reference.rb +136 -0
- data/lib/ucode/audit.rb +32 -0
- data/lib/ucode/cli.rb +366 -41
- data/lib/ucode/commands/audit/browser_command.rb +82 -0
- data/lib/ucode/commands/audit/collection_command.rb +103 -0
- data/lib/ucode/commands/audit/compare_command.rb +188 -0
- data/lib/ucode/commands/audit/font_command.rb +140 -0
- data/lib/ucode/commands/audit/library_command.rb +87 -0
- data/lib/ucode/commands/audit.rb +19 -0
- data/lib/ucode/commands/block_feed.rb +73 -0
- data/lib/ucode/commands/build.rb +4 -0
- data/lib/ucode/commands/canonical_build.rb +137 -0
- data/lib/ucode/commands/fetch.rb +49 -15
- data/lib/ucode/commands/glyphs.rb +25 -67
- data/lib/ucode/commands/lookup.rb +11 -11
- data/lib/ucode/commands/parse.rb +7 -5
- data/lib/ucode/commands/release.rb +114 -0
- data/lib/ucode/commands/universal_set.rb +207 -0
- data/lib/ucode/commands.rb +5 -0
- data/lib/ucode/coordinator/indices.rb +49 -2
- data/lib/ucode/coordinator.rb +138 -5
- data/lib/ucode/error.rb +30 -2
- data/lib/ucode/fetch/font_fetcher/result.rb +39 -0
- data/lib/ucode/fetch/font_fetcher.rb +16 -0
- data/lib/ucode/fetch/specialist_font_fetcher.rb +280 -0
- data/lib/ucode/fetch.rb +7 -3
- data/lib/ucode/glyphs/pipeline.rb +106 -0
- data/lib/ucode/glyphs/real_fonts/cmap_cache.rb +74 -0
- data/lib/ucode/glyphs/real_fonts.rb +1 -0
- data/lib/ucode/glyphs/resolver.rb +62 -0
- data/lib/ucode/glyphs/source.rb +48 -0
- data/lib/ucode/glyphs/source_builder.rb +61 -0
- data/lib/ucode/glyphs/source_config/coverage_assertion.rb +79 -0
- data/lib/ucode/glyphs/source_config/gap_report.rb +54 -0
- data/lib/ucode/glyphs/source_config.rb +104 -0
- data/lib/ucode/glyphs/sources/pillar1_embedded_tounicode.rb +63 -0
- data/lib/ucode/glyphs/sources/pillar3_last_resort.rb +51 -0
- data/lib/ucode/glyphs/sources/tier1_real_font.rb +104 -0
- data/lib/ucode/glyphs/sources.rb +20 -0
- data/lib/ucode/glyphs/universal_set/builder.rb +161 -0
- data/lib/ucode/glyphs/universal_set/coverage_report.rb +139 -0
- data/lib/ucode/glyphs/universal_set/idempotency.rb +86 -0
- data/lib/ucode/glyphs/universal_set/manifest_accumulator.rb +195 -0
- data/lib/ucode/glyphs/universal_set/manifest_writer.rb +61 -0
- data/lib/ucode/glyphs/universal_set/pre_build_check.rb +197 -0
- data/lib/ucode/glyphs/universal_set/validator.rb +204 -0
- data/lib/ucode/glyphs/universal_set.rb +45 -0
- data/lib/ucode/glyphs.rb +7 -0
- data/lib/ucode/models/audit/baseline.rb +6 -0
- data/lib/ucode/models/audit/block_summary.rb +7 -0
- data/lib/ucode/models/audit/codepoint_provenance.rb +39 -0
- data/lib/ucode/models/audit/release_face.rb +42 -0
- data/lib/ucode/models/audit/release_formula.rb +33 -0
- data/lib/ucode/models/audit/release_manifest.rb +43 -0
- data/lib/ucode/models/audit/release_universal_set.rb +37 -0
- data/lib/ucode/models/audit.rb +9 -0
- data/lib/ucode/models/block.rb +2 -0
- data/lib/ucode/models/build_report.rb +109 -0
- data/lib/ucode/models/codepoint/glyph.rb +42 -0
- data/lib/ucode/models/codepoint.rb +3 -0
- data/lib/ucode/models/glyph_source.rb +86 -0
- data/lib/ucode/models/glyph_source_map.rb +138 -0
- data/lib/ucode/models/specialist_font.rb +70 -0
- data/lib/ucode/models/specialist_font_manifest.rb +48 -0
- data/lib/ucode/models/unihan_entry.rb +81 -9
- data/lib/ucode/models/unihan_field.rb +21 -0
- data/lib/ucode/models/universal_set_entry.rb +47 -0
- data/lib/ucode/models/universal_set_manifest.rb +78 -0
- data/lib/ucode/models/validation_report.rb +99 -0
- data/lib/ucode/models.rb +9 -0
- data/lib/ucode/parsers/named_sequences.rb +5 -5
- data/lib/ucode/parsers/unihan.rb +50 -19
- data/lib/ucode/repo/aggregate_writer.rb +82 -288
- data/lib/ucode/repo/block_feed_emitter.rb +153 -0
- data/lib/ucode/repo/build_report_accumulator.rb +138 -0
- data/lib/ucode/repo/build_report_writer.rb +46 -0
- data/lib/ucode/repo/build_validator.rb +229 -0
- data/lib/ucode/repo/codepoint_writer.rb +50 -1
- data/lib/ucode/repo/paths.rb +8 -0
- data/lib/ucode/repo/writers/blocks_writer.rb +73 -0
- data/lib/ucode/repo/writers/enums_writer.rb +38 -0
- data/lib/ucode/repo/writers/indexes_writer.rb +53 -0
- data/lib/ucode/repo/writers/manifest_writer.rb +78 -0
- data/lib/ucode/repo/writers/named_sequences_writer.rb +47 -0
- data/lib/ucode/repo/writers/planes_writer.rb +82 -0
- data/lib/ucode/repo/writers/relationships_writer.rb +71 -0
- data/lib/ucode/repo/writers/scripts_writer.rb +54 -0
- data/lib/ucode/repo/writers.rb +20 -0
- data/lib/ucode/repo.rb +5 -0
- data/lib/ucode/version.rb +1 -1
- data/schema/block-feed.output.schema.yml +134 -0
- metadata +158 -4
- data/Gemfile.lock +0 -406
data/lib/ucode/commands/fetch.rb
CHANGED
|
@@ -4,43 +4,47 @@ require "pathname"
|
|
|
4
4
|
|
|
5
5
|
require "ucode/cache"
|
|
6
6
|
require "ucode/fetch"
|
|
7
|
-
require "ucode/
|
|
7
|
+
require "ucode/glyphs/source_config"
|
|
8
|
+
require "ucode/parsers"
|
|
8
9
|
|
|
9
10
|
module Ucode
|
|
10
11
|
module Commands
|
|
11
12
|
# `ucode fetch` — downloads UCD/Unihan/Code-Charts sources into the
|
|
12
|
-
# per-version cache
|
|
13
|
+
# per-version cache, plus the specialist Tier 1 fonts referenced by
|
|
14
|
+
# the curated source config.
|
|
13
15
|
#
|
|
14
|
-
# Thin shell over `Ucode::Fetch::*`. The command
|
|
15
|
-
#
|
|
16
|
-
#
|
|
16
|
+
# Thin shell over `Ucode::Fetch::*`. The command takes a resolved
|
|
17
|
+
# version string; CLI callers resolve via {VersionResolver.resolve}
|
|
18
|
+
# once and thread it through. See Candidate 4 of the 2026-06-29
|
|
19
|
+
# architecture review.
|
|
17
20
|
class FetchCommand
|
|
18
|
-
|
|
21
|
+
DEFAULT_SPECIALIST_FONTS_MANIFEST =
|
|
22
|
+
Ucode::Glyphs::SourceConfig::DEFAULT_PATH.dirname.join("specialist_fonts.yml")
|
|
23
|
+
private_constant :DEFAULT_SPECIALIST_FONTS_MANIFEST
|
|
24
|
+
|
|
25
|
+
# @param version [String] resolved UCD version
|
|
19
26
|
# @param force [Boolean]
|
|
20
27
|
# @return [Hash] { version:, ucd_dir: }
|
|
21
|
-
def fetch_ucd(
|
|
22
|
-
version = VersionResolver.resolve(version_intent)
|
|
28
|
+
def fetch_ucd(version, force: false)
|
|
23
29
|
Cache.ensure_version_dir!(version)
|
|
24
30
|
path = Fetch::UcdZip.call(version, force: force)
|
|
25
31
|
{ version: version, ucd_dir: path }
|
|
26
32
|
end
|
|
27
33
|
|
|
28
|
-
# @param
|
|
34
|
+
# @param version [String] resolved UCD version
|
|
29
35
|
# @param force [Boolean]
|
|
30
36
|
# @return [Hash] { version:, unihan_dir: }
|
|
31
|
-
def fetch_unihan(
|
|
32
|
-
version = VersionResolver.resolve(version_intent)
|
|
37
|
+
def fetch_unihan(version, force: false)
|
|
33
38
|
Cache.ensure_version_dir!(version)
|
|
34
39
|
path = Fetch::UnihanZip.call(version, force: force)
|
|
35
40
|
{ version: version, unihan_dir: path }
|
|
36
41
|
end
|
|
37
42
|
|
|
38
|
-
# @param
|
|
43
|
+
# @param version [String] resolved UCD version
|
|
39
44
|
# @param block_first_cps [Array<Integer>, nil] nil = all known blocks
|
|
40
45
|
# @param force [Boolean]
|
|
41
46
|
# @return [Hash] { version:, downloaded: }
|
|
42
|
-
def fetch_charts(
|
|
43
|
-
version = VersionResolver.resolve(version_intent)
|
|
47
|
+
def fetch_charts(version, block_first_cps: nil, force: false)
|
|
44
48
|
Cache.ensure_version_dir!(version)
|
|
45
49
|
|
|
46
50
|
cps = block_first_cps || default_block_first_cps(version)
|
|
@@ -48,6 +52,36 @@ module Ucode
|
|
|
48
52
|
{ version: version, downloaded: count }
|
|
49
53
|
end
|
|
50
54
|
|
|
55
|
+
# Fetch specialist Tier 1 fonts listed in the manifest. Returns
|
|
56
|
+
# a structured summary; per-font detail lives on the returned
|
|
57
|
+
# results array (one {Fetch::FontFetcher::Result} per entry).
|
|
58
|
+
#
|
|
59
|
+
# @param manifest_path [String, Pathname, nil] defaults to
|
|
60
|
+
# `config/specialist_fonts.yml`.
|
|
61
|
+
# @param only_label [String, nil] restrict to one label.
|
|
62
|
+
# @param allow_proprietary [Boolean] required for non-OFL entries.
|
|
63
|
+
# @param dry_run [Boolean] plan only; no network or disk writes.
|
|
64
|
+
# @return [Hash] { manifest:, total:, downloaded:, skipped:,
|
|
65
|
+
# failed:, local:, planned:, results: }
|
|
66
|
+
def fetch_fonts(manifest_path: nil, only_label: nil, allow_proprietary: false,
|
|
67
|
+
dry_run: false)
|
|
68
|
+
path = Pathname.new(manifest_path || DEFAULT_SPECIALIST_FONTS_MANIFEST)
|
|
69
|
+
results = Fetch::SpecialistFontFetcher.new(
|
|
70
|
+
manifest_path: path,
|
|
71
|
+
allow_proprietary: allow_proprietary,
|
|
72
|
+
dry_run: dry_run,
|
|
73
|
+
).call(only_label: only_label)
|
|
74
|
+
|
|
75
|
+
{ manifest: path.to_s,
|
|
76
|
+
total: results.size,
|
|
77
|
+
downloaded: results.count(&:downloaded?),
|
|
78
|
+
skipped: results.count(&:skipped?),
|
|
79
|
+
failed: results.count(&:failed?),
|
|
80
|
+
local: results.count(&:local?),
|
|
81
|
+
planned: results.count(&:planned?),
|
|
82
|
+
results: results }
|
|
83
|
+
end
|
|
84
|
+
|
|
51
85
|
private
|
|
52
86
|
|
|
53
87
|
def default_block_first_cps(version)
|
|
@@ -59,4 +93,4 @@ module Ucode
|
|
|
59
93
|
end
|
|
60
94
|
end
|
|
61
95
|
end
|
|
62
|
-
end
|
|
96
|
+
end
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "pathname"
|
|
4
|
-
require "set"
|
|
5
4
|
|
|
6
|
-
require "ucode/cache"
|
|
7
5
|
require "ucode/glyphs"
|
|
8
|
-
require "ucode/parsers"
|
|
9
|
-
require "ucode/version_resolver"
|
|
10
6
|
|
|
11
7
|
module Ucode
|
|
12
8
|
module Commands
|
|
13
9
|
# `ucode glyphs` — extract per-codepoint SVGs from Code Charts PDFs.
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
10
|
+
# Thin Thor-facing wrapper around {Ucode::Glyphs::Pipeline}:
|
|
11
|
+
# opt-in gate + experimental warning live here; the pipeline
|
|
12
|
+
# assembly (block loading, fetcher, per-block specs) lives in
|
|
13
|
+
# {Ucode::Glyphs::Pipeline}.
|
|
17
14
|
#
|
|
18
15
|
# **Status (v0.1): EXPERIMENTAL.** The cell-extraction pipeline
|
|
19
16
|
# currently includes cell-border decorations alongside the actual
|
|
@@ -23,16 +20,16 @@ module Ucode
|
|
|
23
20
|
# pipeline can be iterated on without churning the CLI surface, but
|
|
24
21
|
# callers MUST opt in via `include_glyphs: true` (CLI: `--include-glyphs`)
|
|
25
22
|
# and will receive a printed warning. Tracked for v0.2.
|
|
23
|
+
#
|
|
24
|
+
# Takes a resolved version string; CLI callers resolve via
|
|
25
|
+
# {VersionResolver.resolve} once and thread it through. See
|
|
26
|
+
# Candidate 4 of the 2026-06-29 architecture review.
|
|
26
27
|
class GlyphsCommand
|
|
27
28
|
ExperimentalWarning = "ucode glyphs is experimental in v0.1: " \
|
|
28
29
|
"extracted SVGs include cell-border decorations " \
|
|
29
30
|
"alongside the character outline."
|
|
30
31
|
private_constant :ExperimentalWarning
|
|
31
32
|
|
|
32
|
-
MonolithPath = "CodeCharts.pdf"
|
|
33
|
-
PageMapCache = "data/codecharts_page_map.json"
|
|
34
|
-
private_constant :MonolithPath, :PageMapCache
|
|
35
|
-
|
|
36
33
|
class << self
|
|
37
34
|
# @return [String] the experimental-status banner. Exposed so the
|
|
38
35
|
# CLI and BuildCommand surface the same message verbatim.
|
|
@@ -41,7 +38,7 @@ module Ucode
|
|
|
41
38
|
end
|
|
42
39
|
end
|
|
43
40
|
|
|
44
|
-
# @param
|
|
41
|
+
# @param version [String] resolved UCD version
|
|
45
42
|
# @param output_root [String, Pathname]
|
|
46
43
|
# @param block_filter [Array<String>, nil] block ids to limit to;
|
|
47
44
|
# nil = every block
|
|
@@ -55,75 +52,36 @@ module Ucode
|
|
|
55
52
|
# written here exactly once before work begins.
|
|
56
53
|
# @return [Hash] aggregated Writer tally + version, or a `skipped`
|
|
57
54
|
# payload when opt-in is false.
|
|
58
|
-
def call(
|
|
59
|
-
block_filter: nil, force: false,
|
|
55
|
+
def call(version, output_root:,
|
|
56
|
+
block_filter: nil, force: false,
|
|
57
|
+
monolith_path: Glyphs::Pipeline::DEFAULT_MONOLITH_PATH,
|
|
60
58
|
include_glyphs: false, warn: nil)
|
|
61
|
-
return skipped(
|
|
59
|
+
return skipped(version) unless include_glyphs
|
|
62
60
|
|
|
63
61
|
warn&.puts(ExperimentalWarning)
|
|
64
|
-
version = VersionResolver.resolve(version_intent)
|
|
65
|
-
root = Pathname.new(output_root)
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
pipeline = Glyphs::Pipeline.new(
|
|
64
|
+
version: version,
|
|
65
|
+
block_filter: block_filter,
|
|
66
|
+
monolith_path: monolith_path,
|
|
67
|
+
)
|
|
68
|
+
specs = pipeline.build_specs(force: force)
|
|
70
69
|
|
|
71
|
-
writer = Glyphs::Writer.new(
|
|
72
|
-
|
|
70
|
+
writer = Glyphs::Writer.new(
|
|
71
|
+
output_root: Pathname.new(output_root),
|
|
72
|
+
parallel_workers: workers,
|
|
73
|
+
)
|
|
73
74
|
tally = writer.write_all(specs)
|
|
74
75
|
tally.merge(version: version, block_count: specs.size)
|
|
75
76
|
end
|
|
76
77
|
|
|
77
78
|
private
|
|
78
79
|
|
|
79
|
-
def load_blocks(version, block_filter)
|
|
80
|
-
ucd_dir = Cache.ucd_dir(version)
|
|
81
|
-
path = ucd_dir.join("Blocks.txt")
|
|
82
|
-
return [] unless path.exist?
|
|
83
|
-
|
|
84
|
-
all = Parsers::Blocks.each_record(path).to_a
|
|
85
|
-
return all unless block_filter && !block_filter.empty?
|
|
86
|
-
|
|
87
|
-
filter_set = block_filter.to_set
|
|
88
|
-
all.select { |block| filter_set.include?(block.id) }
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def build_fetcher(version, monolith_path, blocks)
|
|
92
|
-
monolith = Pathname.new(monolith_path)
|
|
93
|
-
monolith = monolith.exist? ? monolith : nil
|
|
94
|
-
Glyphs::PdfFetcher.new(
|
|
95
|
-
version,
|
|
96
|
-
monolith_path: monolith,
|
|
97
|
-
blocks: blocks,
|
|
98
|
-
page_map_cache: PageMapCache,
|
|
99
|
-
)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def spec_for(block, fetcher, force)
|
|
103
|
-
pdf_path = fetcher.fetch(block_first_cp: block.range_first, force: force)
|
|
104
|
-
return nil unless pdf_path
|
|
105
|
-
|
|
106
|
-
{ block: block, pdf_path: pdf_path, page_map: page_map_for(block) }
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Heuristic page map: per-block PDFs are page 1 = title, page 2 =
|
|
110
|
-
# first chart page starting at the block's first codepoint. True for
|
|
111
|
-
# most BMP blocks; multi-page blocks (CJK) need a richer resolver.
|
|
112
|
-
# Mismatches yield placeholder SVGs only — never wrong glyphs.
|
|
113
|
-
def page_map_for(block)
|
|
114
|
-
{ 2 => block.range_first }
|
|
115
|
-
end
|
|
116
|
-
|
|
117
80
|
def workers
|
|
118
81
|
Ucode.configuration.parallel_workers
|
|
119
82
|
end
|
|
120
83
|
|
|
121
|
-
def skipped(
|
|
122
|
-
version = begin
|
|
123
|
-
VersionResolver.resolve(version_intent)
|
|
124
|
-
rescue UnknownVersionError
|
|
125
|
-
version_intent
|
|
126
|
-
end
|
|
84
|
+
def skipped(version)
|
|
127
85
|
{
|
|
128
86
|
version: version,
|
|
129
87
|
skipped: true,
|
|
@@ -133,4 +91,4 @@ module Ucode
|
|
|
133
91
|
end
|
|
134
92
|
end
|
|
135
93
|
end
|
|
136
|
-
end
|
|
94
|
+
end
|
|
@@ -5,42 +5,42 @@ require "pathname"
|
|
|
5
5
|
require "ucode/cache"
|
|
6
6
|
require "ucode/database"
|
|
7
7
|
require "ucode/repo"
|
|
8
|
-
require "ucode/version_resolver"
|
|
9
8
|
|
|
10
9
|
module Ucode
|
|
11
10
|
module Commands
|
|
12
11
|
# `ucode lookup` — read-only lookups against the SQLite cache and
|
|
13
12
|
# the output JSON tree. Three subactions: block, script, char.
|
|
13
|
+
#
|
|
14
|
+
# Takes a resolved version string; CLI callers resolve via
|
|
15
|
+
# {VersionResolver.resolve} once and thread it through. See
|
|
16
|
+
# Candidate 4 of the 2026-06-29 architecture review.
|
|
14
17
|
class LookupCommand
|
|
15
18
|
BlockResult = Struct.new(:codepoint, :block, keyword_init: true)
|
|
16
19
|
ScriptResult = Struct.new(:codepoint, :script, keyword_init: true)
|
|
17
20
|
CharResult = Struct.new(:codepoint, :block_id, :glyph_path, keyword_init: true)
|
|
18
21
|
private_constant :BlockResult, :ScriptResult, :CharResult
|
|
19
22
|
|
|
20
|
-
# @param
|
|
23
|
+
# @param version [String] resolved UCD version
|
|
21
24
|
# @param codepoint [Integer]
|
|
22
25
|
# @return [BlockResult]
|
|
23
|
-
def lookup_block(
|
|
24
|
-
version = VersionResolver.resolve(version_intent)
|
|
26
|
+
def lookup_block(version, codepoint:)
|
|
25
27
|
with_db(version) { |db| db.lookup_block(codepoint) }
|
|
26
28
|
.then { |block| BlockResult.new(codepoint: codepoint, block: block) }
|
|
27
29
|
end
|
|
28
30
|
|
|
29
|
-
# @param
|
|
31
|
+
# @param version [String] resolved UCD version
|
|
30
32
|
# @param codepoint [Integer]
|
|
31
33
|
# @return [ScriptResult]
|
|
32
|
-
def lookup_script(
|
|
33
|
-
version = VersionResolver.resolve(version_intent)
|
|
34
|
+
def lookup_script(version, codepoint:)
|
|
34
35
|
with_db(version) { |db| db.lookup_script(codepoint) }
|
|
35
36
|
.then { |script| ScriptResult.new(codepoint: codepoint, script: script) }
|
|
36
37
|
end
|
|
37
38
|
|
|
38
|
-
# @param
|
|
39
|
+
# @param version [String] resolved UCD version
|
|
39
40
|
# @param codepoint [Integer]
|
|
40
41
|
# @param output_root [String, Pathname]
|
|
41
42
|
# @return [CharResult]
|
|
42
|
-
def lookup_char(
|
|
43
|
-
version = VersionResolver.resolve(version_intent)
|
|
43
|
+
def lookup_char(version, codepoint:, output_root:)
|
|
44
44
|
block_id = with_db(version) { |db| db.lookup_block(codepoint) }
|
|
45
45
|
glyph = block_id ? glyph_path(output_root, block_id, codepoint) : nil
|
|
46
46
|
CharResult.new(codepoint: codepoint, block_id: block_id, glyph_path: glyph)
|
|
@@ -62,4 +62,4 @@ module Ucode
|
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
|
-
end
|
|
65
|
+
end
|
data/lib/ucode/commands/parse.rb
CHANGED
|
@@ -6,19 +6,21 @@ require "ucode/cache"
|
|
|
6
6
|
require "ucode/coordinator"
|
|
7
7
|
require "ucode/parsers"
|
|
8
8
|
require "ucode/repo"
|
|
9
|
-
require "ucode/version_resolver"
|
|
10
9
|
|
|
11
10
|
module Ucode
|
|
12
11
|
module Commands
|
|
13
12
|
# `ucode parse` — streams the Coordinator output into the on-disk
|
|
14
13
|
# JSON tree at `output/`. Single pass: enrich + write per-cp JSON +
|
|
15
14
|
# accumulate aggregates + final flush.
|
|
15
|
+
#
|
|
16
|
+
# Takes a resolved version string; CLI callers resolve via
|
|
17
|
+
# {VersionResolver.resolve} once and thread it through. See
|
|
18
|
+
# Candidate 4 of the 2026-06-29 architecture review.
|
|
16
19
|
class ParseCommand
|
|
17
|
-
# @param
|
|
20
|
+
# @param version [String] resolved UCD version
|
|
18
21
|
# @param output_root [String, Pathname]
|
|
19
22
|
# @return [Hash] { version:, codepoint_count: }
|
|
20
|
-
def call(
|
|
21
|
-
version = VersionResolver.resolve(version_intent)
|
|
23
|
+
def call(version, output_root:)
|
|
22
24
|
root = Pathname.new(output_root)
|
|
23
25
|
ucd_dir = Cache.ucd_dir(version)
|
|
24
26
|
unihan_dir = Cache.unihan_dir(version)
|
|
@@ -59,4 +61,4 @@ module Ucode
|
|
|
59
61
|
end
|
|
60
62
|
end
|
|
61
63
|
end
|
|
62
|
-
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
require "time"
|
|
5
|
+
|
|
6
|
+
require "ucode/audit"
|
|
7
|
+
require "ucode/audit/library_auditor"
|
|
8
|
+
require "ucode/audit/release"
|
|
9
|
+
require "ucode/audit/emitter/paths"
|
|
10
|
+
|
|
11
|
+
module Ucode
|
|
12
|
+
module Commands
|
|
13
|
+
# `ucode release` — assemble the fontist.org-consumable release
|
|
14
|
+
# tree (TODO 27).
|
|
15
|
+
#
|
|
16
|
+
# Walks a directory of per-formula font subdirectories, audits each
|
|
17
|
+
# via {Audit::LibraryAuditor}, and passes the resulting
|
|
18
|
+
# {Audit::Release::FormulaAudits} list to {Audit::Release::Emitter}.
|
|
19
|
+
#
|
|
20
|
+
# The release tree lives at `<output_root>/font_audit_release/`.
|
|
21
|
+
# The CI collector job invokes this after matrix-auditing every
|
|
22
|
+
# formula and pre-staging the universal-set directory.
|
|
23
|
+
class ReleaseCommand
|
|
24
|
+
FormulaSource = Struct.new(:slug, :path, keyword_init: true)
|
|
25
|
+
|
|
26
|
+
Result = Struct.new(:release_root, :formulas_total, :faces_total,
|
|
27
|
+
:formulas, :universal_set_available,
|
|
28
|
+
:library_index_written, :manifest_written,
|
|
29
|
+
:error, keyword_init: true)
|
|
30
|
+
|
|
31
|
+
# @param from [String, Pathname] directory containing one
|
|
32
|
+
# subdirectory per formula. Each subdirectory's name becomes
|
|
33
|
+
# the formula slug; its contents are audited via
|
|
34
|
+
# {Audit::LibraryAuditor} (recursive walk).
|
|
35
|
+
# @param output_root [String, Pathname] parent of the release
|
|
36
|
+
# root. Release tree lives at
|
|
37
|
+
# `<output_root>/font_audit_release/`.
|
|
38
|
+
# @param universal_set_root [String, Pathname, nil] location of
|
|
39
|
+
# the universal_glyph_set directory. Defaults to
|
|
40
|
+
# `<release_root>/universal_glyph_set` inside the release tree.
|
|
41
|
+
# @param unicode_version [String, nil] baseline UCD version.
|
|
42
|
+
# @param recursive [Boolean] recursively walk each formula
|
|
43
|
+
# subdirectory. Default true.
|
|
44
|
+
# @param brief [Boolean] cheap-extractor-only audit mode.
|
|
45
|
+
# @param browse [Boolean] also emit per-face HTML browsers.
|
|
46
|
+
# @param source_config_sha256 [String, nil] sha256 of the Tier 1
|
|
47
|
+
# source-config YAML (TODO 23). Recorded in the manifest for
|
|
48
|
+
# curation provenance.
|
|
49
|
+
# @param reference [Audit::CoverageReference, nil] baseline
|
|
50
|
+
# forwarded to every per-face audit (TODO 25).
|
|
51
|
+
# @param generated_at [String] ISO8601 timestamp. Default: now.
|
|
52
|
+
# @return [Result]
|
|
53
|
+
def call(from:, output_root:, universal_set_root: nil, unicode_version: nil,
|
|
54
|
+
recursive: true, brief: false, browse: true,
|
|
55
|
+
source_config_sha256: nil, reference: nil,
|
|
56
|
+
generated_at: Time.now.utc.iso8601)
|
|
57
|
+
formula_sources = discover_formulas(from)
|
|
58
|
+
formulas = formula_sources.map do |src|
|
|
59
|
+
summary = audit_formula(src.path, recursive: recursive,
|
|
60
|
+
unicode_version: unicode_version,
|
|
61
|
+
brief: brief, reference: reference)
|
|
62
|
+
Ucode::Audit::Release::FormulaAudits.new(slug: src.slug, summary: summary)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
emitter = Ucode::Audit::Release::Emitter.new(
|
|
66
|
+
output_root: output_root,
|
|
67
|
+
universal_set_root: universal_set_root,
|
|
68
|
+
with_missing_glyph_pages: browse,
|
|
69
|
+
)
|
|
70
|
+
emit_result = emitter.emit(
|
|
71
|
+
formulas: formulas,
|
|
72
|
+
unicode_version: unicode_version,
|
|
73
|
+
generated_at: generated_at,
|
|
74
|
+
source_config_sha256: source_config_sha256,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
Result.new(
|
|
78
|
+
release_root: emit_result.release_root,
|
|
79
|
+
formulas_total: emit_result.formulas_total,
|
|
80
|
+
faces_total: emit_result.faces_total,
|
|
81
|
+
formulas: formula_sources,
|
|
82
|
+
universal_set_available: emit_result.universal_set_available,
|
|
83
|
+
library_index_written: emit_result.library_index_written,
|
|
84
|
+
manifest_written: emit_result.manifest_written,
|
|
85
|
+
)
|
|
86
|
+
rescue StandardError => e
|
|
87
|
+
Result.new(error: "#{e.class}: #{e.message}")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def discover_formulas(from)
|
|
93
|
+
Pathname.new(from).children.select(&:directory?).sort.map do |d|
|
|
94
|
+
FormulaSource.new(slug: d.basename.to_s, path: d.to_s)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def audit_formula(path, recursive:, unicode_version:, brief:, reference:)
|
|
99
|
+
options = audit_options(unicode_version: unicode_version, brief: brief)
|
|
100
|
+
auditor = Ucode::Audit::LibraryAuditor.new(
|
|
101
|
+
path, recursive: recursive, options: options, reference: reference
|
|
102
|
+
)
|
|
103
|
+
auditor.audit
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def audit_options(unicode_version:, brief:)
|
|
107
|
+
opts = {}
|
|
108
|
+
opts[:ucd_version] = unicode_version if unicode_version
|
|
109
|
+
opts[:audit_brief] = true if brief
|
|
110
|
+
opts
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "json"
|
|
5
|
+
require "pathname"
|
|
6
|
+
|
|
7
|
+
require "ucode/cache"
|
|
8
|
+
require "ucode/commands"
|
|
9
|
+
require "ucode/coordinator"
|
|
10
|
+
require "ucode/database"
|
|
11
|
+
require "ucode/glyphs"
|
|
12
|
+
require "ucode/models"
|
|
13
|
+
require "ucode/version"
|
|
14
|
+
require "ucode/version_resolver"
|
|
15
|
+
|
|
16
|
+
module Ucode
|
|
17
|
+
module Commands
|
|
18
|
+
# `ucode universal-set` subcommands (TODOs 24 + 31):
|
|
19
|
+
#
|
|
20
|
+
# - `BuildCommand` — drain the codepoint stream through the
|
|
21
|
+
# 4-tier resolver and write glyphs + manifest. Runs
|
|
22
|
+
# {PreBuildCheck} first; emits {CoverageReport} after.
|
|
23
|
+
# - `PreCheckCommand` — standalone pre-build validation
|
|
24
|
+
# (config + fonts + coverage assertion).
|
|
25
|
+
# - `ReportCommand` — re-emit coverage reports from an
|
|
26
|
+
# existing manifest. Useful for iterating on curation without
|
|
27
|
+
# re-running the build.
|
|
28
|
+
# - `ValidateCommand` — post-build structural validation
|
|
29
|
+
# (manifest parses, every entry has a glyph, totals reconcile,
|
|
30
|
+
# provenance recorded).
|
|
31
|
+
#
|
|
32
|
+
# The set is the canonical reference for "what Unicode 17 looks
|
|
33
|
+
# like" — every assigned codepoint has exactly one glyph, with
|
|
34
|
+
# documented provenance. Audits (TODO 25) and the fontist.org
|
|
35
|
+
# consumer (TODO 27) read the manifest to answer "is this
|
|
36
|
+
# codepoint in the universal set?" without re-reading every SVG.
|
|
37
|
+
module UniversalSet
|
|
38
|
+
# `ucode universal-set build` action class. Pure Ruby — Thor
|
|
39
|
+
# (in `lib/ucode/cli.rb`) is responsible only for argument
|
|
40
|
+
# parsing and dispatch.
|
|
41
|
+
class BuildCommand
|
|
42
|
+
# @param version [String] resolved UCD version
|
|
43
|
+
# @param output_root [String, Pathname] directory that will
|
|
44
|
+
# hold `manifest.json`, `glyphs/`, `reports/`.
|
|
45
|
+
# @param source_config_path [String, Pathname, nil] override
|
|
46
|
+
# the Tier 1 font config YAML; nil uses the default at
|
|
47
|
+
# `Ucode::Glyphs::SourceConfig::DEFAULT_PATH`.
|
|
48
|
+
# @param resolver [Ucode::Glyphs::Resolver, nil] inject a
|
|
49
|
+
# pre-built resolver (skips SourceBuilder + PreBuildCheck);
|
|
50
|
+
# used by tests.
|
|
51
|
+
# @param block_filter [String, nil] limit the build to one
|
|
52
|
+
# block (canonical underscore form). Useful for partial
|
|
53
|
+
# rebuilds when iterating on Tier 1 curation.
|
|
54
|
+
# @param parallel_workers [Integer] forwarded to the Builder.
|
|
55
|
+
# Defaults to {Ucode::Configuration#parallel_workers}.
|
|
56
|
+
# @param skip_pre_check [Boolean] when true, skip the
|
|
57
|
+
# {PreBuildCheck} step. Used by tests that inject a custom
|
|
58
|
+
# resolver and don't have a real source config on disk.
|
|
59
|
+
# @return [Hash] { version:, manifest_path:, totals:,
|
|
60
|
+
# by_tier:, coverage:, validation: }
|
|
61
|
+
def call(version, output_root:, source_config_path: nil,
|
|
62
|
+
resolver: nil, block_filter: nil,
|
|
63
|
+
parallel_workers: default_workers, skip_pre_check: false)
|
|
64
|
+
root = Pathname.new(output_root)
|
|
65
|
+
|
|
66
|
+
config_path = source_config_path_or_default(source_config_path)
|
|
67
|
+
sha = source_config_sha256(config_path)
|
|
68
|
+
database = Database.open(version)
|
|
69
|
+
|
|
70
|
+
run_pre_check(config_path, database) unless skip_pre_check
|
|
71
|
+
|
|
72
|
+
resolved_resolver = resolver || build_resolver(version, config_path, database)
|
|
73
|
+
|
|
74
|
+
builder = Glyphs::UniversalSet::Builder.new(
|
|
75
|
+
output_root: root,
|
|
76
|
+
resolver: resolved_resolver,
|
|
77
|
+
unicode_version: version,
|
|
78
|
+
ucode_version: Ucode::VERSION,
|
|
79
|
+
source_config_sha256: sha,
|
|
80
|
+
parallel_workers: parallel_workers,
|
|
81
|
+
block_filter: block_filter,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
manifest_path = builder.build(codepoint_enum(version))
|
|
85
|
+
|
|
86
|
+
manifest = Ucode::Models::UniversalSetManifest.from_hash(
|
|
87
|
+
JSON.parse(manifest_path.read),
|
|
88
|
+
)
|
|
89
|
+
coverage = Glyphs::UniversalSet::CoverageReport
|
|
90
|
+
.new(root, database: database).emit(manifest)
|
|
91
|
+
validation = Glyphs::UniversalSet::Validator
|
|
92
|
+
.new(root, unicode_version: version).validate
|
|
93
|
+
{
|
|
94
|
+
version: version,
|
|
95
|
+
manifest_path: manifest_path,
|
|
96
|
+
totals: manifest.totals.to_hash,
|
|
97
|
+
by_tier: manifest.by_tier,
|
|
98
|
+
coverage: coverage,
|
|
99
|
+
validation: validation,
|
|
100
|
+
}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def default_workers
|
|
106
|
+
Ucode.configuration.parallel_workers
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def source_config_path_or_default(path)
|
|
110
|
+
return Glyphs::SourceConfig::DEFAULT_PATH if path.nil?
|
|
111
|
+
|
|
112
|
+
Pathname.new(path)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def source_config_sha256(path)
|
|
116
|
+
return "" unless path.exist?
|
|
117
|
+
|
|
118
|
+
Digest::SHA256.file(path).hexdigest
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def run_pre_check(config_path, database)
|
|
122
|
+
Glyphs::UniversalSet::PreBuildCheck.new(
|
|
123
|
+
source_config_path: config_path,
|
|
124
|
+
database: database,
|
|
125
|
+
).call
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def build_resolver(_version, config_path, database)
|
|
129
|
+
config = Glyphs::SourceConfig.new(path: config_path)
|
|
130
|
+
builder = Glyphs::SourceBuilder.new(config: config, database: database)
|
|
131
|
+
Glyphs::Resolver.new(sources: builder.tier1_sources(install: false))
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def codepoint_enum(version)
|
|
135
|
+
ucd_dir = Cache.ucd_dir(version)
|
|
136
|
+
unihan_dir = Cache.unihan_dir(version)
|
|
137
|
+
Coordinator.new.each_codepoint(ucd_dir: ucd_dir, unihan_dir: unihan_dir)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# `ucode universal-set pre-check` — standalone pre-build
|
|
142
|
+
# validation. Runs the three TODO 31 §Pre-build validation
|
|
143
|
+
# checks (config loads, fonts present, coverage assertion runs)
|
|
144
|
+
# without starting the 4-hour build.
|
|
145
|
+
class PreCheckCommand
|
|
146
|
+
# @param version [String] resolved UCD version
|
|
147
|
+
# @param source_config_path [String, Pathname, nil]
|
|
148
|
+
# @param cmaps [#covers?] injectable; defaults to
|
|
149
|
+
# RealFonts::CmapCache.
|
|
150
|
+
# @param font_locator [#locate] injectable; defaults to a
|
|
151
|
+
# fresh FontLocator.
|
|
152
|
+
# @return [Ucode::Glyphs::UniversalSet::PreBuildReport]
|
|
153
|
+
# @raise [Ucode::UniversalSetPreBuildError] when missing_fonts
|
|
154
|
+
# is non-empty or the config fails to load.
|
|
155
|
+
def call(version, source_config_path: nil, cmaps: nil,
|
|
156
|
+
font_locator: nil)
|
|
157
|
+
database = Database.open(version)
|
|
158
|
+
config_path = source_config_path || Glyphs::SourceConfig::DEFAULT_PATH
|
|
159
|
+
|
|
160
|
+
kwargs = { source_config_path: config_path, database: database }
|
|
161
|
+
kwargs[:cmaps] = cmaps if cmaps
|
|
162
|
+
kwargs[:font_locator] = font_locator if font_locator
|
|
163
|
+
Glyphs::UniversalSet::PreBuildCheck.new(**kwargs).call
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# `ucode universal-set report` — re-emit coverage reports from
|
|
168
|
+
# an existing manifest. Useful when iterating on the manifest
|
|
169
|
+
# shape (or regenerating reports after a model change) without
|
|
170
|
+
# re-running the build.
|
|
171
|
+
class ReportCommand
|
|
172
|
+
# @param version [String] resolved UCD version
|
|
173
|
+
# @param output_root [String, Pathname] directory holding
|
|
174
|
+
# `manifest.json`.
|
|
175
|
+
# @return [Hash] the {CoverageReport#emit} payload.
|
|
176
|
+
def call(version, output_root:)
|
|
177
|
+
root = Pathname.new(output_root)
|
|
178
|
+
manifest_path = root.join("manifest.json")
|
|
179
|
+
raise Ucode::Error, "manifest not found at #{manifest_path}" unless manifest_path.exist?
|
|
180
|
+
|
|
181
|
+
manifest = Ucode::Models::UniversalSetManifest.from_hash(
|
|
182
|
+
JSON.parse(manifest_path.read),
|
|
183
|
+
)
|
|
184
|
+
database = Database.open(version)
|
|
185
|
+
Glyphs::UniversalSet::CoverageReport.new(root, database: database)
|
|
186
|
+
.emit(manifest)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# `ucode universal-set validate` — post-build structural
|
|
191
|
+
# validation. Reads `manifest.json` + `glyphs/` and runs the
|
|
192
|
+
# four checks (manifest_loadable, glyph_files_present,
|
|
193
|
+
# totals_reconcile, provenance_complete).
|
|
194
|
+
class ValidateCommand
|
|
195
|
+
# @param output_root [String, Pathname]
|
|
196
|
+
# @param version [String, nil] resolved UCD version, used only
|
|
197
|
+
# to stamp the report's unicode_version when the manifest's
|
|
198
|
+
# recorded value is missing.
|
|
199
|
+
# @return [Hash] the {Validator#validate} payload.
|
|
200
|
+
def call(output_root, version: nil)
|
|
201
|
+
Glyphs::UniversalSet::Validator
|
|
202
|
+
.new(output_root, unicode_version: version).validate
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
data/lib/ucode/commands.rb
CHANGED
|
@@ -14,6 +14,11 @@ module Ucode
|
|
|
14
14
|
autoload :LookupCommand, "ucode/commands/lookup"
|
|
15
15
|
autoload :CacheCommand, "ucode/commands/cache"
|
|
16
16
|
autoload :BuildCommand, "ucode/commands/build"
|
|
17
|
+
autoload :CanonicalBuildCommand, "ucode/commands/canonical_build"
|
|
17
18
|
autoload :FontCoverageCommand, "ucode/commands/font_coverage"
|
|
19
|
+
autoload :UniversalSet, "ucode/commands/universal_set"
|
|
20
|
+
autoload :Audit, "ucode/commands/audit"
|
|
21
|
+
autoload :ReleaseCommand, "ucode/commands/release"
|
|
22
|
+
autoload :BlockFeedCommand, "ucode/commands/block_feed"
|
|
18
23
|
end
|
|
19
24
|
end
|