fontist 3.0.29 → 3.0.30
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/fontist.gemspec +3 -2
- data/lib/fontist/cache/store.rb +13 -6
- data/lib/fontist/cli.rb +1 -1
- data/lib/fontist/formula.rb +16 -5
- data/lib/fontist/from_file_cache.rb +4 -0
- data/lib/fontist/helpers.rb +1 -1
- data/lib/fontist/import/convert_formulas.rb +1 -1
- data/lib/fontist/import/google/data_sources/base.rb +3 -3
- data/lib/fontist/import/google/models/font_family.rb +4 -4
- data/lib/fontist/import/helpers/hash_helper.rb +1 -1
- data/lib/fontist/import/v4_to_v5_migrator.rb +1 -1
- data/lib/fontist/index_cli.rb +1 -1
- data/lib/fontist/indexes/index_mixin.rb +4 -4
- data/lib/fontist/json.rb +76 -0
- data/lib/fontist/resource.rb +6 -1
- data/lib/fontist/validate_cli.rb +1 -1
- data/lib/fontist/version.rb +1 -1
- data/lib/fontist/yaml_backend.rb +18 -5
- data/lib/fontist.rb +1 -0
- metadata +4 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d858c57f16d09454000112d1047f120b1a752a76331e3788f65cb441fe7a668c
|
|
4
|
+
data.tar.gz: 60c5f0e33198e6b7b8ad24db123280a5fff7c4eaf8b56581d3ab8c9108114b44
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 581ebfd99f20a966cea6574294b4a3de879cdcea6051f916645e84ac9bd5e9ee16d4d0bfcbbd82f0291b6d6ae3e72dcf1c5524d02f6e1d67d3fbcfd880b03d73
|
|
7
|
+
data.tar.gz: 7188a89be063579e76ffd351c5ddd0c7296836a4c6162c1974ca54f79ca5cddd6134a71b3d48d04455fbc1ac8debbe1e6f2922d414a16aea2e85c83a62742fc2
|
data/fontist.gemspec
CHANGED
|
@@ -8,8 +8,10 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.authors = ["Ribose Inc."]
|
|
9
9
|
spec.email = ["open.source@ribose.com"]
|
|
10
10
|
|
|
11
|
+
# rubocop:disable Layout/LineLength
|
|
11
12
|
spec.summary = "Install openly-licensed fonts on Windows, Linux and Mac!"
|
|
12
13
|
spec.description = "Install openly-licensed fonts on Windows, Linux and Mac!"
|
|
14
|
+
# rubocop:enable Layout/LineLength
|
|
13
15
|
spec.homepage = "https://github.com/fontist/fontist"
|
|
14
16
|
spec.license = "BSD-2-Clause"
|
|
15
17
|
|
|
@@ -34,11 +36,10 @@ Gem::Specification.new do |spec|
|
|
|
34
36
|
spec.add_dependency "fontisan", "~> 0.4", ">= 0.4.47"
|
|
35
37
|
spec.add_dependency "fuzzy_match", "~> 2.1"
|
|
36
38
|
spec.add_dependency "git", "~> 6.0"
|
|
37
|
-
spec.add_dependency "json", "~> 2.0"
|
|
38
39
|
spec.add_dependency "lutaml-model", "~> 0.8.0"
|
|
39
40
|
spec.add_dependency "marcel", "~> 1.0"
|
|
40
41
|
spec.add_dependency "nokogiri", "~> 1.0"
|
|
41
|
-
spec.add_dependency "octokit", "~>
|
|
42
|
+
spec.add_dependency "octokit", "~> 10.0"
|
|
42
43
|
spec.add_dependency "paint", "~> 2.3"
|
|
43
44
|
spec.add_dependency "parallel", "~> 1.24"
|
|
44
45
|
spec.add_dependency "plist", "~> 3.0"
|
data/lib/fontist/cache/store.rb
CHANGED
|
@@ -40,7 +40,7 @@ module Fontist
|
|
|
40
40
|
ensure_cache_dir
|
|
41
41
|
entry = CacheEntry.new(value, ttl)
|
|
42
42
|
write_entry(key, entry)
|
|
43
|
-
rescue TypeError, JSON::
|
|
43
|
+
rescue TypeError, Fontist::JSON::DumpError
|
|
44
44
|
# Values outside the JSON type system (Time, Date, arbitrary
|
|
45
45
|
# objects) skip the write and read back as a miss
|
|
46
46
|
false
|
|
@@ -113,9 +113,9 @@ module Fontist
|
|
|
113
113
|
def read_entry(key)
|
|
114
114
|
return nil unless File.exist?(cache_path(key))
|
|
115
115
|
|
|
116
|
-
payload = JSON.parse(File.read(cache_path(key)))
|
|
116
|
+
payload = Fontist::JSON.parse(File.read(cache_path(key)))
|
|
117
117
|
CacheEntry.new(payload["value"], expires_at: payload["expires_at"])
|
|
118
|
-
rescue JSON::
|
|
118
|
+
rescue Fontist::JSON::ParseError, TypeError, EOFError
|
|
119
119
|
# Corrupted file - delete it and return nil. This can happen
|
|
120
120
|
# on Windows when a file is read while being written, or when
|
|
121
121
|
# cache files from previous runs are corrupted
|
|
@@ -137,9 +137,16 @@ module Fontist
|
|
|
137
137
|
end
|
|
138
138
|
|
|
139
139
|
def entry_payload(entry)
|
|
140
|
-
JSON.dump("version" => FORMAT_VERSION,
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
Fontist::JSON.dump("version" => FORMAT_VERSION,
|
|
141
|
+
"expires_at" => entry.expires_at,
|
|
142
|
+
"value" => entry.value)
|
|
143
|
+
rescue Fontist::JSON::DumpError
|
|
144
|
+
# Values outside the JSON type system (Time, Date, arbitrary
|
|
145
|
+
# objects) degrade to their string form, keeping the cache a
|
|
146
|
+
# best-effort store instead of a source of crashes
|
|
147
|
+
Fontist::JSON.dump("version" => FORMAT_VERSION,
|
|
148
|
+
"expires_at" => entry.expires_at,
|
|
149
|
+
"value" => entry.value.to_s)
|
|
143
150
|
end
|
|
144
151
|
end
|
|
145
152
|
end
|
data/lib/fontist/cli.rb
CHANGED
data/lib/fontist/formula.rb
CHANGED
|
@@ -106,10 +106,16 @@ module Fontist
|
|
|
106
106
|
# Parsing thousands of formula files dominates index rebuilds;
|
|
107
107
|
# worker threads overlap the libyaml C parse, which releases
|
|
108
108
|
# the GVL. The pool is capped like every other fontist pool, so
|
|
109
|
-
# worker counts never scale with host core counts
|
|
109
|
+
# worker counts never scale with host core counts.
|
|
110
|
+
#
|
|
111
|
+
# The parses bypass the shared parse cache: a corpus sweep never
|
|
112
|
+
# hits it (the LRU holds 64 entries against thousands of files),
|
|
113
|
+
# so caching would only evict entries that lookups re-use and
|
|
114
|
+
# pin dozens of parsed formulas in long-lived host processes
|
|
115
|
+
# after the sweep ends
|
|
110
116
|
workers = [Parallel.processor_count, 8].min
|
|
111
117
|
formulas = Parallel.map(paths, in_threads: workers) do |path|
|
|
112
|
-
Formula.from_file(path)
|
|
118
|
+
Formula.from_file(path, cache: false)
|
|
113
119
|
end.compact
|
|
114
120
|
|
|
115
121
|
FormulaCollection.new(formulas)
|
|
@@ -181,7 +187,7 @@ module Fontist
|
|
|
181
187
|
find_by_key(key)
|
|
182
188
|
end
|
|
183
189
|
|
|
184
|
-
def from_file(path)
|
|
190
|
+
def from_file(path, cache: true)
|
|
185
191
|
unless File.exist?(path)
|
|
186
192
|
raise Fontist::Errors::FormulaNotFoundError,
|
|
187
193
|
"Formula file not found: #{path}"
|
|
@@ -192,8 +198,13 @@ module Fontist
|
|
|
192
198
|
# Parsing a formula costs ~10-20ms and the same formula is
|
|
193
199
|
# re-read on every lookup, so the parsed instance is memoized
|
|
194
200
|
# (mtime-validated) and shared between callers; treat as
|
|
195
|
-
# read-only
|
|
196
|
-
|
|
201
|
+
# read-only. Callers that sweep the whole corpus pass
|
|
202
|
+
# cache: false, since their parses would only thrash the cache
|
|
203
|
+
formula = if cache
|
|
204
|
+
Fontist::FromFileCache.memoize(path) { from_yaml(content) }
|
|
205
|
+
else
|
|
206
|
+
from_yaml(content)
|
|
207
|
+
end
|
|
197
208
|
|
|
198
209
|
formula.tap do |f|
|
|
199
210
|
f.path = path
|
data/lib/fontist/helpers.rb
CHANGED
|
@@ -58,8 +58,8 @@ module Fontist
|
|
|
58
58
|
raise "API request failed: #{response.code} #{response.message}"
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
JSON.parse(response.body)
|
|
62
|
-
rescue JSON::
|
|
61
|
+
Fontist::JSON.parse(response.body)
|
|
62
|
+
rescue Fontist::JSON::ParseError => e
|
|
63
63
|
raise "Failed to parse API response: #{e.message}"
|
|
64
64
|
rescue StandardError => e
|
|
65
65
|
raise "Failed to fetch from API: #{e.message}"
|
|
@@ -90,7 +90,7 @@ module Fontist
|
|
|
90
90
|
# @param item [Hash] the raw item data
|
|
91
91
|
# @return [FontFamily] the parsed font family
|
|
92
92
|
def parse_item(item)
|
|
93
|
-
Models::FontFamily.from_json(item
|
|
93
|
+
Models::FontFamily.from_json(Fontist::JSON.dump(item))
|
|
94
94
|
end
|
|
95
95
|
end
|
|
96
96
|
end
|
|
@@ -59,15 +59,15 @@ module Fontist
|
|
|
59
59
|
|
|
60
60
|
# Try to parse as JSON first
|
|
61
61
|
begin
|
|
62
|
-
JSON.parse(files_data)
|
|
63
|
-
rescue JSON::
|
|
62
|
+
Fontist::JSON.parse(files_data)
|
|
63
|
+
rescue Fontist::JSON::ParseError, TypeError
|
|
64
64
|
# If it fails, convert Ruby hash syntax to JSON and parse
|
|
65
65
|
if files_data.is_a?(String)
|
|
66
66
|
# Convert Ruby hash syntax (=>) to JSON syntax (:)
|
|
67
67
|
json_str = files_data.gsub("=>", ":")
|
|
68
68
|
begin
|
|
69
|
-
return JSON.parse(json_str)
|
|
70
|
-
rescue JSON::
|
|
69
|
+
return Fontist::JSON.parse(json_str)
|
|
70
|
+
rescue Fontist::JSON::ParseError
|
|
71
71
|
{}
|
|
72
72
|
end
|
|
73
73
|
end
|
|
@@ -95,7 +95,7 @@ module Fontist
|
|
|
95
95
|
def load_yaml_without_aliases(path)
|
|
96
96
|
data = YAML.safe_load(File.read(path), aliases: true,
|
|
97
97
|
permitted_classes: [Date])
|
|
98
|
-
JSON.parse(JSON.
|
|
98
|
+
Fontist::JSON.parse(Fontist::JSON.dump(data))
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
def file_has_yaml_aliases?(path)
|
data/lib/fontist/index_cli.rb
CHANGED
|
@@ -103,13 +103,13 @@ module Fontist
|
|
|
103
103
|
return entries if entries
|
|
104
104
|
|
|
105
105
|
parse_sidecar_entries(sidecar_path)
|
|
106
|
-
rescue JSON::
|
|
106
|
+
rescue Fontist::JSON::ParseError, TypeError
|
|
107
107
|
nil
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
def rebuild_sidecar_entries
|
|
111
111
|
entries_hash = from_file(path).entries_hash
|
|
112
|
-
File.write("#{path}.json", JSON.dump("entries" => entries_hash))
|
|
112
|
+
File.write("#{path}.json", Fontist::JSON.dump("entries" => entries_hash))
|
|
113
113
|
entries_hash
|
|
114
114
|
end
|
|
115
115
|
|
|
@@ -135,7 +135,7 @@ module Fontist
|
|
|
135
135
|
# as read-only
|
|
136
136
|
def parse_sidecar_entries(sidecar_path)
|
|
137
137
|
mtime = File.mtime(sidecar_path)
|
|
138
|
-
entries = JSON.parse(File.read(sidecar_path))["entries"]
|
|
138
|
+
entries = Fontist::JSON.parse(File.read(sidecar_path))["entries"]
|
|
139
139
|
|
|
140
140
|
sidecar_cache_mutex.synchronize do
|
|
141
141
|
sidecar_cache[sidecar_path] = { mtime: mtime, entries: entries }
|
|
@@ -244,7 +244,7 @@ module Fontist
|
|
|
244
244
|
|
|
245
245
|
def write_lookup_sidecar(file_path)
|
|
246
246
|
File.write("#{file_path}.json",
|
|
247
|
-
JSON.dump("entries" => entries_hash))
|
|
247
|
+
Fontist::JSON.dump("entries" => entries_hash))
|
|
248
248
|
end
|
|
249
249
|
|
|
250
250
|
# key -> formula paths, in the sidecar's JSON-friendly shape
|
data/lib/fontist/json.rb
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require "yeptris"
|
|
2
|
+
require "yeptris/json"
|
|
3
|
+
|
|
4
|
+
module Fontist
|
|
5
|
+
# JSON encoding and decoding through yeptris's native engine, replacing
|
|
6
|
+
# the json gem as fontist's JSON backend. The yeptris parser and
|
|
7
|
+
# generator produce and accept the same plain Ruby structures as the
|
|
8
|
+
# json gem (string keys, arrays, scalars), so this module is a
|
|
9
|
+
# drop-in surface; call sites rescue the module's error classes
|
|
10
|
+
# instead of JSON::ParserError and JSON::GeneratorError.
|
|
11
|
+
module JSON
|
|
12
|
+
class Error < StandardError; end
|
|
13
|
+
class ParseError < Error; end
|
|
14
|
+
class DumpError < Error; end
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
def parse(source)
|
|
18
|
+
unless source.is_a?(String)
|
|
19
|
+
raise ParseError,
|
|
20
|
+
"cannot parse #{source.class}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
Yeptris::JSON.load(source)
|
|
24
|
+
rescue Yeptris::Error, TypeError => e
|
|
25
|
+
raise ParseError, e.message
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def dump(object)
|
|
29
|
+
Yeptris::JSON.dump(object)
|
|
30
|
+
rescue Yeptris::Error, TypeError => e
|
|
31
|
+
raise DumpError, e.message
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Two-space-indented JSON for human-facing CLI output; yeptris's
|
|
35
|
+
# generator is compact-only, so the indentation is applied here
|
|
36
|
+
def pretty(object)
|
|
37
|
+
pretty_value(object, 0)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def pretty_value(value, depth)
|
|
43
|
+
case value
|
|
44
|
+
when Hash then pretty_object(value, depth)
|
|
45
|
+
when Array then pretty_array(value, depth)
|
|
46
|
+
else dump(value)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def pretty_object(hash, depth)
|
|
51
|
+
return "{}" if hash.empty?
|
|
52
|
+
|
|
53
|
+
inner = hash.map do |key, value|
|
|
54
|
+
"#{indent(depth + 1)}#{dump(key.to_s)}: #{pretty_value(value,
|
|
55
|
+
depth + 1)}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
"{\n#{inner.join(",\n")}\n#{indent(depth)}}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def pretty_array(array, depth)
|
|
62
|
+
return "[]" if array.empty?
|
|
63
|
+
|
|
64
|
+
inner = array.map do |value|
|
|
65
|
+
"#{indent(depth + 1)}#{pretty_value(value, depth + 1)}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
"[\n#{inner.join(",\n")}\n#{indent(depth)}]"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def indent(depth)
|
|
72
|
+
" " * depth
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
data/lib/fontist/resource.rb
CHANGED
|
@@ -6,7 +6,12 @@ module Fontist
|
|
|
6
6
|
attribute :name, :string
|
|
7
7
|
attribute :source, :string
|
|
8
8
|
attribute :urls, :string, collection: true
|
|
9
|
-
|
|
9
|
+
# Declared scalar: the corpus writes one digest per resource almost
|
|
10
|
+
# everywhere, and consumers render it verbatim. A handful of
|
|
11
|
+
# resources pair one digest with each mirror URL and write a list;
|
|
12
|
+
# a scalar-typed attribute keeps both forms as written instead of
|
|
13
|
+
# normalizing every single digest into a one-element Array.
|
|
14
|
+
attribute :sha256, :string
|
|
10
15
|
attribute :file_size, :integer
|
|
11
16
|
attribute :family, :string
|
|
12
17
|
attribute :files, :string, collection: true
|
data/lib/fontist/validate_cli.rb
CHANGED
|
@@ -189,7 +189,7 @@ module Fontist
|
|
|
189
189
|
|
|
190
190
|
def output_json_report(report, output_file)
|
|
191
191
|
require "json"
|
|
192
|
-
json_content = JSON.
|
|
192
|
+
json_content = Fontist::JSON.pretty(report.to_hash)
|
|
193
193
|
|
|
194
194
|
if output_file
|
|
195
195
|
File.write(output_file, json_content)
|
data/lib/fontist/version.rb
CHANGED
data/lib/fontist/yaml_backend.rb
CHANGED
|
@@ -14,13 +14,26 @@ module Fontist
|
|
|
14
14
|
ENABLED_VALUE = "yeptris"
|
|
15
15
|
|
|
16
16
|
class << self
|
|
17
|
-
#
|
|
18
|
-
#
|
|
17
|
+
# Pins the YAML adapter explicitly: lutaml-model auto-selects
|
|
18
|
+
# yeptris for a format whenever the yeptris gem is in the bundle,
|
|
19
|
+
# which would silently switch YAML parsing and dumping (and its
|
|
20
|
+
# line-wrap behavior) for every model in the process. The yeptris
|
|
21
|
+
# YAML engine activates only when explicitly enabled with
|
|
22
|
+
# FONTIST_YAML=yeptris; otherwise the standard Psych engine is
|
|
23
|
+
# pinned, because the yeptris x86_64-linux and Windows builds
|
|
24
|
+
# currently mis-parse some plain scalars as single-element Arrays
|
|
25
|
+
# (leptris/yeptris#863). Flip the default once that parity fix
|
|
26
|
+
# lands.
|
|
27
|
+
#
|
|
28
|
+
# Returns the activated adapter class.
|
|
19
29
|
def activate!
|
|
20
|
-
|
|
21
|
-
|
|
30
|
+
adapter =
|
|
31
|
+
if enabled? && yeptris_available?
|
|
32
|
+
Yaml::YeptrisAdapter
|
|
33
|
+
else
|
|
34
|
+
Lutaml::Yaml::Adapter::StandardAdapter
|
|
35
|
+
end
|
|
22
36
|
|
|
23
|
-
adapter = Yaml::YeptrisAdapter
|
|
24
37
|
Lutaml::Model::AdapterResolver.set_adapter_class(:yaml, adapter)
|
|
25
38
|
|
|
26
39
|
adapter
|
data/lib/fontist.rb
CHANGED
|
@@ -18,6 +18,7 @@ module Fontist
|
|
|
18
18
|
# Core classes
|
|
19
19
|
autoload :Config, "#{__dir__}/fontist/config"
|
|
20
20
|
autoload :Css, "#{__dir__}/fontist/css"
|
|
21
|
+
autoload :JSON, "#{__dir__}/fontist/json"
|
|
21
22
|
autoload :Font, "#{__dir__}/fontist/font"
|
|
22
23
|
autoload :FontCollection, "#{__dir__}/fontist/font_collection"
|
|
23
24
|
autoload :FontFile, "#{__dir__}/fontist/font_file"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fontist
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.30
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -92,20 +92,6 @@ dependencies:
|
|
|
92
92
|
- - "~>"
|
|
93
93
|
- !ruby/object:Gem::Version
|
|
94
94
|
version: '6.0'
|
|
95
|
-
- !ruby/object:Gem::Dependency
|
|
96
|
-
name: json
|
|
97
|
-
requirement: !ruby/object:Gem::Requirement
|
|
98
|
-
requirements:
|
|
99
|
-
- - "~>"
|
|
100
|
-
- !ruby/object:Gem::Version
|
|
101
|
-
version: '2.0'
|
|
102
|
-
type: :runtime
|
|
103
|
-
prerelease: false
|
|
104
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
105
|
-
requirements:
|
|
106
|
-
- - "~>"
|
|
107
|
-
- !ruby/object:Gem::Version
|
|
108
|
-
version: '2.0'
|
|
109
95
|
- !ruby/object:Gem::Dependency
|
|
110
96
|
name: lutaml-model
|
|
111
97
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -154,14 +140,14 @@ dependencies:
|
|
|
154
140
|
requirements:
|
|
155
141
|
- - "~>"
|
|
156
142
|
- !ruby/object:Gem::Version
|
|
157
|
-
version: '
|
|
143
|
+
version: '10.0'
|
|
158
144
|
type: :runtime
|
|
159
145
|
prerelease: false
|
|
160
146
|
version_requirements: !ruby/object:Gem::Requirement
|
|
161
147
|
requirements:
|
|
162
148
|
- - "~>"
|
|
163
149
|
- !ruby/object:Gem::Version
|
|
164
|
-
version: '
|
|
150
|
+
version: '10.0'
|
|
165
151
|
- !ruby/object:Gem::Dependency
|
|
166
152
|
name: paint
|
|
167
153
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -473,6 +459,7 @@ files:
|
|
|
473
459
|
- lib/fontist/install_locations/fontist_location.rb
|
|
474
460
|
- lib/fontist/install_locations/system_location.rb
|
|
475
461
|
- lib/fontist/install_locations/user_location.rb
|
|
462
|
+
- lib/fontist/json.rb
|
|
476
463
|
- lib/fontist/macos.rb
|
|
477
464
|
- lib/fontist/macos/catalog.rb
|
|
478
465
|
- lib/fontist/macos/catalog/asset.rb
|