ucode 0.2.0 → 0.2.2

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +10 -2
  3. data/README.md +66 -20
  4. data/Rakefile +19 -8
  5. data/TODO.extract-code-chart/01-pdf-fetch-validation.md +80 -0
  6. data/TODO.extract-code-chart/02-block-name-resolver.md +68 -0
  7. data/TODO.extract-code-chart/03-codechart-namespace.md +82 -0
  8. data/TODO.extract-code-chart/04-codechart-extractor.md +154 -0
  9. data/TODO.extract-code-chart/05-provenance-and-sidecar.md +147 -0
  10. data/TODO.extract-code-chart/06-codechart-writer.md +134 -0
  11. data/TODO.extract-code-chart/07-codechart-cli.md +135 -0
  12. data/TODO.extract-code-chart/08-specs.md +87 -0
  13. data/config/unicode17_universal_glyph_set.yml +1 -1
  14. data/lib/ucode/audit/reference_factory.rb +1 -1
  15. data/lib/ucode/cli.rb +101 -0
  16. data/lib/ucode/code_chart/extractor.rb +120 -0
  17. data/lib/ucode/code_chart/provenance.rb +82 -0
  18. data/lib/ucode/code_chart/sidecar.rb +52 -0
  19. data/lib/ucode/code_chart/writer.rb +128 -0
  20. data/lib/ucode/code_chart.rb +39 -0
  21. data/lib/ucode/commands/fetch.rb +1 -1
  22. data/lib/ucode/commands/glyphs.rb +1 -1
  23. data/lib/ucode/commands/lookup.rb +1 -1
  24. data/lib/ucode/commands/parse.rb +1 -1
  25. data/lib/ucode/coordinator/indices.rb +2 -2
  26. data/lib/ucode/error.rb +11 -0
  27. data/lib/ucode/fetch/code_charts.rb +3 -4
  28. data/lib/ucode/fetch/http.rb +75 -7
  29. data/lib/ucode/glyphs/page_renderer.rb +15 -2
  30. data/lib/ucode/glyphs/pipeline.rb +1 -2
  31. data/lib/ucode/parsers/blocks.rb +34 -0
  32. data/lib/ucode/repo/aggregate_writer.rb +1 -1
  33. data/lib/ucode/repo/writers/blocks_writer.rb +13 -13
  34. data/lib/ucode/repo/writers/enums_writer.rb +2 -2
  35. data/lib/ucode/repo/writers/indexes_writer.rb +4 -4
  36. data/lib/ucode/repo/writers/manifest_writer.rb +4 -4
  37. data/lib/ucode/repo/writers/named_sequences_writer.rb +1 -1
  38. data/lib/ucode/repo/writers/planes_writer.rb +17 -17
  39. data/lib/ucode/repo/writers/relationships_writer.rb +1 -1
  40. data/lib/ucode/repo/writers/scripts_writer.rb +6 -6
  41. data/lib/ucode/repo/writers.rb +1 -1
  42. data/lib/ucode/version.rb +1 -1
  43. data/lib/ucode.rb +3 -0
  44. data/ucode.gemspec +6 -1
  45. metadata +19 -6
@@ -37,6 +37,40 @@ module Ucode
37
37
  nil
38
38
  end
39
39
 
40
+ # Resolves a block by its identifier (the underscored form of
41
+ # the block name, e.g. "Basic_Latin", "Egyptian_Hieroglyphs_Extended-B").
42
+ # Streams `Blocks.txt` once and short-circuits on first match —
43
+ # callers don't need to walk the whole ~340-block file.
44
+ #
45
+ # @param path [Pathname, String] path to a Blocks.txt
46
+ # @param id [String] block identifier (matches `Models::Block#id`)
47
+ # @return [Models::Block, nil] the block, or nil when no block
48
+ # has the given id
49
+ def find_by_id(path, id)
50
+ return nil if id.nil? || id.empty?
51
+
52
+ each_record(path) do |block|
53
+ return block if block.id == id
54
+ end
55
+ nil
56
+ end
57
+
58
+ # Same as {find_by_id} but raises {Ucode::UnknownBlockError} on
59
+ # miss. Use this in callers that can't recover from a missing
60
+ # block (CLI commands, extractors that need a block to proceed).
61
+ #
62
+ # @param path [Pathname, String] path to a Blocks.txt
63
+ # @param id [String] block identifier
64
+ # @return [Models::Block]
65
+ # @raise [Ucode::UnknownBlockError] when no block matches
66
+ def find_by_id!(path, id)
67
+ find_by_id(path, id) or
68
+ raise Ucode::UnknownBlockError.new(
69
+ "unknown Unicode block: #{id.inspect}",
70
+ context: { block_id: id, blocks_txt: path.to_s },
71
+ )
72
+ end
73
+
40
74
  private
