fontisan 0.4.7 → 0.4.8

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/docs/.vitepress/config.ts +0 -7
  4. data/docs/cli/index.md +5 -28
  5. data/docs/index.md +0 -2
  6. data/lib/fontisan/cli.rb +29 -8
  7. data/lib/fontisan/collection/reader/stats.rb +23 -0
  8. data/lib/fontisan/collection/reader.rb +90 -0
  9. data/lib/fontisan/collection.rb +1 -0
  10. data/lib/fontisan/commands/convert_command.rb +96 -18
  11. data/lib/fontisan/commands/multi_format_output.rb +59 -0
  12. data/lib/fontisan/commands/validate_collection_command.rb +121 -0
  13. data/lib/fontisan/commands.rb +2 -0
  14. data/lib/fontisan/error.rb +25 -0
  15. data/lib/fontisan/models.rb +0 -1
  16. data/lib/fontisan/stitcher/collection_result.rb +18 -0
  17. data/lib/fontisan/stitcher/partition_strategy/base.rb +23 -0
  18. data/lib/fontisan/stitcher/partition_strategy/blueprint.rb +24 -0
  19. data/lib/fontisan/stitcher/partition_strategy/by_plane.rb +131 -0
  20. data/lib/fontisan/stitcher/partition_strategy/partition.rb +24 -0
  21. data/lib/fontisan/stitcher/partition_strategy.rb +22 -0
  22. data/lib/fontisan/stitcher.rb +44 -10
  23. data/lib/fontisan/ufo/compile/name.rb +2 -2
  24. data/lib/fontisan/ufo/info.rb +48 -0
  25. data/lib/fontisan/unicode/plane.rb +56 -0
  26. data/lib/fontisan/unicode.rb +17 -0
  27. data/lib/fontisan/version.rb +1 -1
  28. data/lib/fontisan.rb +2 -2
  29. metadata +13 -18
  30. data/docs/cli/audit.md +0 -337
  31. data/lib/fontisan/cldr/aggregator.rb +0 -33
  32. data/lib/fontisan/cldr/cache_manager.rb +0 -110
  33. data/lib/fontisan/cldr/config.rb +0 -59
  34. data/lib/fontisan/cldr/download_error.rb +0 -9
  35. data/lib/fontisan/cldr/downloader.rb +0 -79
  36. data/lib/fontisan/cldr/error.rb +0 -8
  37. data/lib/fontisan/cldr/index.rb +0 -64
  38. data/lib/fontisan/cldr/index_builder.rb +0 -72
  39. data/lib/fontisan/cldr/unicode_set_parser.rb +0 -189
  40. data/lib/fontisan/cldr/unknown_version_error.rb +0 -9
  41. data/lib/fontisan/cldr/version_resolver.rb +0 -91
  42. data/lib/fontisan/cldr.rb +0 -23
  43. data/lib/fontisan/cli/cldr_cli.rb +0 -85
  44. data/lib/fontisan/config/cldr.yml +0 -22
  45. data/lib/fontisan/models/cldr/language_coverage.rb +0 -31
  46. data/lib/fontisan/models/cldr.rb +0 -12
