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
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
require "ucode/audit"
|
|
7
|
+
require "ucode/audit/differ"
|
|
8
|
+
require "ucode/audit/face_auditor"
|
|
9
|
+
require "ucode/audit/formatters"
|
|
10
|
+
require "ucode/glyphs/real_fonts"
|
|
11
|
+
|
|
12
|
+
module Ucode
|
|
13
|
+
module Commands
|
|
14
|
+
module Audit
|
|
15
|
+
# `ucode audit compare LEFT RIGHT` — diff two audits.
|
|
16
|
+
#
|
|
17
|
+
# Each of LEFT and RIGHT can be:
|
|
18
|
+
# - A path to a font file (audited on-the-fly with
|
|
19
|
+
# {Audit::FaceAuditor}).
|
|
20
|
+
# - A path to a face audit directory — its `index.json` is
|
|
21
|
+
# read for the precomputed report shape.
|
|
22
|
+
# - A path to a saved `index.json` file directly.
|
|
23
|
+
#
|
|
24
|
+
# Note: reading from disk only recovers the *derived* overview
|
|
25
|
+
# shape from {Emitter::IndexEmitter}, not a full AuditReport.
|
|
26
|
+
# The compare therefore uses the field subset that the
|
|
27
|
+
# overview shape preserves (identity + coverage totals). For a
|
|
28
|
+
# full-feature diff, audit both inputs fresh from their font
|
|
29
|
+
# paths.
|
|
30
|
+
class CompareCommand
|
|
31
|
+
Result = Struct.new(:left_source, :right_source, :diff, :text,
|
|
32
|
+
:error, keyword_init: true)
|
|
33
|
+
|
|
34
|
+
# @param left [String] font path | audit dir | index.json path
|
|
35
|
+
# @param right [String] same forms as left
|
|
36
|
+
# @param unicode_version [String, nil]
|
|
37
|
+
# @param output_file [String, Pathname, nil] write text to file
|
|
38
|
+
# (default: stdout, captured as `text` in the result)
|
|
39
|
+
# @return [Result]
|
|
40
|
+
def call(left, right, unicode_version: nil, output_file: nil)
|
|
41
|
+
left_report = load_or_audit(left, unicode_version: unicode_version)
|
|
42
|
+
right_report = load_or_audit(right, unicode_version: unicode_version)
|
|
43
|
+
|
|
44
|
+
diff = Ucode::Audit::Differ.new(left_report, right_report).diff
|
|
45
|
+
text = Ucode::Audit::Formatters::AuditDiffText.new(diff).render
|
|
46
|
+
|
|
47
|
+
write_output(text, output_file) if output_file
|
|
48
|
+
|
|
49
|
+
Result.new(left_source: left, right_source: right, diff: diff,
|
|
50
|
+
text: text)
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
Result.new(left_source: left, right_source: right,
|
|
53
|
+
error: "#{e.class}: #{e.message}")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def load_or_audit(spec, unicode_version:)
|
|
59
|
+
case resolve_kind(spec)
|
|
60
|
+
when :font_file then audit_freshly(spec, unicode_version: unicode_version)
|
|
61
|
+
when :audit_dir then load_overview(Pathname.new(spec).join("index.json"))
|
|
62
|
+
when :index_json then load_overview(Pathname.new(spec))
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Heuristic: a path is an index.json if it ends in `.json`;
|
|
67
|
+
# an audit directory if it doesn't end in `.json` and isn't
|
|
68
|
+
# a font file; a font file otherwise.
|
|
69
|
+
def resolve_kind(spec)
|
|
70
|
+
path = Pathname.new(spec)
|
|
71
|
+
return :index_json if path.file? && path.extname == ".json"
|
|
72
|
+
return :audit_dir if path.directory?
|
|
73
|
+
return :audit_dir if path.join("index.json").exist?
|
|
74
|
+
|
|
75
|
+
:font_file
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def audit_freshly(font_path, unicode_version:)
|
|
79
|
+
options = {}
|
|
80
|
+
options[:ucd_version] = unicode_version if unicode_version
|
|
81
|
+
Ucode::Audit::FaceAuditor.new(font_path.to_s, options: options).call
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Reconstructs a partial AuditReport from the derived overview
|
|
85
|
+
# shape. Only the fields Differ consults are populated; others
|
|
86
|
+
# are nil. This is best-effort — see class docs.
|
|
87
|
+
def load_overview(index_json_path)
|
|
88
|
+
hash = JSON.parse(index_json_path.read)
|
|
89
|
+
font = hash["font"] || {}
|
|
90
|
+
totals = hash["totals"] || {}
|
|
91
|
+
Models::Audit::AuditReport.new(
|
|
92
|
+
source_file: font["source_file"],
|
|
93
|
+
source_sha256: font["source_sha256"],
|
|
94
|
+
family_name: font["family_name"],
|
|
95
|
+
subfamily_name: font["subfamily_name"],
|
|
96
|
+
full_name: font["full_name"],
|
|
97
|
+
postscript_name: font["postscript_name"],
|
|
98
|
+
version: font["version"],
|
|
99
|
+
font_revision: font["font_revision"],
|
|
100
|
+
weight_class: font["weight_class"],
|
|
101
|
+
width_class: font["width_class"],
|
|
102
|
+
italic: font["italic"],
|
|
103
|
+
bold: font["bold"],
|
|
104
|
+
panose: font["panose"],
|
|
105
|
+
total_codepoints: totals["covered_codepoints_total"] || font["total_codepoints"],
|
|
106
|
+
total_glyphs: font["total_glyphs"],
|
|
107
|
+
codepoint_ranges: codepoint_ranges(font["codepoint_ranges"]),
|
|
108
|
+
scripts: scripts(hash["script_summaries"]),
|
|
109
|
+
blocks: blocks(hash["block_summaries"]),
|
|
110
|
+
baseline: baseline(hash["baseline"]),
|
|
111
|
+
plane_summaries: plane_summaries(hash["plane_summaries"]),
|
|
112
|
+
discrepancies: [],
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def codepoint_ranges(arr)
|
|
117
|
+
return [] unless arr
|
|
118
|
+
|
|
119
|
+
arr.map do |h|
|
|
120
|
+
Models::Audit::CodepointRange.new(
|
|
121
|
+
first_cp: h["first_cp"], last_cp: h["last_cp"],
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def scripts(arr)
|
|
127
|
+
return [] unless arr
|
|
128
|
+
|
|
129
|
+
arr.map do |h|
|
|
130
|
+
Models::Audit::ScriptSummary.new(
|
|
131
|
+
script_code: h["script_code"], script_name: h["script_name"],
|
|
132
|
+
blocks_total: h["blocks_total"], assigned_total: h["assigned_total"],
|
|
133
|
+
covered_total: h["covered_total"], coverage_percent: h["coverage_percent"],
|
|
134
|
+
status: h["status"],
|
|
135
|
+
)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def blocks(arr)
|
|
140
|
+
return [] unless arr
|
|
141
|
+
|
|
142
|
+
arr.map do |h|
|
|
143
|
+
Models::Audit::BlockSummary.new(
|
|
144
|
+
name: h["name"], first_cp: h["first_cp"], last_cp: h["last_cp"],
|
|
145
|
+
range: h["range"], plane: h["plane"],
|
|
146
|
+
total_assigned: h["total_assigned"],
|
|
147
|
+
covered_count: h["covered_count"],
|
|
148
|
+
missing_count: h["missing_count"],
|
|
149
|
+
coverage_percent: h["coverage_percent"],
|
|
150
|
+
status: h["status"],
|
|
151
|
+
)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def baseline(hash)
|
|
156
|
+
return nil unless hash
|
|
157
|
+
|
|
158
|
+
Models::Audit::Baseline.new(
|
|
159
|
+
unicode_version: hash["unicode_version"],
|
|
160
|
+
ucode_version: hash["ucode_version"],
|
|
161
|
+
fontisan_version: hash["fontisan_version"],
|
|
162
|
+
source: hash["source"],
|
|
163
|
+
generated_at: hash["generated_at"],
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def plane_summaries(arr)
|
|
168
|
+
return [] unless arr
|
|
169
|
+
|
|
170
|
+
arr.map do |h|
|
|
171
|
+
Models::Audit::PlaneSummary.new(
|
|
172
|
+
plane: h["plane"], blocks_total: h["blocks_total"],
|
|
173
|
+
assigned_total: h["assigned_total"],
|
|
174
|
+
covered_total: h["covered_total"],
|
|
175
|
+
coverage_percent: h["coverage_percent"],
|
|
176
|
+
)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def write_output(text, target)
|
|
181
|
+
path = Pathname.new(target)
|
|
182
|
+
path.dirname.mkpath
|
|
183
|
+
path.write(text)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
require "ucode/glyphs/real_fonts"
|
|
7
|
+
require "ucode/audit"
|
|
8
|
+
require "ucode/audit/face_auditor"
|
|
9
|
+
require "ucode/audit/emitter/face_directory"
|
|
10
|
+
require "ucode/audit/emitter/paths"
|
|
11
|
+
|
|
12
|
+
module Ucode
|
|
13
|
+
module Commands
|
|
14
|
+
module Audit
|
|
15
|
+
# `ucode audit font PATH` — audit a single font file or
|
|
16
|
+
# fontist-resolvable name. Writes the per-face directory tree
|
|
17
|
+
# under `<output_root>/font_audit/<label>/`.
|
|
18
|
+
#
|
|
19
|
+
# Auto-detects collection sources (TTC/OTC/dfong) and falls
|
|
20
|
+
# through to collection-style emission in that case — one tree
|
|
21
|
+
# per face. For an explicit, intent-revealing form, use
|
|
22
|
+
# {CollectionCommand}.
|
|
23
|
+
#
|
|
24
|
+
# Pure: Thor never touches this. Real work is delegated to
|
|
25
|
+
# {Glyphs::RealFonts::FontLocator} (resolve spec → path),
|
|
26
|
+
# {Audit::FaceAuditor} (build report), and
|
|
27
|
+
# {Audit::Emitter::FaceDirectory} (write tree).
|
|
28
|
+
class FontCommand
|
|
29
|
+
FaceOutcome = Struct.new(:label, :postscript_name, :output_dir,
|
|
30
|
+
keyword_init: true)
|
|
31
|
+
|
|
32
|
+
Result = Struct.new(:spec, :label, :output_dir, :faces, :error,
|
|
33
|
+
keyword_init: true)
|
|
34
|
+
|
|
35
|
+
# @param spec [String] font spec — direct path, or `label=path`,
|
|
36
|
+
# or a fontist formula name.
|
|
37
|
+
# @param label [String, nil] output label override. Defaults
|
|
38
|
+
# to the report's postscript_name, or the file basename.
|
|
39
|
+
# @param unicode_version [String, nil] baseline UCD version.
|
|
40
|
+
# @param verbose [Boolean] emit per-codepoint detail chunks.
|
|
41
|
+
# @param with_glyphs [Boolean] emit per-codepoint SVG chunks
|
|
42
|
+
# (no-op until TODO 20 wires the 4-tier resolver).
|
|
43
|
+
# @param brief [Boolean] cheap-extractor-only mode.
|
|
44
|
+
# @param output_root [String, Pathname] parent directory; the
|
|
45
|
+
# audit root is `<output_root>/font_audit`.
|
|
46
|
+
# @param browse [Boolean] also write the HTML browsers.
|
|
47
|
+
# @param install [Boolean] allow fontist install on miss.
|
|
48
|
+
# @param reference [Ucode::Audit::CoverageReference, nil] the
|
|
49
|
+
# baseline to compare against (TODO 25). When nil, defaults
|
|
50
|
+
# to UCD-only inside {FaceAuditor}.
|
|
51
|
+
# @param universal_set_root [String, Pathname, nil] forwarded
|
|
52
|
+
# to {Emitter::FaceDirectory} for the face browser's
|
|
53
|
+
# universal-set section (TODO 26).
|
|
54
|
+
# @param with_missing_glyph_pages [Boolean] forward per-block
|
|
55
|
+
# standalone missing-glyph galleries (TODO 26).
|
|
56
|
+
# @return [Result]
|
|
57
|
+
def call(spec, output_root:, label: nil, unicode_version: nil, verbose: false,
|
|
58
|
+
with_glyphs: false, brief: false, browse: false,
|
|
59
|
+
install: true, reference: nil,
|
|
60
|
+
universal_set_root: nil, with_missing_glyph_pages: false)
|
|
61
|
+
located = locate(spec, install: install)
|
|
62
|
+
reports = Array(audit_faces(located.path, unicode_version: unicode_version,
|
|
63
|
+
brief: brief, reference: reference))
|
|
64
|
+
|
|
65
|
+
face_label = label || derived_face_label(reports.first, located)
|
|
66
|
+
sanitized = sanitize(face_label)
|
|
67
|
+
|
|
68
|
+
directory = Ucode::Audit::Emitter::FaceDirectory.new(
|
|
69
|
+
output_root: output_root,
|
|
70
|
+
verbose: verbose,
|
|
71
|
+
with_glyphs: with_glyphs,
|
|
72
|
+
emit_browser: browse,
|
|
73
|
+
universal_set_root: universal_set_root,
|
|
74
|
+
with_missing_glyph_pages: with_missing_glyph_pages,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
face_outcomes, top_dir =
|
|
78
|
+
if reports.one?
|
|
79
|
+
emit_standalone(directory, sanitized, reports.first)
|
|
80
|
+
else
|
|
81
|
+
emit_collection(directory, sanitized, reports, output_root)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
Result.new(spec: spec, label: face_label, output_dir: top_dir.to_s,
|
|
85
|
+
faces: face_outcomes)
|
|
86
|
+
rescue StandardError => e
|
|
87
|
+
Result.new(spec: spec, error: "#{e.class}: #{e.message}")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def locate(spec, install:)
|
|
93
|
+
Ucode::Glyphs::RealFonts::FontLocator.new.locate(spec, install: install)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def audit_faces(path, unicode_version:, brief:, reference: nil)
|
|
97
|
+
options = audit_options(unicode_version: unicode_version, brief: brief)
|
|
98
|
+
mode = brief ? :brief : :full
|
|
99
|
+
Ucode::Audit::FaceAuditor.new(path, options: options, mode: mode,
|
|
100
|
+
reference: reference).call
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def audit_options(unicode_version:, brief:)
|
|
104
|
+
opts = {}
|
|
105
|
+
opts[:ucd_version] = unicode_version if unicode_version
|
|
106
|
+
opts[:audit_brief] = true if brief
|
|
107
|
+
opts
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def derived_face_label(report, located)
|
|
111
|
+
report&.postscript_name || located.name || File.basename(located.path.to_s, ".*")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def emit_standalone(directory, label, report)
|
|
115
|
+
face_dir = directory.emit_face(label: label, report: report)
|
|
116
|
+
outcome = FaceOutcome.new(label: label,
|
|
117
|
+
postscript_name: report.postscript_name,
|
|
118
|
+
output_dir: face_dir.to_s)
|
|
119
|
+
[[outcome], face_dir]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def emit_collection(directory, source_label, reports, output_root)
|
|
123
|
+
subdirs = directory.emit_collection(source_label: source_label,
|
|
124
|
+
reports: reports)
|
|
125
|
+
top = Ucode::Audit::Emitter::Paths.face_dir(output_root, source_label)
|
|
126
|
+
outcomes = reports.zip(subdirs).map do |report, name|
|
|
127
|
+
FaceOutcome.new(label: name,
|
|
128
|
+
postscript_name: report.postscript_name,
|
|
129
|
+
output_dir: top.join(name).to_s)
|
|
130
|
+
end
|
|
131
|
+
[outcomes, top]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def sanitize(name)
|
|
135
|
+
(name || "face").to_s.gsub(/[^A-Za-z0-9._-]/, "_")
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
require "ucode/audit"
|
|
6
|
+
require "ucode/audit/library_auditor"
|
|
7
|
+
require "ucode/audit/emitter/face_directory"
|
|
8
|
+
require "ucode/audit/emitter/paths"
|
|
9
|
+
|
|
10
|
+
module Ucode
|
|
11
|
+
module Commands
|
|
12
|
+
module Audit
|
|
13
|
+
# `ucode audit library DIR` — walk a directory of fonts and
|
|
14
|
+
# produce one per-face audit plus a library-level rollup.
|
|
15
|
+
#
|
|
16
|
+
# Delegates directory walking + per-face audit to
|
|
17
|
+
# {Audit::LibraryAuditor}, then writes the per-face trees +
|
|
18
|
+
# library-level `index.json` via {Audit::Emitter::FaceDirectory}.
|
|
19
|
+
class LibraryCommand
|
|
20
|
+
SkippedFile = Struct.new(:path, :reason, keyword_init: true)
|
|
21
|
+
|
|
22
|
+
Result = Struct.new(:root, :total_files, :total_faces, :output_dir,
|
|
23
|
+
:skipped, :error, keyword_init: true)
|
|
24
|
+
|
|
25
|
+
# @param dir [String, Pathname] directory containing fonts.
|
|
26
|
+
# @param recursive [Boolean] walk subdirectories.
|
|
27
|
+
# @param unicode_version [String, nil] baseline UCD version.
|
|
28
|
+
# @param verbose [Boolean] per-codepoint detail chunks per face.
|
|
29
|
+
# @param with_glyphs [Boolean] per-codepoint SVG chunks.
|
|
30
|
+
# @param brief [Boolean] cheap-extractor-only mode.
|
|
31
|
+
# @param output_root [String, Pathname] parent of the audit root.
|
|
32
|
+
# @param browse [Boolean] also write library + face HTML browsers.
|
|
33
|
+
# @param reference [Ucode::Audit::CoverageReference, nil] baseline
|
|
34
|
+
# forwarded to every per-face audit (TODO 25).
|
|
35
|
+
# @param universal_set_root [String, Pathname, nil] forwarded to
|
|
36
|
+
# {Emitter::FaceDirectory} for the face browser's
|
|
37
|
+
# universal-set section (TODO 26).
|
|
38
|
+
# @param with_missing_glyph_pages [Boolean] emit per-block
|
|
39
|
+
# standalone missing-glyph galleries per face (TODO 26).
|
|
40
|
+
# @return [Result]
|
|
41
|
+
def call(dir, output_root:, recursive: false, unicode_version: nil, verbose: false,
|
|
42
|
+
with_glyphs: false, brief: false, browse: false, reference: nil,
|
|
43
|
+
universal_set_root: nil, with_missing_glyph_pages: false)
|
|
44
|
+
options = library_options(unicode_version: unicode_version, brief: brief)
|
|
45
|
+
auditor = Ucode::Audit::LibraryAuditor.new(dir, recursive: recursive,
|
|
46
|
+
options: options,
|
|
47
|
+
reference: reference)
|
|
48
|
+
summary = auditor.audit
|
|
49
|
+
|
|
50
|
+
directory = Ucode::Audit::Emitter::FaceDirectory.new(
|
|
51
|
+
output_root: output_root,
|
|
52
|
+
verbose: verbose,
|
|
53
|
+
with_glyphs: with_glyphs,
|
|
54
|
+
emit_browser: browse,
|
|
55
|
+
universal_set_root: universal_set_root,
|
|
56
|
+
with_missing_glyph_pages: with_missing_glyph_pages,
|
|
57
|
+
)
|
|
58
|
+
directory.emit_library(summary: summary)
|
|
59
|
+
|
|
60
|
+
Result.new(
|
|
61
|
+
root: dir.to_s,
|
|
62
|
+
total_files: summary.total_files,
|
|
63
|
+
total_faces: summary.total_faces,
|
|
64
|
+
output_dir: Ucode::Audit::Emitter::Paths.library_root(output_root).to_s,
|
|
65
|
+
skipped: auditor.skipped.map { |s| parse_skipped(s) },
|
|
66
|
+
)
|
|
67
|
+
rescue StandardError => e
|
|
68
|
+
Result.new(root: dir.to_s, error: "#{e.class}: #{e.message}")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def library_options(unicode_version:, brief:)
|
|
74
|
+
opts = {}
|
|
75
|
+
opts[:ucd_version] = unicode_version if unicode_version
|
|
76
|
+
opts[:audit_brief] = true if brief
|
|
77
|
+
opts
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def parse_skipped(entry)
|
|
81
|
+
path, _, reason = entry.rpartition(": ")
|
|
82
|
+
SkippedFile.new(path: path, reason: reason)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ucode
|
|
4
|
+
module Commands
|
|
5
|
+
# `ucode audit *` command classes. Pure Ruby — Thor (in
|
|
6
|
+
# `lib/ucode/cli.rb`) is responsible only for argument parsing
|
|
7
|
+
# and dispatch. Each class here is a structured-result command
|
|
8
|
+
# that delegates to the {Ucode::Audit} pipeline
|
|
9
|
+
# ({Audit::FaceAuditor}, {Audit::LibraryAuditor}, {Audit::Differ},
|
|
10
|
+
# {Audit::Emitter::FaceDirectory}, {Audit::Browser::*}).
|
|
11
|
+
module Audit
|
|
12
|
+
autoload :FontCommand, "ucode/commands/audit/font_command"
|
|
13
|
+
autoload :CollectionCommand, "ucode/commands/audit/collection_command"
|
|
14
|
+
autoload :LibraryCommand, "ucode/commands/audit/library_command"
|
|
15
|
+
autoload :CompareCommand, "ucode/commands/audit/compare_command"
|
|
16
|
+
autoload :BrowserCommand, "ucode/commands/audit/browser_command"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
require "ucode/repo"
|
|
6
|
+
require "ucode/version_resolver"
|
|
7
|
+
|
|
8
|
+
module Ucode
|
|
9
|
+
module Commands
|
|
10
|
+
# `ucode block-feed` — emit a compact per-block Unicode data feed
|
|
11
|
+
# from ucode's canonical output tree.
|
|
12
|
+
#
|
|
13
|
+
# Reads ucode's `output/` and produces three artifacts at the target
|
|
14
|
+
# directory:
|
|
15
|
+
#
|
|
16
|
+
# <target>/unicode-blocks.json
|
|
17
|
+
# <target>/unicode-version.json
|
|
18
|
+
# <target>/unicode/blocks/<slug>.json
|
|
19
|
+
#
|
|
20
|
+
# Each per-block file contains the codepoints in that block with
|
|
21
|
+
# their compact Unicode metadata (name, general category, script,
|
|
22
|
+
# combining class, bidi class, mirrored flag). Block slugs are
|
|
23
|
+
# derived from the block name via the standard slug algorithm.
|
|
24
|
+
class BlockFeedCommand
|
|
25
|
+
Result = Struct.new(:ucode_output_root, :block_feed_output_root,
|
|
26
|
+
:unicode_version, :blocks_written,
|
|
27
|
+
:codepoints_written, :unicode_blocks_path,
|
|
28
|
+
:unicode_version_path, keyword_init: true)
|
|
29
|
+
|
|
30
|
+
# @param ucode_output_root [String, Pathname] ucode's `output/`
|
|
31
|
+
# (must contain blocks/index.json, blocks/<ID>/index.json,
|
|
32
|
+
# index/labels.json).
|
|
33
|
+
# @param block_feed_output_root [String, Pathname] target directory.
|
|
34
|
+
# @param unicode_version [String, nil] UCD version to stamp on
|
|
35
|
+
# unicode-version.json. Defaults to the version recorded in
|
|
36
|
+
# ucode's manifest.json.
|
|
37
|
+
# @return [Result]
|
|
38
|
+
def call(ucode_output_root:, block_feed_output_root:, unicode_version: nil)
|
|
39
|
+
ucode_root = Pathname.new(ucode_output_root)
|
|
40
|
+
feed_root = Pathname.new(block_feed_output_root)
|
|
41
|
+
version = unicode_version || manifest_version(ucode_root)
|
|
42
|
+
|
|
43
|
+
emitter = Repo::BlockFeedEmitter.new(ucode_root, feed_root)
|
|
44
|
+
outcome = emitter.emit(ucd_version: version)
|
|
45
|
+
|
|
46
|
+
Result.new(
|
|
47
|
+
ucode_output_root: ucode_root.to_s,
|
|
48
|
+
block_feed_output_root: feed_root.to_s,
|
|
49
|
+
unicode_version: version,
|
|
50
|
+
blocks_written: outcome[:blocks_written],
|
|
51
|
+
codepoints_written: outcome[:codepoints_written],
|
|
52
|
+
unicode_blocks_path: outcome[:unicode_blocks_path],
|
|
53
|
+
unicode_version_path: outcome[:unicode_version_path],
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def manifest_version(ucode_root)
|
|
60
|
+
manifest = ucode_root.join("manifest.json")
|
|
61
|
+
return default_version unless manifest.exist?
|
|
62
|
+
|
|
63
|
+
JSON.parse(manifest.read)["ucd_version"] || default_version
|
|
64
|
+
rescue JSON::ParserError
|
|
65
|
+
default_version
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def default_version
|
|
69
|
+
VersionResolver.resolve(nil)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
data/lib/ucode/commands/build.rb
CHANGED
|
@@ -11,6 +11,10 @@ module Ucode
|
|
|
11
11
|
# parse → (optional) glyphs → (optional) site. Resumable: each step
|
|
12
12
|
# is idempotent and safe to re-run.
|
|
13
13
|
#
|
|
14
|
+
# Resolves the version intent once at the top and threads the
|
|
15
|
+
# resolved string through every sub-command. See Candidate 4 of the
|
|
16
|
+
# 2026-06-29 architecture review.
|
|
17
|
+
#
|
|
14
18
|
# **Glyph step is opt-in as of v0.1** because the SVG cell extractor
|
|
15
19
|
# is still experimental. Pass `include_glyphs: true` to enable it;
|
|
16
20
|
# otherwise the glyphs step is recorded as skipped.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
require "ucode/cache"
|
|
6
|
+
require "ucode/commands"
|
|
7
|
+
require "ucode/coordinator"
|
|
8
|
+
require "ucode/database"
|
|
9
|
+
require "ucode/glyphs"
|
|
10
|
+
require "ucode/models"
|
|
11
|
+
require "ucode/repo"
|
|
12
|
+
require "ucode/version"
|
|
13
|
+
require "ucode/version_resolver"
|
|
14
|
+
|
|
15
|
+
module Ucode
|
|
16
|
+
module Commands
|
|
17
|
+
# `ucode canonical-build` — Mode 1's canonical Unicode dataset build
|
|
18
|
+
# (TODO 21). Single pass: enrich each codepoint via {Coordinator},
|
|
19
|
+
# resolve its glyph via the 4-tier {Ucode::Glyphs::Resolver}, write
|
|
20
|
+
# `index.json` + `glyph.svg` atomically, accumulate per-tier +
|
|
21
|
+
# per-block stats, and emit `output/build-report.json`.
|
|
22
|
+
#
|
|
23
|
+
# This is the v0.2 replacement for the v0.1 cell-extractor pipeline
|
|
24
|
+
# in {GlyphsCommand}. The two coexist until the v0.1 pipeline is
|
|
25
|
+
# removed (TODOs 17-19); CanonicalBuildCommand is the path forward
|
|
26
|
+
# for production dataset runs.
|
|
27
|
+
#
|
|
28
|
+
# == Pre-conditions (per TODO 21)
|
|
29
|
+
#
|
|
30
|
+
# 1. UCD + Unihan fetched for `version` (`ucode fetch ucd`,
|
|
31
|
+
# `ucode fetch unihan`).
|
|
32
|
+
# 2. Ucode::Database built for `version` (`ucode db build`).
|
|
33
|
+
# 3. Tier 1 fonts resolvable via the configured SourceConfig YAML.
|
|
34
|
+
# 4. Code Charts PDFs cached (for Pillar 1) — optional, only if
|
|
35
|
+
# pillar-1 sources are configured.
|
|
36
|
+
# 5. Last Resort UFO cloned (for Pillar 3) — optional, only if
|
|
37
|
+
# pillar-3 fallback is configured.
|
|
38
|
+
#
|
|
39
|
+
# Missing pre-conditions cause silent fallthrough to lower tiers;
|
|
40
|
+
# the build report's `by_tier` totals surface what ran.
|
|
41
|
+
class CanonicalBuildCommand
|
|
42
|
+
# @param version [String] resolved UCD version
|
|
43
|
+
# @param output_root [String, Pathname]
|
|
44
|
+
# @param source_config_path [String, Pathname, nil] override the
|
|
45
|
+
# Tier 1 font config YAML; nil uses the default
|
|
46
|
+
# (`config/unicode17_tier1_fonts.yml`).
|
|
47
|
+
# @param resolver [Ucode::Glyphs::Resolver, nil] inject a
|
|
48
|
+
# pre-built resolver (skips SourceBuilder); used by tests.
|
|
49
|
+
# @param validate [Boolean] run {Ucode::Repo::BuildValidator}
|
|
50
|
+
# after the build and emit `validation-report.json`. Default
|
|
51
|
+
# true; tests that don't care about validation pass false.
|
|
52
|
+
# @param baseline [Hash{String=>Integer}, nil] per-block expected
|
|
53
|
+
# built counts forwarded to the validator when `validate:` is
|
|
54
|
+
# true. nil skips the block_coverage check.
|
|
55
|
+
# @return [Hash] { version:, codepoint_count:, report_path:,
|
|
56
|
+
# validation_report_path:, validation_passed: }
|
|
57
|
+
def call(version, output_root:, source_config_path: nil,
|
|
58
|
+
resolver: nil, validate: true, baseline: nil)
|
|
59
|
+
root = Pathname.new(output_root)
|
|
60
|
+
|
|
61
|
+
resolved_resolver = resolver || build_resolver(version, source_config_path)
|
|
62
|
+
accumulator = Repo::BuildReportAccumulator.new(
|
|
63
|
+
unicode_version: version,
|
|
64
|
+
ucode_version: Ucode::VERSION,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
coordinator = Coordinator.new
|
|
68
|
+
writer = Repo::CodepointWriter.new(
|
|
69
|
+
root,
|
|
70
|
+
parallel_workers: workers,
|
|
71
|
+
resolver: resolved_resolver,
|
|
72
|
+
observer: accumulator,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
ucd_dir = Cache.ucd_dir(version)
|
|
76
|
+
unihan_dir = Cache.unihan_dir(version)
|
|
77
|
+
codepoint_count = iterate(coordinator, ucd_dir, unihan_dir, writer,
|
|
78
|
+
accumulator)
|
|
79
|
+
|
|
80
|
+
report = accumulator.to_report
|
|
81
|
+
report_path = Repo::BuildReportWriter.new(root).write(report)
|
|
82
|
+
|
|
83
|
+
result = {
|
|
84
|
+
version: version,
|
|
85
|
+
codepoint_count: codepoint_count,
|
|
86
|
+
report_path: report_path,
|
|
87
|
+
totals: report.totals.to_hash,
|
|
88
|
+
}
|
|
89
|
+
return result unless validate
|
|
90
|
+
|
|
91
|
+
merge_validation_result(result, root, version, baseline)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def merge_validation_result(result, root, version, baseline)
|
|
97
|
+
outcome = Repo::BuildValidator.new(
|
|
98
|
+
root, unicode_version: version, baseline: baseline,
|
|
99
|
+
).validate
|
|
100
|
+
result.merge(
|
|
101
|
+
validation_report_path: outcome[:report_path],
|
|
102
|
+
validation_passed: outcome[:passed],
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def workers
|
|
107
|
+
Ucode.configuration.parallel_workers
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def iterate(coordinator, ucd_dir, unihan_dir, writer, accumulator)
|
|
111
|
+
count = 0
|
|
112
|
+
coordinator.each_codepoint(ucd_dir: ucd_dir, unihan_dir: unihan_dir) do |cp|
|
|
113
|
+
begin
|
|
114
|
+
writer.write(cp)
|
|
115
|
+
rescue StandardError => e
|
|
116
|
+
accumulator.record_failure(cp, e)
|
|
117
|
+
end
|
|
118
|
+
count += 1
|
|
119
|
+
end
|
|
120
|
+
count
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def build_resolver(version, source_config_path)
|
|
124
|
+
database = Database.open(version)
|
|
125
|
+
config = Glyphs::SourceConfig.new(path: source_config_path_or_default(source_config_path))
|
|
126
|
+
builder = Glyphs::SourceBuilder.new(config: config, database: database)
|
|
127
|
+
Glyphs::Resolver.new(sources: builder.tier1_sources(install: false))
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def source_config_path_or_default(path)
|
|
131
|
+
return Glyphs::SourceConfig::DEFAULT_PATH if path.nil?
|
|
132
|
+
|
|
133
|
+
Pathname.new(path)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|