41
75
 
42
76
  def build_block(range, name)
@@ -155,4 +155,4 @@ module Ucode
155
155
  end
156
156
  end
157
157
  end
158
- end
158
+ end
@@ -45,12 +45,12 @@ module Ucode
45
45
  path = Paths.blocks_index_path(@output_root)
46
46
  summary = @blocks.map do |block|
47
47
  {
48
- "id" => block.id,
49
- "name" => block.name,
50
- "first_cp" => block.range_first,
51
- "last_cp" => block.range_last,
48
+ "id" => block.id,
49
+ "name" => block.name,
50
+ "first_cp" => block.range_first,
51
+ "last_cp" => block.range_last,
52
52
  "plane_number" => block.plane_number,
53
- "age" => @block_ages[block.id],
53
+ "age" => @block_ages[block.id],
54
54
  }
55
55
  end
56
56
  write_atomic(path, to_pretty_json(summary)) ? 1 : 0
@@ -58,16 +58,16 @@ module Ucode
58
58
 
59
59
  def block_payload(block)
60
60
  to_pretty_json(
61
- "id" => block.id,
62
- "name" => block.name,
63
- "range_first" => block.range_first,
64
- "range_last" => block.range_last,
65
- "plane_number" => block.plane_number,
66
- "age" => @block_ages[block.id],
67
- "codepoint_ids" => (@block_codepoint_ids[block.id] || []),
61
+ "id" => block.id,
62
+ "name" => block.name,
63
+ "range_first" => block.range_first,
64
+ "range_last" => block.range_last,
65
+ "plane_number" => block.plane_number,
66
+ "age" => @block_ages[block.id],
67
+ "codepoint_ids" => @block_codepoint_ids[block.id] || [],
68
68
  )
69
69
  end
70
70
  end
71
71
  end
72
72
  end
73
- end
73
+ end
@@ -27,7 +27,7 @@ module Ucode
27
27
  def write
28
28
  path = Pathname(@output_root).join("enums.json")
29
29
  payload = {
30
- "properties" => @property_aliases.map(&:to_yaml_hash),
30
+ "properties" => @property_aliases.map(&:to_yaml_hash),
31
31
  "property_values" => @property_value_aliases.map(&:to_yaml_hash),
32
32
  }
33
33
  write_atomic(path, to_pretty_json(payload)) ? 1 : 0
@@ -35,4 +35,4 @@ module Ucode
35
35
  end
36
36
  end
37
37
  end
38
- end
38
+ end
@@ -34,11 +34,11 @@ module Ucode
34
34
  def write
35
35
  count = 0
36
36
  count += 1 if write_atomic(Paths.names_index_path(@output_root),
37
- to_pretty_json(@names))
37
+ to_pretty_json(@names))
38
38
  count += 1 if write_atomic(Paths.labels_index_path(@output_root),
39
- to_pretty_json(@labels))
39
+ to_pretty_json(@labels))
40
40
  count += 1 if write_atomic(codepoint_to_block_path,
41
- to_pretty_json(@cp_to_block))
41
+ to_pretty_json(@cp_to_block))
42
42
  count
43
43
  end
44
44
 
@@ -50,4 +50,4 @@ module Ucode
50
50
  end
51
51
  end
52
52
  end
53
- end
53
+ end
@@ -45,10 +45,10 @@ module Ucode
45
45
  def write
46
46
  path = Paths.manifest_path(@output_root)
47
47
  content = {
48
- "ucd_version" => @ucd_version,
48
+ "ucd_version" => @ucd_version,
49
49
  "codepoint_count" => @codepoint_count,
50
- "glyph_count" => @glyph_count,
51
- "schema_version" => SCHEMA_VERSION,
50
+ "glyph_count" => @glyph_count,
51
+ "schema_version" => SCHEMA_VERSION,
52
52
  }