@@ -1,79 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "net/http"
4
- require "uri"
5
- require "tempfile"
6
- require "zip"
7
-
8
- module Fontisan
9
- module Cldr
10
- # Fetches CLDR JSON archives from unicode-org/cldr-json GitHub releases
11
- # and unpacks them into the cache.
12
- #
13
- # Single entry point: `Downloader.download(version, force:)`.
14
- # Idempotent unless `force: true`. Returns the path to the extracted
15
- # `main/` characters directory.
16
- module Downloader
17
- class << self
18
- # Download and unpack CLDR JSON for `version`.
19
- #
20
- # @param version [String] e.g. "46.0.0"
21
- # @param force [Boolean] if false and cache already has the
22
- # extracted files, return without re-fetching.
23
- # @return [Pathname] path to the extracted main/ characters dir
24
- # @raise [DownloadError] on HTTP failure or zip extraction failure
25
- def download(version, force: false)
26
- target = CacheManager.characters_main_dir(version)
27
- return target if target.exist? && !force
28
-
29
- CacheManager.ensure_version_dir!(version)
30
- zip_data = fetch_zip(version)
31
- extract_archive(zip_data, CacheManager.json_dir(version))
32
- target
33
- end
34
-
35
- private
36
-
37
- def fetch_zip(version)
38
- uri = URI(Config.archive_url_for(version))
39
- response = Net::HTTP.get_response(uri)
40
- unless response.is_a?(Net::HTTPSuccess)
41
- raise DownloadError,
42
- "GET #{uri} returned HTTP #{response.code}: #{response.message}"
43
- end
44
-
45
- body = response.body
46
- if body.nil? || body.empty?
47
- raise DownloadError, "GET #{uri} returned an empty body"
48
- end
49
-
50
- body
51
- rescue StandardError => e
52
- raise e if e.is_a?(DownloadError)
53
-
54
- raise DownloadError, "Failed to fetch #{uri}: #{e.message}"
55
- end
56
-
57
- def extract_archive(zip_data, target_dir)
58
- Tempfile.create(["fontisan-cldr", ".zip"]) do |tmp|
59
- tmp.binmode
60
- tmp.write(zip_data)
61
- tmp.flush
62
- tmp.rewind
63
-
64
- target_dir.mkpath
65
- Zip::File.open(tmp.path) do |zip|
66
- zip.each do |entry|
67
- next unless entry.file?
68
-
69
- out = target_dir.join(entry.name)
70
- out.dirname.mkpath
71
- entry.extract(out.to_s) { true } # overwrite
72
- end
73
- end
74
- end
75
- end
76
- end
77
- end
78
- end
79
- end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Fontisan
4
- module Cldr
5
- # Base error class for all CLDR-related failures.
6
- class Error < StandardError; end
7
- end
8
- end
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Fontisan
4
- module Cldr
5
- # In-memory per-language codepoint lookup.
6
- #
7
- # Loads a YAML index of `{language: [codepoint, ...]}`. Each language's
8
- # codepoints are stored as a Set<Integer> for O(1) intersection checks.
9
- #
10
- # Used by {Cldr::Aggregator} to compute per-language coverage %.
11
- class Index
12
- include Enumerable
13
-
14
- # @param entries [Hash{String=>Set<Integer>, Array<Integer>}]
15
- def initialize(entries = {})
16
- @entries = entries.transform_values do |cps|
17
- cps.is_a?(Set) ? cps : Set.new(cps)
18
- end
19
- end
20
-
21
- # @return [Hash{String=>Set<Integer>}]
22
- attr_reader :entries
23
-
24
- def each(&)
25
- @entries.each(&)
26
- end
27
-
28
- def size
29
- @entries.size
30
- end
31
-
32
- def languages
33
- @entries.keys.sort
34
- end
35
-
36
- # @param language [String]
37
- # @return [Set<Integer>, nil]
38
- def lookup(language)
39
- @entries[language]
40
- end
41
-
42
- def include?(language)
43
- @entries.key?(language)
44
- end
45
-
46
- # Serialize to a YAML file.
47
- # @param path [String, Pathname]
48
- # @return [void]
49
- def save(path)
50
- File.open(path, "w") do |file|
51
- YAML.dump(@entries.transform_values(&:sort), file)
52
- end
53
- end
54
-
55
- # Load from a YAML file previously written by #save.
56
- # @param path [String, Pathname]
57
- # @return [Index]
58
- def self.load(path)
59
- hash = YAML.load_file(path)
60
- new(hash)
61
- end
62
- end
63
- end
64
- end
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
-
5
- module Fontisan
6
- module Cldr
7
- # Builds the per-language codepoint index from a cached CLDR JSON
8
- # archive and persists it as a YAML file for future Index loads.
9
- #
10
- # Walks every `main/<lang>/characters.json` file under the cached
11
- # archive, extracts `exemplarCharacters` (plus auxiliary and index
12
- # sets when present), parses each via {UnicodeSetParser}, and unions
13
- # the result into a single codepoint set per language.
14
- module IndexBuilder
15
- class << self
16
- # Build + persist the languages index for a cached version.
17
- # @param version [String]
18
- # @return [Index]
19
- def build(version)
20
- entries = collect_from_cache(version)
21
- CacheManager.index_dir(version).mkpath
22
- index = Index.new(entries)
23
- index.save(CacheManager.languages_index_path(version))
24
- index
25
- end
26
-
27
- # Pure: build an Index from a hash of `language => exemplar_string`.
28
- # @param exemplars_by_lang [Hash{String=>String}]
29
- # @return [Index]
30
- def build_from_exemplars(exemplars_by_lang)
31
- entries = exemplars_by_lang.transform_values do |set_str|
32
- set_str.nil? ? Set.new : Set.new(UnicodeSetParser.call(set_str))
33
- end
34
- Index.new(entries)
35
- end
36
-
37
- private
38
-
39
- def collect_from_cache(version)
40
- main_dir = CacheManager.characters_main_dir(version)
41
- return {} unless main_dir.exist?
42
-
43
- main_dir.children.select(&:directory?).each_with_object({}) do |lang_dir, hash|
44
- file = lang_dir.join("characters.json")
45
- next unless file.exist?
46
-
47
- lang = lang_dir.basename.to_s
48
- hash[lang] = parse_language_file(file)
49
- end
50
- end
51
-
52
- def parse_language_file(file)
53
- data = JSON.parse(file.read)
54
- lang_key = data.dig("main", "locale") ||
55
- data["main"]&.keys&.first
56
- return Set.new unless lang_key
57
-
58
- chars_node = data.dig("main", lang_key, "characters") || {}
59
- sets = %w[exemplarCharacters auxiliary exemplarCharactersIndex
60
- exemplarCharactersPunctuation].filter_map do |field|
61
- chars_node[field]
62
- end
63
- sets.inject(Set.new) do |acc, set_str|
64
- acc | Set.new(UnicodeSetParser.call(set_str))
65
- rescue UnicodeSetParser::ParseError
66
- acc
67
- end
68
- end
69
- end
70
- end
71
- end
72
- end
@@ -1,189 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Fontisan
4
- module Cldr
5
- # Parses ICU UnicodeSet bracket notation as used in CLDR
6
- # exemplarCharacters fields.
7
- #
8
- # Supported syntax (sufficient for exemplar sets):
9
- # - Single chars: `a`, `à`, any BMP or supplementary codepoint
10
- # - Ranges: `a-z`, `A-Z`
11
- # - Escapes: `\uXXXX`, `\UXXXXXXXX`, `\u{XXXX...}`
12
- # - Negation: `[^...]` (inverts against 0..0x10FFFF)
13
- #
14
- # Unsupported (CLDR exemplars do not use these; raise ParseError):
15
- # - Property syntax `[:script=Latin:]`
16
- # - Set operations `[a-z & [b-c]]`
17
- # - Nested sets `[a[b-c]]`
18
- # - Named sequences `{a b c}`
19
- #
20
- # Output: sorted, deduplicated Array<Integer> of codepoints.
21
- module UnicodeSetParser
22
- class ParseError < Cldr::Error; end
23
- MAX_CODEPOINT = 0x10FFFF
24
- private_constant :MAX_CODEPOINT
25
-
26
- module_function
27
-
28
- # @param set_string [String] bracketed ICU UnicodeSet, e.g. "[a-zà]"
29
- # @return [Array<Integer>] sorted, deduplicated codepoints
30
- def call(set_string)
31
- unless set_string.start_with?("[") && set_string.end_with?("]")
32
- raise ParseError,
33
- "input must be bracketed"
34
- end
35
-
36
- body = set_string[1..-2]
37
- negate = body.start_with?("^")
38
- body = body[1..] if negate
39
-
40
- cps = parse_body(body)
41
- cps = invert(cps) if negate
42
- cps.sort.uniq
43
- end
44
-
45
- # Walk the body char by char, emitting codepoints and ranges.
46
- def parse_body(body)
47
- cps = []
48
- chars = body.chars.to_a
49
- i = 0
50
- prev_cp = nil
51
-
52
- while i < chars.length
53
- ch = chars[i]
54
-
55
- case ch
56
- when "\\"
57
- cp, advance = parse_escape(chars, i)
58
- cps << cp
59
- prev_cp = cp
60
- i += advance
61
- when "-"
62
- raise ParseError, "dangling '-' at start" if prev_cp.nil?
63
- raise ParseError, "dangling '-' at end" if i + 1 >= chars.length
64
-
65
- next_cp, advance = read_next_codepoint(chars, i + 1)
66
- raise ParseError, "range with no upper bound" if next_cp.nil?
67
-
68
- cps.concat(((prev_cp + 1)..next_cp).to_a)
69
- prev_cp = next_cp
70
- i += 1 + advance
71
- when "[", "]"
72
- raise ParseError, "nested set syntax is not supported"
73
- when "{"
74
- raise ParseError, "named sequences ({...}) are not supported"
75
- when ":"
76
- raise ParseError, "property syntax ([:...:]) is not supported"
77
- else
78
- cps << ch.ord
79
- prev_cp = ch.ord
80
- i += 1
81
- end
82
- end
83
-
84
- cps
85
- end
86
- private_class_method :parse_body
87
-
88
- # Read the next codepoint starting at index `start`. Handles escapes.
89
- # @return [Array(Integer, Integer)] codepoint + chars consumed, or
90
- # [nil, 0] if no codepoint is available.
91
- def read_next_codepoint(chars, start)
92
- return [nil, 0] if start >= chars.length
93
-
94
- ch = chars[start]
95
- if ch == "\\"
96
- parse_escape(chars, start)
97
- else
98
- [ch.ord, 1]
99
- end
100
- end
101
- private_class_method :read_next_codepoint
102
-
103
- # Parse a backslash escape sequence.
104
- # Supports \uXXXX, \UXXXXXXXX, \u{XXXX...}, and standard backslash
105
- # escapes (\\, \[, \], \-, \^).
106
- # @return [Array(Integer, Integer)] codepoint + chars consumed
107
- def parse_escape(chars, start)
108
- # chars[start] is "\\"
109
- return [0, 1] if start + 1 >= chars.length
110
-
111
- marker = chars[start + 1]
112
- case marker
113
- when "u"
114
- brace_form(chars, start) || four_hex(chars, start, "u")
115
- when "U"
116
- eight_hex(chars, start)
117
- when "\\"
118
- [0x5C, 2]
119
- when "[", "]", "-", "^"
120
- [marker.ord, 2]
121
- else
122
- raise ParseError, "unknown escape sequence \\#{marker}"
123
- end
124
- end
125
- private_class_method :parse_escape
126
-
127
- def brace_form(chars, start)
128
- return nil unless chars[start + 2] == "{"
129
-
130
- # \u{XXX...} variable hex
131
- end_idx = (start + 3..).find do |j|
132
- j >= chars.length || chars[j] == "}"
133
- end
134
- if end_idx.nil? || chars[end_idx] != "}"
135
- raise ParseError,
136
- "unclosed \\u{ escape"
137
- end
138
-
139
- hex = chars[(start + 3)...end_idx].join
140
- cp = hex.to_i(16)
141
- raise ParseError, "\\u{ escape with no digits" if cp.zero? && hex.empty?
142
-
143
- [cp, (end_idx - start) + 1]
144
- end
145
- private_class_method :brace_form
146
-
147
- def four_hex(chars, start, marker)
148
- # \uXXXX — exactly 4 hex digits
149
- hex = chars[(start + 2), 4]&.join
150
- if hex.nil? || hex.length < 4
151
- raise ParseError,
152
- "truncated \\#{marker} escape"
153
- end
154
-
155
- cp = hex.to_i(16)
156
- if cp.zero? && !hex.match?(/\A0+\z/)
157
- raise ParseError,
158
- "\\#{marker} escape with non-hex digits"
159
- end
160
-
161
- [cp, 6]
162
- end
163
- private_class_method :four_hex
164
-
165
- def eight_hex(chars, start)
166
- # \UXXXXXXXX — exactly 8 hex digits
167
- hex = chars[(start + 2), 8]&.join
168
- raise ParseError, "truncated \\U escape" if hex.nil? || hex.length < 8
169
-
170
- cp = hex.to_i(16)
171
- if cp.zero? && !hex.match?(/\A0+\z/)
172
- raise ParseError,
173
- "\\U escape with non-hex digits"
174
- end
175
-
176
- [cp, 10]
177
- end
178
- private_class_method :eight_hex
179
-
180
- def invert(cps)
181
- set = cps.to_set
182
- (0..MAX_CODEPOINT).each_with_object([]) do |cp, arr|
183
- arr << cp unless set.include?(cp)
184
- end
185
- end
186
- private_class_method :invert
187
- end
188
- end
189
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Fontisan
4
- module Cldr
5
- # Raised by Cldr::VersionResolver when a user-supplied version string
6
- # is not in Cldr::Config.known_versions.
7
- class UnknownVersionError < Cldr::Error; end
8
- end
9
- end
@@ -1,91 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "net/http"
4
- require "uri"
5
- require "json"
6
- require "rubygems"
7
-
8
- module Fontisan
9
- module Cldr
10
- # Resolves a user-supplied version intent to a concrete CLDR version.
11
- #
12
- # Three input modes:
13
- #
14
- # resolve(nil) # default_version from config
15
- # resolve(:default) # default_version from config
16
- # resolve("46.0.0") # explicit; validated against known_versions
17
- # resolve(:latest) # probes GitHub releases, picks highest;
18
- # # falls back to default on failure
19
- module VersionResolver
20
- GITHUB_RELEASE_TAG = %r{ref/tags/(\d+(?:\.\d+)+)}
21
- private_constant :GITHUB_RELEASE_TAG
22
-
23
- class << self
24
- # @param intent [nil, :default, :latest, String]
25
- # @return [String] a concrete version string
26
- def resolve(intent)
27
- case intent
28
- when nil, :default
29
- Config.default_version
30
- when :latest
31
- probe_latest
32
- else
33
- validate!(intent)
34
- intent
35
- end
36
- end
37
-
38
- # Raise UnknownVersionError unless `version` is in known_versions.
39
- # @param version [String]
40
- # @return [void]
41
- def validate!(version)
42
- return if Config.known?(version)
43
-
44
- raise UnknownVersionError,
45
- "CLDR version #{version.inspect} is not recognized. " \
46
- "Known versions: #{Config.known_versions.join(', ')}"
47
- end
48
-
49
- private
50
-
51
- # Best-effort probe of the GitHub releases API for cldr-json.
52
- # Returns the highest semver found among tagged releases, or
53
- # Config.default_version on any failure.
54
- def probe_latest
55
- versions = fetch_release_versions
56
- return fallback_latest("releases listing was empty") if versions.empty?
57
-
58
- highest = versions.max_by { |v| Gem::Version.new(v) }
59
- if Config.known?(highest)
60
- highest
61
- else
62
- fallback_latest("#{highest.inspect} is not in known_versions; using default")
63
- end
64
- rescue StandardError => e
65
- fallback_latest(e.message)
66
- end
67
-
68
- def fallback_latest(reason)
69
- warn "Cldr::VersionResolver: --latest probe failed (#{reason}); " \
70
- "falling back to default #{Config.default_version.inspect}"
71
- Config.default_version
72
- end
73
-
74
- def fetch_release_versions
75
- uri = URI(Config.listing_url)
76
- response = Net::HTTP.get_response(uri)
77
- return [] unless response.is_a?(Net::HTTPSuccess)
78
-
79
- releases = JSON.parse(response.body || "[]")
80
- releases.filter_map do |release|
81
- tag = release["tag_name"]
82
- next unless tag
83
-
84
- match = tag.match(/\A(\d+(?:\.\d+)+)\z/)
85
- match && match[1]
86
- end
87
- end
88
- end
89
- end
90
- end
91
- end
data/lib/fontisan/cldr.rb DELETED
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Namespace hub for CLDR (Common Locale Data Repository) support.
4
- #
5
- # Provides per-language exemplar character sets so the audit can
6
- # compute "this font covers X% of language Y". All Cldr::* constants
7
- # are autoloaded from here.
8
-
9
- module Fontisan
10
- module Cldr
11
- autoload :Config, "fontisan/cldr/config"
12
- autoload :CacheManager, "fontisan/cldr/cache_manager"
13
- autoload :Downloader, "fontisan/cldr/downloader"
14
- autoload :VersionResolver, "fontisan/cldr/version_resolver"
15
- autoload :IndexBuilder, "fontisan/cldr/index_builder"
16
- autoload :Index, "fontisan/cldr/index"
17
- autoload :Aggregator, "fontisan/cldr/aggregator"
18
- autoload :UnicodeSetParser, "fontisan/cldr/unicode_set_parser"
19
- autoload :Error, "fontisan/cldr/error"
20
- autoload :DownloadError, "fontisan/cldr/download_error"
21
- autoload :UnknownVersionError, "fontisan/cldr/unknown_version_error"
22
- end
23
- end
@@ -1,85 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "thor"
4
-
5
- module Fontisan
6
- # Thor subcommand for managing the local CLDR (Common Locale Data
7
- # Repository) cache used by `fontisan audit` for per-language coverage.
8
- #
9
- # fontisan cldr download [VERSION] fetch + index CLDR exemplars
10
- # fontisan cldr status show what's cached
11
- # fontisan cldr path [VERSION] print local cache path
12
- # fontisan cldr list list known versions
13
- # fontisan cldr remove VERSION delete a cached version
14
- #
15
- # With no arguments, `download` resolves the configured default version
16
- # (see lib/fontisan/config/cldr.yml).
17
- class CldrCli < Thor
18
- desc "download [VERSION]",
19
- "Download and index CLDR exemplar characters (default: configured default version)"
20
- option :force, type: :boolean, default: false,
21
- desc: "Re-download even if already cached"
22
- option :latest, type: :boolean, default: false,
23
- desc: "Probe GitHub releases for the latest version"
24
- def download(version = nil)
25
- intent = resolve_intent(version, options[:latest])
26
- actual = Cldr::VersionResolver.resolve(intent)
27
-
28
- Cldr::Downloader.download(actual, force: options[:force])
29
- Cldr::IndexBuilder.build(actual) unless index_present?(actual)
30
- puts "CLDR #{actual} ready at: #{Cldr::CacheManager.version_dir(actual)}"
31
- rescue Cldr::Error => e
32
- warn "ERROR: #{e.message}"
33
- exit 1
34
- end
35
-
36
- desc "status", "Show cached CLDR versions and default version"
37
- def status
38
- cached = Cldr::CacheManager.cached_versions
39
- puts "Default version: #{Cldr::Config.default_version}"
40
- puts "Cache root: #{Cldr::CacheManager.root}"
41
- puts "Cached versions: #{cached.empty? ? '(none)' : cached.join(', ')}"
42
- end
43
-
44
- desc "path [VERSION]", "Print local cache directory for a version"
45
- def path(version = nil)
46
- actual = Cldr::VersionResolver.resolve(version)
47
- puts Cldr::CacheManager.version_dir(actual)
48
- rescue Cldr::UnknownVersionError => e
49
- warn "ERROR: #{e.message}"
50
- exit 1
51
- end
52
-
53
- desc "list", "List CLDR versions known to this Fontisan release"
54
- def list
55
- Cldr::Config.known_versions.each { |v| puts v }
56
- end
57
-
58
- desc "remove VERSION", "Remove a cached CLDR version"
59
- def remove(version)
60
- Cldr::VersionResolver.validate!(version)
61
- unless Cldr::CacheManager.cached?(version)
62
- warn "Version #{version} is not cached; nothing to remove."
63
- return
64
- end
65
-
66
- Cldr::CacheManager.remove_version(version)
67
- puts "Removed CLDR #{version}."
68
- rescue Cldr::UnknownVersionError => e
69
- warn "ERROR: #{e.message}"
70
- exit 1
71
- end
72
-
73
- private
74
-
75
- def resolve_intent(version, latest)
76
- return :latest if latest && version.nil?
77
-
78
- version
79
- end
80
-
81
- def index_present?(version)
82
- Cldr::CacheManager.languages_index_path(version).exist?
83
- end
84
- end
85
- end
@@ -1,22 +0,0 @@
1
- # UCD alternative: CLDR (Common Locale Data Repository) configuration.
2
- #
3
- # This file is the single source of truth for which CLDR version Fontisan
4
- # uses by default, what versions are recognized, and where to fetch them.
5
- #
6
- # Update `default_version` and `known_versions` when a new CLDR release
7
- # ships (typically 2× per year, alongside Unicode releases).
8
- # Users override at runtime via `fontisan cldr download --version X.Y.Z`
9
- # or `fontisan audit --cldr-version X.Y.Z`.
10
-
11
- default_version: "46.0.0"
12
-
13
- known_versions:
14
- - "45.0.0"
15
- - "46.0.0"
16
-
17
- # CLDR JSON releases are published on GitHub.
18
- # Full URL: <base_url>/<version>/cldr-<version>-json-full.zip
19
- base_url: "https://github.com/unicode-org/cldr-json/releases/download"
20
-
21
- # Releases listing URL for `--latest` probing. JSON API.
22
- listing_url: "https://api.github.com/repos/unicode-org/cldr-json/releases"
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "lutaml/model"
4
-
5
- module Fontisan
6
- module Models
7
- module Cldr
8
- # Per-language coverage for one face against a CLDR exemplar set.
9
- #
10
- # `coverage_ratio` is in [0.0, 1.0] rounded to 4 decimal places;
11
- # `fully_supported` is true only when every required codepoint
12
- # (total > 0) is covered. A language with total == 0 (empty exemplar
13
- # set in the index) is reported as ratio 0.0, fully_supported false.
14
- class LanguageCoverage < Lutaml::Model::Serializable
15
- attribute :language, :string
16
- attribute :covered, :integer
17
- attribute :total, :integer
18
- attribute :coverage_ratio, :float
19
- attribute :fully_supported, Lutaml::Model::Type::Boolean
20
-
21
- key_value do
22
- map "language", to: :language
23
- map "covered", to: :covered
24
- map "total", to: :total
25
- map "coverage_ratio", to: :coverage_ratio
26
- map "fully_supported", to: :fully_supported
27
- end
28
- end
29
- end
30
- end
31
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "lutaml/model"
4
-
5
- module Fontisan
6
- module Models
7
- # Namespace for CLDR-derived audit models.
8
- module Cldr
9
- autoload :LanguageCoverage, "fontisan/models/cldr/language_coverage"
10
- end
11
- end
12
- end