fontist 2.1.1 → 2.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2805581df1364005181f92bd7c897fe641deb6231a2b95d40753c3cb8157a98f
4
- data.tar.gz: bb9e6d7daedf79aedabf7a8f485739d2ef7639d1c5469bbe2d914ad3b5e3111b
3
+ metadata.gz: b0aad02a62b4c3f295e80229cbca11af23ccbad51850b3f87dc4e6652e548947
4
+ data.tar.gz: c97a29591a24ee8c84ecb3316a3f66dade12994c752196b1ff86f48c266b3df5
5
5
  SHA512:
6
- metadata.gz: 4bbd444d044c539b6548da83a719b274d4abe0d2af85f6490c48a5e82b9676d7f8b54ad9e8f38a306cc535476d37e84fc616ec8d1859f695e8af969984f40f6e
7
- data.tar.gz: 1893ffb8d4179ad1dd70bb92ed12dcee3ac7aab0c8337f4faff6c7515e806ee6fb2b9ad975710fafb9efec8b272dc32e7a45e0e3983f04c86ccc0df6cf344171
6
+ metadata.gz: 7b6c0acf8fe7acd3a49770beaba2a5e6c1af8a742eb5db204ab8955be9ffaf013723d20170fe557d49983eb8234cfe7283ab2ba3a94c0231e7a0c10454219c3f
7
+ data.tar.gz: 6715acf2088a92c6d51e33d4fb07b88b48318fdb39a2f05084369109801149ecf57d9ed4f7cadef389cd39f9e26442548d7c121909013ce20909b6c6bc677609
@@ -1,4 +1,8 @@
1
1
  name: deploy-pages
2
+
3
+ permissions:
4
+ contents: read
5
+
2
6
  on:
3
7
  push:
4
8
  branches: "main"