53
53
  ts = preserved_or_new_timestamp(path, content)
54
54
  payload = content.merge("generated_at" => ts)
@@ -75,4 +75,4 @@ module Ucode
75
75
  end
76
76
  end
77
77
  end
78
- end
78
+ end
@@ -44,4 +44,4 @@ module Ucode
44
44
  end
45
45
  end
46
46
  end
47
- end
47
+ end
@@ -16,16 +16,16 @@ module Ucode
16
16
  # Static metadata for the 17 Unicode planes. Planes 4–13 are
17
17
  # unassigned in Unicode 17; their entries use placeholder names.
18
18
  PLANE_TABLE = {
19
- 0 => ["Basic Multilingual Plane", "BMP"],
20
- 1 => ["Supplementary Multilingual Plane", "SMP"],
21
- 2 => ["Supplementary Ideographic Plane", "SIP"],
22
- 3 => ["Tertiary Ideographic Plane", "TIP"],
23
- 4 => ["Unassigned Plane 4", "—"],
24
- 5 => ["Unassigned Plane 5", "—"],
25
- 6 => ["Unassigned Plane 6", "—"],
26
- 7 => ["Unassigned Plane 7", "—"],
27
- 8 => ["Unassigned Plane 8", "—"],
28
- 9 => ["Unassigned Plane 9", "—"],
19
+ 0 => ["Basic Multilingual Plane", "BMP"],
20
+ 1 => ["Supplementary Multilingual Plane", "SMP"],
21
+ 2 => ["Supplementary Ideographic Plane", "SIP"],
22
+ 3 => ["Tertiary Ideographic Plane", "TIP"],
23
+ 4 => ["Unassigned Plane 4", "—"],
24
+ 5 => ["Unassigned Plane 5", "—"],
25
+ 6 => ["Unassigned Plane 6", "—"],
26
+ 7 => ["Unassigned Plane 7", "—"],
27
+ 8 => ["Unassigned Plane 8", "—"],
28
+ 9 => ["Unassigned Plane 9", "—"],
29
29
  10 => ["Unassigned Plane 10", "—"],
30
30
  11 => ["Unassigned Plane 11", "—"],
31
31
  12 => ["Unassigned Plane 12", "—"],
@@ -68,15 +68,15 @@ module Ucode
68
68
  range_first = plane_number * 0x10000
69
69
  range_last = range_first + 0xFFFF
70
70
  to_pretty_json(
71
- "number" => plane_number,
72
- "name" => name,
73
- "abbrev" => abbrev,
74
- "range_first" => range_first,
75
- "range_last" => range_last,
76
- "block_ids" => block_ids,
71
+ "number" => plane_number,
72
+ "name" => name,
73
+ "abbrev" => abbrev,
74
+ "range_first" => range_first,
75
+ "range_last" => range_last,
76
+ "block_ids" => block_ids,
77
77
  )
78
78
  end
79
79
  end
80
80
  end
81
81
  end
82
- end
82
+ end
@@ -68,4 +68,4 @@ module Ucode
68
68
  end
69
69
  end
70
70
  end
71
- end
71
+ end
@@ -41,14 +41,14 @@ module Ucode
41
41
 
42
42
  def script_payload(code, ranges)
43
43
  to_pretty_json(
44
- "code" => code,
45
- "name" => ranges.first&.name,
46
- "range_first" => ranges.map(&:range_first).min,
47
- "range_last" => ranges.map(&:range_last).max,
48
- "codepoint_ids" => (@script_codepoint_ids[code] || []),
44
+ "code" => code,
45
+ "name" => ranges.first&.name,
46
+ "range_first" => ranges.map(&:range_first).min,
47
+ "range_last" => ranges.map(&:range_last).max,
48
+ "codepoint_ids" => @script_codepoint_ids[code] || [],
49
49
  )
50
50
  end
51
51
  end
52
52
  end
53
53
  end
54
- end
54
+ end
@@ -17,4 +17,4 @@ module Ucode
17
17
  autoload :ManifestWriter, "ucode/repo/writers/manifest_writer"
18
18
  end
19
19
  end
20
- end
20
+ end
data/lib/ucode/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ucode
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.2"
5
5
  end
data/lib/ucode.rb CHANGED
@@ -30,11 +30,13 @@ module Ucode
30
30
  autoload :DatabaseMissingError, "ucode/error"
31
31
  autoload :DatabaseSchemaError, "ucode/error"
32
32
  autoload :UnknownVersionError, "ucode/error"
33
+ autoload :UnknownBlockError, "ucode/error"
33
34
  autoload :GlyphError, "ucode/error"
34
35
  autoload :PdfRenderError, "ucode/error"
35
36
  autoload :GridDetectionError, "ucode/error"
36
37
  autoload :LastResortMissingError, "ucode/error"
37
38
  autoload :EmbeddedFontsMissingError, "ucode/error"
39
+ autoload :CodeChartNotFoundError, "ucode/error"
38
40
 
39
41
  # Infrastructure
40
42
  autoload :Cache, "ucode/cache"
@@ -54,6 +56,7 @@ module Ucode
54
56
  autoload :Repo, "ucode/repo"
55
57
  autoload :Glyphs, "ucode/glyphs"
56
58
  autoload :Audit, "ucode/audit"
59
+ autoload :CodeChart, "ucode/code_chart"
57
60
  autoload :Site, "ucode/site"
58
61
  autoload :Commands, "ucode/commands"
59
62
  autoload :Cli, "ucode/cli"
data/ucode.gemspec CHANGED
@@ -45,7 +45,12 @@ Gem::Specification.new do |spec|
45
45
  spec.require_paths = ["lib"]
46
46
 
47
47
  spec.add_dependency "base64"
48
- spec.add_dependency "fontisan", "~> 0.2"
48
+ # Pin fontisan to 0.2.22 — 0.2.23+ removed
49
+ # `Fontisan::Commands::AuditCommand` (used by RealFonts::CoverageAuditor)
50
+ # and 0.4.x removed the entire Audit subsystem. Until
51
+ # CoverageAuditor is rewritten against the new fontisan API, this
52
+ # pin keeps the existing audit path working.
53
+ spec.add_dependency "fontisan", "= 0.2.22"
49
54
  spec.add_dependency "fontist", "~> 3.0"
50
55
  spec.add_dependency "logger"
51
56
  spec.add_dependency "lutaml-model", "~> 0.8"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ucode
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-06-29 00:00:00.000000000 Z
11
+ date: 2026-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: fontisan
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: '0.2'
33
+ version: 0.2.22
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: '0.2'
40
+ version: 0.2.22
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: fontist
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -156,6 +156,14 @@ files:
156
156
  - Gemfile
157
157
  - README.md
158
158
  - Rakefile
159
+ - TODO.extract-code-chart/01-pdf-fetch-validation.md
160
+ - TODO.extract-code-chart/02-block-name-resolver.md
161
+ - TODO.extract-code-chart/03-codechart-namespace.md
162
+ - TODO.extract-code-chart/04-codechart-extractor.md
163
+ - TODO.extract-code-chart/05-provenance-and-sidecar.md
164
+ - TODO.extract-code-chart/06-codechart-writer.md
165
+ - TODO.extract-code-chart/07-codechart-cli.md
166
+ - TODO.extract-code-chart/08-specs.md
159
167
  - TODO.full/00-README.md
160
168
  - TODO.full/01-panglyph-vision.md
161
169
  - TODO.full/02-panglyph-repo-bootstrap.md
@@ -295,6 +303,11 @@ files:
295
303
  - lib/ucode/audit/universal_set_reference.rb
296
304
  - lib/ucode/cache.rb
297
305
  - lib/ucode/cli.rb
306
+ - lib/ucode/code_chart.rb
307
+ - lib/ucode/code_chart/extractor.rb
308
+ - lib/ucode/code_chart/provenance.rb
309
+ - lib/ucode/code_chart/sidecar.rb
310
+ - lib/ucode/code_chart/writer.rb
298
311
  - lib/ucode/commands.rb
299
312
  - lib/ucode/commands/audit.rb
300
313
  - lib/ucode/commands/audit/browser_command.rb