classy-yaml 1.7.0 → 1.7.1
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/README.md +13 -1
- data/lib/classy/yaml/component_helpers.rb +4 -3
- data/lib/classy/yaml/helpers.rb +39 -62
- data/lib/classy/yaml/version.rb +1 -1
- data/lib/classy/yaml.rb +42 -26
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 94e216b419acc7e44aa7a69c78c127319dcfa402c15104c529f68ea1b841ab93
|
|
4
|
+
data.tar.gz: 335441bc701254faa6a92bda5e682f57234761908ec4c4af2d3209f6115f8b70
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 006251bb6ddbd941c5bfe7f08a75cbafe7400178ac1550c316acf1499b100764f26ae9255d2acf14731db4028750c3b67af814cec277fb0907fd9d4e52ff305b
|
|
7
|
+
data.tar.gz: 4a04bb0454b7e3ad8d1165b41923b87ff49df5c52051295c032396ceb13b91d986dc18466e731ca06617530a81cea7b51317444aa8a1ab5cdabde8443c9adf0a
|
data/README.md
CHANGED
|
@@ -101,6 +101,10 @@ In your views or components:
|
|
|
101
101
|
<div class="px-4 py-2 rounded font-medium bg-blue-500 text-white hover:bg-blue-600 m-4"></div>
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
The helper reads argument hashes and arrays without changing them. You can reuse frozen arguments across calls. The `add` option also works when no YAML file exists.
|
|
105
|
+
|
|
106
|
+
Classy YAML checks component files, extra files, the default file, and engine files in that priority order. Base classes and specific classes fall back independently. Control options such as `skip_base` and `classy_files` are not YAML lookup keys.
|
|
107
|
+
|
|
104
108
|
### ViewComponent Integration
|
|
105
109
|
|
|
106
110
|
Include the component helpers in your ViewComponent:
|
|
@@ -138,12 +142,20 @@ btn:
|
|
|
138
142
|
Classy YAML detects if [tailwind_merge](https://github.com/gjtorikian/tailwind_merge) is installed and will leverage it when fetching classes with `yass`. Please read their documentation for installation and benefits.
|
|
139
143
|
|
|
140
144
|
|
|
145
|
+
## Caching and development reloads
|
|
146
|
+
|
|
147
|
+
Classy YAML reuses parsed YAML when file metadata has not changed. In development and tests, each call checks the default, engine, extra, and component files. Edits, additions, and deletions take effect on the next call without a restart.
|
|
148
|
+
|
|
149
|
+
In production, the default and engine YAML remain cached until configuration resets or the process restarts. Extra and component files still receive metadata checks. `Classy::Yaml.setup` clears the YAML caches.
|
|
150
|
+
|
|
151
|
+
When `tailwind_merge` is available, Classy YAML reuses one merger per process in all environments. A lock protects the merger cache during concurrent calls.
|
|
152
|
+
|
|
141
153
|
## Configuration Options
|
|
142
154
|
|
|
143
155
|
| Option | Default | Description |
|
|
144
156
|
|--------|---------|-------------|
|
|
145
157
|
| `default_file` | `"config/utility_classes.yml"` | Path to the main YAML file |
|
|
146
|
-
| `extra_files` | `[]` | Array of additional YAML files (
|
|
158
|
+
| `extra_files` | `[]` | Array of additional YAML files (above default and engine files; below component files) |
|
|
147
159
|
| `engine_files` | `[]` | Array of engine YAML files (lowest priority) |
|
|
148
160
|
| `override_tag_helpers` | `false` | Automatically process class symbols/hashes in Rails tag helpers |
|
|
149
161
|
|
|
@@ -2,8 +2,9 @@ module Classy
|
|
|
2
2
|
module Yaml
|
|
3
3
|
module ComponentHelpers
|
|
4
4
|
def yass(*args)
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
source_file = Object.const_source_location(self.class.name).first
|
|
6
|
+
calling_path = File.dirname(source_file)
|
|
7
|
+
calling_file = File.basename(source_file).split(".").first
|
|
7
8
|
component_name = self.class.name.underscore.split("/").last.split(".").first
|
|
8
9
|
|
|
9
10
|
classy_files = [ "#{calling_path}/#{component_name}.yml",
|
|
@@ -11,7 +12,7 @@ module Classy
|
|
|
11
12
|
"#{calling_path}/#{calling_file}/#{component_name}.yml" ]
|
|
12
13
|
|
|
13
14
|
if args.first.is_a?(Hash)
|
|
14
|
-
args.first.merge
|
|
15
|
+
args[0] = args.first.merge(classy_files: classy_files.uniq)
|
|
15
16
|
else
|
|
16
17
|
args << { classy_files: classy_files.uniq }
|
|
17
18
|
end
|
data/lib/classy/yaml/helpers.rb
CHANGED
|
@@ -3,14 +3,15 @@ module Classy
|
|
|
3
3
|
module Helpers
|
|
4
4
|
# Fetches utility classes from YAML files based on the provided keys.
|
|
5
5
|
# The method follows a priority order:
|
|
6
|
-
# 1.
|
|
7
|
-
# 2.
|
|
8
|
-
# 3.
|
|
6
|
+
# 1. Component files (highest priority)
|
|
7
|
+
# 2. Extra files
|
|
8
|
+
# 3. Default YAML
|
|
9
|
+
# 4. Engine files (lowest priority)
|
|
9
10
|
#
|
|
10
11
|
# @param args [Array] Array of keys to look up in the YAML files
|
|
11
12
|
# @return [String] Space-separated list of CSS classes
|
|
12
13
|
def yass(*args)
|
|
13
|
-
# Start with
|
|
14
|
+
# Start with engine YAMLs (lowest priority)
|
|
14
15
|
classy_yamls = Classy::Yaml.cached_engine_yamls.dup
|
|
15
16
|
|
|
16
17
|
# Add default YAML (next priority)
|
|
@@ -23,16 +24,14 @@ module Classy
|
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
# Add classy_files (highest priority)
|
|
26
|
-
classy_files_hash = args.find { |arg| arg.is_a?(Hash) && arg.
|
|
27
|
+
classy_files_hash = args.find { |arg| arg.is_a?(Hash) && arg.key?(:classy_files) } || { classy_files: [] }
|
|
27
28
|
classy_files_hash[:classy_files].each do |file_path|
|
|
28
29
|
load_yaml_file(file_path, classy_yamls, "classy")
|
|
29
30
|
end
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
skip_base_hash = args.find { |arg| arg.is_a?(Hash) && arg.keys.include?(:skip_base) } || {}
|
|
32
|
+
skip_base_hash = args.find { |arg| arg.is_a?(Hash) && arg.key?(:skip_base) } || {}
|
|
34
33
|
keys, add_classes = flatten_args(values: args)
|
|
35
|
-
classes = fetch_classes(keys, classy_yamls: classy_yamls, skip_base: skip_base_hash[:skip_base])
|
|
34
|
+
classes = fetch_classes(keys.uniq, classy_yamls: classy_yamls, skip_base: skip_base_hash[:skip_base])
|
|
36
35
|
classes += add_classes
|
|
37
36
|
|
|
38
37
|
# Use tailwind_merge if available, otherwise fall back to simple join
|
|
@@ -49,7 +48,7 @@ module Classy
|
|
|
49
48
|
|
|
50
49
|
if tailwind_merge_available?
|
|
51
50
|
# Use tailwind_merge for intelligent class merging
|
|
52
|
-
|
|
51
|
+
Classy::Yaml.merge_classes(classes)
|
|
53
52
|
else
|
|
54
53
|
# Fall back to simple space-joined classes
|
|
55
54
|
classes.join(" ")
|
|
@@ -73,18 +72,8 @@ module Classy
|
|
|
73
72
|
# @param classy_yamls [Array] Array to add the parsed YAML to
|
|
74
73
|
# @param file_type [String] Type of file being loaded (for error messages)
|
|
75
74
|
def load_yaml_file(file_path, classy_yamls, file_type)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if File.exist?(path_obj)
|
|
79
|
-
content = File.read(path_obj, encoding: "UTF-8")
|
|
80
|
-
parsed_yaml = YAML.safe_load(content, permitted_classes: [ Symbol, String, Array, Hash ], aliases: true)
|
|
81
|
-
classy_yamls << parsed_yaml if parsed_yaml && parsed_yaml.is_a?(Hash)
|
|
82
|
-
end
|
|
83
|
-
rescue Psych::SyntaxError => e
|
|
84
|
-
Rails.logger.error "Classy::Yaml (yass helper): Failed to parse #{file_type} YAML file #{file_path}: #{e.message}"
|
|
85
|
-
rescue => e
|
|
86
|
-
Rails.logger.error "Classy::Yaml (yass helper): Error loading #{file_type} YAML file #{file_path}: #{e.message}"
|
|
87
|
-
end
|
|
75
|
+
parsed_yaml = Classy::Yaml.cached_yaml_file(file_path, file_type)
|
|
76
|
+
classy_yamls << parsed_yaml if parsed_yaml
|
|
88
77
|
end
|
|
89
78
|
|
|
90
79
|
# Flattens the arguments into keys and classes
|
|
@@ -94,27 +83,27 @@ module Classy
|
|
|
94
83
|
# @param added_classes [Array] Array to store found classes
|
|
95
84
|
# @return [Array] Tuple of [keys, added_classes]
|
|
96
85
|
def flatten_args(root: [], values: [], keys: [], added_classes: [])
|
|
86
|
+
parent_keys = []
|
|
97
87
|
values.each do |value|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
88
|
+
case value
|
|
89
|
+
when Hash
|
|
90
|
+
added_classes << value[:add] if value.key?(:add)
|
|
91
|
+
value.each do |key, child|
|
|
92
|
+
next if key == :add
|
|
93
|
+
next if root.empty? && [ :skip_base, :classy_files ].include?(key)
|
|
94
|
+
|
|
95
|
+
path = root + [ key.to_s ]
|
|
96
|
+
parent_keys << path
|
|
97
|
+
flatten_args(root: path, values: [ child ], keys: keys, added_classes: added_classes)
|
|
107
98
|
end
|
|
99
|
+
when Array
|
|
100
|
+
flatten_args(root: root, values: value, keys: keys, added_classes: added_classes)
|
|
108
101
|
else
|
|
109
|
-
|
|
110
|
-
flatten_args(root: root, values: value, keys: keys, added_classes: added_classes)
|
|
111
|
-
else
|
|
112
|
-
keys << (root + [ value.to_s ])
|
|
113
|
-
end
|
|
102
|
+
keys << (root + [ value.to_s ])
|
|
114
103
|
end
|
|
115
104
|
end
|
|
116
|
-
|
|
117
|
-
|
|
105
|
+
keys.concat(parent_keys)
|
|
106
|
+
[ keys, added_classes ]
|
|
118
107
|
end
|
|
119
108
|
|
|
120
109
|
# Fetches classes from the YAML files based on the provided keys
|
|
@@ -125,42 +114,30 @@ module Classy
|
|
|
125
114
|
def fetch_classes(keys, classy_yamls: [], skip_base: false)
|
|
126
115
|
classes = []
|
|
127
116
|
|
|
128
|
-
keys.
|
|
117
|
+
keys.each do |key|
|
|
129
118
|
base_classes = nil
|
|
130
119
|
fetched_classes = nil
|
|
131
120
|
|
|
132
121
|
classy_yamls.reverse_each do |classy_yaml|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
classy_yaml.send(:dig, *(key + [ "base" ]))
|
|
122
|
+
begin
|
|
123
|
+
value = classy_yaml.dig(*key)
|
|
124
|
+
unless skip_base == true || base_classes
|
|
125
|
+
base_value = if value.is_a?(Hash)
|
|
126
|
+
value["base"]
|
|
139
127
|
elsif key.length > 1
|
|
140
|
-
|
|
141
|
-
else
|
|
142
|
-
nil
|
|
128
|
+
classy_yaml.dig(*(key[0...-1] + [ "base" ]))
|
|
143
129
|
end
|
|
144
|
-
|
|
145
|
-
base_classes ||= normalized_base
|
|
146
|
-
rescue
|
|
147
|
-
Rails.logger.warn(Classy::Yaml::InvalidKeyError.new(data: key))
|
|
130
|
+
base_classes = normalize_original(base_value).presence
|
|
148
131
|
end
|
|
149
|
-
end
|
|
150
132
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
value = classy_yaml.send(:dig, *key)
|
|
154
|
-
unless value.is_a?(Hash)
|
|
155
|
-
normalized_fetched = normalize_original(value)
|
|
156
|
-
fetched_classes ||= normalized_fetched
|
|
133
|
+
unless fetched_classes || value.is_a?(Hash)
|
|
134
|
+
fetched_classes = normalize_original(value).presence
|
|
157
135
|
end
|
|
158
|
-
rescue
|
|
136
|
+
rescue StandardError
|
|
159
137
|
Rails.logger.warn(Classy::Yaml::InvalidKeyError.new(data: key))
|
|
160
138
|
end
|
|
161
139
|
|
|
162
|
-
|
|
163
|
-
fetched_classes = nil if fetched_classes.blank?
|
|
140
|
+
break if fetched_classes && (skip_base == true || base_classes)
|
|
164
141
|
end
|
|
165
142
|
|
|
166
143
|
classes << base_classes unless base_classes.blank?
|
data/lib/classy/yaml/version.rb
CHANGED
data/lib/classy/yaml.rb
CHANGED
|
@@ -25,6 +25,9 @@ module Classy
|
|
|
25
25
|
@cached_engine_yamls = nil
|
|
26
26
|
@cached_default_yaml = nil
|
|
27
27
|
@load_lock = Mutex.new # Prevent race conditions during lazy loading
|
|
28
|
+
@file_cache = {}
|
|
29
|
+
@file_lock = Mutex.new
|
|
30
|
+
@merger_lock = Mutex.new
|
|
28
31
|
|
|
29
32
|
# -- Configuration Setters with Path Resolution --
|
|
30
33
|
def self.engine_files=(value)
|
|
@@ -34,7 +37,7 @@ module Classy
|
|
|
34
37
|
|
|
35
38
|
def self.extra_files=(value)
|
|
36
39
|
@@extra_files = Array.wrap(value).reject(&:blank?).map { |file| Rails.root.join(file) }
|
|
37
|
-
#
|
|
40
|
+
# Parsed files use the shared file cache.
|
|
38
41
|
end
|
|
39
42
|
|
|
40
43
|
def self.default_file=(value)
|
|
@@ -49,7 +52,7 @@ module Classy
|
|
|
49
52
|
|
|
50
53
|
# -- Cached Data Accessors (Lazy Loading) --
|
|
51
54
|
def self.cached_engine_yamls
|
|
52
|
-
#
|
|
55
|
+
# Check file metadata in development and test environments
|
|
53
56
|
return load_engine_yamls if Rails.env.development? || Rails.env.test?
|
|
54
57
|
|
|
55
58
|
# Use cache in other environments
|
|
@@ -63,7 +66,7 @@ module Classy
|
|
|
63
66
|
end
|
|
64
67
|
|
|
65
68
|
def self.cached_default_yaml
|
|
66
|
-
#
|
|
69
|
+
# Check file metadata in development and test environments
|
|
67
70
|
return load_default_yaml if Rails.env.development? || Rails.env.test?
|
|
68
71
|
|
|
69
72
|
# Use cache in other environments
|
|
@@ -81,6 +84,7 @@ module Classy
|
|
|
81
84
|
# Clear all caches when configuration changes
|
|
82
85
|
@cached_engine_yamls = nil
|
|
83
86
|
@cached_default_yaml = nil
|
|
87
|
+
@file_lock.synchronize { @file_cache.clear }
|
|
84
88
|
# Apply tag helper override if enabled
|
|
85
89
|
apply_tag_helper_override if @@override_tag_helpers
|
|
86
90
|
end
|
|
@@ -106,37 +110,49 @@ module Classy
|
|
|
106
110
|
end
|
|
107
111
|
|
|
108
112
|
def self.load_engine_yamls
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
engine_files.map { |path| cached_yaml_file(path, "engine") }.compact
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def self.load_default_yaml
|
|
117
|
+
cached_yaml_file(default_file, "default")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Check metadata on each access so edits do not require a Rails reload.
|
|
121
|
+
def self.cached_yaml_file(file_path, file_type)
|
|
122
|
+
@file_lock.synchronize do
|
|
111
123
|
begin
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
124
|
+
path = Rails.root.join(file_path).to_s
|
|
125
|
+
stat = File.stat(path)
|
|
126
|
+
signature = [ stat.mtime, stat.ctime, stat.size, stat.ino, stat.dev ]
|
|
127
|
+
cached = @file_cache[path]
|
|
128
|
+
return cached[:yaml] if cached && cached[:signature] == signature
|
|
129
|
+
|
|
130
|
+
content = File.read(path, encoding: "UTF-8")
|
|
131
|
+
parsed = YAML.safe_load(content, permitted_classes: [ Symbol, String, Array, Hash ], aliases: true)
|
|
132
|
+
yaml = parsed.is_a?(Hash) ? parsed : nil
|
|
133
|
+
@file_cache[path] = { signature: signature, yaml: yaml }
|
|
134
|
+
yaml
|
|
135
|
+
rescue Errno::ENOENT, Errno::ENOTDIR
|
|
136
|
+
@file_cache.delete(path)
|
|
137
|
+
nil
|
|
117
138
|
rescue Psych::SyntaxError => e
|
|
118
|
-
|
|
139
|
+
@file_cache.delete(path)
|
|
140
|
+
Rails.logger.error "Classy::Yaml: Failed to parse #{file_type} YAML file #{file_path}: #{e.message}"
|
|
141
|
+
nil
|
|
119
142
|
rescue => e
|
|
120
|
-
|
|
143
|
+
@file_cache.delete(path)
|
|
144
|
+
Rails.logger.error "Classy::Yaml: Error loading #{file_type} YAML file #{file_path}: #{e.message}"
|
|
145
|
+
nil
|
|
121
146
|
end
|
|
122
147
|
end
|
|
123
|
-
yamls
|
|
124
148
|
end
|
|
125
149
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
parsed_yaml = YAML.safe_load(content, permitted_classes: [ Symbol, String, Array, Hash ], aliases: true)
|
|
132
|
-
return parsed_yaml if parsed_yaml && parsed_yaml.is_a?(Hash)
|
|
133
|
-
end
|
|
134
|
-
rescue Psych::SyntaxError => e
|
|
135
|
-
Rails.logger.error "Classy::Yaml: Failed to parse default YAML file #{default_path}: #{e.message}"
|
|
136
|
-
rescue => e
|
|
137
|
-
Rails.logger.error "Classy::Yaml: Error loading default YAML file #{default_path}: #{e.message}"
|
|
150
|
+
# The merger cache is mutable, so concurrent calls must use the same lock.
|
|
151
|
+
def self.merge_classes(classes)
|
|
152
|
+
@merger_lock.synchronize do
|
|
153
|
+
@merger ||= TailwindMerge::Merger.new
|
|
154
|
+
@merger.merge(classes)
|
|
138
155
|
end
|
|
139
|
-
nil # Return nil if file doesn't exist or fails to load/parse
|
|
140
156
|
end
|
|
141
157
|
end
|
|
142
158
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: classy-yaml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.7.
|
|
4
|
+
version: 1.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tonksthebear
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|