@@ -10,9 +14,11 @@ on:
10
14
  - .github/**
11
15
  - "!.github/workflows/deploy-pages.yml"
12
16
  workflow_dispatch:
17
+
13
18
  concurrency:
14
19
  group: ${{ github.workflow }}
15
20
  cancel-in-progress: true
21
+
16
22
  jobs:
17
23
  deploy-pages:
18
24
  environment:
@@ -0,0 +1,287 @@
1
+ name: formulas-auto-update-test
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ # Cancel in-progress runs for the same workflow and branch
13
+ concurrency:
14
+ group: '${{ github.workflow }}-${{ github.head_ref || github.ref_name }}'
15
+ cancel-in-progress: true
16
+
17
+ env:
18
+ BUNDLER_VER: latest
19
+
20
+ jobs:
21
+ # This job tests that fonts can be installed WITHOUT explicit `fontist update`.
22
+ # This verifies the lazy initialization feature works correctly on all platforms.
23
+ #
24
+ # Lazy initialization ensures formulas are auto-downloaded on first access,
25
+ # fixing the issue where CI workflows failed because formulas weren't available.
26
+ test-formulas-auto-update:
27
+ name: Formulas Auto-Update Test (${{ matrix.os }})
28
+ runs-on: ${{ matrix.os }}
29
+
30
+ strategy:
31
+ fail-fast: false
32
+ matrix:
33
+ os: [ubuntu-latest, macos-latest, windows-latest]
34
+ # Test on all platforms to ensure lazy initialization works everywhere
35
+ # macOS: STIX Two Math is pre-installed, but we still test the mechanism
36
+ # Linux/Windows: No pre-installed fonts, tests full lazy initialization
37
+
38
+ steps:
39
+ - name: Checkout
40
+ uses: actions/checkout@v4
41
+
42
+ - name: Setup Ruby
43
+ uses: ruby/setup-ruby@v1
44
+ with:
45
+ ruby-version: '3.3'
46
+ rubygems: latest
47
+ bundler: ${{ env.BUNDLER_VER }}
48
+ bundler-cache: true
49
+
50
+ - name: Verify fontist installation
51
+ shell: bash
52
+ run: |
53
+ echo "=== Fontist Version ==="
54
+ bundle exec fontist --version
55
+ echo ""
56
+ echo "=== Fontist Home Path ==="
57
+ echo "Fontist home: $HOME/.fontist"
58
+ echo "Formulas path: $HOME/.fontist/versions/v4/formulas"
59
+
60
+ # CRITICAL: Remove any existing formulas directory to simulate fresh CI environment
61
+ # This ensures we're testing lazy initialization from a clean state
62
+ - name: Remove existing formulas directory (simulate fresh CI)
63
+ shell: bash
64
+ run: |
65
+ echo "=== Simulating Fresh CI Environment ==="
66
+ echo "Removing any existing formulas directory..."
67
+ if [ -d "$HOME/.fontist/versions/v4/formulas" ]; then
68
+ echo " ✓ Found existing formulas directory, removing..."
69
+ rm -rf "$HOME/.fontist/versions/v4/formulas"
70
+ echo " ✓ Formulas directory removed"
71
+ else
72
+ echo " ✓ No existing formulas directory (clean state)"
73
+ fi
74
+ echo ""
75
+ echo "Verifying formulas directory does not exist:"
76
+ if [ -d "$HOME/.fontist/versions/v4/formulas" ]; then
77
+ echo " ✗ ERROR: Formulas directory still exists!"
78
+ exit 1
79
+ else
80
+ echo " ✓ Confirmed: Formulas directory does not exist"
81
+ fi
82
+
83
+ - name: Verify formulas directory doesn't exist before test
84
+ shell: bash
85
+ run: |
86
+ echo "=== Pre-test State Verification ==="
87
+ echo "Checking formulas directory status:"
88
+ FORMULAS_DIR="$HOME/.fontist/versions/v4/formulas/Formulas"
89
+ if [ -d "$FORMULAS_DIR" ]; then
90
+ echo " ✗ ERROR: Formulas directory exists at $FORMULAS_DIR"
91
+ echo " This will invalidate the test!"
92
+ exit 1
93
+ else
94
+ echo " ✓ Formulas directory does not exist (as expected)"
95
+ fi
96
+ echo ""
97
+ echo "Listing .fontist directory contents (if any):"
98
+ ls -la "$HOME/.fontist/" 2>/dev/null || echo " (No .fontist directory yet)"
99
+ echo ""
100
+ echo "Listing versions directory (if any):"
101
+ ls -la "$HOME/.fontist/versions/" 2>/dev/null || echo " (No versions directory yet)"
102
+
103
+ # CRITICAL: Remove formulas directory again right before install test
104
+ # This is necessary because bundler-cache may restore .fontist directory
105
+ - name: Force remove formulas directory before test
106
+ shell: bash
107
+ run: |
108
+ echo "=== Force removing formulas directory before test ==="
109
+ rm -rf "$HOME/.fontist/versions/v4/formulas"
110
+ echo "✓ Formulas directory removed"
111
+ echo ""
112
+ echo "Verifying removal:"
113
+ if [ -d "$HOME/.fontist/versions/v4/formulas" ]; then
114
+ echo " ✗ ERROR: Formulas directory still exists!"
115
+ exit 1
116
+ else
117
+ echo " ✓ Formulas directory successfully removed"
118
+ fi
119
+
120
+ # CRITICAL TEST: Install Andale Mono WITHOUT running `fontist update` first
121
+ # Andale Mono is NOT pre-installed on any platform (macOS/Linux/Windows),
122
+ # so this properly tests lazy initialization on ALL platforms.
123
+ # Unlike STIX Two Math which is pre-installed on macOS.
124
+ - name: Install Andale Mono (WITHOUT fontist update)
125
+ shell: bash
126
+ run: |
127
+ echo "========================================================================="
128
+ echo " CRITICAL TEST: Lazy Initialization"
129
+ echo "========================================================================="
130
+ echo ""
131
+ echo "This test installs Andale Mono WITHOUT running 'fontist update' first."
132
+ echo "Andale Mono is NOT pre-installed on any platform, ensuring we test"
133
+ echo "lazy initialization on ALL platforms (macOS/Linux/Windows)."
134
+ echo ""
135
+ echo "Platform: ${{ matrix.os }}"
136
+ echo "Ruby: $(ruby --version)"
137
+ echo ""
138
+ echo "--- Step 1: Verify formulas don't exist yet ---"
139
+ FORMULAS_DIR="$HOME/.fontist/versions/v4/formulas/Formulas"
140
+ if [ -d "$FORMULAS_DIR" ]; then
141
+ echo " ✗ ERROR: Formulas directory already exists!"
142
+ echo " This invalidates the test - lazy initialization won't be tested."
143
+ exit 1
144
+ else
145
+ echo " ✓ Formulas directory does not exist (as expected)"
146
+ fi
147
+ echo ""
148
+
149
+ echo "--- Step 2: Install Andale Mono (should trigger lazy update) ---"
150
+ echo "Running: bundle exec fontist install 'Andale Mono' --accept-all-licenses --force"
151
+ echo ""
152
+ if bundle exec fontist install "Andale Mono" --accept-all-licenses --force 2>&1; then
153
+ echo ""
154
+ echo "✓ SUCCESS: Andale Mono installation completed"
155
+ INSTALL_SUCCESS=true
156
+ else
157
+ EXIT_CODE=$?
158
+ echo ""
159
+ echo "✗ FAILED: Andale Mono installation failed with exit code: $EXIT_CODE"
160
+ INSTALL_SUCCESS=false
161
+ fi
162
+ echo ""
163
+
164
+ echo "--- Step 3: Verify formulas were auto-downloaded ---"
165
+ if [ -d "$FORMULAS_DIR" ]; then
166
+ echo " ✓ Formulas directory was created (lazy initialization worked!)"
167
+ echo ""
168
+ echo " Formulas directory contents (first 20 entries):"
169
+ ls -1 "$FORMULAS_DIR" | head -20
170
+ else
171
+ echo " ✗ ERROR: Formulas directory was NOT created!"
172
+ echo " This indicates lazy initialization FAILED."
173
+ INSTALL_SUCCESS=false
174
+ fi
175
+ echo ""
176
+
177
+ echo "--- Step 4: Verify Andale Mono formula exists ---"
178
+ ANDALE_FORMULA="$FORMULAS_DIR/andale.yml"
179
+ if [ -f "$ANDALE_FORMULA" ]; then
180
+ echo " ✓ Andale Mono formula exists at andale.yml"
181
+ else
182
+ echo " ✗ ERROR: Andale Mono formula NOT found!"
183
+ echo " Expected: $ANDALE_FORMULA"
184
+ INSTALL_SUCCESS=false
185
+ fi
186
+ echo ""
187
+
188
+ echo "--- Step 5: Verify Andale Mono font was installed ---"
189
+ # Check if font file exists
190
+ FONT_PATH=$(find "$HOME/.fontist/fonts" -name "*Andale*" -type f 2>/dev/null | head -1)
191
+ if [ -n "$FONT_PATH" ]; then
192
+ echo " ✓ Andale Mono font file found:"
193
+ echo " $FONT_PATH"
194
+ else
195
+ echo " ⚠ WARNING: Andale Mono font file not found in .fontist/fonts"
196
+ echo " Checking with 'fontist list'..."
197
+ if bundle exec fontist list "Andale Mono" 2>&1 | grep -q "Andale"; then
198
+ echo " ✓ Font is discoverable via fontist list"
199
+ else
200
+ echo " ✗ ERROR: Font is NOT discoverable"
201
+ INSTALL_SUCCESS=false
202
+ fi
203
+ fi
204
+ echo ""
205
+
206
+ echo "--- Step 6: Final Result ---"
207
+ if [ "$INSTALL_SUCCESS" = true ]; then
208
+ echo "========================================================================="
209
+ echo " ✓✓✓ TEST PASSED: Lazy initialization works correctly ✓✓✓"
210
+ echo "========================================================================="
211
+ echo ""
212
+ echo "Andale Mono was successfully installed WITHOUT running 'fontist update'"
213
+ echo "This confirms that formulas are auto-downloaded on first access."
214
+ else
215
+ echo "========================================================================="
216
+ echo " ✗✗✗ TEST FAILED: Lazy initialization not working ✗✗✗"
217
+ echo "========================================================================="
218
+ echo ""
219
+ echo "Andale Mono installation failed or formulas were not auto-downloaded."
220
+ echo "This indicates a regression in the lazy initialization feature."
221
+ exit 1
222
+ fi
223
+
224
+ - name: Debug output on failure
225
+ if: failure()
226
+ shell: bash
227
+ run: |
228
+ echo "=== Debug Information ==="
229
+ echo ""
230
+ echo "Fontist version:"
231
+ bundle exec fontist --version || true
232
+ echo ""
233
+ echo ".fontist directory structure:"
234
+ find "$HOME/.fontist" -type d 2>/dev/null || true
235
+ echo ""
236
+ echo "Formulas directory:"
237
+ ls -la "$HOME/.fontist/versions/v4/formulas/" 2>/dev/null || echo " (Does not exist)"
238
+ echo ""
239
+ echo "Formulas/Formulas directory:"
240
+ ls -la "$HOME/.fontist/versions/v4/formulas/Formulas/" 2>/dev/null || echo " (Does not exist)"
241
+ echo ""
242
+ echo "Fonts directory:"
243
+ ls -la "$HOME/.fontist/fonts/" 2>/dev/null || echo " (Does not exist)"
244
+ echo ""
245
+ echo "Andale Mono formula file:"
246
+ cat "$HOME/.fontist/versions/v4/formulas/Formulas/andale.yml" 2>/dev/null || echo " (Does not exist)"
247
+
248
+ # Additional test: Verify that explicit 'fontist update' still works
249
+ test-explicit-update-still-works:
250
+ name: Explicit fontist update still works
251
+ runs-on: ubuntu-latest
252
+
253
+ steps:
254
+ - name: Checkout
255
+ uses: actions/checkout@v4
256
+
257
+ - name: Setup Ruby
258
+ uses: ruby/setup-ruby@v1
259
+ with:
260
+ ruby-version: '3.3'
261
+ rubygems: latest
262
+ bundler: ${{ env.BUNDLER_VER }}
263
+ bundler-cache: true
264
+
265
+ - name: Remove existing formulas directory
266
+ shell: bash
267
+ run: rm -rf "$HOME/.fontist/versions/v4/formulas"
268
+
269
+ - name: Run explicit fontist update
270
+ shell: bash
271
+ run: |
272
+ echo "=== Testing explicit 'fontist update' ==="
273
+ echo ""
274
+ echo "Running: bundle exec fontist update"
275
+ bundle exec fontist update
276
+ echo ""
277
+ echo "✓ Explicit update completed"
278
+ echo ""
279
+ echo "Verifying formulas directory exists:"
280
+ FORMULAS_DIR="$HOME/.fontist/versions/v4/formulas/Formulas"
281
+ if [ -d "$FORMULAS_DIR" ]; then
282
+ echo " ✓ Formulas directory exists"
283
+ echo " Formula count: $(ls -1 "$FORMULAS_DIR" | wc -l)"
284
+ else
285
+ echo " ✗ ERROR: Formulas directory does not exist"
286
+ exit 1
287
+ fi
@@ -3,12 +3,16 @@ name: post-rake
3
3
  on:
4
4
  workflow_dispatch:
5
5
  workflow_run:
6
- workflows:
6
+ workflows:
7
7
  - rake
8
8
  - rake-metanorma
9
9
  types:
10
10
  - completed
11
11
 
12
+ permissions:
13
+ contents: write
14
+ packages: write
15
+
12
16
  jobs:
13
17
  post-rake:
14
18
  if: ${{ github.event.workflow_run.conclusion == 'success' && contains(github.ref, 'refs/tags/v') }}
@@ -6,6 +6,9 @@ on:
6
6
  tags: [ 'v*' ]
7
7
  pull_request:
8
8
 
9
+ permissions:
10
+ contents: read
11
+
9
12
  concurrency:
10
13
  group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
11
14
  cancel-in-progress: true
@@ -10,6 +10,9 @@ on:
10
10
  paths-ignore:
11
11
  - '**.adoc'
12
12
 
13
+ permissions:
14
+ contents: read
15
+
13
16
  concurrency:
14
17
  group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
15
18
  cancel-in-progress: true
@@ -45,7 +48,7 @@ jobs:
45
48
  run: |
46
49
  git clone --branch v4 --depth 1 https://github.com/fontist/formulas.git C:\temp\fontist\versions\v4\formulas
47
50
  shell: powershell
48
-
51
+
49
52
  - uses: actions/checkout@v4
50
53
 
51
54
  - uses: ruby/setup-ruby@v1
@@ -12,6 +12,10 @@ on:
12
12
  repository_dispatch:
13
13
  types: [ do-release ]
14
14
 
15
+ permissions:
16
+ contents: read
17
+ packages: write
18
+
15
19
  jobs:
16
20
  release:
17
21
  permissions:
@@ -5,6 +5,10 @@ on:
5
5
  types: [ do-release ]
6
6
  workflow_dispatch:
7
7
 
8
+ permissions:
9
+ contents: read
10
+ packages: write
11
+
8
12
  concurrency:
9
13
  group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
10
14
  cancel-in-progress: true
data/fontist.gemspec CHANGED
@@ -36,8 +36,10 @@ Gem::Specification.new do |spec|
36
36
  spec.add_dependency "git", "> 1.0"
37
37
  spec.add_dependency "json", "~> 2.0"
38
38
  spec.add_dependency "lutaml-model", "~> 0.7"
39
+ spec.add_dependency "lutaml-xsd", "~> 1.0"
39
40
  spec.add_dependency "marcel", "~> 1.0"
40
41
  spec.add_dependency "nokogiri", "~> 1.0"
42
+ spec.add_dependency "octokit", "~> 4.0"
41
43
  spec.add_dependency "paint", "~> 2.3"
42
44
  spec.add_dependency "parallel", "~> 1.24"
43
45
  spec.add_dependency "plist", "~> 3.0"
@@ -42,6 +42,16 @@ module Fontist
42
42
  end
43
43
  end
44
44
 
45
+ def cleanup_temp_files
46
+ # Clean up orphaned .tmp files from interrupted writes
47
+ # This can happen if the process crashes between File.write and File.rename
48
+ Dir.glob(File.join(@cache_dir, "*.tmp")).each do |tmp|
49
+ File.delete(tmp)
50
+ rescue StandardError
51
+ nil
52
+ end
53
+ end
54
+
45
55
  private
46
56
 
47
57
  attr_reader :cache_dir
@@ -68,11 +78,30 @@ module Fontist
68
78
  def read_entry(key)
69
79
  return nil unless File.exist?(cache_path(key))
70
80
 
71
- Marshal.load(File.read(cache_path(key)))
81
+ begin
82
+ Marshal.load(File.read(cache_path(key)))
83
+ rescue ArgumentError, TypeError => e
84
+ # Cache file is corrupted - delete it and return nil
85
+ # This can happen on Windows when file is read while being written,
86
+ # or when cache files from previous runs are corrupted
87
+ File.delete(cache_path(key)) rescue nil
88
+ nil
89
+ end
72
90
  end
73
91
 
74
92
  def write_entry(key, entry)
75
- File.write(cache_path(key), Marshal.dump(entry))
93
+ # Use temp file + atomic rename to prevent race conditions
94
+ # This ensures readers never see partial writes, even on Windows
95
+ temp_path = cache_path(key) + ".tmp"
96
+
97
+ File.write(temp_path, Marshal.dump(entry))
98
+ # Atomic rename (overwrites target atomically)
99
+ # File.rename is atomic on all platforms for same filesystem
100
+ File.rename(temp_path, cache_path(key))
101
+ rescue => e
102
+ # Clean up temp file if rename fails
103
+ File.delete(temp_path) rescue nil
104
+ raise e
76
105
  end
77
106
 
78
107
  # Cache entry with TTL support
@@ -79,13 +79,13 @@ location: nil)
79
79
  end
80
80
 
81
81
  def validate_platform_compatibility!
82
- formula = Fontist::Formula.find(name)
83
- return if formula.nil?
84
- return if formula.compatible_with_platform?
82
+ formula = Fontist::Formula.find_many(name)
83
+ return if formula.empty?
84
+ return if formula.any?(&:compatible_with_platform?)
85
85
 
86
86
  raise Fontist::Errors::PlatformMismatchError.new(
87
87
  name,
88
- formula.platforms,
88
+ formula.map(&:platforms).flatten.uniq,
89
89
  Fontist::Utils::System.user_os,
90
90
  )
91
91
  end
@@ -1,5 +1,6 @@
1
1
  require "lutaml/model"
2
2
  require "marcel"
3
+ require_relative "locking"
3
4
 
4
5
  module Fontist
5
6
  module Utils
@@ -1,4 +1,6 @@
1
1
  require_relative "cache"
2
+ require_relative "github_url"
3
+ require_relative "github_client"
2
4
 
3
5
  module Fontist
4
6
  module Utils
@@ -59,16 +61,30 @@ module Fontist
59
61
  end
60
62
 
61
63
  def download_file
62
- tries = tries ? tries + 1 : 1
64
+ @tries ||= 0
65
+ @tries += 1
63
66
  print_download_start if @verbose
64
67
  do_download_file
65
68
  rescue Down::Error => e
66
- retry if tries < 3
69
+ if @tries < max_retries
70
+ sleep(backoff_time(@tries))
71
+ retry
72
+ end
67
73
 
68
74
  raise Fontist::Errors::InvalidResourceError,
69
75
  "Invalid URL: #{@file}. Error: #{e.inspect}."
70
76
  end
71
77
 
78
+ def max_retries
79
+ @max_retries ||= 3
80
+ end
81
+
82
+ def backoff_time(attempt)
83
+ # Exponential backoff: 2^attempt seconds, max 30 seconds
84
+ # 1st retry: 2s, 2nd: 4s, 3rd: 8s
85
+ [2**attempt, 30].min
86
+ end
87
+
72
88
  def print_download_start
73
89
  Fontist.ui.say("Downloading from: #{Paint[url, :cyan]}")
74
90
  if @verbose
@@ -115,8 +131,10 @@ module Fontist
115
131
  # rubocop:enable Metrics/MethodLength
116
132
 
117
133
  def url
118
- obj = Helpers.url_object(@file)
119
- obj.respond_to?(:url) ? obj.url : obj
134
+ @url ||= begin
135
+ raw_url = extract_raw_url
136
+ github_aware_url(raw_url)
137
+ end
120
138
  end
121
139
 
122
140
  def headers
@@ -126,6 +144,22 @@ module Fontist
126
144
  obj.headers.to_h.map { |k, v| [k.to_s, v] }.to_h || # rubocop:disable Style/HashTransformKeys, Metrics/LineLength
127
145
  {}
128
146
  end
147
+
148
+ private
149
+
150
+ def extract_raw_url
151
+ obj = Helpers.url_object(@file)
152
+ obj.respond_to?(:url) ? obj.url : obj
153
+ end
154
+
155
+ def github_aware_url(raw_url)
156
+ parsed = GitHubUrl.parse(raw_url)
157
+ if parsed.matched?
158
+ GitHubClient.authenticated_download_url(parsed)
159
+ else
160
+ raw_url
161
+ end
162
+ end
129
163
  end
130
164
 
131
165
  class ProgressBar
@@ -0,0 +1,43 @@
1
+ require "octokit"
2
+
3
+ module Fontist
4
+ module Utils
5
+ class GitHubClient
6
+ class << self
7
+ def authenticated_download_url(parsed_url)
8
+ return parsed_url.original_url unless parsed_url.matched?
9
+
10
+ client = create_client
11
+ release = fetch_release(client, parsed_url)
12
+
13
+ find_asset_url(release, parsed_url.asset) || parsed_url.original_url
14
+ rescue Octokit::Error => e
15
+ Fontist.ui.say("GitHub API error: #{e.message}. Falling back to direct download.")
16
+ parsed_url.original_url
17
+ end
18
+
19
+ private
20
+
21
+ def create_client
22
+ if github_token
23
+ Octokit::Client.new(access_token: github_token)
24
+ else
25
+ Octokit::Client.new
26
+ end
27
+ end
28
+
29
+ def github_token
30
+ ENV.fetch("GITHUB_TOKEN", nil)
31
+ end
32
+
33
+ def fetch_release(client, parsed_url)
34
+ client.release_for_tag("#{parsed_url.owner}/#{parsed_url.repo}", parsed_url.tag)
35
+ end
36
+
37
+ def find_asset_url(release, asset_name)
38
+ release.assets.find { |asset| asset.name == asset_name }&.browser_download_url
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,51 @@
1
+ module Fontist
2
+ module Utils
3
+ class GitHubUrl
4
+ GITHUB_RELEASE_PATTERN =
5
+ %r{^https?://github\.com/(?<owner>[^/]+)/(?<repo>[^/]+)/releases/download/(?<tag>[^/]+)/(?<asset>.+)$}
6
+
7
+ class << self
8
+ def match?(url)
9
+ parse(url).matched?
10
+ end
11
+
12
+ def parse(url)
13
+ url_string = url.to_s
14
+ match = url_string.match(GITHUB_RELEASE_PATTERN)
15
+
16
+ if match
17
+ ParsedUrl.new(
18
+ owner: match[:owner],
19
+ repo: match[:repo],
20
+ tag: match[:tag],
21
+ asset: match[:asset],
22
+ original_url: url_string
23
+ )
24
+ else
25
+ ParsedUrl.from_non_github_url(url_string)
26
+ end
27
+ end
28
+ end
29
+
30
+ class ParsedUrl
31
+ attr_reader :owner, :repo, :tag, :asset, :original_url
32
+
33
+ def initialize(owner:, repo:, tag:, asset:, original_url:)
34
+ @owner = owner
35
+ @repo = repo
36
+ @tag = tag
37
+ @asset = asset
38
+ @original_url = original_url
39
+ end
40
+
41
+ def self.from_non_github_url(original_url)
42
+ new(owner: nil, repo: nil, tag: nil, asset: nil, original_url: original_url)
43
+ end
44
+
45
+ def matched?
46
+ !owner.nil?
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,3 +1,3 @@
1
1
  module Fontist
2
- VERSION = "2.1.1".freeze
2
+ VERSION = "2.1.2".freeze
3
3
  end
data/lib/fontist.rb CHANGED
@@ -188,9 +188,12 @@ module Fontist
188
188
  def self.formulas_repo_path_exists!
189
189
  return true if Dir.exist?(Fontist.formulas_repo_path.join("Formulas"))
190
190
 
191
- raise Errors::MainRepoNotFoundError.new(
192
- "Please fetch formulas with `fontist update`.",
193
- )
191
+ # Auto-update formulas repo if it doesn't exist (lazy initialization).
192
+ # This ensures formulas are always discoverable without requiring
193
+ # explicit `fontist update`.
194
+ Formula.update_formulas_repo
195
+
196
+ true
194
197
  end
195
198
  end
196
199
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fontist
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.1
4
+ version: 2.1.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-02-03 00:00:00.000000000 Z
11
+ date: 2026-02-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: down
@@ -120,6 +120,20 @@ dependencies:
120
120
  - - "~>"
121
121
  - !ruby/object:Gem::Version
122
122
  version: '0.7'
123
+ - !ruby/object:Gem::Dependency
124
+ name: lutaml-xsd
125
+ requirement: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '1.0'
130
+ type: :runtime
131
+ prerelease: false
132
+ version_requirements: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - "~>"
135
+ - !ruby/object:Gem::Version
136
+ version: '1.0'
123
137
  - !ruby/object:Gem::Dependency
124
138
  name: marcel
125
139
  requirement: !ruby/object:Gem::Requirement
@@ -148,6 +162,20 @@ dependencies:
148
162
  - - "~>"
149
163
  - !ruby/object:Gem::Version
150
164
  version: '1.0'
165
+ - !ruby/object:Gem::Dependency
166
+ name: octokit
167
+ requirement: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - "~>"
170
+ - !ruby/object:Gem::Version
171
+ version: '4.0'
172
+ type: :runtime
173
+ prerelease: false
174
+ version_requirements: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - "~>"
177
+ - !ruby/object:Gem::Version
178
+ version: '4.0'
151
179
  - !ruby/object:Gem::Dependency
152
180
  name: paint
153
181
  requirement: !ruby/object:Gem::Requirement
@@ -242,6 +270,7 @@ extra_rdoc_files: []
242
270
  files:
243
271
  - ".github/workflows/deploy-pages.yml"
244
272
  - ".github/workflows/discover-fonts.yml"
273
+ - ".github/workflows/formulas-auto-update-test.yml"
245
274
  - ".github/workflows/post-rake.yml"
246
275
  - ".github/workflows/rake-metanorma.yaml"
247
276
  - ".github/workflows/rake.yml"
@@ -403,6 +432,8 @@ files:
403
432
  - lib/fontist/utils/downloader.rb
404
433
  - lib/fontist/utils/file_magic.rb
405
434
  - lib/fontist/utils/file_ops.rb
435
+ - lib/fontist/utils/github_client.rb
436
+ - lib/fontist/utils/github_url.rb
406
437
  - lib/fontist/utils/locking.rb
407
438
  - lib/fontist/utils/system.rb
408
439
  - lib/fontist/utils/ui.rb